UNPKG

testplane

Version:

Tests framework based on mocha and wdio

302 lines 13.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TestRunner = void 0; const node_crypto_1 = __importDefault(require("node:crypto")); const lodash_1 = __importDefault(require("lodash")); const socket_io_client_1 = require("socket.io-client"); const url_join_1 = __importDefault(require("url-join")); const promise_1 = require("../../../../utils/promise"); const test_runner_1 = __importDefault(require("../../../runner/test-runner")); const execution_thread_1 = require("./execution-thread"); const types_1 = require("./types"); const constants_1 = require("./constants"); const constants_2 = require("../../../../runner/browser-env/vite/constants"); const logger = __importStar(require("../../../../utils/logger")); const runtime_config_1 = __importDefault(require("../../../../config/runtime-config")); const abort_on_reconnect_error_1 = require("../../../../errors/abort-on-reconnect-error"); const types_2 = require("../../../../runner/browser-env/vite/types"); const prepareData = (data) => { return JSON.parse(JSON.stringify(data, Object.getOwnPropertyNames(data))); }; const hasElementRef = (data) => { return lodash_1.default.isObject(data) && Object.keys(data).some(key => key.startsWith("element-")); }; const getElementRef = (elementRefWithExtraProps) => { return lodash_1.default.pickBy(elementRefWithExtraProps, (_v, key) => key.startsWith("element-")); }; class TestRunner extends test_runner_1.default { constructor(opts) { super(opts); this._runUuid = node_crypto_1.default.randomUUID(); this._isReconnected = false; this._broInitResOnReconnect = null; this._socket = (0, socket_io_client_1.io)(runtime_config_1.default.getInstance().viteBaseUrl, { transports: ["websocket"], auth: { runUuid: this._runUuid, type: constants_1.WORKER_EVENT_PREFIX, }, }); this._socket.on("connect_error", err => { if (!this._socket.active) { logger.warn(`Worker with pid=${process.pid} and runUuid=${this._runUuid} was disconnected from the Vite server:`, err); } }); } async prepareBrowser(opts) { this._runOpts = opts; return super.prepareBrowser(opts); } async run() { let error; try { await super.prepareToRun(); error = await this._runWithAbort(throwIfAborted => { const ExecutionThreadCls = (0, execution_thread_1.wrapExecutionThread)(this._socket, throwIfAborted); return super.runRunnables(ExecutionThreadCls); }); } catch (err) { error = err; } while (error instanceof abort_on_reconnect_error_1.AbortOnReconnectError) { error = null; try { await this._waitBroInitOnReconnect(); error = await this._runWithAbort(throwIfAborted => { const ExecutionThreadCls = (0, execution_thread_1.wrapExecutionThread)(this._socket, throwIfAborted); return super.runRunnables(ExecutionThreadCls); }); } catch (err) { error = err; } } const results = await super.finishRun(error); this._socket.emit(types_1.WorkerEventNames.finalize); if (error) { throw error; } return results; } _runWithAbort(cb) { const controller = new AbortController(); const { signal } = controller; let isAborted = signal.aborted; signal.addEventListener("abort", () => { isAborted = true; }); const throwIfAborted = () => { if (isAborted) { throw new abort_on_reconnect_error_1.AbortOnReconnectError(); } }; this._socket.once(types_2.BrowserEventNames.reconnect, () => { this._broInitResOnReconnect = null; this._socket.once(types_2.BrowserEventNames.initialize, errors => { this._broInitResOnReconnect = errors; }); if (this._browser.state.onReplMode) { runtime_config_1.default.getInstance().replServer.close(); } this._isReconnected = true; controller.abort(); }); return cb(throwIfAborted); } _waitBroInitOnReconnect() { let intervalId = null; return (0, promise_1.promiseTimeout)(new Promise((resolve, reject) => { intervalId = setInterval(() => { if (lodash_1.default.isNull(this._broInitResOnReconnect)) { return; } if (intervalId) { clearInterval(intervalId); } if (lodash_1.default.isEmpty(this._broInitResOnReconnect)) { resolve(); } else { reject(this._broInitResOnReconnect[0]); } }, constants_1.BRO_INIT_INTERVAL_ON_RECONNECT).unref(); }), constants_1.BRO_INIT_TIMEOUT_ON_RECONNECT, `Browser didn't connect to the Vite server after reconnect in ${constants_1.BRO_INIT_TIMEOUT_ON_RECONNECT}ms`).catch(err => { if (intervalId) { clearInterval(intervalId); } throw err; }); } _getPreparePageActions(browser, history) { if (this._isReconnected) { return super._getPreparePageActions(browser, history); } return [ async () => { const { default: expectMatchers } = await Promise.resolve().then(() => __importStar(require("expect-webdriverio/lib/matchers"))); this._socket.on(types_2.BrowserEventNames.callConsoleMethod, payload => { console[payload.method](...(payload.args || [])); }); this._socket.on(types_2.BrowserEventNames.runBrowserCommand, this._handleRunBrowserCommand(browser)); this._socket.on(types_2.BrowserEventNames.runExpectMatcher, this._handleRunExpectMatcher(browser, expectMatchers)); const err = (await this._socket.timeout(constants_2.SOCKET_MAX_TIMEOUT).emitWithAck(types_1.WorkerEventNames.initialize, { file: this._file, sessionId: this._runOpts.sessionId, capabilities: this._runOpts.sessionCaps, requestedCapabilities: this._runOpts.sessionOpts.capabilities, customCommands: browser.customCommands, config: this._config, expectMatchers: Object.getOwnPropertyNames(expectMatchers), })); if (err) { throw err; } await history.runGroup({ callstack: browser.callstackHistory, snapshotsPromiseRef: this._browser.snapshotsPromiseRef, session: this._browser.publicAPI, config: this._config, }, "openVite", async () => { await this._openViteUrl(browser); }); }, ...super._getPreparePageActions(browser, history), ]; } _handleRunBrowserCommand(browser) { const { publicAPI: session } = browser; return async (payload, cb) => { const { name, args, element } = payload; const wdioInstance = await getWdioInstance(session, element); const wdioInstanceName = element ? "element" : "browser"; const cmdName = name; if (typeof wdioInstance[cmdName] !== "function") { cb([ prepareData(new Error(`"${wdioInstanceName}.${name}" does not exists in ${wdioInstanceName} instance`)), ]); return; } try { const result = await wdioInstance[cmdName](...args); if (lodash_1.default.isError(result)) { return cb([prepareData(result)]); } // Testing Library queries return large objects, containing many additional props. Here, we strip everything but element ref if (lodash_1.default.isArray(result)) { if (result.every((item) => hasElementRef(item))) { const elementRefs = result.map((item) => getElementRef(item)); return cb([null, elementRefs.map(prepareData)]); } return cb([null, result.map(prepareData)]); } if (hasElementRef(result)) { const elementRef = getElementRef(result); return cb([null, prepareData(elementRef)]); } cb([null, lodash_1.default.isObject(result) ? prepareData(result) : result]); } catch (err) { cb([prepareData(err)]); } }; } _handleRunExpectMatcher(browser, expectMatchers) { const { publicAPI: session } = browser; return async (payload, cb) => { if (typeof expect === "undefined") { return cb([{ pass: false, message: "Couldn't find expect module" }]); } const matcher = expectMatchers[payload.name]; if (!matcher) { return cb([{ pass: false, message: `Couldn't find expect matcher with name "${payload.name}"` }]); } try { let context = payload.context || session; if (payload.element) { if (lodash_1.default.isArray(payload.element)) { context = await session.$$(payload.element); } else if (payload.element.elementId) { context = await session.$(payload.element); context.selector = payload.element.selector; } else { context = await session.$(payload.element.selector); } } const result = await matcher.apply(payload.scope, [context, ...payload.args.map(transformExpectArg)]); cb([{ pass: result.pass, message: result.message() }]); } catch (err) { cb([{ pass: false, message: `Failed to execute expect command "${payload.name}": ${err}` }]); } }; } async _openViteUrl(browser) { const browserInitialize = new Promise((resolve, reject) => { this._socket.once(types_2.BrowserEventNames.initialize, errors => { lodash_1.default.isEmpty(errors) ? resolve() : reject(errors[0]); }); }); const timeout = this._config.urlHttpTimeout || this._config.httpTimeout; const uri = (0, url_join_1.default)(this._config.baseUrl, constants_2.VITE_RUN_UUID_ROUTE, this._runUuid); await Promise.all([ (0, promise_1.promiseTimeout)(browserInitialize, timeout, `Browser didn't connect to the Vite server in ${timeout}ms`), browser.publicAPI.url(uri), ]); } } exports.TestRunner = TestRunner; // eslint-disable-next-line @typescript-eslint/no-explicit-any function transformExpectArg(arg) { if (typeof arg === "object" && "$$typeof" in arg && Object.keys(constants_1.SUPPORTED_ASYMMETRIC_MATCHER).includes(arg.$$typeof)) { const matcherKey = constants_1.SUPPORTED_ASYMMETRIC_MATCHER[arg.$$typeof]; // eslint-disable-next-line @typescript-eslint/no-explicit-any const matcher = arg.inverse ? expect[matcherKey] : expect[matcherKey]; if (!matcher) { throw new Error(`Matcher "${matcherKey}" is not supported by expect-webdriverio`); } return matcher(arg.sample); } return arg; } async function getWdioInstance(session, element) { const wdioInstance = element ? await session.$(element) : session; if (isWdioElement(wdioInstance) && !wdioInstance.selector) { wdioInstance.selector = element?.selector; } return wdioInstance; } function isWdioElement(ctx) { return Boolean(ctx.elementId); } //# sourceMappingURL=index.js.map