"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var workerMain_exports = {}; __export(workerMain_exports, { WorkerMain: () => WorkerMain, create: () => create }); module.exports = __toCommonJS(workerMain_exports); var import_utils = require("playwright-core/lib/utils"); var import_utils2 = require("playwright-core/lib/utils"); var import_configLoader = require("../common/configLoader"); var import_globals = require("../common/globals"); var import_ipc = require("../common/ipc"); var import_util = require("../util"); var import_fixtureRunner = require("./fixtureRunner"); var import_testInfo = require("./testInfo"); var import_util2 = require("./util"); var import_fixtures = require("../common/fixtures"); var import_poolBuilder = require("../common/poolBuilder"); var import_process = require("../common/process"); var import_suiteUtils = require("../common/suiteUtils"); var import_testLoader = require("../common/testLoader"); class WorkerMain extends import_process.ProcessRunner { constructor(params) { super(); // Accumulated fatal errors that cannot be attributed to a test. this._fatalErrors = []; // The stage of the full cleanup. Once "finished", we can safely stop running anything. this._didRunFullCleanup = false; // Whether the worker was requested to stop. this._isStopped = false; // This promise resolves once the single "run test group" call finishes. this._runFinished = new import_utils.ManualPromise(); this._currentTest = null; this._lastRunningTests = []; this._totalRunningTests = 0; // Suites that had their beforeAll hooks, but not afterAll hooks executed. // These suites still need afterAll hooks to be executed for the proper cleanup. // Contains dynamic annotations originated by modifiers with a callback, e.g. `test.skip(() => true)`. this._activeSuites = /* @__PURE__ */ new Map(); process.env.TEST_WORKER_INDEX = String(params.workerIndex); process.env.TEST_PARALLEL_INDEX = String(params.parallelIndex); (0, import_globals.setIsWorkerProcess)(); this._params = params; this._fixtureRunner = new import_fixtureRunner.FixtureRunner(); this._runFinished.resolve(); process.on("unhandledRejection", (reason) => this.unhandledError(reason)); process.on("uncaughtException", (error) => this.unhandledError(error)); process.stdout.write = (chunk, cb) => { this.dispatchEvent("stdOut", (0, import_ipc.stdioChunkToParams)(chunk)); this._currentTest?._tracing.appendStdioToTrace("stdout", chunk); if (typeof cb === "function") process.nextTick(cb); return true; }; if (!process.env.PW_RUNNER_DEBUG) { process.stderr.write = (chunk, cb) => { this.dispatchEvent("stdErr", (0, import_ipc.stdioChunkToParams)(chunk)); this._currentTest?._tracing.appendStdioToTrace("stderr", chunk); if (typeof cb === "function") process.nextTick(cb); return true; }; } } _stop() { if (!this._isStopped) { this._isStopped = true; this._currentTest?._interrupt(); } return this._runFinished; } async gracefullyClose() { try { await this._stop(); if (!this._config) { return; } const fakeTestInfo = new import_testInfo.TestInfoImpl(this._config, this._project, this._params, void 0, 0, () => { }, () => { }, () => { }, () => { }); const runnable = { type: "teardown" }; await fakeTestInfo._runWithTimeout(runnable, () => this._loadIfNeeded()).catch(() => { }); await this._fixtureRunner.teardownScope("test", fakeTestInfo, runnable).catch(() => { }); await this._fixtureRunner.teardownScope("worker", fakeTestInfo, runnable).catch(() => { }); await fakeTestInfo._runWithTimeout(runnable, () => (0, import_utils.gracefullyCloseAll)()).catch(() => { }); this._fatalErrors.push(...fakeTestInfo.errors); } catch (e) { this._fatalErrors.push((0, import_util2.testInfoError)(e)); } if (this._fatalErrors.length) { this._appendProcessTeardownDiagnostics(this._fatalErrors[this._fatalErrors.length - 1]); const payload = { fatalErrors: this._fatalErrors }; this.dispatchEvent("teardownErrors", payload); } } _appendProcessTeardownDiagnostics(error) { if (!this._lastRunningTests.length) return; const count = this._totalRunningTests === 1 ? "1 test" : `${this._totalRunningTests} tests`; let lastMessage = ""; if (this._lastRunningTests.length < this._totalRunningTests) lastMessage = `, last ${this._lastRunningTests.length} tests were`; const message = [ "", "", import_utils2.colors.red(`Failed worker ran ${count}${lastMessage}:`), ...this._lastRunningTests.map((test) => formatTestTitle(test, this._project.project.name)) ].join("\n"); if (error.message) { if (error.stack) { let index = error.stack.indexOf(error.message); if (index !== -1) { index += error.message.length; error.stack = error.stack.substring(0, index) + message + error.stack.substring(index); } } error.message += message; } else if (error.value) { error.value += message; } } unhandledError(error) { if (!this._currentTest) { if (!this._fatalErrors.length) this._fatalErrors.push((0, import_util2.testInfoError)(error)); void this._stop(); return; } if (!this._currentTest._hasUnhandledError) { this._currentTest._hasUnhandledError = true; this._currentTest._failWithError(error); } const isExpectError = error instanceof Error && !!error.matcherResult; const shouldContinueInThisWorker = this._currentTest.expectedStatus === "failed" && isExpectError; if (!shouldContinueInThisWorker) void this._stop(); } async _loadIfNeeded() { if (this._config) return; const config = await (0, import_configLoader.deserializeConfig)(this._params.config); const project = config.projects.find((p) => p.id === this._params.projectId); if (!project) throw new Error(`Project "${this._params.projectId}" not found in the worker process. Make sure project name does not change.`); this._config = config; this._project = project; this._poolBuilder = import_poolBuilder.PoolBuilder.createForWorker(this._project); } async runTestGroup(runPayload) { this._runFinished = new import_utils.ManualPromise(); const entries = new Map(runPayload.entries.map((e) => [e.testId, e])); let fatalUnknownTestIds; try { await this._loadIfNeeded(); const fileSuite = await (0, import_testLoader.loadTestFile)(runPayload.file, this._config.config.rootDir); const suite = (0, import_suiteUtils.bindFileSuiteToProject)(this._project, fileSuite); if (this._params.repeatEachIndex) (0, import_suiteUtils.applyRepeatEachIndex)(this._project, suite, this._params.repeatEachIndex); const hasEntries = (0, import_suiteUtils.filterTestsRemoveEmptySuites)(suite, (test) => entries.has(test.id)); if (hasEntries) { this._poolBuilder.buildPools(suite); this._activeSuites = /* @__PURE__ */ new Map(); this._didRunFullCleanup = false; const tests = suite.allTests(); for (let i = 0; i < tests.length; i++) { if (this._isStopped && this._didRunFullCleanup) break; const entry = entries.get(tests[i].id); entries.delete(tests[i].id); (0, import_util.debugTest)(`test started "${tests[i].title}"`); await this._runTest(tests[i], entry.retry, tests[i + 1]); (0, import_util.debugTest)(`test finished "${tests[i].title}"`); } } else { fatalUnknownTestIds = runPayload.entries.map((e) => e.testId); void this._stop(); } } catch (e) { this._fatalErrors.push((0, import_util2.testInfoError)(e)); void this._stop(); } finally { const donePayload = { fatalErrors: this._fatalErrors, skipTestsDueToSetupFailure: [], fatalUnknownTestIds }; for (const test of this._skipRemainingTestsInSuite?.allTests() || []) { if (entries.has(test.id)) donePayload.skipTestsDueToSetupFailure.push(test.id); } this.dispatchEvent("done", donePayload); this._fatalErrors = []; this._skipRemainingTestsInSuite = void 0; this._runFinished.resolve(); } } resumeAfterStepError(params) { this._currentTest?.resumeAfterStepError(params); } async _runTest(test, retry, nextTest) { const testInfo = new import_testInfo.TestInfoImpl( this._config, this._project, this._params, test, retry, (stepBeginPayload) => this.dispatchEvent("stepBegin", stepBeginPayload), (stepRecoverFromErrorPayload) => this.dispatchEvent("stepRecoverFromError", stepRecoverFromErrorPayload), (stepEndPayload) => this.dispatchEvent("stepEnd", stepEndPayload), (attachment) => this.dispatchEvent("attach", attachment) ); const processAnnotation = (annotation) => { testInfo.annotations.push(annotation); switch (annotation.type) { case "fixme": case "skip": testInfo.expectedStatus = "skipped"; break; case "fail": if (testInfo.expectedStatus !== "skipped") testInfo.expectedStatus = "failed"; break; case "slow": testInfo._timeoutManager.slow(); break; } }; if (!this._isStopped) this._fixtureRunner.setPool(test._pool); const suites = getSuites(test); const reversedSuites = suites.slice().reverse(); const nextSuites = new Set(getSuites(nextTest)); testInfo._timeoutManager.setTimeout(test.timeout); for (const annotation of test.annotations) processAnnotation(annotation); for (const suite of suites) { const extraAnnotations = this._activeSuites.get(suite) || []; for (const annotation of extraAnnotations) processAnnotation(annotation); } this._currentTest = testInfo; (0, import_globals.setCurrentTestInfo)(testInfo); this.dispatchEvent("testBegin", buildTestBeginPayload(testInfo)); const isSkipped = testInfo.expectedStatus === "skipped"; const hasAfterAllToRunBeforeNextTest = reversedSuites.some((suite) => { return this._activeSuites.has(suite) && !nextSuites.has(suite) && suite._hooks.some((hook) => hook.type === "afterAll"); }); if (isSkipped && nextTest && !hasAfterAllToRunBeforeNextTest) { testInfo.status = "skipped"; this.dispatchEvent("testEnd", buildTestEndPayload(testInfo)); return; } this._totalRunningTests++; this._lastRunningTests.push(test); if (this._lastRunningTests.length > 10) this._lastRunningTests.shift(); let shouldRunAfterEachHooks = false; testInfo._allowSkips = true; await (async () => { await testInfo._runWithTimeout({ type: "test" }, async () => { const traceFixtureRegistration = test._pool.resolve("trace"); if (!traceFixtureRegistration) return; if (typeof traceFixtureRegistration.fn === "function") throw new Error(`"trace" option cannot be a function`); await testInfo._tracing.startIfNeeded(traceFixtureRegistration.fn); }); if (this._isStopped || isSkipped) { testInfo.status = "skipped"; return; } await (0, import_utils.removeFolders)([testInfo.outputDir]); let testFunctionParams = null; await testInfo._runAsStep({ title: "Before Hooks", category: "hook" }, async () => { for (const suite of suites) await this._runBeforeAllHooksForSuite(suite, testInfo); shouldRunAfterEachHooks = true; await this._runEachHooksForSuites(suites, "beforeEach", testInfo); testFunctionParams = await this._fixtureRunner.resolveParametersForFunction(test.fn, testInfo, "test", { type: "test" }); }); if (testFunctionParams === null) { return; } await testInfo._runWithTimeout({ type: "test" }, async () => { const fn = test.fn; await fn(testFunctionParams, testInfo); }); })().catch(() => { }); testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed | 0; testInfo._allowSkips = true; const afterHooksTimeout = calculateMaxTimeout(this._project.project.timeout, testInfo.timeout); const afterHooksSlot = { timeout: afterHooksTimeout, elapsed: 0 }; await testInfo._runAsStep({ title: "After Hooks", category: "hook" }, async () => { let firstAfterHooksError; try { await testInfo._runWithTimeout({ type: "test", slot: afterHooksSlot }, async () => testInfo._onDidFinishTestFunction?.()); } catch (error) { firstAfterHooksError = firstAfterHooksError ?? error; } try { if (shouldRunAfterEachHooks) await this._runEachHooksForSuites(reversedSuites, "afterEach", testInfo, afterHooksSlot); } catch (error) { firstAfterHooksError = firstAfterHooksError ?? error; } testInfo._tracing.didFinishTestFunctionAndAfterEachHooks(); try { await this._fixtureRunner.teardownScope("test", testInfo, { type: "test", slot: afterHooksSlot }); } catch (error) { firstAfterHooksError = firstAfterHooksError ?? error; } for (const suite of reversedSuites) { if (!nextSuites.has(suite) || testInfo._isFailure()) { try { await this._runAfterAllHooksForSuite(suite, testInfo); } catch (error) { firstAfterHooksError = firstAfterHooksError ?? error; } } } if (firstAfterHooksError) throw firstAfterHooksError; }).catch(() => { }); if (testInfo._isFailure()) this._isStopped = true; if (this._isStopped) { this._didRunFullCleanup = true; await testInfo._runAsStep({ title: "Worker Cleanup", category: "hook" }, async () => { let firstWorkerCleanupError; const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 }; try { await this._fixtureRunner.teardownScope("test", testInfo, { type: "test", slot: teardownSlot }); } catch (error) { firstWorkerCleanupError = firstWorkerCleanupError ?? error; } for (const suite of reversedSuites) { try { await this._runAfterAllHooksForSuite(suite, testInfo); } catch (error) { firstWorkerCleanupError = firstWorkerCleanupError ?? error; } } try { await this._fixtureRunner.teardownScope("worker", testInfo, { type: "teardown", slot: teardownSlot }); } catch (error) { firstWorkerCleanupError = firstWorkerCleanupError ?? error; } if (firstWorkerCleanupError) throw firstWorkerCleanupError; }).catch(() => { }); } const tracingSlot = { timeout: this._project.project.timeout, elapsed: 0 }; await testInfo._runWithTimeout({ type: "test", slot: tracingSlot }, async () => { await testInfo._tracing.stopIfNeeded(); }).catch(() => { }); testInfo.duration = testInfo._timeoutManager.defaultSlot().elapsed + afterHooksSlot.elapsed | 0; this._currentTest = null; (0, import_globals.setCurrentTestInfo)(null); this.dispatchEvent("testEnd", buildTestEndPayload(testInfo)); const preserveOutput = this._config.config.preserveOutput === "always" || this._config.config.preserveOutput === "failures-only" && testInfo._isFailure(); if (!preserveOutput) await (0, import_utils.removeFolders)([testInfo.outputDir]); } _collectHooksAndModifiers(suite, type, testInfo) { const runnables = []; for (const modifier of suite._modifiers) { const modifierType = this._fixtureRunner.dependsOnWorkerFixturesOnly(modifier.fn, modifier.location) ? "beforeAll" : "beforeEach"; if (modifierType !== type) continue; const fn = async (fixtures) => { const result = await modifier.fn(fixtures); testInfo._modifier(modifier.type, modifier.location, [!!result, modifier.description]); }; (0, import_fixtures.inheritFixtureNames)(modifier.fn, fn); runnables.push({ title: `${modifier.type} modifier`, location: modifier.location, type: modifier.type, fn }); } runnables.push(...suite._hooks.filter((hook) => hook.type === type)); return runnables; } async _runBeforeAllHooksForSuite(suite, testInfo) { if (this._activeSuites.has(suite)) return; const extraAnnotations = []; this._activeSuites.set(suite, extraAnnotations); await this._runAllHooksForSuite(suite, testInfo, "beforeAll", extraAnnotations); } async _runAllHooksForSuite(suite, testInfo, type, extraAnnotations) { let firstError; for (const hook of this._collectHooksAndModifiers(suite, type, testInfo)) { try { await testInfo._runAsStep({ title: hook.title, category: "hook", location: hook.location }, async () => { const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 }; const runnable = { type: hook.type, slot: timeSlot, location: hook.location }; const existingAnnotations = new Set(testInfo.annotations); try { await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, "all-hooks-only", runnable); } finally { if (extraAnnotations) { const newAnnotations = testInfo.annotations.filter((a) => !existingAnnotations.has(a)); extraAnnotations.push(...newAnnotations); } await this._fixtureRunner.teardownScope("test", testInfo, runnable); } }); } catch (error) { firstError = firstError ?? error; if (type === "beforeAll" && error instanceof import_testInfo.TestSkipError) break; if (type === "beforeAll" && !this._skipRemainingTestsInSuite) { this._skipRemainingTestsInSuite = suite; } } } if (firstError) throw firstError; } async _runAfterAllHooksForSuite(suite, testInfo) { if (!this._activeSuites.has(suite)) return; this._activeSuites.delete(suite); await this._runAllHooksForSuite(suite, testInfo, "afterAll"); } async _runEachHooksForSuites(suites, type, testInfo, slot) { let firstError; const hooks = suites.map((suite) => this._collectHooksAndModifiers(suite, type, testInfo)).flat(); for (const hook of hooks) { const runnable = { type: hook.type, location: hook.location, slot }; if (testInfo._timeoutManager.isTimeExhaustedFor(runnable)) { continue; } try { await testInfo._runAsStep({ title: hook.title, category: "hook", location: hook.location }, async () => { await this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, "test", runnable); }); } catch (error) { firstError = firstError ?? error; if (error instanceof import_testInfo.TestSkipError) break; } } if (firstError) throw firstError; } } function buildTestBeginPayload(testInfo) { return { testId: testInfo.testId, startWallTime: testInfo._startWallTime }; } function buildTestEndPayload(testInfo) { return { testId: testInfo.testId, duration: testInfo.duration, status: testInfo.status, errors: testInfo.errors, hasNonRetriableError: testInfo._hasNonRetriableError, expectedStatus: testInfo.expectedStatus, annotations: testInfo.annotations, timeout: testInfo.timeout }; } function getSuites(test) { const suites = []; for (let suite = test?.parent; suite; suite = suite.parent) suites.push(suite); suites.reverse(); return suites; } function formatTestTitle(test, projectName) { const [, ...titles] = test.titlePath(); const location = `${(0, import_util.relativeFilePath)(test.location.file)}:${test.location.line}:${test.location.column}`; const projectTitle = projectName ? `[${projectName}] \u203A ` : ""; return `${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`; } function calculateMaxTimeout(t1, t2) { return !t1 || !t2 ? 0 : Math.max(t1, t2); } const create = (params) => new WorkerMain(params); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { WorkerMain, create });