"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; 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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // packages/playwright/src/runner/index.ts var index_exports = {}; __export(index_exports, { ListModeReporter: () => listModeReporter_default, ListReporter: () => list_default, TestServerConnection: () => TestServerConnection, base: () => base_exports, html: () => html_exports, merge: () => merge_exports, projectUtils: () => projectUtils_exports, runnerReporters: () => reporters_exports, testRunner: () => testRunner_exports, testServer: () => testServer_exports, watchMode: () => watchMode_exports, webServer: () => webServer }); module.exports = __toCommonJS(index_exports); // packages/playwright/src/runner/testRunner.ts var testRunner_exports = {}; __export(testRunner_exports, { TestRunner: () => TestRunner, TestRunnerEvent: () => TestRunnerEvent, runAllTestsWithConfig: () => runAllTestsWithConfig }); var import_events2 = __toESM(require("events")); var import_fs13 = __toESM(require("fs")); var import_path17 = __toESM(require("path")); var import_coreBundle2 = require("playwright-core/lib/coreBundle"); var import_common9 = require("../common"); // packages/playwright/src/runner/fsWatcher.ts var chokidar = require("playwright-core/lib/utilsBundle").chokidar; var FSWatcher = class { constructor(onChange) { this._watchedPaths = []; this._ignoredFolders = []; this._collector = []; this._onChange = onChange; } async update(watchedPaths, ignoredFolders, reportPending) { if (JSON.stringify([this._watchedPaths, this._ignoredFolders]) === JSON.stringify([watchedPaths, ignoredFolders])) return; if (reportPending) this._reportEventsIfAny(); this._watchedPaths = watchedPaths; this._ignoredFolders = ignoredFolders; void this._fsWatcher?.close(); this._fsWatcher = void 0; this._collector.length = 0; clearTimeout(this._throttleTimer); this._throttleTimer = void 0; if (!this._watchedPaths.length) return; const ignored = [...this._ignoredFolders, "**/node_modules/**"]; this._fsWatcher = chokidar.watch(watchedPaths, { ignoreInitial: true, ignored }).on("all", async (event, file) => { if (this._throttleTimer) clearTimeout(this._throttleTimer); this._collector.push({ event, file }); this._throttleTimer = setTimeout(() => this._reportEventsIfAny(), 250); }); await new Promise((resolve, reject) => this._fsWatcher.once("ready", resolve).once("error", reject)); } async close() { await this._fsWatcher?.close(); } _reportEventsIfAny() { if (this._collector.length) this._onChange(this._collector.slice()); this._collector.length = 0; } }; // packages/playwright/src/isomorphic/teleReceiver.ts var TeleReporterReceiver = class { constructor(reporter, options = {}) { this.isListing = false; this._tests = /* @__PURE__ */ new Map(); this._rootSuite = new TeleSuite("", "root"); this._options = options; this._reporter = reporter; } reset() { this._rootSuite._entries = []; this._tests.clear(); } dispatch(message) { const { method, params } = message; if (method === "onConfigure") { this._onConfigure(params.config); return; } if (method === "onProject") { this._onProject(params.project); return; } if (method === "onBegin") { this._onBegin(); return; } if (method === "onTestBegin") { this._onTestBegin(params.testId, params.result); return; } if (method === "onTestPaused") { this._onTestPaused(params.testId, params.resultId, params.errors); return; } if (method === "onTestEnd") { this._onTestEnd(params.test, params.result); return; } if (method === "onStepBegin") { this._onStepBegin(params.testId, params.resultId, params.step); return; } if (method === "onAttach") { this._onAttach(params.testId, params.resultId, params.attachments); return; } if (method === "onStepEnd") { this._onStepEnd(params.testId, params.resultId, params.step); return; } if (method === "onError") { this._onError(params.error, params.workerInfo); return; } if (method === "onStdIO") { this._onStdIO(params.type, params.testId, params.resultId, params.data, params.isBase64); return; } if (method === "onEnd") return this._onEnd(params.result); if (method === "onExit") return this._onExit(); } _onConfigure(config2) { this._rootDir = config2.rootDir; this._config = this._parseConfig(config2); this._reporter.onConfigure?.(this._config); } _onProject(project) { let projectSuite = this._options.mergeProjects ? this._rootSuite.suites.find((suite) => suite.project().name === project.name) : void 0; if (!projectSuite) { projectSuite = new TeleSuite(project.name, "project"); this._rootSuite._addSuite(projectSuite); } const parsed = this._parseProject(project); projectSuite._project = parsed; let index = -1; if (this._options.mergeProjects) index = this._config.projects.findIndex((p) => p.name === project.name); if (index === -1) this._config.projects.push(parsed); else this._config.projects[index] = parsed; for (const suite of project.suites) this._mergeSuiteInto(suite, projectSuite); } _onBegin() { this._reporter.onBegin?.(this._rootSuite); } _onTestBegin(testId, payload) { const test = this._tests.get(testId); if (this._options.clearPreviousResultsWhenTestBegins) test.results = []; const testResult = test._createTestResult(payload.id); testResult.retry = payload.retry; testResult.workerIndex = payload.workerIndex; testResult.parallelIndex = payload.parallelIndex; testResult.setStartTimeNumber(payload.startTime); this._reporter.onTestBegin?.(test, testResult); } _onTestPaused(testId, resultId, errors) { const test = this._tests.get(testId); const result = test.results.find((r) => r._id === resultId); result.errors.push(...errors); result.error = result.errors[0]; void this._reporter.onTestPaused?.(test, result); } _onTestEnd(testEndPayload, payload) { const test = this._tests.get(testEndPayload.testId); test.timeout = testEndPayload.timeout; test.expectedStatus = testEndPayload.expectedStatus; const result = test.results.find((r) => r._id === payload.id); result.duration = payload.duration; result.status = payload.status; result.errors.push(...payload.errors ?? []); result.error = result.errors[0]; if (!!payload.attachments) result.attachments = this._parseAttachments(payload.attachments); if (payload.annotations) { this._absoluteAnnotationLocationsInplace(payload.annotations); result.annotations = payload.annotations; test.annotations = payload.annotations; } this._reporter.onTestEnd?.(test, result); result._stepMap = /* @__PURE__ */ new Map(); } _onStepBegin(testId, resultId, payload) { const test = this._tests.get(testId); const result = test.results.find((r) => r._id === resultId); const parentStep = payload.parentStepId ? result._stepMap.get(payload.parentStepId) : void 0; const location = this._absoluteLocation(payload.location); const step = new TeleTestStep(payload, parentStep, location, result); if (parentStep) parentStep.steps.push(step); else result.steps.push(step); result._stepMap.set(payload.id, step); this._reporter.onStepBegin?.(test, result, step); } _onStepEnd(testId, resultId, payload) { const test = this._tests.get(testId); const result = test.results.find((r) => r._id === resultId); const step = result._stepMap.get(payload.id); step._endPayload = payload; step.duration = payload.duration; step.error = payload.error; this._reporter.onStepEnd?.(test, result, step); } _onAttach(testId, resultId, attachments) { const test = this._tests.get(testId); const result = test.results.find((r) => r._id === resultId); result.attachments.push(...attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path, body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0 }))); } _onError(error, workerInfo) { let fullWorkerInfo; if (workerInfo) { const project = this._config.projects.find((p) => p.name === workerInfo.projectName); if (project) { fullWorkerInfo = { workerIndex: workerInfo.workerIndex, parallelIndex: workerInfo.parallelIndex, config: this._config, project }; } } this._reporter.onError?.(error, fullWorkerInfo); } _onStdIO(type, testId, resultId, data, isBase64) { const chunk = isBase64 ? globalThis.Buffer ? Buffer.from(data, "base64") : atob(data) : data; const test = testId ? this._tests.get(testId) : void 0; const result = test && resultId ? test.results.find((r) => r._id === resultId) : void 0; if (type === "stdout") { result?.stdout.push(chunk); this._reporter.onStdOut?.(chunk, test, result); } else { result?.stderr.push(chunk); this._reporter.onStdErr?.(chunk, test, result); } } async _onEnd(result) { await this._reporter.onEnd?.(asFullResult(result)); } _onExit() { return this._reporter.onExit?.(); } _parseConfig(config2) { const result = asFullConfig(config2); if (this._options.configOverrides) { result.configFile = this._options.configOverrides.configFile; result.reportSlowTests = this._options.configOverrides.reportSlowTests; result.quiet = this._options.configOverrides.quiet; result.reporter = [...this._options.configOverrides.reporter]; } return result; } _parseProject(project) { return { metadata: project.metadata, name: project.name, outputDir: this._absolutePath(project.outputDir), repeatEach: project.repeatEach, retries: project.retries, testDir: this._absolutePath(project.testDir), testIgnore: parseRegexPatterns(project.testIgnore), testMatch: parseRegexPatterns(project.testMatch), timeout: project.timeout, grep: parseRegexPatterns(project.grep), grepInvert: parseRegexPatterns(project.grepInvert), dependencies: project.dependencies, teardown: project.teardown, snapshotDir: this._absolutePath(project.snapshotDir), ignoreSnapshots: project.ignoreSnapshots ?? false, use: project.use }; } _parseAttachments(attachments) { return attachments.map((a) => { return { ...a, body: a.base64 && globalThis.Buffer ? Buffer.from(a.base64, "base64") : void 0 }; }); } _mergeSuiteInto(jsonSuite, parent) { let targetSuite = parent.suites.find((s) => s.title === jsonSuite.title); if (!targetSuite) { targetSuite = new TeleSuite(jsonSuite.title, parent.type === "project" ? "file" : "describe"); parent._addSuite(targetSuite); } targetSuite.location = this._absoluteLocation(jsonSuite.location); jsonSuite.entries.forEach((e) => { if ("testId" in e) this._mergeTestInto(e, targetSuite); else this._mergeSuiteInto(e, targetSuite); }); } _mergeTestInto(jsonTest, parent) { let targetTest = this._options.mergeTestCases ? parent.tests.find((s) => s.title === jsonTest.title && s.repeatEachIndex === jsonTest.repeatEachIndex) : void 0; if (!targetTest) { targetTest = new TeleTestCase(jsonTest.testId, jsonTest.title, this._absoluteLocation(jsonTest.location), jsonTest.repeatEachIndex); parent._addTest(targetTest); this._tests.set(targetTest.id, targetTest); } this._updateTest(jsonTest, targetTest); } _updateTest(payload, test) { test.id = payload.testId; test.location = this._absoluteLocation(payload.location); test.retries = payload.retries; test.tags = payload.tags ?? []; test.annotations = payload.annotations ?? []; this._absoluteAnnotationLocationsInplace(test.annotations); return test; } _absoluteAnnotationLocationsInplace(annotations) { for (const annotation of annotations) { if (annotation.location) annotation.location = this._absoluteLocation(annotation.location); } } _absoluteLocation(location) { if (!location) return location; return { ...location, file: this._absolutePath(location.file) }; } _absolutePath(relativePath) { if (relativePath === void 0) return; return this._options.resolvePath ? this._options.resolvePath(this._rootDir, relativePath) : this._rootDir + "/" + relativePath; } }; var TeleSuite = class { constructor(title, type) { this._entries = []; this._requireFile = ""; this._parallelMode = "none"; this.title = title; this._type = type; } get type() { return this._type; } get suites() { return this._entries.filter((e) => e.type !== "test"); } get tests() { return this._entries.filter((e) => e.type === "test"); } entries() { return this._entries; } allTests() { const result = []; const visit = (suite) => { for (const entry of suite.entries()) { if (entry.type === "test") result.push(entry); else visit(entry); } }; visit(this); return result; } titlePath() { const titlePath = this.parent ? this.parent.titlePath() : []; if (this.title || this._type !== "describe") titlePath.push(this.title); return titlePath; } project() { return this._project ?? this.parent?.project(); } _addTest(test) { test.parent = this; this._entries.push(test); } _addSuite(suite) { suite.parent = this; this._entries.push(suite); } }; var TeleTestCase = class { constructor(id, title, location, repeatEachIndex) { this.fn = () => { }; this.results = []; this.type = "test"; this.expectedStatus = "passed"; this.timeout = 0; this.annotations = []; this.retries = 0; this.tags = []; this.repeatEachIndex = 0; this.id = id; this.title = title; this.location = location; this.repeatEachIndex = repeatEachIndex; } titlePath() { const titlePath = this.parent ? this.parent.titlePath() : []; titlePath.push(this.title); return titlePath; } outcome() { return computeTestCaseOutcome(this); } ok() { const status = this.outcome(); return status === "expected" || status === "flaky" || status === "skipped"; } _createTestResult(id) { const result = new TeleTestResult(this.results.length, id); this.results.push(result); return result; } }; var TeleTestStep = class { constructor(payload, parentStep, location, result) { this.duration = -1; this.steps = []; this._startTime = 0; this.title = payload.title; this.category = payload.category; this.location = location; this.parent = parentStep; this._startTime = payload.startTime; this._result = result; } titlePath() { const parentPath = this.parent?.titlePath() || []; return [...parentPath, this.title]; } get startTime() { return new Date(this._startTime); } set startTime(value) { this._startTime = +value; } get attachments() { return this._endPayload?.attachments?.map((index) => this._result.attachments[index]) ?? []; } get annotations() { return this._endPayload?.annotations ?? []; } }; var TeleTestResult = class { constructor(retry, id) { this.parallelIndex = -1; this.workerIndex = -1; this.duration = -1; this.stdout = []; this.stderr = []; this.attachments = []; this.annotations = []; this.status = "skipped"; this.steps = []; this.errors = []; this._stepMap = /* @__PURE__ */ new Map(); this._startTime = 0; this.retry = retry; this._id = id; } setStartTimeNumber(startTime) { this._startTime = startTime; } get startTime() { return new Date(this._startTime); } set startTime(value) { this._startTime = +value; } }; var baseFullConfig = { forbidOnly: false, fullyParallel: false, globalSetup: null, globalTeardown: null, globalTimeout: 0, grep: /.*/, grepInvert: null, maxFailures: 0, metadata: {}, preserveOutput: "always", projects: [], reporter: [[process.env.CI ? "dot" : "list"]], reportSlowTests: { max: 5, threshold: 3e5 /* 5 minutes */ }, configFile: "", rootDir: "", quiet: false, shard: null, tags: [], updateSnapshots: "missing", updateSourceMethod: "patch", version: "", workers: 0, webServer: null }; function serializeRegexPatterns(patterns) { if (!Array.isArray(patterns)) patterns = [patterns]; return patterns.map((s) => { if (typeof s === "string") return { s }; return { r: { source: s.source, flags: s.flags } }; }); } function parseRegexPatterns(patterns) { return patterns.map((p) => { if (p.s !== void 0) return p.s; return new RegExp(p.r.source, p.r.flags); }); } function computeTestCaseOutcome(test) { let skipped = 0; let didNotRun = 0; let expected = 0; let interrupted = 0; let unexpected = 0; for (const result of test.results) { if (result.status === "interrupted") { ++interrupted; } else if (result.status === "skipped" && test.expectedStatus === "skipped") { ++skipped; } else if (result.status === "skipped") { ++didNotRun; } else if (result.status === test.expectedStatus) { ++expected; } else { ++unexpected; } } if (expected === 0 && unexpected === 0) return "skipped"; if (unexpected === 0) return "expected"; if (expected === 0 && skipped === 0) return "unexpected"; return "flaky"; } function asFullResult(result) { return { status: result.status, startTime: new Date(result.startTime), duration: result.duration }; } function asFullConfig(config2) { return { ...baseFullConfig, ...config2 }; } // packages/playwright/src/plugins/gitCommitInfoPlugin.ts var fs = __toESM(require("fs")); var { monotonicTime } = require("playwright-core/lib/coreBundle").iso; var { spawnAsync } = require("playwright-core/lib/coreBundle").utils; var GIT_OPERATIONS_TIMEOUT_MS = 3e3; var addGitCommitInfoPlugin = (fullConfig) => { fullConfig.plugins.push({ factory: gitCommitInfoPlugin.bind(null, fullConfig) }); }; function print(s, ...args) { console.log("GitCommitInfo: " + s, ...args); } function debug(s, ...args) { if (!process.env.DEBUG_GIT_COMMIT_INFO) return; print(s, ...args); } var gitCommitInfoPlugin = (fullConfig) => { return { name: "playwright:git-commit-info", setup: async (config2, configDir) => { const metadata = config2.metadata; const ci = await ciInfo(); if (!metadata.ci && ci) { debug("ci info", ci); metadata.ci = ci; } if (fullConfig.captureGitInfo?.commit || fullConfig.captureGitInfo?.commit === void 0 && ci) { const git = await gitCommitInfo(configDir).catch((e) => print("failed to get git commit info", e)); if (git) { debug("commit info", git); metadata.gitCommit = git; } } if (fullConfig.captureGitInfo?.diff || fullConfig.captureGitInfo?.diff === void 0 && ci) { const diffResult = await gitDiff(configDir, ci).catch((e) => print("failed to get git diff", e)); if (diffResult) { debug(`diff length ${diffResult.length}`); metadata.gitDiff = diffResult; } } } }; }; async function ciInfo() { if (process.env.GITHUB_ACTIONS) { let pr; try { const json = JSON.parse(await fs.promises.readFile(process.env.GITHUB_EVENT_PATH, "utf8")); pr = { title: json.pull_request.title, number: json.pull_request.number, baseHash: json.pull_request.base.sha }; } catch { } return { commitHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/commit/${process.env.GITHUB_SHA}`, commitHash: process.env.GITHUB_SHA, prHref: pr ? `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/pull/${pr.number}` : void 0, prTitle: pr?.title, prBaseHash: pr?.baseHash, buildHref: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}` }; } if (process.env.GITLAB_CI) { return { commitHref: `${process.env.CI_PROJECT_URL}/-/commit/${process.env.CI_COMMIT_SHA}`, commitHash: process.env.CI_COMMIT_SHA, buildHref: process.env.CI_JOB_URL, branch: process.env.CI_COMMIT_REF_NAME }; } if (process.env.JENKINS_URL && process.env.BUILD_URL) { return { commitHref: process.env.BUILD_URL, commitHash: process.env.GIT_COMMIT, branch: process.env.GIT_BRANCH }; } } async function gitCommitInfo(gitDir) { const separator2 = `---786eec917292---`; const tokens = [ "%H", // commit hash "%h", // abbreviated commit hash "%s", // subject "%B", // raw body (unwrapped subject and body) "%an", // author name "%ae", // author email "%at", // author date, UNIX timestamp "%cn", // committer name "%ce", // committer email "%ct", // committer date, UNIX timestamp "" // branch ]; const output = await runGit(`git log -1 --pretty=format:"${tokens.join(separator2)}" && git rev-parse --abbrev-ref HEAD`, gitDir); if (!output) return void 0; const [hash, shortHash, subject, body, authorName, authorEmail, authorTime, committerName, committerEmail, committerTime, branch] = output.split(separator2); return { shortHash, hash, subject, body, author: { name: authorName, email: authorEmail, time: +authorTime * 1e3 }, committer: { name: committerName, email: committerEmail, time: +committerTime * 1e3 }, branch: branch.trim() }; } async function gitDiff(gitDir, ci) { const diffLimit = 1e5; if (ci?.prBaseHash) { await runGit(`git fetch origin ${ci.prBaseHash} --depth=1 --no-auto-maintenance --no-auto-gc --no-tags --no-recurse-submodules`, gitDir); const diff3 = await runGit(`git diff ${ci.prBaseHash} HEAD`, gitDir); if (diff3) return diff3.substring(0, diffLimit); } if (ci) return; const uncommitted = await runGit("git diff", gitDir); if (uncommitted === void 0) { return; } if (uncommitted) return uncommitted.substring(0, diffLimit); const diff2 = await runGit("git diff HEAD~1", gitDir); return diff2?.substring(0, diffLimit); } async function runGit(command, cwd) { debug(`running "${command}"`); const start = monotonicTime(); const result = await spawnAsync( command, [], { stdio: "pipe", cwd, timeout: GIT_OPERATIONS_TIMEOUT_MS, shell: true } ); if (monotonicTime() - start > GIT_OPERATIONS_TIMEOUT_MS) { print(`timeout of ${GIT_OPERATIONS_TIMEOUT_MS}ms exceeded while running "${command}"`); return; } if (result.code) debug(`failure, code=${result.code} ${result.stderr}`); else debug(`success`); return result.code ? void 0 : result.stdout.trim(); } // packages/playwright/src/plugins/webServerPlugin.ts var import_net = __toESM(require("net")); var import_path = __toESM(require("path")); var colors = require("playwright-core/lib/utilsBundle").colors; var debug2 = require("playwright-core/lib/utilsBundle").debug; var { ManualPromise } = require("playwright-core/lib/coreBundle").iso; var { monotonicTime: monotonicTime2 } = require("playwright-core/lib/coreBundle").iso; var { raceAgainstDeadline } = require("playwright-core/lib/coreBundle").iso; var { isURLAvailable } = require("playwright-core/lib/coreBundle").utils; var { launchProcess } = require("playwright-core/lib/coreBundle").utils; var DEFAULT_ENVIRONMENT_VARIABLES = { "BROWSER": "none", // Disable that create-react-app will open the page in the browser "FORCE_COLOR": "1", "DEBUG_COLORS": "1" }; var debugWebServer = debug2("pw:webserver"); var WebServerPlugin = class { constructor(options, checkPortOnly) { this.name = "playwright:webserver"; this._options = options; this._checkPortOnly = checkPortOnly; } async setup(config2, configDir, reporter) { this._reporter = reporter; if (this._options.url) this._isAvailableCallback = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter)); this._options.cwd = this._options.cwd ? import_path.default.resolve(configDir, this._options.cwd) : configDir; try { await this._startProcess(); await this._waitForProcess(); } catch (error) { await this.teardown(); throw error; } } async teardown() { debugWebServer(`Terminating the WebServer`); await this._killProcess?.(); debugWebServer(`Terminated the WebServer`); } async _startProcess() { let processExitedReject = (error) => { }; this._processExitedPromise = new Promise((_, reject) => processExitedReject = reject); const isAlreadyAvailable = await this._isAvailableCallback?.(); if (isAlreadyAvailable) { debugWebServer(`WebServer is already available`); if (this._options.reuseExistingServer) return; const port = new URL(this._options.url).port; throw new Error(`${this._options.url ?? `http://localhost${port ? ":" + port : ""}`} is already used, make sure that nothing is running on the port/url or set reuseExistingServer:true in config.webServer.`); } if (!this._options.command) throw new Error("config.webServer.command cannot be empty"); debugWebServer(`Starting WebServer process ${this._options.command}...`); const { launchedProcess, gracefullyClose } = await launchProcess({ command: this._options.command, env: { ...DEFAULT_ENVIRONMENT_VARIABLES, ...process.env, ...this._options.env }, cwd: this._options.cwd, stdio: "stdin", shell: true, attemptToGracefullyClose: async () => { if (process.platform === "win32") throw new Error("Graceful shutdown is not supported on Windows"); if (!this._options.gracefulShutdown) throw new Error("skip graceful shutdown"); const { signal, timeout = 0 } = this._options.gracefulShutdown; process.kill(-launchedProcess.pid, signal); return new Promise((resolve, reject) => { const timer = timeout !== 0 ? setTimeout(() => reject(new Error(`process didn't close gracefully within timeout`)), timeout) : void 0; launchedProcess.once("close", (...args) => { clearTimeout(timer); resolve(); }); }); }, log: () => { }, onExit: (code) => processExitedReject(new Error(code ? `Process from config.webServer was not able to start. Exit code: ${code}` : "Process from config.webServer exited early.")), tempDirectories: [] }); this._killProcess = gracefullyClose; debugWebServer(`Process started`); if (this._options.wait?.stdout || this._options.wait?.stderr) this._waitForStdioPromise = new ManualPromise(); const stdioWaitCollectors = { stdout: this._options.wait?.stdout ? "" : void 0, stderr: this._options.wait?.stderr ? "" : void 0 }; launchedProcess.stdout.on("data", (data) => { if (debugWebServer.enabled || this._options.stdout === "pipe") this._reporter.onStdOut?.(prefixOutputLines(data.toString(), this._options.name)); }); launchedProcess.stderr.on("data", (data) => { if (debugWebServer.enabled || (this._options.stderr === "pipe" || !this._options.stderr)) this._reporter.onStdErr?.(prefixOutputLines(data.toString(), this._options.name)); }); const resolveStdioPromise = () => { stdioWaitCollectors.stdout = void 0; stdioWaitCollectors.stderr = void 0; this._waitForStdioPromise?.resolve(); }; for (const stdio of ["stdout", "stderr"]) { launchedProcess[stdio].on("data", (data) => { if (!this._options.wait?.[stdio] || stdioWaitCollectors[stdio] === void 0) return; stdioWaitCollectors[stdio] += data.toString(); this._options.wait[stdio].lastIndex = 0; const result = this._options.wait[stdio].exec(stdioWaitCollectors[stdio]); if (result) { for (const [key, value] of Object.entries(result.groups || {})) process.env[key.toUpperCase()] = value; resolveStdioPromise(); } }); } } async _waitForProcess() { if (!this._isAvailableCallback && !this._waitForStdioPromise) { this._processExitedPromise.catch(() => { }); return; } debugWebServer(`Waiting for availability...`); const launchTimeout = this._options.timeout || 60 * 1e3; const cancellationToken = { canceled: false }; const deadline = monotonicTime2() + launchTimeout; const racingPromises = [this._processExitedPromise]; if (this._isAvailableCallback) racingPromises.push(raceAgainstDeadline(() => waitFor(this._isAvailableCallback, cancellationToken), deadline)); if (this._waitForStdioPromise) racingPromises.push(raceAgainstDeadline(() => this._waitForStdioPromise, deadline)); const { timedOut } = await Promise.race(racingPromises); cancellationToken.canceled = true; if (timedOut) throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`); debugWebServer(`WebServer available`); } }; async function isPortUsed(port) { const innerIsPortUsed = (host) => new Promise((resolve) => { const conn = import_net.default.connect(port, host).on("error", () => { resolve(false); }).on("connect", () => { conn.end(); resolve(true); }); }); return new Promise((resolve) => { let pending = 2; const onResult = (result) => { if (result) resolve(true); else if (--pending === 0) resolve(false); }; void innerIsPortUsed("127.0.0.1").then(onResult); void innerIsPortUsed("::1").then(onResult); }); } async function waitFor(waitFn, cancellationToken) { const logScale = [100, 250, 500]; while (!cancellationToken.canceled) { const connected = await waitFn(); if (connected) return; const delay = logScale.shift() || 1e3; debugWebServer(`Waiting ${delay}ms`); await new Promise((x) => setTimeout(x, delay)); } } function getIsAvailableFunction(url, checkPortOnly, ignoreHTTPSErrors, onStdErr) { const urlObject = new URL(url); if (!checkPortOnly) return () => isURLAvailable(urlObject, ignoreHTTPSErrors, debugWebServer, onStdErr); const port = urlObject.port; return () => isPortUsed(+port); } var webServer = (options) => { return new WebServerPlugin(options, false); }; var webServerPluginsForConfig = (config2) => { const shouldSetBaseUrl = !!config2.config.webServer; const webServerPlugins = []; for (const webServerConfig of config2.webServers) { if (webServerConfig.port && webServerConfig.url) throw new Error(`Either 'port' or 'url' should be specified in config.webServer.`); let url; if (webServerConfig.port || webServerConfig.url) { url = webServerConfig.url || `http://localhost:${webServerConfig.port}`; if (shouldSetBaseUrl && !webServerConfig.url) process.env.PLAYWRIGHT_TEST_BASE_URL = url; } webServerPlugins.push(new WebServerPlugin({ ...webServerConfig, url }, webServerConfig.port !== void 0)); } return webServerPlugins; }; function prefixOutputLines(output, prefixName = "WebServer") { const lastIsNewLine = output[output.length - 1] === "\n"; let lines = output.split("\n"); if (lastIsNewLine) lines.pop(); lines = lines.map((line) => colors.dim(`[${prefixName}] `) + line); if (lastIsNewLine) lines.push(""); return lines.join("\n"); } // packages/playwright/src/reporters/base.ts var base_exports = {}; __export(base_exports, { TerminalReporter: () => TerminalReporter, formatError: () => formatError, formatFailure: () => formatFailure, formatResultFailure: () => formatResultFailure, formatRetry: () => formatRetry, internalScreen: () => internalScreen, kOutputSymbol: () => kOutputSymbol, markErrorsAsReported: () => markErrorsAsReported, nonTerminalScreen: () => nonTerminalScreen, prepareErrorStack: () => prepareErrorStack, relativeFilePath: () => relativeFilePath, resolveOutputFile: () => resolveOutputFile, separator: () => separator, stepSuffix: () => stepSuffix, terminalScreen: () => terminalScreen }); var import_path2 = __toESM(require("path")); var import_util = require("../util"); var realColors = require("playwright-core/lib/utilsBundle").colors; var { noColors } = require("playwright-core/lib/coreBundle").iso; var { msToString } = require("playwright-core/lib/coreBundle").iso; var { parseErrorStack } = require("playwright-core/lib/coreBundle").iso; var { getPackageManagerExecCommand } = require("playwright-core/lib/coreBundle").utils; var { fitToWidth } = require("playwright-core/lib/coreBundle").utils; var kOutputSymbol = Symbol("output"); var DEFAULT_TTY_WIDTH = 100; var DEFAULT_TTY_HEIGHT = 40; var originalProcessStdout = process.stdout; var originalProcessStderr = process.stderr; var terminalScreen = (() => { let isTTY = !!originalProcessStdout.isTTY; let ttyWidth = originalProcessStdout.columns || 0; let ttyHeight = originalProcessStdout.rows || 0; if (process.env.PLAYWRIGHT_FORCE_TTY === "false" || process.env.PLAYWRIGHT_FORCE_TTY === "0") { isTTY = false; ttyWidth = 0; ttyHeight = 0; } else if (process.env.PLAYWRIGHT_FORCE_TTY === "true" || process.env.PLAYWRIGHT_FORCE_TTY === "1") { isTTY = true; ttyWidth = originalProcessStdout.columns || DEFAULT_TTY_WIDTH; ttyHeight = originalProcessStdout.rows || DEFAULT_TTY_HEIGHT; } else if (process.env.PLAYWRIGHT_FORCE_TTY) { isTTY = true; const sizeMatch = process.env.PLAYWRIGHT_FORCE_TTY.match(/^(\d+)x(\d+)$/); if (sizeMatch) { ttyWidth = +sizeMatch[1]; ttyHeight = +sizeMatch[2]; } else { ttyWidth = +process.env.PLAYWRIGHT_FORCE_TTY; ttyHeight = DEFAULT_TTY_HEIGHT; } if (isNaN(ttyWidth)) ttyWidth = DEFAULT_TTY_WIDTH; if (isNaN(ttyHeight)) ttyHeight = DEFAULT_TTY_HEIGHT; } let useColors = isTTY; if (process.env.DEBUG_COLORS === "0" || process.env.DEBUG_COLORS === "false" || process.env.FORCE_COLOR === "0" || process.env.FORCE_COLOR === "false") useColors = false; else if (process.env.DEBUG_COLORS || process.env.FORCE_COLOR) useColors = true; const colors7 = useColors ? realColors : noColors; return { resolveFiles: "cwd", isTTY, ttyWidth, ttyHeight, colors: colors7, stdout: originalProcessStdout, stderr: originalProcessStderr }; })(); var nonTerminalScreen = { colors: terminalScreen.colors, isTTY: false, ttyWidth: 0, ttyHeight: 0, resolveFiles: "rootDir" }; var internalScreen = { colors: realColors, isTTY: false, ttyWidth: 0, ttyHeight: 0, resolveFiles: "rootDir" }; var TerminalReporter = class { constructor(options = {}) { this.totalTestCount = 0; this.fileDurations = /* @__PURE__ */ new Map(); this._fatalErrors = []; this._failureCount = 0; this.screen = options.screen ?? terminalScreen; this._options = options; } version() { return "v2"; } onConfigure(config2) { this.config = config2; } onBegin(suite) { this.suite = suite; this.totalTestCount = suite.allTests().length; } onStdOut(chunk, test, result) { this._appendOutput({ chunk, type: "stdout" }, result); } onStdErr(chunk, test, result) { this._appendOutput({ chunk, type: "stderr" }, result); } _appendOutput(output, result) { if (!result) return; result[kOutputSymbol] = result[kOutputSymbol] || []; result[kOutputSymbol].push(output); } onTestEnd(test, result) { if (result.status !== "skipped" && result.status !== test.expectedStatus) ++this._failureCount; const projectName = test.titlePath()[1]; const relativePath = relativeTestPath(this.screen, this.config, test); const fileAndProject = (projectName ? `[${projectName}] \u203A ` : "") + relativePath; const entry = this.fileDurations.get(fileAndProject) || { duration: 0, workers: /* @__PURE__ */ new Set() }; entry.duration += result.duration; entry.workers.add(result.workerIndex); this.fileDurations.set(fileAndProject, entry); } onError(error) { this._fatalErrors.push(error); } async onEnd(result) { this.result = result; } fitToScreen(line, prefix) { if (!this.screen.ttyWidth) { return line; } return fitToWidth(line, this.screen.ttyWidth, prefix); } generateStartingMessage() { const jobs = this.config.metadata.actualWorkers ?? this.config.workers; const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : ""; if (!this.totalTestCount) return ""; return "\n" + this.screen.colors.dim("Running ") + this.totalTestCount + this.screen.colors.dim(` test${this.totalTestCount !== 1 ? "s" : ""} using `) + jobs + this.screen.colors.dim(` worker${jobs !== 1 ? "s" : ""}${shardDetails}`); } getSlowTests() { if (!this.config.reportSlowTests) return []; const fileDurations = [...this.fileDurations.entries()].filter(([key, value]) => value.workers.size === 1).map(([key, value]) => [key, value.duration]); fileDurations.sort((a, b) => b[1] - a[1]); const count = Math.min(fileDurations.length, this.config.reportSlowTests.max || Number.POSITIVE_INFINITY); const threshold = this.config.reportSlowTests.threshold; return fileDurations.filter(([, duration]) => duration > threshold).slice(0, count); } generateSummaryMessage({ didNotRun, skipped, expected, interrupted, unexpected, flaky, fatalErrors }) { const tokens = []; if (unexpected.length) { tokens.push(this.screen.colors.red(` ${unexpected.length} failed`)); for (const test of unexpected) tokens.push(this.screen.colors.red(this.formatTestHeader(test, { indent: " " }))); } if (interrupted.length) { tokens.push(this.screen.colors.yellow(` ${interrupted.length} interrupted`)); for (const test of interrupted) tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " }))); } if (flaky.length) { tokens.push(this.screen.colors.yellow(` ${flaky.length} flaky`)); for (const test of flaky) tokens.push(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " }))); } if (skipped) tokens.push(this.screen.colors.yellow(` ${skipped} skipped`)); if (didNotRun) tokens.push(this.screen.colors.yellow(` ${didNotRun} did not run`)); if (expected) tokens.push(this.screen.colors.green(` ${expected} passed`) + this.screen.colors.dim(` (${msToString(this.result.duration)})`)); if (fatalErrors.length && expected + unexpected.length + interrupted.length + flaky.length > 0) tokens.push(this.screen.colors.red(` ${fatalErrors.length === 1 ? "1 error was not a part of any test" : fatalErrors.length + " errors were not a part of any test"}, see above for details`)); return tokens.join("\n"); } generateSummary() { let didNotRun = 0; let skipped = 0; let expected = 0; const interrupted = []; const interruptedToPrint = []; const unexpected = []; const flaky = []; this.suite.allTests().forEach((test) => { switch (test.outcome()) { case "skipped": { if (test.results.some((result) => result.status === "interrupted")) { if (test.results.some((result) => !!result.error)) interruptedToPrint.push(test); interrupted.push(test); } else if (!test.results.length || test.expectedStatus !== "skipped") { ++didNotRun; } else { ++skipped; } break; } case "expected": ++expected; break; case "unexpected": unexpected.push(test); break; case "flaky": flaky.push(test); break; } }); const failuresToPrint = [...unexpected, ...flaky, ...interruptedToPrint]; return { didNotRun, skipped, expected, interrupted, unexpected, flaky, failuresToPrint, fatalErrors: this._fatalErrors }; } epilogue(full) { const summary = this.generateSummary(); const summaryMessage = this.generateSummaryMessage(summary); if (full && summary.failuresToPrint.length && !this._options.omitFailures) this._printFailures(summary.failuresToPrint); this._printSlowTests(); this._printSummary(summaryMessage); } _printFailures(failures) { this.writeLine(""); failures.forEach((test, index) => { this.writeLine(this.formatFailure(test, index + 1)); }); } _printSlowTests() { const slowTests = this.getSlowTests(); slowTests.forEach(([file, duration]) => { this.writeLine(this.screen.colors.yellow(" Slow test file: ") + file + this.screen.colors.yellow(` (${msToString(duration)})`)); }); if (slowTests.length) this.writeLine(this.screen.colors.yellow(" Consider running tests from slow files in parallel. See: https://playwright.dev/docs/test-parallel")); } _printSummary(summary) { if (summary.trim()) this.writeLine(summary); } willRetry(test) { return test.outcome() === "unexpected" && test.results.length <= test.retries; } formatTestTitle(test, step) { return formatTestTitle(this.screen, this.config, test, step, this._options); } formatTestHeader(test, options = {}) { return formatTestHeader(this.screen, this.config, test, { ...options, includeTestId: this._options.includeTestId }); } formatFailure(test, index) { return formatFailure(this.screen, this.config, test, index, this._options); } formatError(error) { return formatError(this.screen, error); } formatResultErrors(test, result) { return formatResultErrors(this.screen, test, result); } writeLine(line) { this.screen.stdout?.write(line ? line + "\n" : "\n"); } }; function formatResultErrors(screen, test, result) { const lines = []; if (test.outcome() === "unexpected") { const errorDetails = formatResultFailure(screen, test, result, " "); if (errorDetails.length > 0) lines.push(""); for (const error of errorDetails) lines.push(error.message, ""); } return lines.join("\n"); } function formatFailure(screen, config2, test, index, options) { const lines = []; let printedHeader = false; for (const result of test.results) { const resultLines = []; const errors = formatResultFailure(screen, test, result, " "); if (!errors.length) continue; if (!printedHeader) { const header = formatTestHeader(screen, config2, test, { indent: " ", index, mode: "error", includeTestId: options?.includeTestId }); lines.push(screen.colors.red(header)); printedHeader = true; } if (result.retry) { resultLines.push(""); resultLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`))); } resultLines.push(...errors.map((error) => "\n" + error.message)); const attachmentGroups = groupAttachments(result.attachments); for (let i = 0; i < attachmentGroups.length; ++i) { const attachment = attachmentGroups[i]; if (attachment.name === "error-context" && attachment.path) { resultLines.push(""); resultLines.push(screen.colors.dim(` Error Context: ${relativeFilePath(screen, config2, attachment.path)}`)); continue; } if (attachment.name.startsWith("_")) continue; const hasPrintableContent = attachment.contentType.startsWith("text/"); if (!attachment.path && !hasPrintableContent) continue; resultLines.push(""); resultLines.push(screen.colors.dim(separator(screen, ` attachment #${i + 1}: ${screen.colors.bold(attachment.name)} (${attachment.contentType})`))); if (attachment.actual?.path) { if (attachment.expected?.path) { const expectedPath = relativeFilePath(screen, config2, attachment.expected.path); resultLines.push(screen.colors.dim(` Expected: ${expectedPath}`)); } const actualPath = relativeFilePath(screen, config2, attachment.actual.path); resultLines.push(screen.colors.dim(` Received: ${actualPath}`)); if (attachment.previous?.path) { const previousPath = relativeFilePath(screen, config2, attachment.previous.path); resultLines.push(screen.colors.dim(` Previous: ${previousPath}`)); } if (attachment.diff?.path) { const diffPath = relativeFilePath(screen, config2, attachment.diff.path); resultLines.push(screen.colors.dim(` Diff: ${diffPath}`)); } } else if (attachment.path) { const relativePath = relativeFilePath(screen, config2, attachment.path); resultLines.push(screen.colors.dim(` ${relativePath}`)); if (attachment.name === "trace") { const packageManagerCommand = getPackageManagerExecCommand(); resultLines.push(screen.colors.dim(` Usage:`)); resultLines.push(""); resultLines.push(screen.colors.dim(` ${packageManagerCommand} playwright show-trace ${quotePathIfNeeded(relativePath)}`)); resultLines.push(""); } } else { if (attachment.contentType.startsWith("text/") && attachment.body) { let text = attachment.body.toString(); if (text.length > 300) text = text.slice(0, 300) + "..."; for (const line of text.split("\n")) resultLines.push(screen.colors.dim(` ${line}`)); } } resultLines.push(screen.colors.dim(separator(screen, " "))); } lines.push(...resultLines); } lines.push(""); return lines.join("\n"); } function formatRetry(screen, result) { const retryLines = []; if (result.retry) { retryLines.push(""); retryLines.push(screen.colors.gray(separator(screen, ` Retry #${result.retry}`))); } return retryLines; } function quotePathIfNeeded(path20) { if (/\s/.test(path20)) return `"${path20}"`; return path20; } var kReportedSymbol = Symbol("reported"); function markErrorsAsReported(result) { result[kReportedSymbol] = result.errors.length; } function formatResultFailure(screen, test, result, initialIndent) { const errorDetails = []; if (result.status === "passed" && test.expectedStatus === "failed") { errorDetails.push({ message: indent(screen.colors.red(`Expected to fail, but passed.`), initialIndent) }); } if (result.status === "interrupted") { errorDetails.push({ message: indent(screen.colors.red(`Test was interrupted.`), initialIndent) }); } const reportedIndex = result[kReportedSymbol] || 0; for (const error of result.errors.slice(reportedIndex)) { const formattedError = formatError(screen, error); errorDetails.push({ message: indent(formattedError.message, initialIndent), location: formattedError.location }); } return errorDetails; } function relativeFilePath(screen, config2, file) { if (screen.resolveFiles === "cwd") return import_path2.default.relative(process.cwd(), file); return import_path2.default.relative(config2.rootDir, file); } function relativeTestPath(screen, config2, test) { return relativeFilePath(screen, config2, test.location.file); } function stepSuffix(step) { const stepTitles = step ? step.titlePath() : []; return stepTitles.map((t2) => t2.split("\n")[0]).map((t2) => " \u203A " + t2).join(""); } function formatTestTitle(screen, config2, test, step, options = {}) { const [, projectName, , ...titles] = test.titlePath(); const location = `${relativeTestPath(screen, config2, test)}:${test.location.line}:${test.location.column}`; const testId = options.includeTestId ? `[id=${test.id}] ` : ""; const projectLabel = options.includeTestId ? `project=` : ""; const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : ""; const testTitle = `${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`; const extraTags = test.tags.filter((t2) => !testTitle.includes(t2) && !config2.tags.includes(t2)); return `${testTitle}${stepSuffix(step)}${extraTags.length ? " " + extraTags.join(" ") : ""}`; } function formatTestHeader(screen, config2, test, options = {}) { const title = formatTestTitle(screen, config2, test, void 0, options); const header = `${options.indent || ""}${options.index ? options.index + ") " : ""}${title}`; let fullHeader = header; if (options.mode === "error") { const stepPaths = /* @__PURE__ */ new Set(); for (const result of test.results.filter((r) => !!r.errors.length)) { const stepPath = []; const visit = (steps) => { const errors = steps.filter((s) => s.error); if (errors.length > 1) return; if (errors.length === 1 && errors[0].category === "test.step") { stepPath.push(errors[0].title); visit(errors[0].steps); } }; visit(result.steps); stepPaths.add(["", ...stepPath].join(" \u203A ")); } fullHeader = header + (stepPaths.size === 1 ? stepPaths.values().next().value : ""); } return separator(screen, fullHeader); } function formatError(screen, error) { const message = error.message || error.value || ""; const stack = error.stack; if (!stack && !error.location) return { message }; const tokens = []; const parsedStack = stack ? prepareErrorStack(stack) : void 0; tokens.push(parsedStack?.message || message); if (error.snippet) { let snippet = error.snippet; if (!screen.colors.enabled) snippet = (0, import_util.stripAnsiEscapes)(snippet); tokens.push(""); tokens.push(snippet); } if (parsedStack && parsedStack.stackLines.length) tokens.push(screen.colors.dim(parsedStack.stackLines.join("\n"))); let location = error.location; if (parsedStack && !location) location = parsedStack.location; if (error.cause) tokens.push(screen.colors.dim("[cause]: ") + formatError(screen, error.cause).message); return { location, message: tokens.join("\n") }; } function separator(screen, text = "") { if (text) text += " "; const columns = Math.min(100, screen.ttyWidth || 100); return text + screen.colors.dim("\u2500".repeat(Math.max(0, columns - (0, import_util.stripAnsiEscapes)(text).length))); } function indent(lines, tab) { return lines.replace(/^(?=.+$)/gm, tab); } function prepareErrorStack(stack) { return parseErrorStack(stack, import_path2.default.sep, !!process.env.PWDEBUGIMPL); } function resolveFromEnv(name) { const value = process.env[name]; if (value) return import_path2.default.resolve(process.cwd(), value); return void 0; } function resolveOutputFile(reporterName, options) { const name = reporterName.toUpperCase(); let outputFile = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_FILE`); if (!outputFile && options.outputFile) outputFile = import_path2.default.resolve(options.configDir, options.outputFile); if (outputFile) return { outputFile }; let outputDir = resolveFromEnv(`PLAYWRIGHT_${name}_OUTPUT_DIR`); if (!outputDir && options.outputDir) outputDir = import_path2.default.resolve(options.configDir, options.outputDir); if (!outputDir && options.default) outputDir = (0, import_util.resolveReporterOutputPath)(options.default.outputDir, options.configDir, void 0); if (!outputDir) outputDir = options.configDir; const reportName = process.env[`PLAYWRIGHT_${name}_OUTPUT_NAME`] ?? options.fileName ?? options.default?.fileName; if (!reportName) return void 0; outputFile = import_path2.default.resolve(outputDir, reportName); return { outputFile, outputDir }; } function groupAttachments(attachments) { const result = []; const attachmentsByPrefix = /* @__PURE__ */ new Map(); for (const attachment of attachments) { if (!attachment.path) { result.push(attachment); continue; } const match = attachment.name.match(/^(.*)-(expected|actual|diff|previous)(\.[^.]+)?$/); if (!match) { result.push(attachment); continue; } const [, name, category] = match; let group = attachmentsByPrefix.get(name); if (!group) { group = { ...attachment, name }; attachmentsByPrefix.set(name, group); result.push(group); } if (category === "expected") group.expected = attachment; else if (category === "actual") group.actual = attachment; else if (category === "diff") group.diff = attachment; else if (category === "previous") group.previous = attachment; } return result; } // packages/playwright/src/reporters/internalReporter.ts var import_fs = __toESM(require("fs")); // packages/playwright/src/reporters/multiplexer.ts var Multiplexer = class { constructor(reporters) { this._reporters = reporters; } version() { return "v2"; } onConfigure(config2) { for (const reporter of this._reporters) wrap(() => reporter.onConfigure?.(config2)); } onBegin(suite) { for (const reporter of this._reporters) wrap(() => reporter.onBegin?.(suite)); } onTestBegin(test, result) { for (const reporter of this._reporters) wrap(() => reporter.onTestBegin?.(test, result)); } onStdOut(chunk, test, result) { for (const reporter of this._reporters) wrap(() => reporter.onStdOut?.(chunk, test, result)); } onStdErr(chunk, test, result) { for (const reporter of this._reporters) wrap(() => reporter.onStdErr?.(chunk, test, result)); } async onTestPaused(test, result) { for (const reporter of this._reporters) await wrapAsync(() => reporter.onTestPaused?.(test, result)); } onTestEnd(test, result) { for (const reporter of this._reporters) wrap(() => reporter.onTestEnd?.(test, result)); } onReportConfigure(params) { for (const reporter of this._reporters) wrap(() => reporter.onReportConfigure?.(params)); } onReportEnd(params) { for (const reporter of this._reporters) wrap(() => reporter.onReportEnd?.(params)); } async onEnd(result) { for (const reporter of this._reporters) { const outResult = await wrapAsync(() => reporter.onEnd?.(result)); if (outResult?.status) result.status = outResult.status; } return result; } async onExit() { for (const reporter of this._reporters) await wrapAsync(() => reporter.onExit?.()); } onError(error, workerInfo) { for (const reporter of this._reporters) wrap(() => reporter.onError?.(error, workerInfo)); } onStepBegin(test, result, step) { for (const reporter of this._reporters) wrap(() => reporter.onStepBegin?.(test, result, step)); } onStepEnd(test, result, step) { for (const reporter of this._reporters) wrap(() => reporter.onStepEnd?.(test, result, step)); } printsToStdio() { return this._reporters.some((r) => { let prints = false; wrap(() => prints = r.printsToStdio ? r.printsToStdio() : true); return prints; }); } }; async function wrapAsync(callback) { try { return await callback(); } catch (e) { console.error("Error in reporter", e); } } function wrap(callback) { try { callback(); } catch (e) { console.error("Error in reporter", e); } } // packages/playwright/src/reporters/internalReporter.ts var import_common = require("../common"); var babel = __toESM(require("../transform/babelBundle")); // packages/playwright/src/reporters/reporterV2.ts function wrapReporterAsV2(reporter) { try { if ("version" in reporter && reporter.version() === "v2") return reporter; } catch (e) { } return new ReporterV2Wrapper(reporter); } var ReporterV2Wrapper = class { constructor(reporter) { this._deferred = []; this._reporter = reporter; } version() { return "v2"; } onConfigure(config2) { this._config = config2; } onBegin(suite) { this._reporter.onBegin?.(this._config, suite); const deferred = this._deferred; this._deferred = null; for (const item of deferred) { if (item.error) this.onError(item.error.error, item.error.workerInfo); if (item.stdout) this.onStdOut(item.stdout.chunk, item.stdout.test, item.stdout.result); if (item.stderr) this.onStdErr(item.stderr.chunk, item.stderr.test, item.stderr.result); } } onTestBegin(test, result) { this._reporter.onTestBegin?.(test, result); } onStdOut(chunk, test, result) { if (this._deferred) { this._deferred.push({ stdout: { chunk, test, result } }); return; } this._reporter.onStdOut?.(chunk, test, result); } onStdErr(chunk, test, result) { if (this._deferred) { this._deferred.push({ stderr: { chunk, test, result } }); return; } this._reporter.onStdErr?.(chunk, test, result); } onTestEnd(test, result) { this._reporter.onTestEnd?.(test, result); } async onEnd(result) { return await this._reporter.onEnd?.(result); } async onExit() { await this._reporter.onExit?.(); } onError(error, workerInfo) { if (this._deferred) { this._deferred.push({ error: { error, workerInfo } }); return; } this._reporter.onError?.(error, workerInfo); } onStepBegin(test, result, step) { this._reporter.onStepBegin?.(test, result, step); } onStepEnd(test, result, step) { this._reporter.onStepEnd?.(test, result, step); } printsToStdio() { return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true; } }; // packages/playwright/src/reporters/internalReporter.ts var { monotonicTime: monotonicTime3 } = require("playwright-core/lib/coreBundle").iso; var InternalReporter = class { constructor(reporters) { this._didBegin = false; this._reporter = new Multiplexer(reporters.map(wrapReporterAsV2)); } version() { return "v2"; } onConfigure(config2) { this._config = config2; this._startTime = /* @__PURE__ */ new Date(); this._monotonicStartTime = monotonicTime3(); this._reporter.onConfigure?.(config2); } onBegin(suite) { this._didBegin = true; this._reporter.onBegin?.(suite); } onTestBegin(test, result) { this._reporter.onTestBegin?.(test, result); } onStdOut(chunk, test, result) { this._reporter.onStdOut?.(chunk, test, result); } onStdErr(chunk, test, result) { this._reporter.onStdErr?.(chunk, test, result); } async onTestPaused(test, result) { this._addSnippetToTestErrors(test, result); return await this._reporter.onTestPaused?.(test, result); } onTestEnd(test, result) { this._addSnippetToTestErrors(test, result); this._reporter.onTestEnd?.(test, result); } async onEnd(result) { if (!this._didBegin) { this.onBegin(new import_common.test.Suite("", "root")); } return await this._reporter.onEnd?.({ ...result, startTime: this._startTime, duration: monotonicTime3() - this._monotonicStartTime }); } async onExit() { await this._reporter.onExit?.(); } onError(error, workerInfo) { addLocationAndSnippetToError(this._config, error); this._reporter.onError?.(error, workerInfo); } onStepBegin(test, result, step) { this._reporter.onStepBegin?.(test, result, step); } onStepEnd(test, result, step) { this._addSnippetToStepError(test, step); this._reporter.onStepEnd?.(test, result, step); } printsToStdio() { return this._reporter.printsToStdio ? this._reporter.printsToStdio() : true; } _addSnippetToTestErrors(test, result) { for (const error of result.errors) addLocationAndSnippetToError(this._config, error, test.location.file); } _addSnippetToStepError(test, step) { if (step.error) addLocationAndSnippetToError(this._config, step.error, test.location.file); } }; function addLocationAndSnippetToError(config2, error, file) { if (error.stack && !error.location) error.location = prepareErrorStack(error.stack).location; const location = error.location; if (!location) return; if (!!error.snippet) return; try { const tokens = []; const source = import_fs.default.readFileSync(location.file, "utf8"); const codeFrame = babel.codeFrameColumns(source, { start: location }, { highlightCode: true }); if (!file || import_fs.default.realpathSync(file) !== location.file) { tokens.push(internalScreen.colors.gray(` at `) + `${relativeFilePath(internalScreen, config2, location.file)}:${location.line}`); tokens.push(""); } tokens.push(codeFrame); error.snippet = tokens.join("\n"); } catch (e) { } } // packages/playwright/src/runner/testRunner.ts var import_util13 = require("../util"); // packages/playwright/src/runner/reporters.ts var reporters_exports = {}; __export(reporters_exports, { createErrorCollectingReporter: () => createErrorCollectingReporter, createReporters: () => createReporters }); var import_fs8 = __toESM(require("fs")); // packages/playwright/src/runner/loadUtils.ts var import_path4 = __toESM(require("path")); var import_fs3 = __toESM(require("fs")); // packages/playwright/src/runner/processHost.ts var import_child_process = __toESM(require("child_process")); var import_events = require("events"); var debug3 = require("playwright-core/lib/utilsBundle").debug; var { assert } = require("playwright-core/lib/coreBundle").iso; var { monotonicTime: monotonicTime4, timeOrigin } = require("playwright-core/lib/coreBundle").iso; var { raceAgainstDeadline: raceAgainstDeadline2 } = require("playwright-core/lib/coreBundle").iso; var ProcessHost = class extends import_events.EventEmitter { constructor(entryScript, processName, env) { super(); this._didSendStop = false; this._processDidExit = false; this._didExitAndRanOnExit = false; this._lastMessageId = 0; this._callbacks = /* @__PURE__ */ new Map(); this._producedEnv = {}; this._requestHandlers = /* @__PURE__ */ new Map(); this._entryScript = entryScript; this._processName = processName; this._extraEnv = env; } async startRunner(runnerParams, options = {}) { assert(!this.process, "Internal error: starting the same process twice"); this.process = import_child_process.default.fork(this._entryScript, { // Note: we pass detached:false, so that workers are in the same process group. // This way Ctrl+C or a kill command can shutdown all workers in case they misbehave. // Otherwise user can end up with a bunch of workers stuck in a busy loop without self-destructing. detached: false, env: { ...process.env, ...this._extraEnv }, stdio: [ "ignore", options.onStdOut ? "pipe" : "inherit", options.onStdErr && !process.env.PW_RUNNER_DEBUG ? "pipe" : "inherit", "ipc" ] }); this.process.on("exit", async (code, signal) => { this._processDidExit = true; await this.onExit(); this._didExitAndRanOnExit = true; this.emit("exit", { unexpectedly: !this._didSendStop, code, signal }); }); this.process.on("error", (e) => { }); this.process.on("message", (message) => { if (debug3.enabled("pw:test:protocol")) debug3("pw:test:protocol")("\u25C0 RECV " + JSON.stringify(message)); if (message.method === "__env_produced__") { const producedEnv = message.params; this._producedEnv = Object.fromEntries(producedEnv.map((e) => [e[0], e[1] ?? void 0])); } else if (message.method === "__dispatch__") { const { id, error: error2, method, params, result } = message.params; if (id && this._callbacks.has(id)) { const { resolve, reject } = this._callbacks.get(id); this._callbacks.delete(id); if (error2) { const errorObject = new Error(error2.message); errorObject.stack = error2.stack; reject(errorObject); } else { resolve(result); } } else { this.emit(method, params); } } else if (message.method === "__request__") { const { id, method, params } = message.params; const handler = this._requestHandlers.get(method); if (!handler) { this.send({ method: "__response__", params: { id, error: { message: "Unknown method" } } }); } else { handler(params).then((result) => { this.send({ method: "__response__", params: { id, result } }); }).catch((error2) => { this.send({ method: "__response__", params: { id, error: { message: error2.message } } }); }); } } else { this.emit(message.method, message.params); } }); if (options.onStdOut) this.process.stdout?.on("data", options.onStdOut); if (options.onStdErr) this.process.stderr?.on("data", options.onStdErr); const error = await new Promise((resolve) => { this.process.once("exit", (code, signal) => resolve({ unexpectedly: true, code, signal })); this.once("ready", () => resolve(void 0)); }); if (error) return error; const processParams = { processName: this._processName, timeOrigin: timeOrigin() }; this.send({ method: "__init__", params: { processParams, runnerParams } }); } sendMessage(message) { const id = ++this._lastMessageId; this.send({ method: "__dispatch__", params: { id, ...message } }); return new Promise((resolve, reject) => { this._callbacks.set(id, { resolve, reject }); }); } sendMessageNoReply(message) { this.sendMessage(message).catch(() => { }); } async onExit() { } onRequest(method, handler) { this._requestHandlers.set(method, handler); } async stop() { if (!this._processDidExit && !this._didSendStop) { this.send({ method: "__stop__" }); this._didSendStop = true; } if (this._didExitAndRanOnExit) return; const exitPromise = new Promise((f) => this.once("exit", () => f())); const timeout = +(process.env.PWTEST_CHILD_PROCESS_TIMEOUT || 5 * 60 * 1e3); const result = await raceAgainstDeadline2(() => exitPromise, monotonicTime4() + timeout); if (result.timedOut) { this.emit("processError", { message: `Error: ${this._processName} process did not exit within ${timeout}ms after stop, force-killed it` }); this._forceKill(); await exitPromise; } } _forceKill() { const pid = this.process?.pid; if (!pid) return; try { if (process.platform === "win32") import_child_process.default.spawnSync(`taskkill /pid ${pid} /T /F`, { shell: true }); else process.kill(pid, "SIGKILL"); } catch { } } didSendStop() { return this._didSendStop; } producedEnv() { return this._producedEnv; } send(message) { if (debug3.enabled("pw:test:protocol")) debug3("pw:test:protocol")("SEND \u25BA " + JSON.stringify(message)); this.process?.send(message); } }; // packages/playwright/src/runner/loaderHost.ts var import_common2 = require("../common"); var InProcessLoaderHost = class { constructor(config2) { this._config = config2; this._poolBuilder = import_common2.poolBuilder.PoolBuilder.createForLoader(); } async start(errors) { return true; } async loadTestFile(file, testErrors) { const result = await import_common2.testLoader.loadTestFile(file, this._config, testErrors); this._poolBuilder.buildPools(result, testErrors); return result; } async stop() { await import_common2.esm.incorporateCompilationCache(); } }; var OutOfProcessLoaderHost = class { constructor(config2) { this._config = config2; this._processHost = new ProcessHost(require.resolve("../loader/loaderProcessEntry.js"), "loader", {}); } async start(errors) { const startError = await this._processHost.startRunner(import_common2.ipc.serializeConfig(this._config, false)); if (startError) { errors.push({ message: `Test loader process failed to start with code "${startError.code}" and signal "${startError.signal}"` }); return false; } return true; } async loadTestFile(file, testErrors) { const result = await this._processHost.sendMessage({ method: "loadTestFile", params: { file } }); testErrors.push(...result.testErrors); return import_common2.test.Suite._deepParse(result.fileSuite); } async stop() { const result = await this._processHost.sendMessage({ method: "getCompilationCacheFromLoader" }); import_common2.cc.addToCompilationCache(result); await this._processHost.stop(); } }; // packages/playwright/src/runner/loadUtils.ts var import_util4 = require("../util"); // packages/playwright/src/runner/projectUtils.ts var projectUtils_exports = {}; __export(projectUtils_exports, { buildDependentProjects: () => buildDependentProjects, buildProjectsClosure: () => buildProjectsClosure, buildTeardownToSetupsMap: () => buildTeardownToSetupsMap, collectFilesForProject: () => collectFilesForProject, filterProjects: () => filterProjects, findTopLevelProjects: () => findTopLevelProjects }); var import_fs2 = __toESM(require("fs")); var import_path3 = __toESM(require("path")); var import_util2 = require("util"); var import_util3 = require("../util"); var minimatch = require("playwright-core/lib/utilsBundle").minimatch; var { escapeRegExp } = require("playwright-core/lib/coreBundle").iso; var readFileAsync = (0, import_util2.promisify)(import_fs2.default.readFile); var readDirAsync = (0, import_util2.promisify)(import_fs2.default.readdir); function wildcardPatternToRegExp(pattern) { return new RegExp("^" + pattern.split("*").map(escapeRegExp).join(".*") + "$", "ig"); } function filterProjects(projects, projectNames) { if (!projectNames) return [...projects]; const projectNamesToFind = /* @__PURE__ */ new Set(); const unmatchedProjectNames = /* @__PURE__ */ new Map(); const patterns = /* @__PURE__ */ new Set(); for (const name of projectNames) { const lowerCaseName = name.toLocaleLowerCase(); if (lowerCaseName.includes("*")) { patterns.add(wildcardPatternToRegExp(lowerCaseName)); } else { projectNamesToFind.add(lowerCaseName); unmatchedProjectNames.set(lowerCaseName, name); } } const result = projects.filter((project) => { const lowerCaseName = project.project.name.toLocaleLowerCase(); if (projectNamesToFind.has(lowerCaseName)) { unmatchedProjectNames.delete(lowerCaseName); return true; } for (const regex of patterns) { regex.lastIndex = 0; if (regex.test(lowerCaseName)) return true; } return false; }); if (unmatchedProjectNames.size) { const unknownProjectNames = Array.from(unmatchedProjectNames.values()).map((n) => `"${n}"`).join(", "); throw new Error(`Project(s) ${unknownProjectNames} not found. Available projects: ${projects.map((p) => `"${p.project.name}"`).join(", ")}`); } if (!result.length) { const allProjects = projects.map((p) => `"${p.project.name}"`).join(", "); throw new Error(`No projects matched. Available projects: ${allProjects}`); } return result; } function buildTeardownToSetupsMap(projects) { const result = /* @__PURE__ */ new Map(); for (const project of projects) { if (project.teardown) { const setups = result.get(project.teardown) || []; setups.push(project); result.set(project.teardown, setups); } } return result; } function buildProjectsClosure(projects, hasTests) { const result = /* @__PURE__ */ new Map(); const visit = (depth, project) => { if (depth > 100) { const error = new Error("Circular dependency detected between projects."); error.stack = ""; throw error; } if (depth === 0 && hasTests && !hasTests(project)) return; if (result.get(project) !== "dependency") result.set(project, depth ? "dependency" : "top-level"); for (const dep of project.deps) visit(depth + 1, dep); if (project.teardown) visit(depth + 1, project.teardown); }; for (const p of projects) visit(0, p); return result; } function findTopLevelProjects(config2) { const closure = buildProjectsClosure(config2.projects); return [...closure].filter((entry) => entry[1] === "top-level").map((entry) => entry[0]); } function buildDependentProjects(forProjects, projects) { const reverseDeps = new Map(projects.map((p) => [p, []])); for (const project of projects) { for (const dep of project.deps) reverseDeps.get(dep).push(project); } const result = /* @__PURE__ */ new Set(); const visit = (depth, project) => { if (depth > 100) { const error = new Error("Circular dependency detected between projects."); error.stack = ""; throw error; } result.add(project); for (const reverseDep of reverseDeps.get(project)) visit(depth + 1, reverseDep); if (project.teardown) visit(depth + 1, project.teardown); }; for (const forProject of forProjects) visit(0, forProject); return result; } async function collectFilesForProject(project, fsCache = /* @__PURE__ */ new Map()) { const extensions = /* @__PURE__ */ new Set([".js", ".ts", ".mjs", ".mts", ".cjs", ".cts", ".jsx", ".tsx", ".mjsx", ".mtsx", ".cjsx", ".ctsx"]); const testFileExtension = (file) => extensions.has(import_path3.default.extname(file)); const allFiles = await cachedCollectFiles(project.project.testDir, project.respectGitIgnore, fsCache); const testMatch = (0, import_util3.createFileMatcher)(project.project.testMatch); const testIgnore = (0, import_util3.createFileMatcher)(project.project.testIgnore); const testFiles = allFiles.filter((file) => { if (!testFileExtension(file)) return false; const isTest = !testIgnore(file) && testMatch(file); if (!isTest) return false; return true; }); return testFiles; } async function cachedCollectFiles(testDir, respectGitIgnore, fsCache) { const key = testDir + ":" + respectGitIgnore; let result = fsCache.get(key); if (!result) { result = await collectFiles(testDir, respectGitIgnore); fsCache.set(key, result); } return result; } async function collectFiles(testDir, respectGitIgnore) { if (!import_fs2.default.existsSync(testDir)) return []; if (!import_fs2.default.statSync(testDir).isDirectory()) return []; const checkIgnores = (entryPath, rules, isDirectory, parentStatus) => { let status = parentStatus; for (const rule of rules) { const ruleIncludes = rule.negate; if (status === "included" === ruleIncludes) continue; const relative = import_path3.default.relative(rule.dir, entryPath); if (rule.match("/" + relative) || rule.match(relative)) { status = ruleIncludes ? "included" : "ignored"; } else if (isDirectory && (rule.match("/" + relative + "/") || rule.match(relative + "/"))) { status = ruleIncludes ? "included" : "ignored"; } else if (isDirectory && ruleIncludes && (rule.match("/" + relative, true) || rule.match(relative, true))) { status = "ignored-but-recurse"; } } return status; }; const files = []; const visit = async (dir, rules, status) => { const entries = await readDirAsync(dir, { withFileTypes: true }); entries.sort((a, b) => a.name.localeCompare(b.name)); if (respectGitIgnore) { const gitignore = entries.find((e) => e.isFile() && e.name === ".gitignore"); if (gitignore) { const content = await readFileAsync(import_path3.default.join(dir, gitignore.name), "utf8"); const newRules = content.split(/\r?\n/).map((s) => { s = s.trim(); if (!s) return; const rule = new minimatch.Minimatch(s, { matchBase: true, dot: true, flipNegate: true }); if (rule.comment) return; rule.dir = dir; return rule; }).filter((rule) => !!rule); rules = [...rules, ...newRules]; } } for (const entry of entries) { if (entry.name === "." || entry.name === "..") continue; if (entry.isFile() && entry.name === ".gitignore") continue; if (entry.isDirectory() && entry.name === "node_modules") continue; const entryPath = import_path3.default.join(dir, entry.name); const entryStatus = checkIgnores(entryPath, rules, entry.isDirectory(), status); if (entry.isDirectory() && entryStatus !== "ignored") await visit(entryPath, rules, entryStatus); else if (entry.isFile() && entryStatus === "included") files.push(entryPath); } }; await visit(testDir, [], "included"); return files; } // packages/playwright/src/runner/testGroups.ts function createTestGroups(projectSuite, expectedParallelism) { const groups = /* @__PURE__ */ new Map(); const createGroup = (test) => { return { workerHash: test._workerHash, requireFile: test._requireFile, repeatEachIndex: test.repeatEachIndex, projectId: test._projectId, tests: [] }; }; for (const test of projectSuite.allTests()) { let withWorkerHash = groups.get(test._workerHash); if (!withWorkerHash) { withWorkerHash = /* @__PURE__ */ new Map(); groups.set(test._workerHash, withWorkerHash); } let withRequireFile = withWorkerHash.get(test._requireFile); if (!withRequireFile) { withRequireFile = { general: createGroup(test), parallel: /* @__PURE__ */ new Map(), parallelWithHooks: createGroup(test) }; withWorkerHash.set(test._requireFile, withRequireFile); } let insideParallel = false; let outerMostSequentialSuite; let hasAllHooks = false; for (let parent = test.parent; parent; parent = parent.parent) { if (parent._parallelMode === "serial" || parent._parallelMode === "default") outerMostSequentialSuite = parent; insideParallel = insideParallel || parent._parallelMode === "parallel"; hasAllHooks = hasAllHooks || parent._hooks.some((hook) => hook.type === "beforeAll" || hook.type === "afterAll"); } if (insideParallel) { if (hasAllHooks && !outerMostSequentialSuite) { withRequireFile.parallelWithHooks.tests.push(test); } else { const key = outerMostSequentialSuite || test; let group = withRequireFile.parallel.get(key); if (!group) { group = createGroup(test); withRequireFile.parallel.set(key, group); } group.tests.push(test); } } else { withRequireFile.general.tests.push(test); } } const result = []; for (const withWorkerHash of groups.values()) { for (const withRequireFile of withWorkerHash.values()) { if (withRequireFile.general.tests.length) result.push(withRequireFile.general); result.push(...withRequireFile.parallel.values()); const parallelWithHooksGroupSize = Math.ceil(withRequireFile.parallelWithHooks.tests.length / expectedParallelism); let lastGroup; for (const test of withRequireFile.parallelWithHooks.tests) { if (!lastGroup || lastGroup.tests.length >= parallelWithHooksGroupSize) { lastGroup = createGroup(test); result.push(lastGroup); } lastGroup.tests.push(test); } } } return result; } function filterForShard(shard, weights, testGroups) { weights ??= Array.from({ length: shard.total }, () => 1); if (weights.length !== shard.total) throw new Error(`PWTEST_SHARD_WEIGHTS number of weights must match the shard total of ${shard.total}`); const totalWeight = weights.reduce((a, b) => a + b, 0); let shardableTotal = 0; for (const group of testGroups) shardableTotal += group.tests.length; const shardSizes = weights.map((w) => Math.floor(w * shardableTotal / totalWeight)); const remainder = shardableTotal - shardSizes.reduce((a, b) => a + b, 0); for (let i = 0; i < remainder; i++) { shardSizes[i % shardSizes.length]++; } let from = 0; for (let i = 0; i < shard.current - 1; i++) from += shardSizes[i]; const to = from + shardSizes[shard.current - 1]; let current = 0; const result = /* @__PURE__ */ new Set(); for (const group of testGroups) { if (current >= from && current < to) result.add(group); current += group.tests.length; } return result; } // packages/playwright/src/runner/loadUtils.ts var import_common3 = require("../common"); var sourceMapSupport = require("playwright-core/lib/utilsBundle").sourceMapSupport; var { toPosixPath } = require("playwright-core/lib/coreBundle").utils; async function collectProjectsAndTestFiles(testRun, doNotRunTestsOutsideProjectFilter) { const fsCache = /* @__PURE__ */ new Map(); const sourceMapCache = /* @__PURE__ */ new Map(); const allFilesForProject = /* @__PURE__ */ new Map(); for (const project of testRun.filteredProjects) { const files = await collectFilesForProject(project, fsCache); allFilesForProject.set(project, files); } const filesToRunByProject = /* @__PURE__ */ new Map(); for (const [project, files] of allFilesForProject) { const matchedFiles = files.filter((file) => { if (!testRun.loadFileFilters.length) { return true; } const hasMatchingSources = sourceMapSources(file, sourceMapCache).some((source) => { const matchesAllFileFilters = testRun.loadFileFilters.every((filter) => filter(source)); return matchesAllFileFilters; }); return hasMatchingSources; }); const filteredFiles = matchedFiles.filter(Boolean); filesToRunByProject.set(project, filteredFiles); } const projectClosure = buildProjectsClosure([...filesToRunByProject.keys()]); for (const [project, type] of projectClosure) { if (type === "dependency") { const treatProjectAsEmpty = doNotRunTestsOutsideProjectFilter && !testRun.filteredProjects.includes(project); const files = treatProjectAsEmpty ? [] : allFilesForProject.get(project) || await collectFilesForProject(project, fsCache); filesToRunByProject.set(project, files); } } testRun.projectFiles = filesToRunByProject; testRun.projectSuites = /* @__PURE__ */ new Map(); } async function loadFileSuites(testRun, mode, errors) { const config2 = testRun.config; const allTestFiles = /* @__PURE__ */ new Set(); for (const files of testRun.projectFiles.values()) files.forEach((file) => allTestFiles.add(file)); const fileSuiteByFile = /* @__PURE__ */ new Map(); const loaderHost = mode === "out-of-process" ? new OutOfProcessLoaderHost(config2) : new InProcessLoaderHost(config2); if (await loaderHost.start(errors)) { for (const file of allTestFiles) { const fileSuite = await loaderHost.loadTestFile(file, errors); fileSuiteByFile.set(file, fileSuite); errors.push(...createDuplicateTitlesErrors(config2, fileSuite)); } await loaderHost.stop(); } for (const file of allTestFiles) { for (const dependency of import_common3.cc.dependenciesForTestFile(file)) { if (allTestFiles.has(dependency)) { const importer = import_path4.default.relative(config2.config.rootDir, file); const importee = import_path4.default.relative(config2.config.rootDir, dependency); errors.push({ message: `Error: test file "${importer}" should not import test file "${importee}"`, location: { file, line: 1, column: 1 } }); } } } for (const [project, files] of testRun.projectFiles) { const suites = files.map((file) => fileSuiteByFile.get(file)).filter(Boolean); testRun.projectSuites.set(project, suites); } } async function createRootSuite(testRun, errors, shouldFilterOnly) { const config2 = testRun.config; const rootSuite = new import_common3.test.Suite("", "root"); const projectSuites = /* @__PURE__ */ new Map(); const filteredProjectSuites = /* @__PURE__ */ new Map(); { for (const [project, fileSuites] of testRun.projectSuites) { const projectSuite = createProjectSuite(project, fileSuites); projectSuites.set(project, projectSuite); const filteredProjectSuite = filterProjectSuite(projectSuite, testRun.preOnlyTestFilters); filteredProjectSuites.set(project, filteredProjectSuite); } } if (shouldFilterOnly) { const filteredRoot = new import_common3.test.Suite("", "root"); for (const filteredProjectSuite of filteredProjectSuites.values()) filteredRoot._addSuite(filteredProjectSuite); import_common3.suiteUtils.filterOnly(filteredRoot); for (const [project, filteredProjectSuite] of filteredProjectSuites) { if (!filteredRoot.suites.includes(filteredProjectSuite)) filteredProjectSuites.delete(project); } } const projectClosure = buildProjectsClosure([...filteredProjectSuites.keys()], (project) => filteredProjectSuites.get(project)._hasTests()); for (const [project, type] of projectClosure) { if (type === "top-level") { project.project.repeatEach = project.fullConfig.configCLIOverrides.repeatEach ?? project.project.repeatEach; rootSuite._addSuite(buildProjectSuite(project, filteredProjectSuites.get(project))); } } if (config2.config.forbidOnly) { const onlyTestsAndSuites = rootSuite._getOnlyItems(); if (onlyTestsAndSuites.length > 0) { const configFilePath = config2.config.configFile ? import_path4.default.relative(config2.config.rootDir, config2.config.configFile) : void 0; errors.push(...createForbidOnlyErrors(onlyTestsAndSuites, config2.configCLIOverrides.forbidOnly, configFilePath)); } } if (config2.config.shard) { const testGroups = []; for (const projectSuite of rootSuite.suites) { for (const group of createTestGroups(projectSuite, config2.config.shard.total)) testGroups.push(group); } const testGroupsInThisShard = filterForShard(config2.config.shard, testRun.options.shardWeights, testGroups); const testsInThisShard = /* @__PURE__ */ new Set(); for (const group of testGroupsInThisShard) { for (const test of group.tests) testsInThisShard.add(test); } import_common3.suiteUtils.filterTestsRemoveEmptySuites(rootSuite, (test) => testsInThisShard.has(test)); } if (testRun.postShardTestFilters.length) import_common3.suiteUtils.filterTestsRemoveEmptySuites(rootSuite, (test) => testRun.postShardTestFilters.every((filter) => filter(test))); const topLevelProjects = []; { const projectClosure2 = new Map(buildProjectsClosure(rootSuite.suites.map((suite) => suite._fullProject))); for (const [project, level] of projectClosure2.entries()) { if (level === "dependency") rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project))); else topLevelProjects.push(project); } } testRun.rootSuite = rootSuite; testRun.topLevelProjects = topLevelProjects; } function createProjectSuite(project, fileSuites) { const projectSuite = new import_common3.test.Suite(project.project.name, "project"); for (const fileSuite of fileSuites) projectSuite._addSuite(import_common3.suiteUtils.bindFileSuiteToProject(project, fileSuite)); const grepMatcher = (0, import_util4.createTitleMatcher)(project.project.grep); const grepInvertMatcher = project.project.grepInvert ? (0, import_util4.createTitleMatcher)(project.project.grepInvert) : null; import_common3.suiteUtils.filterTestsRemoveEmptySuites(projectSuite, (test) => { const grepTitle = test._grepTitleWithTags(); if (grepInvertMatcher?.(grepTitle)) return false; return grepMatcher(grepTitle); }); return projectSuite; } function filterProjectSuite(projectSuite, testFilters) { if (!testFilters.length) return projectSuite; const result = projectSuite._deepClone(); import_common3.suiteUtils.filterTestsRemoveEmptySuites(result, (test) => testFilters.every((filter) => filter(test))); return result; } function buildProjectSuite(project, projectSuite) { const result = new import_common3.test.Suite(project.project.name, "project"); result._fullProject = project; if (project.fullyParallel) result._parallelMode = "parallel"; for (const fileSuite of projectSuite.suites) { result._addSuite(fileSuite); for (let repeatEachIndex = 1; repeatEachIndex < project.project.repeatEach; repeatEachIndex++) { const clone = fileSuite._deepClone(); import_common3.suiteUtils.applyRepeatEachIndex(project, clone, repeatEachIndex); result._addSuite(clone); } } return result; } function createForbidOnlyErrors(onlyTestsAndSuites, forbidOnlyCLIFlag, configFilePath) { const errors = []; for (const testOrSuite of onlyTestsAndSuites) { const title = testOrSuite.titlePath().slice(2).join(" "); const configFilePathName = configFilePath ? `'${configFilePath}'` : "the Playwright configuration file"; const forbidOnlySource = forbidOnlyCLIFlag ? `'--forbid-only' CLI flag` : `'forbidOnly' option in ${configFilePathName}`; const error = { message: `Error: item focused with '.only' is not allowed due to the ${forbidOnlySource}: "${title}"`, location: testOrSuite.location }; errors.push(error); } return errors; } function createDuplicateTitlesErrors(config2, fileSuite) { const errors = []; const testsByFullTitle = /* @__PURE__ */ new Map(); for (const test of fileSuite.allTests()) { const fullTitle = test.titlePath().slice(1).join(" \u203A "); const existingTest = testsByFullTitle.get(fullTitle); if (existingTest) { const error = { message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config2.config.rootDir, existingTest)}`, location: test.location }; errors.push(error); } testsByFullTitle.set(fullTitle, test); } return errors; } function buildItemLocation(rootDir, testOrSuite) { if (!testOrSuite.location) return ""; return `${import_path4.default.relative(rootDir, testOrSuite.location.file)}:${testOrSuite.location.line}`; } async function requireOrImportDefaultFunction(file, expectConstructor) { let func = await import_common3.transform.requireOrImport(file); if (func && typeof func === "object" && "default" in func) func = func["default"]; if (typeof func !== "function") throw (0, import_util4.errorWithFile)(file, `file must export a single ${expectConstructor ? "class" : "function"}.`); return func; } function loadGlobalHook(config2, file) { return requireOrImportDefaultFunction(import_path4.default.resolve(config2.config.rootDir, file), false); } function loadReporter(config2, file) { return requireOrImportDefaultFunction(config2 ? import_path4.default.resolve(config2.config.rootDir, file) : file, true); } function sourceMapSources(file, cache) { let sources = [file]; if (!file.endsWith(".js")) return sources; if (cache.has(file)) return cache.get(file); try { const sourceMap = sourceMapSupport.retrieveSourceMap(file); const sourceMapData = typeof sourceMap?.map === "string" ? JSON.parse(sourceMap.map) : sourceMap?.map; if (sourceMapData?.sources) sources = sourceMapData.sources.map((source) => import_path4.default.resolve(import_path4.default.dirname(file), source)); } finally { cache.set(file, sources); return sources; } } async function loadTestList(config2, filePath) { try { const content = await import_fs3.default.promises.readFile(filePath, "utf-8"); const lines = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")); const descriptions = lines.map((line) => { const delimiter = line.includes("\u203A") ? "\u203A" : ">"; const tokens = line.split(delimiter).map((token) => token.trim()); let project; if (tokens[0].startsWith("[")) { if (!tokens[0].endsWith("]")) throw new Error(`Malformed test description: ${line}`); project = tokens[0].substring(1, tokens[0].length - 1); tokens.shift(); } return { project, file: toPosixPath((0, import_util4.parseLocationArg)(tokens[0]).file), titlePath: tokens.slice(1) }; }); const testFilter = (test) => descriptions.some((d) => { const [projectName, , ...titles] = test.titlePath(); if (d.project !== void 0 && d.project !== projectName) return false; const relativeFile = toPosixPath(import_path4.default.relative(config2.config.rootDir, test.location.file)); if (relativeFile !== d.file) return false; return d.titlePath.length <= titles.length && d.titlePath.every((_, index) => titles[index] === d.titlePath[index]); }); const fileFilter = (file) => { const relativeFile = toPosixPath(import_path4.default.relative(config2.config.rootDir, file)); return descriptions.some((d) => d.file === relativeFile); }; return { testFilter, fileFilter }; } catch (e) { throw (0, import_util4.errorWithFile)(filePath, "Cannot read test list file: " + e.message); } } // packages/playwright/src/reporters/blob.ts var import_fs4 = __toESM(require("fs")); var import_path6 = __toESM(require("path")); var import_stream = require("stream"); var import_coreBundle = require("playwright-core/lib/coreBundle"); // packages/playwright/src/reporters/teleEmitter.ts var import_path5 = __toESM(require("path")); var { createGuid } = require("playwright-core/lib/coreBundle").utils; var TeleReporterEmitter = class { constructor(messageSink, options = {}) { this._resultKnownAttachmentCounts = /* @__PURE__ */ new Map(); this._resultKnownErrorCounts = /* @__PURE__ */ new Map(); // In case there is blob reporter and UI mode, make sure one doesn't override // the id assigned by the other. this._idSymbol = Symbol("id"); this._messageSink = messageSink; this._emitterOptions = options; } version() { return "v2"; } onConfigure(config2) { this._rootDir = config2.rootDir; this._messageSink({ method: "onConfigure", params: { config: this._serializeConfig(config2) } }); } onBegin(suite) { const projects = suite.suites.map((projectSuite) => this._serializeProject(projectSuite)); for (const project of projects) this._messageSink({ method: "onProject", params: { project } }); this._messageSink({ method: "onBegin", params: void 0 }); } onTestBegin(test, result) { result[this._idSymbol] = createGuid(); this._messageSink({ method: "onTestBegin", params: { testId: test.id, result: this._serializeResultStart(result) } }); } async onTestPaused(test, result) { const resultId = result[this._idSymbol]; this._resultKnownErrorCounts.set(resultId, result.errors.length); this._messageSink({ method: "onTestPaused", params: { testId: test.id, resultId, errors: result.errors } }); await new Promise(() => { }); } onTestEnd(test, result) { const testEnd = { testId: test.id, expectedStatus: test.expectedStatus, timeout: test.timeout, annotations: [] }; this._sendNewAttachments(result, test.id); this._messageSink({ method: "onTestEnd", params: { test: testEnd, result: this._serializeResultEnd(result) } }); const resultId = result[this._idSymbol]; this._resultKnownAttachmentCounts.delete(resultId); this._resultKnownErrorCounts.delete(resultId); } onStepBegin(test, result, step) { step[this._idSymbol] = createGuid(); this._messageSink({ method: "onStepBegin", params: { testId: test.id, resultId: result[this._idSymbol], step: this._serializeStepStart(step) } }); } onStepEnd(test, result, step) { const resultId = result[this._idSymbol]; this._sendNewAttachments(result, test.id); this._messageSink({ method: "onStepEnd", params: { testId: test.id, resultId, step: this._serializeStepEnd(step, result) } }); } onError(error, workerInfo) { this._messageSink({ method: "onError", params: { error, workerInfo: workerInfo ? { workerIndex: workerInfo.workerIndex, parallelIndex: workerInfo.parallelIndex, projectName: workerInfo.project.name } : void 0 } }); } onStdOut(chunk, test, result) { this._onStdIO("stdout", chunk, test, result); } onStdErr(chunk, test, result) { this._onStdIO("stderr", chunk, test, result); } _onStdIO(type, chunk, test, result) { if (this._emitterOptions.omitOutput) return; const isBase64 = typeof chunk !== "string"; const data = isBase64 ? chunk.toString("base64") : chunk; this._messageSink({ method: "onStdIO", params: { testId: test?.id, resultId: result ? result[this._idSymbol] : void 0, type, data, isBase64 } }); } async onEnd(result) { const resultPayload = { status: result.status, startTime: result.startTime.getTime(), duration: result.duration }; this._messageSink({ method: "onEnd", params: { result: resultPayload } }); } printsToStdio() { return false; } _serializeConfig(config2) { return { configFile: this._relativePath(config2.configFile), globalTimeout: config2.globalTimeout, maxFailures: config2.maxFailures, metadata: config2.metadata, rootDir: config2.rootDir, shard: config2.shard, version: config2.version, workers: config2.workers, globalSetup: config2.globalSetup, globalTeardown: config2.globalTeardown, tags: config2.tags, webServer: config2.webServer }; } _serializeProject(suite) { const project = suite.project(); const report = { metadata: project.metadata, name: project.name, outputDir: this._relativePath(project.outputDir), repeatEach: project.repeatEach, retries: project.retries, testDir: this._relativePath(project.testDir), testIgnore: serializeRegexPatterns(project.testIgnore), testMatch: serializeRegexPatterns(project.testMatch), timeout: project.timeout, suites: suite.suites.map((fileSuite) => { return this._serializeSuite(fileSuite); }), grep: serializeRegexPatterns(project.grep), grepInvert: serializeRegexPatterns(project.grepInvert || []), dependencies: project.dependencies, snapshotDir: this._relativePath(project.snapshotDir), teardown: project.teardown, ignoreSnapshots: project.ignoreSnapshots ? true : void 0, use: this._serializeProjectUseOptions(project.use) }; return report; } _serializeProjectUseOptions(use) { return { testIdAttribute: use.testIdAttribute }; } _serializeSuite(suite) { const result = { title: suite.title, location: this._relativeLocation(suite.location), entries: suite.entries().map((e) => { if (e.type === "test") return this._serializeTest(e); return this._serializeSuite(e); }) }; return result; } _serializeTest(test) { return { testId: test.id, title: test.title, location: this._relativeLocation(test.location), retries: test.retries, tags: test.tags, repeatEachIndex: test.repeatEachIndex, annotations: this._relativeAnnotationLocations(test.annotations) }; } _serializeResultStart(result) { return { id: result[this._idSymbol], retry: result.retry, workerIndex: result.workerIndex, parallelIndex: result.parallelIndex, startTime: +result.startTime }; } _serializeResultEnd(result) { const id = result[this._idSymbol]; return { id, duration: result.duration, status: result.status, errors: this._resultKnownErrorCounts.has(id) ? result.errors.slice(this._resultKnownAttachmentCounts.get(id)) : result.errors, annotations: result.annotations?.length ? this._relativeAnnotationLocations(result.annotations) : void 0 }; } _sendNewAttachments(result, testId) { const resultId = result[this._idSymbol]; const knownAttachmentCount = this._resultKnownAttachmentCounts.get(resultId) ?? 0; if (result.attachments.length > knownAttachmentCount) { this._messageSink({ method: "onAttach", params: { testId, resultId, attachments: this._serializeAttachments(result.attachments.slice(knownAttachmentCount)) } }); } this._resultKnownAttachmentCounts.set(resultId, result.attachments.length); } _serializeAttachments(attachments) { return attachments.map((a) => { const { body, ...rest } = a; return { ...rest, // There is no Buffer in the browser, so there is no point in sending the data there. base64: body && !this._emitterOptions.omitBuffers ? body.toString("base64") : void 0 }; }); } _serializeStepStart(step) { return { id: step[this._idSymbol], parentStepId: step.parent?.[this._idSymbol], title: step.title, category: step.category, startTime: +step.startTime, location: this._relativeLocation(step.location) }; } _serializeStepEnd(step, result) { return { id: step[this._idSymbol], duration: step.duration, error: step.error, attachments: step.attachments.length ? step.attachments.map((a) => result.attachments.indexOf(a)) : void 0, annotations: step.annotations.length ? this._relativeAnnotationLocations(step.annotations) : void 0 }; } _relativeAnnotationLocations(annotations) { return annotations.map((annotation) => ({ ...annotation, location: annotation.location ? this._relativeLocation(annotation.location) : void 0 })); } _relativeLocation(location) { if (!location) return location; return { ...location, file: this._relativePath(location.file) }; } _relativePath(absolutePath) { if (!absolutePath) return absolutePath; return import_path5.default.relative(this._rootDir, absolutePath); } }; // packages/playwright/src/reporters/blob.ts var mime = require("playwright-core/lib/utilsBundle").mime; var yazl = require("playwright-core/lib/utilsBundle").yazl; var { ManualPromise: ManualPromise2 } = require("playwright-core/lib/coreBundle").iso; var { calculateSha1, createGuid: createGuid2 } = require("playwright-core/lib/coreBundle").utils; var { removeFolders, sanitizeForFilePath } = require("playwright-core/lib/coreBundle").utils; var currentBlobReportVersion = 2; var BlobReporter = class extends TeleReporterEmitter { constructor(options) { super((message) => this._messages.push(message)); this._messages = []; this._attachments = []; this._options = options; if (this._options.fileName && !this._options.fileName.endsWith(".zip")) throw new Error(`Blob report file name must end with .zip extension: ${this._options.fileName}`); this._salt = createGuid2(); } onConfigure(config2) { const metadata = { version: currentBlobReportVersion, userAgent: (0, import_coreBundle.getUserAgent)(), // TODO: remove after some time, recommend config.tag instead. name: process.env.PWTEST_BOT_NAME, shard: config2.shard ?? void 0, pathSeparator: import_path6.default.sep }; this._messages.push({ method: "onBlobReportMetadata", params: metadata }); this._config = config2; super.onConfigure(config2); } async onTestPaused(test, result) { } async onEnd(result) { await super.onEnd(result); const zipFileName = await this._prepareOutputFile(); const zipFile = new yazl.ZipFile(); const zipFinishPromise = new ManualPromise2(); const finishPromise = zipFinishPromise.catch((e) => { throw new Error(`Failed to write report ${zipFileName}: ` + e.message); }); zipFile.on("error", (error) => zipFinishPromise.reject(error)); zipFile.outputStream.pipe(import_fs4.default.createWriteStream(zipFileName)).on("close", () => { zipFinishPromise.resolve(void 0); }).on("error", (error) => zipFinishPromise.reject(error)); for (const { originalPath, zipEntryPath } of this._attachments) { if (!import_fs4.default.statSync(originalPath, { throwIfNoEntry: false })?.isFile()) continue; zipFile.addFile(originalPath, zipEntryPath); } const lines = this._messages.map((m) => JSON.stringify(m) + "\n"); const content = import_stream.Readable.from(lines); zipFile.addReadStream(content, "report.jsonl"); zipFile.end(); await finishPromise; } async _prepareOutputFile() { const { outputFile, outputDir } = resolveOutputFile("BLOB", { ...this._options, default: { fileName: this._defaultReportName(this._config), outputDir: "blob-report" } }); if (!process.env.PWTEST_BLOB_DO_NOT_REMOVE) await removeFolders([outputDir]); await import_fs4.default.promises.mkdir(import_path6.default.dirname(outputFile), { recursive: true }); return outputFile; } _defaultReportName(config2) { let reportName = "report"; if (this._options._commandHash) reportName += "-" + sanitizeForFilePath(this._options._commandHash); if (config2.shard) { const paddedNumber = `${config2.shard.current}`.padStart(`${config2.shard.total}`.length, "0"); reportName = `${reportName}-${paddedNumber}`; } return `${reportName}.zip`; } _serializeAttachments(attachments) { return super._serializeAttachments(attachments).map((attachment) => { if (!attachment.path) return attachment; const sha1 = calculateSha1(attachment.path + this._salt); const extension = mime.getExtension(attachment.contentType) || "dat"; const newPath = `resources/${sha1}.${extension}`; this._attachments.push({ originalPath: attachment.path, zipEntryPath: newPath }); return { ...attachment, path: newPath }; }); } }; // packages/playwright/src/reporters/dot.ts var DotReporter = class extends TerminalReporter { constructor() { super(...arguments); this._counter = 0; } onBegin(suite) { super.onBegin(suite); this.writeLine(this.generateStartingMessage()); } onStdOut(chunk, test, result) { super.onStdOut(chunk, test, result); if (!this.config.quiet) this.screen.stdout.write(chunk); } onStdErr(chunk, test, result) { super.onStdErr(chunk, test, result); if (!this.config.quiet) this.screen.stderr.write(chunk); } onTestEnd(test, result) { super.onTestEnd(test, result); if (this._counter === 80) { this.screen.stdout.write("\n"); this._counter = 0; } ++this._counter; if (result.status === "skipped") { this.screen.stdout.write(this.screen.colors.yellow("\xB0")); return; } if (this.willRetry(test)) { this.screen.stdout.write(this.screen.colors.gray("\xD7")); return; } switch (test.outcome()) { case "expected": this.screen.stdout.write(this.screen.colors.green("\xB7")); break; case "unexpected": this.screen.stdout.write(this.screen.colors.red(result.status === "timedOut" ? "T" : "F")); break; case "flaky": this.screen.stdout.write(this.screen.colors.yellow("\xB1")); break; } } onError(error) { super.onError(error); this.writeLine("\n" + this.formatError(error).message); this._counter = 0; } async onTestPaused(test, result) { if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS) return; this.screen.stdout.write("\n"); if (test.outcome() === "unexpected") { this.writeLine(this.screen.colors.red(this.formatTestHeader(test, { indent: " " }))); this.writeLine(this.formatResultErrors(test, result)); markErrorsAsReported(result); this.writeLine(this.screen.colors.yellow(" Paused on error. Press Ctrl+C to end.") + "\n"); } else { this.writeLine(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " }))); this.writeLine(this.screen.colors.yellow(" Paused at test end. Press Ctrl+C to end.") + "\n"); } this._counter = 0; await new Promise(() => { }); } async onEnd(result) { await super.onEnd(result); this.screen.stdout.write("\n"); this.epilogue(true); } }; var dot_default = DotReporter; // packages/playwright/src/reporters/empty.ts var EmptyReporter = class { version() { return "v2"; } printsToStdio() { return false; } }; var empty_default = EmptyReporter; // packages/playwright/src/reporters/github.ts var import_path7 = __toESM(require("path")); var import_util5 = require("../util"); var { noColors: noColors2 } = require("playwright-core/lib/coreBundle").iso; var { msToString: msToString2 } = require("playwright-core/lib/coreBundle").iso; var GitHubLogger = class { _log(message, type = "notice", options = {}) { message = message.replace(/\n/g, "%0A"); const configs = Object.entries(options).map(([key, option]) => `${key}=${option}`).join(","); process.stdout.write((0, import_util5.stripAnsiEscapes)(`::${type} ${configs}::${message} `)); } debug(message, options) { this._log(message, "debug", options); } error(message, options) { this._log(message, "error", options); } notice(message, options) { this._log(message, "notice", options); } warning(message, options) { this._log(message, "warning", options); } }; var GitHubReporter = class extends TerminalReporter { constructor(options = {}) { super(options); this.githubLogger = new GitHubLogger(); this._failedTestCount = 0; this.screen = { ...this.screen, colors: noColors2 }; } printsToStdio() { return false; } onTestEnd(test, result) { super.onTestEnd(test, result); if (this.willRetry(test)) return; if (!this._shouldPrintFailureAnnotations(test)) return; this._failedTestCount++; for (const r of test.results) this._printFailureAnnotation(test, r, this._failedTestCount); } _shouldPrintFailureAnnotations(test) { switch (test.outcome()) { case "unexpected": case "flaky": return true; case "skipped": return test.results.some((r) => r.status === "interrupted") && test.results.some((r) => !!r.error); default: return false; } } async onEnd(result) { await super.onEnd(result); this._printAnnotations(); } onError(error) { const errorMessage = this.formatError(error).message; this.githubLogger.error(errorMessage); } _printAnnotations() { const summary = this.generateSummary(); const summaryMessage = this.generateSummaryMessage(summary); this._printSlowTestAnnotations(); this._printSummaryAnnotation(summaryMessage); } _printSlowTestAnnotations() { this.getSlowTests().forEach(([file, duration]) => { const filePath = workspaceRelativePath(import_path7.default.join(process.cwd(), file)); this.githubLogger.warning(`${filePath} took ${msToString2(duration)}`, { title: "Slow Test", file: filePath }); }); } _printSummaryAnnotation(summary) { this.githubLogger.notice(summary, { title: "\u{1F3AD} Playwright Run Summary" }); } _printFailureAnnotation(test, result, index) { const title = this.formatTestTitle(test); const header = this.formatTestHeader(test, { indent: " ", index, mode: "error" }); const errors = formatResultFailure(this.screen, test, result, " "); for (const error of errors) { const options = { file: workspaceRelativePath(error.location?.file || test.location.file), title }; if (error.location) { options.line = error.location.line; options.col = error.location.column; } const message = [header, ...formatRetry(this.screen, result), error.message].join("\n"); this.githubLogger.error(message, options); } } }; function workspaceRelativePath(filePath) { return import_path7.default.relative(process.env["GITHUB_WORKSPACE"] ?? "", filePath); } var github_default = GitHubReporter; // packages/playwright/src/reporters/html.ts var html_exports = {}; __export(html_exports, { default: () => html_default, showHTMLReport: () => showHTMLReport }); var import_fs5 = __toESM(require("fs")); var import_os = __toESM(require("os")); var import_path8 = __toESM(require("path")); var import_stream2 = require("stream"); var babel2 = __toESM(require("../transform/babelBundle")); var import_util6 = require("../util"); var colors2 = require("playwright-core/lib/utilsBundle").colors; var mime2 = require("playwright-core/lib/utilsBundle").mime; var open = require("playwright-core/lib/utilsBundle").open; var yazl2 = require("playwright-core/lib/utilsBundle").yazl; var { MultiMap } = require("playwright-core/lib/coreBundle").iso; var { calculateSha1: calculateSha12 } = require("playwright-core/lib/coreBundle").utils; var { copyFileAndMakeWritable, removeFolders: removeFolders2, sanitizeForFilePath: sanitizeForFilePath2, toPosixPath: toPosixPath2 } = require("playwright-core/lib/coreBundle").utils; var { getPackageManagerExecCommand: getPackageManagerExecCommand2, isCodingAgent } = require("playwright-core/lib/coreBundle").utils; var { HttpServer, serveFolder } = require("playwright-core/lib/coreBundle").utils; var { gracefullyProcessExitDoNotHang } = require("playwright-core/lib/coreBundle").utils; var { extractZip } = require("playwright-core/lib/coreBundle").utils; var htmlReportOptions = ["always", "never", "on-failure"]; var isHtmlReportOption = (type) => { return htmlReportOptions.includes(type); }; var HtmlReporter = class { constructor(options) { this._topLevelErrors = []; this._reportConfigs = /* @__PURE__ */ new Map(); this._machines = []; this._options = options; } version() { return "v2"; } printsToStdio() { return false; } onConfigure(config2) { this.config = config2; } onBegin(suite) { const { outputFolder, open: open3, attachmentsBaseURL, host, port } = this._resolveOptions(); this._outputFolder = outputFolder; this._open = open3; this._host = host; this._port = port; this._attachmentsBaseURL = attachmentsBaseURL; const reportedWarnings = /* @__PURE__ */ new Set(); for (const project of this.config.projects) { if (this._isSubdirectory(outputFolder, project.outputDir) || this._isSubdirectory(project.outputDir, outputFolder)) { const key = outputFolder + "|" + project.outputDir; if (reportedWarnings.has(key)) continue; reportedWarnings.add(key); writeLine(colors2.red(`Configuration Error: HTML reporter output folder clashes with the tests output folder:`)); writeLine(` html reporter folder: ${colors2.bold(outputFolder)} test results folder: ${colors2.bold(project.outputDir)}`); writeLine(""); writeLine(`HTML reporter will clear its output directory prior to being generated, which will lead to the artifact loss. `); } } this.suite = suite; } _resolveOptions() { const outputFolder = reportFolderFromEnv() ?? (0, import_util6.resolveReporterOutputPath)("playwright-report", this._options.configDir, this._options.outputFolder); return { outputFolder, open: getHtmlReportOptionProcessEnv() || this._options.open || "on-failure", attachmentsBaseURL: process.env.PLAYWRIGHT_HTML_ATTACHMENTS_BASE_URL || this._options.attachmentsBaseURL || "data/", host: process.env.PLAYWRIGHT_HTML_HOST || this._options.host, port: process.env.PLAYWRIGHT_HTML_PORT ? +process.env.PLAYWRIGHT_HTML_PORT : this._options.port }; } _isSubdirectory(parentDir, dir) { const relativePath = import_path8.default.relative(parentDir, dir); return !!relativePath && !relativePath.startsWith("..") && !import_path8.default.isAbsolute(relativePath); } onError(error) { this._topLevelErrors.push(error); } onReportConfigure(params) { this._reportConfigs.set(params.reportPath, params.config); } onReportEnd(params) { const config2 = this._reportConfigs.get(params.reportPath); if (config2) this._machines.push({ config: config2, result: params.result, reportPath: params.reportPath }); } async onEnd(result) { const projectSuites = this.suite.suites; await removeFolders2([this._outputFolder]); const noSnippets = parseBooleanEnvVar("PLAYWRIGHT_HTML_NO_SNIPPETS") ?? this._options.noSnippets; const noCopyPrompt = parseBooleanEnvVar("PLAYWRIGHT_HTML_NO_COPY_PROMPT") ?? this._options.noCopyPrompt; const doNotInlineAssets = parseBooleanEnvVar("PLAYWRIGHT_HTML_DO_NOT_INLINE_ASSETS") ?? this._options.doNotInlineAssets ?? false; const builder = new HtmlBuilder(yazl2, this.config, this._outputFolder, this._attachmentsBaseURL, doNotInlineAssets, { title: process.env.PLAYWRIGHT_HTML_TITLE || this._options.title, noSnippets, noCopyPrompt }); this._buildResult = await builder.build(this.config.metadata, projectSuites, result, this._topLevelErrors, this._machines); } async onExit() { if (process.env.CI || !this._buildResult) return; const { ok, singleTestId } = this._buildResult; const shouldOpen = !isCodingAgent() && !!process.stdin.isTTY && (this._open === "always" || !ok && this._open === "on-failure"); if (shouldOpen) { await showHTMLReport(this._outputFolder, this._host, this._port, singleTestId); } else if (this._options._mode === "test" && !!process.stdin.isTTY) { const packageManagerCommand = getPackageManagerExecCommand2(); const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? "" : " " + import_path8.default.relative(process.cwd(), this._outputFolder); const hostArg = this._host ? ` --host ${this._host}` : ""; const portArg = this._port ? ` --port ${this._port}` : ""; writeLine(""); writeLine("To open last HTML report run:"); writeLine(colors2.cyan(` ${packageManagerCommand} playwright show-report${relativeReportPath}${hostArg}${portArg} `)); } } }; function reportFolderFromEnv() { const envValue = process.env.PLAYWRIGHT_HTML_OUTPUT_DIR || process.env.PLAYWRIGHT_HTML_REPORT; return envValue ? import_path8.default.resolve(envValue) : void 0; } function getHtmlReportOptionProcessEnv() { const htmlOpenEnv = process.env.PLAYWRIGHT_HTML_OPEN || process.env.PW_TEST_HTML_REPORT_OPEN; if (!htmlOpenEnv) return void 0; if (!isHtmlReportOption(htmlOpenEnv)) { writeLine(colors2.red(`Configuration Error: HTML reporter Invalid value for PLAYWRIGHT_HTML_OPEN: ${htmlOpenEnv}. Valid values are: ${htmlReportOptions.join(", ")}`)); return void 0; } return htmlOpenEnv; } function parseBooleanEnvVar(name) { const value = process.env[name]; if (value === "false" || value === "0") return false; if (value) return true; return void 0; } function standaloneDefaultFolder() { return reportFolderFromEnv() ?? (0, import_util6.resolveReporterOutputPath)("playwright-report", process.cwd(), void 0); } async function resolveReportFolder(reportPath) { const stat = await import_fs5.default.promises.stat(reportPath).catch(() => null); if (!stat) throw new Error(`No report found at "${reportPath}"`); if (stat.isDirectory()) return reportPath; if (stat.isFile() && reportPath.toLowerCase().endsWith(".zip")) return await extractReportZip(reportPath); throw new Error(`No report found at "${reportPath}"`); } async function extractReportZip(zipPath) { const tempDir = await import_fs5.default.promises.mkdtemp(import_path8.default.join(import_os.default.tmpdir(), "playwright-show-report-")); const cleanup = () => { try { import_fs5.default.rmSync(tempDir, { recursive: true, force: true }); } catch { } }; process.on("exit", cleanup); try { await extractZip(zipPath, { dir: tempDir }); } catch (e) { cleanup(); throw new Error(`Failed to extract report from "${zipPath}": ${e.message}`); } const hasIndex = await import_fs5.default.promises.access(import_path8.default.join(tempDir, "index.html")).then(() => true, () => false); if (!hasIndex) { cleanup(); throw new Error(`No "index.html" found at the top level of "${zipPath}"`); } return tempDir; } async function showHTMLReport(reportFolder, host = "localhost", port, testId) { const requestedPath = reportFolder ?? standaloneDefaultFolder(); let folder; try { folder = await resolveReportFolder(requestedPath); } catch (e) { writeLine(colors2.red(e.message)); gracefullyProcessExitDoNotHang(1); return; } const server = false ? await serveHtmlReportWithHMR(folder) : serveFolder(folder); await server.start({ port, host, preferredPort: port ? void 0 : 9323 }); let url = server.urlPrefix("human-readable"); writeLine(""); writeLine(colors2.cyan(` Serving HTML report at ${url}. Press Ctrl+C to quit.`)); if (testId) url += `#?testId=${testId}`; url = url.replace("0.0.0.0", "localhost"); if (!isCodingAgent()) await open(url, { wait: true }).catch(() => { }); await new Promise(() => { }); } var HtmlBuilder = class { constructor(yazl3, config2, outputDir, attachmentsBaseURL, doNotInlineAssets, options) { this._stepsInFile = new MultiMap(); this._hasTraces = false; this._dataZipFile = new yazl3.ZipFile(); this._config = config2; this._reportFolder = outputDir; this._options = options; this._doNotInlineAssets = doNotInlineAssets; import_fs5.default.mkdirSync(this._reportFolder, { recursive: true }); this._attachmentsBaseURL = attachmentsBaseURL; } async build(metadata, projectSuites, result, topLevelErrors, machines) { const data = /* @__PURE__ */ new Map(); for (const projectSuite of projectSuites) { const projectName = projectSuite.project().name; for (const fileSuite of projectSuite.suites) { const fileName = this._relativeLocation(fileSuite.location).file; this._createEntryForSuite(data, projectName, fileSuite, fileName, true); } } if (!this._options.noSnippets) createSnippets(this._stepsInFile); let ok = true; for (const [fileId, { testFile, testFileSummary }] of data) { const stats = testFileSummary.stats; for (const test of testFileSummary.tests) { if (test.outcome === "expected") ++stats.expected; if (test.outcome === "skipped") ++stats.skipped; if (test.outcome === "unexpected") ++stats.unexpected; if (test.outcome === "flaky") ++stats.flaky; ++stats.total; } stats.ok = stats.unexpected + stats.flaky === 0; if (!stats.ok) ok = false; const testCaseSummaryComparator = (t1, t2) => { const w1 = (t1.outcome === "unexpected" ? 1e3 : 0) + (t1.outcome === "flaky" ? 1 : 0); const w2 = (t2.outcome === "unexpected" ? 1e3 : 0) + (t2.outcome === "flaky" ? 1 : 0); return w2 - w1; }; testFileSummary.tests.sort(testCaseSummaryComparator); this._addDataFile(fileId + ".json", testFile); } const htmlReport = { metadata, startTime: result.startTime.getTime(), duration: result.duration, files: [...data.values()].map((e) => e.testFileSummary), projectNames: projectSuites.map((r) => r.project().name), stats: { ...[...data.values()].reduce((a, e) => addStats(a, e.testFileSummary.stats), emptyStats()) }, errors: topLevelErrors.map((error) => formatError(internalScreen, error).message), options: this._options, machines: machines.map((machine) => ({ duration: machine.result.duration, startTime: machine.result.startTime.getTime(), tag: machine.config.tags, shardIndex: machine.config.shard?.current })) }; htmlReport.files.sort((f1, f2) => { const w1 = f1.stats.unexpected * 1e3 + f1.stats.flaky; const w2 = f2.stats.unexpected * 1e3 + f2.stats.flaky; return w2 - w1; }); this._addDataFile("report.json", htmlReport); let singleTestId; if (htmlReport.stats.total === 1) { const testFile = data.values().next().value.testFile; singleTestId = testFile.tests[0].testId; } const reportIndexFile = await this._writeStaticAssets(); if (this._hasTraces) { const traceViewerFolder = import_path8.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "traceViewer"); const traceViewerTargetFolder = import_path8.default.join(this._reportFolder, "trace"); const traceViewerAssetsTargetFolder = import_path8.default.join(traceViewerTargetFolder, "assets"); import_fs5.default.mkdirSync(traceViewerAssetsTargetFolder, { recursive: true }); for (const file of import_fs5.default.readdirSync(traceViewerFolder)) { if (file.endsWith(".map") || file.includes("watch") || file.includes("assets")) continue; await copyFileAndMakeWritable(import_path8.default.join(traceViewerFolder, file), import_path8.default.join(traceViewerTargetFolder, file)); } for (const file of import_fs5.default.readdirSync(import_path8.default.join(traceViewerFolder, "assets"))) { if (file.endsWith(".map") || file.includes("xtermModule")) continue; await copyFileAndMakeWritable(import_path8.default.join(traceViewerFolder, "assets", file), import_path8.default.join(traceViewerAssetsTargetFolder, file)); } } await this._writeReportData(reportIndexFile); return { ok, singleTestId }; } async _writeStaticAssets() { const appFolder = import_path8.default.join(require.resolve("playwright-core"), "..", "lib", "vite", "htmlReport"); const reportIndexFile = import_path8.default.join(this._reportFolder, "index.html"); if (this._doNotInlineAssets) { const html = await import_fs5.default.promises.readFile(import_path8.default.join(appFolder, "index.html"), "utf-8"); await Promise.all([ import_fs5.default.promises.writeFile(reportIndexFile, html), import_fs5.default.promises.copyFile(import_path8.default.join(appFolder, "report.js"), import_path8.default.join(this._reportFolder, "report.js")), import_fs5.default.promises.copyFile(import_path8.default.join(appFolder, "report.css"), import_path8.default.join(this._reportFolder, "report.css")) ]); } else { let html = await import_fs5.default.promises.readFile(import_path8.default.join(appFolder, "index.html"), "utf-8"); const [js, css] = await Promise.all([ import_fs5.default.promises.readFile(import_path8.default.join(appFolder, "report.js"), "utf-8"), import_fs5.default.promises.readFile(import_path8.default.join(appFolder, "report.css"), "utf-8") ]); html = html.replace(/`); html = html.replace(/]*>/, () => ``); await import_fs5.default.promises.writeFile(reportIndexFile, html); } return reportIndexFile; } async _writeReportData(filePath) { import_fs5.default.appendFileSync(filePath, '"); } _addDataFile(fileName, data) { this._dataZipFile.addBuffer(Buffer.from(JSON.stringify(data)), fileName); } _createEntryForSuite(data, projectName, suite, fileName, deep) { const fileId = calculateSha12(fileName).slice(0, 20); let fileEntry = data.get(fileId); if (!fileEntry) { fileEntry = { testFile: { fileId, fileName, tests: [] }, testFileSummary: { fileId, fileName, tests: [], stats: emptyStats() } }; data.set(fileId, fileEntry); } const { testFile, testFileSummary } = fileEntry; const testEntries = []; this._processSuite(suite, projectName, [], deep, testEntries); for (const test of testEntries) { testFile.tests.push(test.testCase); testFileSummary.tests.push(test.testCaseSummary); } } _processSuite(suite, projectName, path20, deep, outTests) { const newPath = [...path20, suite.title]; suite.entries().forEach((e) => { if (e.type === "test") outTests.push(this._createTestEntry(e, projectName, newPath)); else if (deep) this._processSuite(e, projectName, newPath, deep, outTests); }); } _createTestEntry(test, projectName, path20) { const duration = test.results.reduce((a, r) => a + r.duration, 0); const location = this._relativeLocation(test.location); path20 = path20.slice(1).filter((path21) => path21.length > 0); const results = test.results.map((r) => this._createTestResult(test, r)); return { testCase: { testId: test.id, title: test.title, projectName, location, duration, annotations: this._serializeAnnotations(test.annotations), tags: [...new Set(test.tags)], outcome: test.outcome(), path: path20, results, repeatEachIndex: test.repeatEachIndex || void 0, // Do not include zero. ok: test.outcome() === "expected" || test.outcome() === "flaky" }, testCaseSummary: { testId: test.id, title: test.title, projectName, location, duration, annotations: this._serializeAnnotations(test.annotations), tags: [...new Set(test.tags)], outcome: test.outcome(), path: path20, ok: test.outcome() === "expected" || test.outcome() === "flaky", repeatEachIndex: test.repeatEachIndex || void 0, // Do not include zero. results: results.map((result) => { return { attachments: result.attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path })), startTime: result.startTime, workerIndex: result.workerIndex }; }) } }; } _serializeAttachments(attachments) { let lastAttachment; return attachments.map((a) => { if (a.name === "trace") this._hasTraces = true; if ((a.name === "stdout" || a.name === "stderr") && a.contentType === "text/plain") { if (lastAttachment && lastAttachment.name === a.name && lastAttachment.contentType === a.contentType) { lastAttachment.body += (0, import_util6.stripAnsiEscapes)(a.body); return null; } a.body = (0, import_util6.stripAnsiEscapes)(a.body); lastAttachment = a; return a; } if (a.path) { let fileName = a.path; try { const buffer = import_fs5.default.readFileSync(a.path); const sha1 = calculateSha12(buffer) + import_path8.default.extname(a.path); fileName = this._attachmentsBaseURL + sha1; import_fs5.default.mkdirSync(import_path8.default.join(this._reportFolder, "data"), { recursive: true }); import_fs5.default.writeFileSync(import_path8.default.join(this._reportFolder, "data", sha1), buffer); } catch (e) { } return { name: a.name, contentType: a.contentType, path: fileName, body: a.body }; } if (a.body instanceof Buffer) { if (isTextContentType(a.contentType)) { const charset = a.contentType.match(/charset=(.*)/)?.[1]; try { const body = a.body.toString(charset || "utf-8"); return { name: a.name, contentType: a.contentType, body }; } catch (e) { } } import_fs5.default.mkdirSync(import_path8.default.join(this._reportFolder, "data"), { recursive: true }); const extension = sanitizeForFilePath2(import_path8.default.extname(a.name).replace(/^\./, "")) || mime2.getExtension(a.contentType) || "dat"; const sha1 = calculateSha12(a.body) + "." + extension; import_fs5.default.writeFileSync(import_path8.default.join(this._reportFolder, "data", sha1), a.body); return { name: a.name, contentType: a.contentType, path: this._attachmentsBaseURL + sha1 }; } return { name: a.name, contentType: a.contentType, body: a.body }; }).filter(Boolean); } _serializeAnnotations(annotations) { return annotations.map((a) => ({ type: a.type, description: a.description === void 0 ? void 0 : String(a.description), location: a.location ? { file: a.location.file, line: a.location.line, column: a.location.column } : void 0 })); } _createTestResult(test, result) { return { duration: result.duration, startTime: result.startTime.toISOString(), retry: result.retry, steps: dedupeSteps(result.steps).map((s) => this._createTestStep(s, result)), errors: formatResultFailure(internalScreen, test, result, "").map((error) => { return { message: error.message, codeframe: error.location ? createErrorCodeframe(error.message, error.location) : void 0 }; }), status: result.status, annotations: this._serializeAnnotations(result.annotations), attachments: this._serializeAttachments([ ...result.attachments, ...result.stdout.map((m) => stdioAttachment(m, "stdout")), ...result.stderr.map((m) => stdioAttachment(m, "stderr")) ]), workerIndex: result.workerIndex }; } _createTestStep(dedupedStep, result) { const { step, duration, count } = dedupedStep; const skipped = dedupedStep.step.annotations?.find((a) => a.type === "skip"); let title = step.title; if (skipped) title = `${title} (skipped${skipped.description ? ": " + skipped.description : ""})`; const testStep = { title, startTime: step.startTime.toISOString(), duration, steps: dedupeSteps(step.steps).map((s) => this._createTestStep(s, result)), attachments: step.attachments.map((s) => { const index = result.attachments.indexOf(s); if (index === -1) throw new Error("Unexpected, attachment not found"); return index; }), location: this._relativeLocation(step.location), error: step.error?.message, count, skipped: !!skipped }; if (step.location) this._stepsInFile.set(step.location.file, testStep); return testStep; } _relativeLocation(location) { if (!location) return void 0; const file = toPosixPath2(import_path8.default.relative(this._config.rootDir, location.file)); return { file, line: location.line, column: location.column }; } }; var emptyStats = () => { return { total: 0, expected: 0, unexpected: 0, flaky: 0, skipped: 0, ok: true }; }; var addStats = (stats, delta) => { stats.total += delta.total; stats.skipped += delta.skipped; stats.expected += delta.expected; stats.unexpected += delta.unexpected; stats.flaky += delta.flaky; stats.ok = stats.ok && delta.ok; return stats; }; var Base64Encoder = class extends import_stream2.Transform { _transform(chunk, encoding, callback) { if (this._remainder) { chunk = Buffer.concat([this._remainder, chunk]); this._remainder = void 0; } const remaining = chunk.length % 3; if (remaining) { this._remainder = chunk.slice(chunk.length - remaining); chunk = chunk.slice(0, chunk.length - remaining); } chunk = chunk.toString("base64"); this.push(Buffer.from(chunk)); callback(); } _flush(callback) { if (this._remainder) this.push(Buffer.from(this._remainder.toString("base64"))); callback(); } }; function isTextContentType(contentType) { return contentType.startsWith("text/") || contentType.startsWith("application/json"); } function stdioAttachment(chunk, type) { return { name: type, contentType: "text/plain", body: typeof chunk === "string" ? chunk : chunk.toString("utf-8") }; } function dedupeSteps(steps) { const result = []; let lastResult = void 0; for (const step of steps) { const canDedupe = !step.error && step.duration >= 0 && step.location?.file && !step.steps.length; const lastStep = lastResult?.step; if (canDedupe && lastResult && lastStep && step.category === lastStep.category && step.title === lastStep.title && step.location?.file === lastStep.location?.file && step.location?.line === lastStep.location?.line && step.location?.column === lastStep.location?.column) { ++lastResult.count; lastResult.duration += step.duration; continue; } lastResult = { step, count: 1, duration: step.duration }; result.push(lastResult); if (!canDedupe) lastResult = void 0; } return result; } function createSnippets(stepsInFile) { for (const file of stepsInFile.keys()) { let source; try { source = import_fs5.default.readFileSync(file, "utf-8") + "\n//"; } catch (e) { continue; } const lines = source.split("\n").length; const highlighted = babel2.codeFrameColumns(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 }); const highlightedLines = highlighted.split("\n"); const lineWithArrow = highlightedLines[highlightedLines.length - 1]; for (const step of stepsInFile.get(file)) { if (step.location.line < 2 || step.location.line >= lines) continue; const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1); const index = lineWithArrow.indexOf("^"); const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index); snippetLines.splice(2, 0, shiftedArrow); step.snippet = snippetLines.join("\n"); } } } function createErrorCodeframe(message, location) { let source; try { source = import_fs5.default.readFileSync(location.file, "utf-8") + "\n//"; } catch (e) { return; } return babel2.codeFrameColumns( source, { start: { line: location.line, column: location.column } }, { highlightCode: false, linesAbove: 100, linesBelow: 100, message: (0, import_util6.stripAnsiEscapes)(message).split("\n")[0] || void 0 } ); } function writeLine(line) { process.stdout.write(line + "\n"); } var html_default = HtmlReporter; // packages/playwright/src/reporters/json.ts var import_fs6 = __toESM(require("fs")); var import_path9 = __toESM(require("path")); var import_common4 = require("../common"); var { MultiMap: MultiMap2 } = require("playwright-core/lib/coreBundle").iso; var { toPosixPath: toPosixPath3 } = require("playwright-core/lib/coreBundle").utils; var JSONReporter = class { constructor(options) { this._errors = []; this._resolvedOutputFile = resolveOutputFile("JSON", options)?.outputFile; } version() { return "v2"; } printsToStdio() { return !this._resolvedOutputFile; } onConfigure(config2) { this.config = config2; } onBegin(suite) { this.suite = suite; } onError(error) { this._errors.push(error); } async onEnd(result) { await outputReport(this._serializeReport(result), this._resolvedOutputFile); } _serializeReport(result) { const report = { config: { ...removePrivateFields(this.config), rootDir: toPosixPath3(this.config.rootDir), projects: this.config.projects.map((project) => { return { outputDir: toPosixPath3(project.outputDir), repeatEach: project.repeatEach, retries: project.retries, metadata: project.metadata, id: import_common4.config.getProjectId(project), name: project.name, testDir: toPosixPath3(project.testDir), testIgnore: serializePatterns(project.testIgnore), testMatch: serializePatterns(project.testMatch), timeout: project.timeout }; }) }, suites: this._mergeSuites(this.suite.suites), errors: this._errors, stats: { startTime: result.startTime.toISOString(), duration: result.duration, expected: 0, skipped: 0, unexpected: 0, flaky: 0 } }; for (const test of this.suite.allTests()) ++report.stats[test.outcome()]; return report; } _mergeSuites(suites) { const fileSuites = new MultiMap2(); for (const projectSuite of suites) { const projectId = import_common4.config.getProjectId(projectSuite.project()); const projectName = projectSuite.project().name; for (const fileSuite of projectSuite.suites) { const file = fileSuite.location.file; const serialized = this._serializeSuite(projectId, projectName, fileSuite); if (serialized) fileSuites.set(file, serialized); } } const results = []; for (const [, suites2] of fileSuites) { const result = { title: suites2[0].title, file: suites2[0].file, column: 0, line: 0, specs: [] }; for (const suite of suites2) this._mergeTestsFromSuite(result, suite); results.push(result); } return results; } _relativeLocation(location) { if (!location) return { file: "", line: 0, column: 0 }; return { file: toPosixPath3(import_path9.default.relative(this.config.rootDir, location.file)), line: location.line, column: location.column }; } _locationMatches(s1, s2) { return s1.file === s2.file && s1.line === s2.line && s1.column === s2.column; } _mergeTestsFromSuite(to, from) { for (const fromSuite of from.suites || []) { const toSuite = (to.suites || []).find((s) => s.title === fromSuite.title && this._locationMatches(s, fromSuite)); if (toSuite) { this._mergeTestsFromSuite(toSuite, fromSuite); } else { if (!to.suites) to.suites = []; to.suites.push(fromSuite); } } for (const spec of from.specs || []) { const toSpec = to.specs.find((s) => s.title === spec.title && s.file === toPosixPath3(import_path9.default.relative(this.config.rootDir, spec.file)) && s.line === spec.line && s.column === spec.column); if (toSpec) toSpec.tests.push(...spec.tests); else to.specs.push(spec); } } _serializeSuite(projectId, projectName, suite) { if (!suite.allTests().length) return null; const suites = suite.suites.map((suite2) => this._serializeSuite(projectId, projectName, suite2)).filter((s) => s); return { title: suite.title, ...this._relativeLocation(suite.location), specs: suite.tests.map((test) => this._serializeTestSpec(projectId, projectName, test)), suites: suites.length ? suites : void 0 }; } _serializeTestSpec(projectId, projectName, test) { return { title: test.title, ok: test.ok(), tags: test.tags.map((tag) => tag.substring(1)), // Strip '@'. tests: [this._serializeTest(projectId, projectName, test)], id: test.id, ...this._relativeLocation(test.location) }; } _serializeTest(projectId, projectName, test) { return { timeout: test.timeout, annotations: test.annotations, expectedStatus: test.expectedStatus, projectId, projectName, results: test.results.map((r) => this._serializeTestResult(r, test)), status: test.outcome() }; } _serializeTestResult(result, test) { const steps = result.steps.filter((s) => s.category === "test.step"); const jsonResult = { workerIndex: result.workerIndex, parallelIndex: result.parallelIndex, status: result.status, duration: result.duration, error: result.error, errors: result.errors.map((e) => this._serializeError(e)), stdout: result.stdout.map((s) => stdioEntry(s)), stderr: result.stderr.map((s) => stdioEntry(s)), retry: result.retry, steps: steps.length ? steps.map((s) => this._serializeTestStep(s)) : void 0, startTime: result.startTime.toISOString(), annotations: result.annotations, attachments: result.attachments.map((a) => ({ name: a.name, contentType: a.contentType, path: a.path, body: a.body?.toString("base64") })) }; if (result.error?.stack) jsonResult.errorLocation = prepareErrorStack(result.error.stack).location; return jsonResult; } _serializeError(error) { return formatError(nonTerminalScreen, error); } _serializeTestStep(step) { const steps = step.steps.filter((s) => s.category === "test.step"); return { title: step.title, duration: step.duration, error: step.error, steps: steps.length ? steps.map((s) => this._serializeTestStep(s)) : void 0 }; } }; async function outputReport(report, resolvedOutputFile) { const reportString = JSON.stringify(report, void 0, 2); if (resolvedOutputFile) { await import_fs6.default.promises.mkdir(import_path9.default.dirname(resolvedOutputFile), { recursive: true }); await import_fs6.default.promises.writeFile(resolvedOutputFile, reportString); } else { console.log(reportString); } } function stdioEntry(s) { if (typeof s === "string") return { text: s }; return { buffer: s.toString("base64") }; } function removePrivateFields(config2) { return Object.fromEntries(Object.entries(config2).filter(([name, value]) => !name.startsWith("_"))); } function serializePatterns(patterns) { if (!Array.isArray(patterns)) patterns = [patterns]; return patterns.map((s) => s.toString()); } var json_default = JSONReporter; // packages/playwright/src/reporters/junit.ts var import_fs7 = __toESM(require("fs")); var import_path10 = __toESM(require("path")); var import_util7 = require("../util"); var { getAsBooleanFromENV } = require("playwright-core/lib/coreBundle").utils; var JUnitReporter = class { constructor(options) { this.totalTests = 0; this.totalFailures = 0; this.totalErrors = 0; this.totalSkipped = 0; this.stripANSIControlSequences = false; this.includeProjectInTestName = false; this.includeRetries = false; this.stripANSIControlSequences = getAsBooleanFromENV("PLAYWRIGHT_JUNIT_STRIP_ANSI", !!options.stripANSIControlSequences); this.includeProjectInTestName = getAsBooleanFromENV("PLAYWRIGHT_JUNIT_INCLUDE_PROJECT_IN_TEST_NAME", !!options.includeProjectInTestName); this.includeRetries = getAsBooleanFromENV("PLAYWRIGHT_JUNIT_INCLUDE_RETRIES", !!options.includeRetries); this.configDir = options.configDir; this.resolvedOutputFile = resolveOutputFile("JUNIT", options)?.outputFile; } version() { return "v2"; } printsToStdio() { return !this.resolvedOutputFile; } onConfigure(config2) { this.config = config2; } onBegin(suite) { this.suite = suite; this.timestamp = /* @__PURE__ */ new Date(); } async onEnd(result) { const children = []; for (const projectSuite of this.suite.suites) { for (const fileSuite of projectSuite.suites) children.push(await this._buildTestSuite(projectSuite.title, fileSuite)); } const tokens = []; const self = this; const root = { name: "testsuites", attributes: { id: process.env[`PLAYWRIGHT_JUNIT_SUITE_ID`] || "", name: process.env[`PLAYWRIGHT_JUNIT_SUITE_NAME`] || "", tests: self.totalTests, failures: self.totalFailures, skipped: self.totalSkipped, errors: self.totalErrors, time: result.duration / 1e3 }, children }; serializeXML(root, tokens, this.stripANSIControlSequences); const reportString = tokens.join("\n"); if (this.resolvedOutputFile) { await import_fs7.default.promises.mkdir(import_path10.default.dirname(this.resolvedOutputFile), { recursive: true }); await import_fs7.default.promises.writeFile(this.resolvedOutputFile, reportString); } else { console.log(reportString); } } async _buildTestSuite(projectName, suite) { let tests = 0; let skipped = 0; let failures = 0; let errors = 0; let duration = 0; const children = []; const testCaseNamePrefix = projectName && this.includeProjectInTestName ? `[${projectName}] ` : ""; for (const test of suite.allTests()) { ++tests; if (test.outcome() === "skipped") ++skipped; for (const result of test.results) duration += result.duration; const classification = await this._addTestCase(suite.title, testCaseNamePrefix, test, children); if (classification === "error") ++errors; else if (classification === "failure") ++failures; } this.totalTests += tests; this.totalSkipped += skipped; this.totalFailures += failures; this.totalErrors += errors; const entry = { name: "testsuite", attributes: { name: suite.title, timestamp: this.timestamp.toISOString(), hostname: projectName, tests, failures, skipped, time: duration / 1e3, errors }, children }; return entry; } async _addTestCase(suiteName, namePrefix, test, entries) { const entry = { name: "testcase", attributes: { // Skip root, project, file name: namePrefix + test.titlePath().slice(3).join(" \u203A "), // filename classname: suiteName }, children: [] }; entries.push(entry); const properties = { name: "properties", children: [] }; for (const annotation of test.annotations) { const property = { name: "property", attributes: { name: annotation.type, value: annotation?.description ? annotation.description : "" } }; properties.children?.push(property); } if (properties.children?.length) entry.children.push(properties); if (test.outcome() === "skipped") { entry.children.push({ name: "skipped" }); return null; } if (this.includeRetries && test.ok()) { const passResult = test.results[test.results.length - 1]; entry.attributes.time = passResult.duration / 1e3; await this._appendStdIO(entry, [passResult]); for (let i = 0; i < test.results.length - 1; i++) { const result = test.results[i]; if (result.status === "passed" || result.status === "skipped") continue; entry.children.push(await this._buildRetryEntry(result, "flaky")); } return null; } if (this.includeRetries) { entry.attributes.time = test.results[0].duration / 1e3; await this._appendStdIO(entry, [test.results[0]]); for (let i = 1; i < test.results.length; i++) { const result = test.results[i]; if (result.status === "passed" || result.status === "skipped") continue; entry.children.push(await this._buildRetryEntry(result, "rerun")); } return this._addFailureEntry(test, classifyResultError(test.results[0]), entry); } entry.attributes.time = test.results.reduce((acc, value) => acc + value.duration, 0) / 1e3; await this._appendStdIO(entry, test.results); if (test.ok()) return null; return this._addFailureEntry(test, classifyTestError(test), entry); } _addFailureEntry(test, errorInfo, entry) { if (errorInfo) { entry.children.push({ name: errorInfo.elementName, attributes: { message: errorInfo.message, type: errorInfo.type }, text: (0, import_util7.stripAnsiEscapes)(formatFailure(nonTerminalScreen, this.config, test)) }); return errorInfo.elementName; } entry.children.push({ name: "failure", attributes: { message: `${import_path10.default.basename(test.location.file)}:${test.location.line}:${test.location.column} ${test.title}`, type: "FAILURE" }, text: (0, import_util7.stripAnsiEscapes)(formatFailure(nonTerminalScreen, this.config, test)) }); return "failure"; } async _appendStdIO(entry, results) { const systemOut = []; const systemErr = []; for (const result of results) { for (const item of result.stdout) systemOut.push(item.toString()); for (const item of result.stderr) systemErr.push(item.toString()); for (const attachment of result.attachments) { if (!attachment.path) continue; let attachmentPath = import_path10.default.relative(this.configDir, attachment.path); try { if (this.resolvedOutputFile) attachmentPath = import_path10.default.relative(import_path10.default.dirname(this.resolvedOutputFile), attachment.path); } catch { systemOut.push(` Warning: Unable to make attachment path ${attachment.path} relative to report output file ${this.resolvedOutputFile}`); } try { await import_fs7.default.promises.access(attachment.path); systemOut.push(` [[ATTACHMENT|${attachmentPath}]] `); } catch { systemErr.push(` Warning: attachment ${attachmentPath} is missing`); } } } if (systemOut.length) entry.children.push({ name: "system-out", text: systemOut.join("") }); if (systemErr.length) entry.children.push({ name: "system-err", text: systemErr.join("") }); } async _buildRetryEntry(result, prefix) { const errorInfo = classifyResultError(result); const entry = { name: `${prefix}${errorInfo?.elementName === "error" ? "Error" : "Failure"}`, attributes: { message: errorInfo?.message || "", type: errorInfo?.type || "FAILURE", time: result.duration / 1e3 }, children: [] }; const stackTrace = result.error?.stack || result.error?.message || result.error?.value || ""; entry.children.push({ name: "stackTrace", text: (0, import_util7.stripAnsiEscapes)(stackTrace) }); await this._appendStdIO(entry, [result]); return entry; } }; function classifyResultError(result) { const error = result.error; if (!error) return null; const rawMessage = (0, import_util7.stripAnsiEscapes)(error.message || error.value || ""); const nameMatch = rawMessage.match(/^(\w+): /); const errorName = nameMatch ? nameMatch[1] : ""; const messageBody = nameMatch ? rawMessage.slice(nameMatch[0].length) : rawMessage; const firstLine = messageBody.split("\n")[0].trim(); const matcherMatch = rawMessage.match(/expect\(.*?\)\.(not\.)?(\w+)/); if (matcherMatch) { const matcherName = `expect.${matcherMatch[1] || ""}${matcherMatch[2]}`; return { elementName: "failure", type: matcherName, message: firstLine }; } return { elementName: "error", type: errorName || "Error", message: firstLine }; } function classifyTestError(test) { for (const result of test.results) { const info = classifyResultError(result); if (info) return info; } return null; } function serializeXML(entry, tokens, stripANSIControlSequences) { const attrs = []; for (const [name, value] of Object.entries(entry.attributes || {})) attrs.push(`${name}="${escape(String(value), stripANSIControlSequences, false)}"`); tokens.push(`<${entry.name}${attrs.length ? " " : ""}${attrs.join(" ")}>`); for (const child of entry.children || []) serializeXML(child, tokens, stripANSIControlSequences); if (entry.text) tokens.push(escape(entry.text, stripANSIControlSequences, true)); tokens.push(``); } var discouragedXMLCharacters = /[\u0000-\u0008\u000b-\u000c\u000e-\u001f\u007f-\u0084\u0086-\u009f]/g; function escape(text, stripANSIControlSequences, isCharacterData) { if (stripANSIControlSequences) text = (0, import_util7.stripAnsiEscapes)(text); if (isCharacterData) { text = "/g, "]]>") + "]]>"; } else { const escapeRe = /[&"'<>]/g; text = text.replace(escapeRe, (c) => ({ "&": "&", '"': """, "'": "'", "<": "<", ">": ">" })[c]); } text = text.replace(discouragedXMLCharacters, ""); return text; } var junit_default = JUnitReporter; // packages/playwright/src/reporters/line.ts var LineReporter = class extends TerminalReporter { constructor() { super(...arguments); this._current = 0; this._failures = 0; this._didBegin = false; } onBegin(suite) { super.onBegin(suite); const startingMessage = this.generateStartingMessage(); if (startingMessage) { this.writeLine(startingMessage); this.writeLine(); } this._didBegin = true; } onStdOut(chunk, test, result) { super.onStdOut(chunk, test, result); this._dumpToStdio(test, chunk, this.screen.stdout); } onStdErr(chunk, test, result) { super.onStdErr(chunk, test, result); this._dumpToStdio(test, chunk, this.screen.stderr); } _dumpToStdio(test, chunk, stream) { if (this.config.quiet) return; if (!process.env.PW_TEST_DEBUG_REPORTERS) stream.write(`\x1B[1A\x1B[2K`); if (test && this._lastTest !== test) { const title = this.screen.colors.dim(this.formatTestTitle(test)); stream.write(this.fitToScreen(title) + ` `); this._lastTest = test; } stream.write(chunk); if (chunk[chunk.length - 1] !== "\n") this.writeLine(); this.writeLine(); } onTestBegin(test, result) { ++this._current; this._updateLine(test, result, void 0); } onStepBegin(test, result, step) { if (this.screen.isTTY && step.category === "test.step") this._updateLine(test, result, step); } onStepEnd(test, result, step) { if (this.screen.isTTY && step.category === "test.step") this._updateLine(test, result, step.parent); } async onTestPaused(test, result) { if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS) return; if (!process.env.PW_TEST_DEBUG_REPORTERS) this.screen.stdout.write(`\x1B[1A\x1B[2K`); if (test.outcome() === "unexpected") { this.writeLine(this.screen.colors.red(this.formatTestHeader(test, { indent: " ", index: ++this._failures }))); this.writeLine(this.formatResultErrors(test, result)); markErrorsAsReported(result); this.writeLine(this.screen.colors.yellow(` Paused on error. Press Ctrl+C to end.`) + "\n\n"); } else { this.writeLine(this.screen.colors.yellow(this.formatTestHeader(test, { indent: " " }))); this.writeLine(this.screen.colors.yellow(` Paused at test end. Press Ctrl+C to end.`) + "\n\n"); } this._updateLine(test, result, void 0); await new Promise(() => { }); } onTestEnd(test, result) { super.onTestEnd(test, result); if (!this.willRetry(test) && (test.outcome() === "flaky" || test.outcome() === "unexpected" || result.status === "interrupted")) { if (!process.env.PW_TEST_DEBUG_REPORTERS) this.screen.stdout.write(`\x1B[1A\x1B[2K`); this.writeLine(this.formatFailure(test, ++this._failures)); this.writeLine(); } } _updateLine(test, result, step) { const retriesPrefix = result.retry ? ` (retries)` : ``; const prefix = `[${this._current}/${this.totalTestCount}]${retriesPrefix} `; const currentRetrySuffix = result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : ""; const title = this.formatTestTitle(test, step) + currentRetrySuffix; if (process.env.PW_TEST_DEBUG_REPORTERS) this.screen.stdout.write(`${prefix + title} `); else this.screen.stdout.write(`\x1B[1A\x1B[2K${prefix + this.fitToScreen(title, prefix)} `); } onError(error) { super.onError(error); const message = this.formatError(error).message + "\n"; if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) this.screen.stdout.write(`\x1B[1A\x1B[2K`); this.screen.stdout.write(message); this.writeLine(); } async onEnd(result) { if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin) this.screen.stdout.write(`\x1B[1A\x1B[2K`); await super.onEnd(result); this.epilogue(false); } }; var line_default = LineReporter; // packages/playwright/src/reporters/list.ts var import_util8 = require("../util"); var { msToString: msToString3 } = require("playwright-core/lib/coreBundle").iso; var { getAsBooleanFromENV: getAsBooleanFromENV2 } = require("playwright-core/lib/coreBundle").utils; var DOES_NOT_SUPPORT_UTF8_IN_TERMINAL = process.platform === "win32" && process.env.TERM_PROGRAM !== "vscode" && !process.env.WT_SESSION; var POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "ok" : "\u2713"; var NEGATIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "x" : "\u2718"; var ListReporter = class extends TerminalReporter { constructor(options) { super(options); this._lastRow = 0; this._lastColumn = 0; this._testRows = /* @__PURE__ */ new Map(); this._stepRows = /* @__PURE__ */ new Map(); this._resultIndex = /* @__PURE__ */ new Map(); this._stepIndex = /* @__PURE__ */ new Map(); this._needNewLine = false; this._paused = /* @__PURE__ */ new Set(); this._printSteps = getAsBooleanFromENV2("PLAYWRIGHT_LIST_PRINT_STEPS", options?.printSteps); } onBegin(suite) { super.onBegin(suite); const startingMessage = this.generateStartingMessage(); if (startingMessage) { this.writeLine(startingMessage); this.writeLine(""); } } onTestBegin(test, result) { const index = String(this._resultIndex.size + 1); this._resultIndex.set(result, index); if (!this.screen.isTTY) return; this._maybeWriteNewLine(); this._testRows.set(test, this._lastRow); const prefix = this._testPrefix(index, ""); const line = this.screen.colors.dim(this.formatTestTitle(test)) + this._retrySuffix(result); this._appendLine(line, prefix); } onStdOut(chunk, test, result) { super.onStdOut(chunk, test, result); this._dumpToStdio(test, chunk, this.screen.stdout, "out"); } onStdErr(chunk, test, result) { super.onStdErr(chunk, test, result); this._dumpToStdio(test, chunk, this.screen.stderr, "err"); } getStepIndex(testIndex, result, step) { if (this._stepIndex.has(step)) return this._stepIndex.get(step); const ordinal = (result[lastStepOrdinalSymbol] || 0) + 1; result[lastStepOrdinalSymbol] = ordinal; const stepIndex = `${testIndex}.${ordinal}`; this._stepIndex.set(step, stepIndex); return stepIndex; } onStepBegin(test, result, step) { if (step.category !== "test.step") return; const testIndex = this._resultIndex.get(result) || ""; if (!this.screen.isTTY) return; if (this._printSteps) { this._maybeWriteNewLine(); this._stepRows.set(step, this._lastRow); const prefix = this._testPrefix(this.getStepIndex(testIndex, result, step), ""); const line = test.title + this.screen.colors.dim(stepSuffix(step)); this._appendLine(line, prefix); } else { this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step)) + this._retrySuffix(result), this._testPrefix(testIndex, "")); } } onStepEnd(test, result, step) { if (step.category !== "test.step") return; const testIndex = this._resultIndex.get(result) || ""; if (!this._printSteps) { if (this.screen.isTTY) this._updateOrAppendLine(this._testRows, test, this.screen.colors.dim(this.formatTestTitle(test, step.parent)) + this._retrySuffix(result), this._testPrefix(testIndex, "")); return; } const index = this.getStepIndex(testIndex, result, step); const title = this.screen.isTTY ? test.title + this.screen.colors.dim(stepSuffix(step)) : this.formatTestTitle(test, step); const prefix = this._testPrefix(index, ""); let text = ""; if (step.error) text = this.screen.colors.red(title); else text = title; text += this.screen.colors.dim(` (${msToString3(step.duration)})`); this._updateOrAppendLine(this._stepRows, step, text, prefix); } _maybeWriteNewLine() { if (this._needNewLine) { this._needNewLine = false; this.screen.stdout.write("\n"); ++this._lastRow; this._lastColumn = 0; } } _updateLineCountAndNewLineFlagForOutput(text) { this._needNewLine = text[text.length - 1] !== "\n"; if (!this.screen.ttyWidth) return; for (const ch of text) { if (ch === "\n") { this._lastColumn = 0; ++this._lastRow; continue; } ++this._lastColumn; if (this._lastColumn > this.screen.ttyWidth) { this._lastColumn = 0; ++this._lastRow; } } } _dumpToStdio(test, chunk, stream, stdio) { if (this.config.quiet) return; const text = chunk.toString("utf-8"); this._updateLineCountAndNewLineFlagForOutput(text); stream.write(chunk); } async onTestPaused(test, result) { if (!process.stdin.isTTY && !process.env.PW_TEST_DEBUG_REPORTERS) return; this._paused.add(result); this._updateTestLine(test, result); this._maybeWriteNewLine(); if (test.outcome() === "unexpected") { const errors = this.formatResultErrors(test, result); this.writeLine(errors); this._updateLineCountAndNewLineFlagForOutput(errors); markErrorsAsReported(result); } this._appendLine(this.screen.colors.yellow(`Paused ${test.outcome() === "unexpected" ? "on error" : "at test end"}. Press Ctrl+C to end.`), this._testPrefix("", "")); await new Promise(() => { }); } onTestEnd(test, result) { super.onTestEnd(test, result); const wasPaused = this._paused.delete(result); if (!wasPaused) this._updateTestLine(test, result); } _updateTestLine(test, result) { const title = this.formatTestTitle(test); let prefix = ""; let text = ""; let index = this._resultIndex.get(result); if (!index) { index = String(this._resultIndex.size + 1); this._resultIndex.set(result, index); } if (result.status === "skipped") { prefix = this._testPrefix(index, this.screen.colors.green("-")); text = this.screen.colors.cyan(title) + this._retrySuffix(result); } else { const statusMark = result.status === "passed" ? POSITIVE_STATUS_MARK : NEGATIVE_STATUS_MARK; if (result.status === test.expectedStatus) { prefix = this._testPrefix(index, this.screen.colors.green(statusMark)); text = title; } else { prefix = this._testPrefix(index, this.screen.colors.red(statusMark)); text = this.screen.colors.red(title); } text += this._retrySuffix(result) + this.screen.colors.dim(` (${msToString3(result.duration)})`); } this._updateOrAppendLine(this._testRows, test, text, prefix); } _updateOrAppendLine(entityRowNumbers, entity, text, prefix) { const row = entityRowNumbers.get(entity); if (row !== void 0 && this.screen.isTTY && this._lastRow - row < this.screen.ttyHeight) { this._updateLine(row, text, prefix); } else { this._maybeWriteNewLine(); entityRowNumbers.set(entity, this._lastRow); this._appendLine(text, prefix); } } _appendLine(text, prefix) { const line = prefix + this.fitToScreen(text, prefix); if (process.env.PW_TEST_DEBUG_REPORTERS) { this.screen.stdout.write("#" + this._lastRow + " : " + line + "\n"); } else { this.screen.stdout.write(line); this.screen.stdout.write("\n"); } ++this._lastRow; this._lastColumn = 0; } _updateLine(row, text, prefix) { const line = prefix + this.fitToScreen(text, prefix); if (process.env.PW_TEST_DEBUG_REPORTERS) this.screen.stdout.write("#" + row + " : " + line + "\n"); else this._updateLineForTTY(row, line); } _updateLineForTTY(row, line) { if (row !== this._lastRow) this.screen.stdout.write(`\x1B[${this._lastRow - row}A`); this.screen.stdout.write("\x1B[2K\x1B[0G"); this.screen.stdout.write(line); if (row !== this._lastRow) this.screen.stdout.write(`\x1B[${this._lastRow - row}E`); } _testPrefix(index, statusMark) { const statusMarkLength = (0, import_util8.stripAnsiEscapes)(statusMark).length; const indexLength = Math.ceil(Math.log10(this.totalTestCount + 1)); return " " + statusMark + " ".repeat(3 - statusMarkLength) + this.screen.colors.dim(index.padStart(indexLength) + " "); } _retrySuffix(result) { return result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : ""; } onError(error) { super.onError(error); this._maybeWriteNewLine(); const message = this.formatError(error).message + "\n"; this._updateLineCountAndNewLineFlagForOutput(message); this.screen.stdout.write(message); } async onEnd(result) { await super.onEnd(result); this.screen.stdout.write("\n"); this.epilogue(true); } }; var lastStepOrdinalSymbol = Symbol("lastStepOrdinal"); var list_default = ListReporter; // packages/playwright/src/reporters/listModeReporter.ts var import_path11 = __toESM(require("path")); var ListModeReporter = class { constructor(options = {}) { this._options = options; this.screen = options?.screen ?? terminalScreen; } version() { return "v2"; } onConfigure(config2) { this.config = config2; } onBegin(suite) { this._writeLine(`Listing tests:`); const tests = suite.allTests(); const files = /* @__PURE__ */ new Set(); for (const test of tests) { const [, projectName, , ...titles] = test.titlePath(); const location = `${import_path11.default.relative(this.config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`; const testId = this._options.includeTestId ? `[id=${test.id}] ` : ""; const projectLabel = this._options.includeTestId ? `project=` : ""; const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : ""; this._writeLine(` ${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`); files.add(test.location.file); } this._writeLine(`Total: ${tests.length} ${tests.length === 1 ? "test" : "tests"} in ${files.size} ${files.size === 1 ? "file" : "files"}`); } onError(error) { this.screen.stderr.write("\n" + formatError(terminalScreen, error).message + "\n"); } _writeLine(line) { this.screen.stdout.write(line + "\n"); } }; var listModeReporter_default = ListModeReporter; // packages/playwright/src/runner/reporters.ts var { calculateSha1: calculateSha13 } = require("playwright-core/lib/coreBundle").utils; async function createReporters(config2, mode, descriptions, runOptions) { const defaultReporters = { blob: BlobReporter, dot: mode === "list" ? listModeReporter_default : dot_default, line: mode === "list" ? listModeReporter_default : line_default, list: mode === "list" ? listModeReporter_default : list_default, github: github_default, json: json_default, junit: junit_default, null: empty_default, html: html_default }; const reporters = []; descriptions ??= config2.config.reporter; if (runOptions?.additionalReporters) descriptions = [...descriptions, ...runOptions.additionalReporters]; const reportOptions = reporterCommandOptions(config2, mode, runOptions); for (const r of descriptions) { const [name, arg] = r; const options = { ...reportOptions, ...arg }; if (name in defaultReporters) { reporters.push(new defaultReporters[name](options)); } else { const reporterConstructor = await loadReporter(config2, name); reporters.push(wrapReporterAsV2(new reporterConstructor(options))); } } if (process.env.PW_TEST_REPORTER) { const name = process.env.PW_TEST_REPORTER; if (name in defaultReporters) { reporters.push(new defaultReporters[name](reportOptions)); } else { const reporterConstructor = await loadReporter(config2, name); reporters.push(wrapReporterAsV2(new reporterConstructor(reportOptions))); } } const someReporterPrintsToStdio = reporters.some((r) => r.printsToStdio ? r.printsToStdio() : true); if (reporters.length && !someReporterPrintsToStdio) { if (mode === "list") reporters.unshift(new listModeReporter_default()); else if (mode !== "merge") reporters.unshift(!process.env.CI ? new line_default() : new dot_default()); } return reporters; } function createErrorCollectingReporter(screen) { const errors = []; return { version: () => "v2", onError(error) { errors.push(error); screen.stderr?.write(formatError(screen, error).message + "\n"); }, errors: () => errors }; } function reporterCommandOptions(config2, mode, runOptions) { return { configDir: config2.configDir, _mode: mode, _commandHash: computeCommandHash(config2, runOptions) }; } function computeCommandHash(config2, runOptions) { const parts = []; if (runOptions?.projectFilter) parts.push(...runOptions.projectFilter); const command = {}; if (runOptions?.locations?.length) command.locations = runOptions.locations; if (runOptions?.grep) command.grep = runOptions.grep; if (runOptions?.grepInvert) command.grepInvert = runOptions.grepInvert; if (runOptions?.onlyChanged) command.onlyChanged = runOptions.onlyChanged; if (config2.config.tags.length) command.tags = config2.config.tags.join(" "); if (runOptions?.testList) command.testList = calculateSha13(import_fs8.default.readFileSync(runOptions.testList)); if (runOptions?.testListInvert) command.testListInvert = calculateSha13(import_fs8.default.readFileSync(runOptions.testListInvert)); if (Object.keys(command).length) parts.push(calculateSha13(JSON.stringify(command)).substring(0, 7)); return parts.join("-"); } // packages/playwright/src/runner/tasks.ts var import_fs11 = __toESM(require("fs")); var import_path15 = __toESM(require("path")); var import_util11 = require("util"); // packages/playwright/src/runner/rebase.ts var import_fs9 = __toESM(require("fs")); var import_path12 = __toESM(require("path")); var babel3 = __toESM(require("../transform/babelBundle")); var colors3 = require("playwright-core/lib/utilsBundle").colors; var diff = require("playwright-core/lib/utilsBundle").diff; var { MultiMap: MultiMap3 } = require("playwright-core/lib/coreBundle").iso; var t = babel3.types; var suggestedRebaselines = new MultiMap3(); function addSuggestedRebaseline(location, suggestedRebaseline) { suggestedRebaselines.set(location.file, { location, code: suggestedRebaseline }); } function clearSuggestedRebaselines() { suggestedRebaselines.clear(); } async function applySuggestedRebaselines(config2, reporter, filteredProjects) { if (config2.config.updateSnapshots === "none") return; if (!suggestedRebaselines.size) return; const [project] = filteredProjects; if (!project) return; const patches = []; const files = []; const gitCache = /* @__PURE__ */ new Map(); const patchFile = import_path12.default.join(project.project.outputDir, "rebaselines.patch"); for (const fileName of [...suggestedRebaselines.keys()].sort()) { const source = await import_fs9.default.promises.readFile(fileName, "utf8"); const lines = source.split("\n"); const replacements = suggestedRebaselines.get(fileName); const fileNode = babel3.babelParse(source, fileName, true); const ranges = []; babel3.traverse(fileNode, { CallExpression: (path20) => { const node = path20.node; if (node.arguments.length < 1) return; if (!t.isMemberExpression(node.callee)) return; const argument = node.arguments[0]; if (!t.isStringLiteral(argument) && !t.isTemplateLiteral(argument)) return; const prop = node.callee.property; if (!prop.loc || !argument.start || !argument.end) return; for (const replacement of replacements) { if (prop.loc.start.line !== replacement.location.line) continue; if (prop.loc.start.column + 1 !== replacement.location.column) continue; const indent2 = lines[prop.loc.start.line - 1].match(/^\s*/)[0]; const newText = replacement.code.replace(/\{indent\}/g, indent2); ranges.push({ start: argument.start, end: argument.end, oldText: source.substring(argument.start, argument.end), newText }); break; } } }); ranges.sort((a, b) => b.start - a.start); let result = source; for (const range of ranges) result = result.substring(0, range.start) + range.newText + result.substring(range.end); const relativeName = import_path12.default.relative(process.cwd(), fileName); files.push(relativeName); if (config2.config.updateSourceMethod === "overwrite") { await import_fs9.default.promises.writeFile(fileName, result); } else if (config2.config.updateSourceMethod === "3way") { await import_fs9.default.promises.writeFile(fileName, applyPatchWithConflictMarkers(source, result)); } else { const gitFolder = findGitRoot(import_path12.default.dirname(fileName), gitCache); const relativeToGit = import_path12.default.relative(gitFolder || process.cwd(), fileName); patches.push(createPatch(relativeToGit, source, result)); } } const fileList = files.map((file) => " " + colors3.dim(file)).join("\n"); reporter.onStdErr(` New baselines created for: ${fileList} `); if (config2.config.updateSourceMethod === "patch") { await import_fs9.default.promises.mkdir(import_path12.default.dirname(patchFile), { recursive: true }); await import_fs9.default.promises.writeFile(patchFile, patches.join("\n")); reporter.onStdErr(` ` + colors3.cyan("git apply " + import_path12.default.relative(process.cwd(), patchFile)) + "\n"); } } function createPatch(fileName, before, after) { const file = fileName.replace(/\\/g, "/"); const text = diff.createPatch(file, before, after, void 0, void 0, { context: 3 }); return [ "diff --git a/" + file + " b/" + file, "--- a/" + file, "+++ b/" + file, ...text.split("\n").slice(4) ].join("\n"); } function findGitRoot(dir, cache) { const result = cache.get(dir); if (result !== void 0) return result; const gitPath = import_path12.default.join(dir, ".git"); if (import_fs9.default.existsSync(gitPath) && import_fs9.default.lstatSync(gitPath).isDirectory()) { cache.set(dir, dir); return dir; } const parentDir = import_path12.default.dirname(dir); if (dir === parentDir) { cache.set(dir, null); return null; } const parentResult = findGitRoot(parentDir, cache); cache.set(dir, parentResult); return parentResult; } function applyPatchWithConflictMarkers(oldText, newText) { const diffResult = diff.diffLines(oldText, newText); let result = ""; let conflict = false; diffResult.forEach((part) => { if (part.added) { if (conflict) { result += part.value; result += ">>>>>>> SNAPSHOT\n"; conflict = false; } else { result += "<<<<<<< HEAD\n"; result += part.value; result += "=======\n"; conflict = true; } } else if (part.removed) { result += "<<<<<<< HEAD\n"; result += part.value; result += "=======\n"; conflict = true; } else { if (conflict) { result += ">>>>>>> SNAPSHOT\n"; conflict = false; } result += part.value; } }); if (conflict) result += ">>>>>>> SNAPSHOT\n"; return result; } // packages/playwright/src/runner/workerHost.ts var import_fs10 = __toESM(require("fs")); var import_path13 = __toESM(require("path")); var import_common5 = require("../common"); // packages/playwright/src/isomorphic/folders.ts function artifactsFolderName(workerIndex) { return `.playwright-artifacts-${workerIndex}`; } // packages/playwright/src/runner/workerHost.ts var { removeFolders: removeFolders3 } = require("playwright-core/lib/coreBundle").utils; var lastWorkerIndex = 0; var WorkerHost = class extends ProcessHost { constructor(testGroup, options) { const workerIndex = lastWorkerIndex++; super(require.resolve("../worker/workerProcessEntry.js"), `worker-${workerIndex}`, { ...options.extraEnv, FORCE_COLOR: "1", DEBUG_COLORS: process.env.DEBUG_COLORS === void 0 ? "1" : process.env.DEBUG_COLORS }); this._didFail = false; this.workerIndex = workerIndex; this.parallelIndex = options.parallelIndex; this._hash = testGroup.workerHash; this._params = { workerIndex: this.workerIndex, parallelIndex: options.parallelIndex, repeatEachIndex: testGroup.repeatEachIndex, projectId: testGroup.projectId, config: options.config, artifactsDir: import_path13.default.join(options.outputDir, artifactsFolderName(workerIndex)), pauseOnError: options.pauseOnError, pauseAtEnd: options.pauseAtEnd }; } async start() { await import_fs10.default.promises.mkdir(this._params.artifactsDir, { recursive: true }); return await this.startRunner(this._params, { onStdOut: (chunk) => this.emit("stdOut", import_common5.ipc.stdioChunkToParams(chunk)), onStdErr: (chunk) => this.emit("stdErr", import_common5.ipc.stdioChunkToParams(chunk)) }); } async onExit() { await removeFolders3([this._params.artifactsDir]); } async stop(didFail) { if (didFail) this._didFail = true; await super.stop(); } runTestGroup(runPayload) { this.sendMessageNoReply({ method: "runTestGroup", params: runPayload }); } async sendCustomMessage(payload) { return await this.sendMessage({ method: "customMessage", params: payload }); } sendResume(payload) { this.sendMessageNoReply({ method: "resume", params: payload }); } hash() { return this._hash; } projectId() { return this._params.projectId; } didFail() { return this._didFail; } }; // packages/playwright/src/runner/dispatcher.ts var import_common6 = require("../common"); var import_util9 = require("../util"); var colors4 = require("playwright-core/lib/utilsBundle").colors; var { ManualPromise: ManualPromise3 } = require("playwright-core/lib/coreBundle").iso; var { eventsHelper } = require("playwright-core/lib/coreBundle").utils; var Dispatcher = class { constructor(testRun) { // Worker slot is claimed when it has jobDispatcher assigned. this._workerSlots = []; this._queue = []; this._workerLimitPerProjectId = /* @__PURE__ */ new Map(); this._queuedOrRunningHashCount = /* @__PURE__ */ new Map(); this._finished = new ManualPromise3(); this._isStopped = true; this._extraEnvByProjectId = /* @__PURE__ */ new Map(); this._producedEnvByProjectId = /* @__PURE__ */ new Map(); this._testRun = testRun; for (const project of testRun.config.projects) { if (project.workers) this._workerLimitPerProjectId.set(project.id, project.workers); } } _findFirstJobToRun() { for (let index = 0; index < this._queue.length; index++) { const job = this._queue[index]; const projectIdWorkerLimit = this._workerLimitPerProjectId.get(job.projectId); if (!projectIdWorkerLimit) return index; const runningWorkersWithSameProjectId = this._workerSlots.filter((w) => w.jobDispatcher?.job.projectId === job.projectId).length; if (runningWorkersWithSameProjectId < projectIdWorkerLimit) return index; } return -1; } _scheduleJob() { if (this._isStopped) return; const jobIndex = this._findFirstJobToRun(); if (jobIndex === -1) return; const job = this._queue[jobIndex]; let workerIndex = this._workerSlots.findIndex((w) => !w.jobDispatcher && w.worker && w.worker.hash() === job.workerHash && !w.worker.didSendStop()); if (workerIndex === -1) workerIndex = this._workerSlots.findIndex((w) => !w.jobDispatcher); if (workerIndex === -1) { return; } this._queue.splice(jobIndex, 1); const jobDispatcher = new JobDispatcher(job, this._testRun, () => this.stop().catch(() => { })); this._workerSlots[workerIndex].jobDispatcher = jobDispatcher; void this._runJobInWorker(workerIndex, jobDispatcher).then(() => { this._workerSlots[workerIndex].jobDispatcher = void 0; this._checkFinished(); this._scheduleJob(); }); } async _runJobInWorker(index, jobDispatcher) { const job = jobDispatcher.job; if (jobDispatcher.skipWholeJob()) return; let worker = this._workerSlots[index].worker; if (worker && (worker.hash() !== job.workerHash || worker.didSendStop())) { await worker.stop(); worker = void 0; if (this._isStopped) return; } let startError; if (!worker) { worker = this._createWorker(job, index, import_common6.ipc.serializeConfig(this._testRun.config, true)); this._workerSlots[index].worker = worker; worker.on("exit", () => this._workerSlots[index].worker = void 0); startError = await worker.start(); if (this._isStopped) return; } if (startError) jobDispatcher.onExit(startError); else jobDispatcher.runInWorker(worker); const result = await jobDispatcher.jobResult; this._updateCounterForWorkerHash(job.workerHash, -1); if (result.didFail) void worker.stop( true /* didFail */ ); else if (this._isWorkerRedundant(worker)) void worker.stop(); if (!this._isStopped && result.newJob) { this._queue.unshift(result.newJob); this._updateCounterForWorkerHash(result.newJob.workerHash, 1); } } _checkFinished() { if (this._finished.isDone()) return; if (this._queue.length && !this._isStopped) return; if (this._workerSlots.some((w) => !!w.jobDispatcher)) return; this._finished.resolve(); } _isWorkerRedundant(worker) { let workersWithSameHash = 0; for (const slot of this._workerSlots) { if (slot.worker && !slot.worker.didSendStop() && slot.worker.hash() === worker.hash()) workersWithSameHash++; } return workersWithSameHash > this._queuedOrRunningHashCount.get(worker.hash()); } _updateCounterForWorkerHash(hash, delta) { this._queuedOrRunningHashCount.set(hash, delta + (this._queuedOrRunningHashCount.get(hash) || 0)); } async run(testGroups, extraEnvByProjectId) { this._extraEnvByProjectId = extraEnvByProjectId; this._queue = testGroups; for (const group of testGroups) this._updateCounterForWorkerHash(group.workerHash, 1); this._isStopped = false; this._workerSlots = []; if (this._testRun.hasReachedMaxFailures()) void this.stop(); for (let i = 0; i < this._testRun.config.config.workers; i++) this._workerSlots.push({}); for (let i = 0; i < this._workerSlots.length; i++) this._scheduleJob(); this._checkFinished(); await this._finished; } _createWorker(testGroup, parallelIndex, loaderData) { const project = this._testRun.config.projects.find((p) => p.id === testGroup.projectId); const pauseAtEnd = this._testRun.topLevelProjects.includes(project) && !!this._testRun.options.pauseAtEnd; const worker = new WorkerHost(testGroup, { parallelIndex, config: loaderData, extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir: project.project.outputDir, pauseOnError: !!this._testRun.options.pauseOnError, pauseAtEnd }); const handleOutput = (params) => { const chunk = chunkFromParams(params); if (worker.didFail()) { return { chunk }; } const currentlyRunning = this._workerSlots[parallelIndex].jobDispatcher?.currentlyRunning(); if (!currentlyRunning) return { chunk }; return { chunk, test: currentlyRunning.test, result: currentlyRunning.result }; }; worker.on("stdOut", (params) => { const { chunk, test, result } = handleOutput(params); result?.stdout.push(chunk); this._testRun.reporter.onStdOut?.(chunk, test, result); }); worker.on("stdErr", (params) => { const { chunk, test, result } = handleOutput(params); result?.stderr.push(chunk); this._testRun.reporter.onStdErr?.(chunk, test, result); }); worker.on("teardownErrors", (params) => { this._testRun.hasWorkerErrors = true; const workerInfo = { config: this._testRun.config.config, project: project.project, workerIndex: worker.workerIndex, parallelIndex: worker.parallelIndex }; for (const error of params.fatalErrors) this._testRun.reporter.onError?.(error, workerInfo); }); worker.on("processError", (error) => { this._testRun.hasWorkerErrors = true; this._testRun.reporter.onError?.(error); }); worker.on("exit", () => { const producedEnv = this._producedEnvByProjectId.get(testGroup.projectId) || {}; this._producedEnvByProjectId.set(testGroup.projectId, { ...producedEnv, ...worker.producedEnv() }); }); return worker; } producedEnvByProjectId() { return this._producedEnvByProjectId; } async stop() { if (this._isStopped) return; this._isStopped = true; await Promise.all(this._workerSlots.map(({ worker }) => worker?.stop())); this._checkFinished(); } }; var JobDispatcher = class { constructor(job, testRun, stopCallback) { this.jobResult = new ManualPromise3(); this._listeners = []; this._failedTests = /* @__PURE__ */ new Set(); this._failedWithNonRetriableError = /* @__PURE__ */ new Set(); this._remainingByTestId = /* @__PURE__ */ new Map(); this._dataByTestId = /* @__PURE__ */ new Map(); this._parallelIndex = 0; this._workerIndex = 0; this.job = job; this._testRun = testRun; this._stopCallback = stopCallback; this._remainingByTestId = new Map(this.job.tests.map((e) => [e.id, e])); } _onTestBegin(params) { const test = this._remainingByTestId.get(params.testId); if (!test) { return; } const result = test._appendTestResult(); this._dataByTestId.set(test.id, { test, result, steps: /* @__PURE__ */ new Map() }); result.parallelIndex = this._parallelIndex; result.workerIndex = this._workerIndex; result.startTime = new Date(params.startWallTime); this._testRun.reporter.onTestBegin?.(test, result); this._currentlyRunning = { test, result }; } _onTestEnd(params) { if (this._testRun.hasReachedMaxFailures()) { params.status = "interrupted"; params.errors = []; } const data = this._dataByTestId.get(params.testId); if (!data) { return; } this._dataByTestId.delete(params.testId); this._remainingByTestId.delete(params.testId); const { result, test } = data; result.duration = params.duration; result.errors = params.errors; result.error = result.errors[0]; result.status = params.status; result.annotations = params.annotations; test.annotations = [...params.annotations]; test.expectedStatus = params.expectedStatus; test.timeout = params.timeout; const isFailure = result.status !== "skipped" && result.status !== test.expectedStatus; if (isFailure) this._failedTests.add(test); if (params.hasNonRetriableError) this._addNonretriableTestAndSerialModeParents(test); this._reportTestEnd(test, result); this._currentlyRunning = void 0; } _addNonretriableTestAndSerialModeParents(test) { this._failedWithNonRetriableError.add(test); for (let parent = test.parent; parent; parent = parent.parent) { if (parent._parallelMode === "serial") this._failedWithNonRetriableError.add(parent); } } _onStepBegin(params) { const data = this._dataByTestId.get(params.testId); if (!data) { return; } const { result, steps, test } = data; const parentStep = params.parentStepId ? steps.get(params.parentStepId) : void 0; const step = { title: params.title, titlePath: () => { const parentPath = parentStep?.titlePath() || []; return [...parentPath, params.title]; }, parent: parentStep, category: params.category, startTime: new Date(params.wallTime), duration: -1, steps: [], attachments: [], annotations: [], location: params.location }; steps.set(params.stepId, step); (parentStep || result).steps.push(step); this._testRun.reporter.onStepBegin?.(test, result, step); } _onStepEnd(params) { const data = this._dataByTestId.get(params.testId); if (!data) { return; } const { result, steps, test } = data; const step = steps.get(params.stepId); if (!step) { this._testRun.reporter.onStdErr?.("Internal error: step end without step begin: " + params.stepId, test, result); return; } step.duration = params.wallTime - step.startTime.getTime(); if (params.error) step.error = params.error; if (params.suggestedRebaseline) addSuggestedRebaseline(step.location, params.suggestedRebaseline); step.annotations = params.annotations; steps.delete(params.stepId); this._testRun.reporter.onStepEnd?.(test, result, step); } _onAttach(params) { const data = this._dataByTestId.get(params.testId); if (!data) { return; } const attachment = { name: params.name, path: params.path, contentType: params.contentType, body: params.body !== void 0 ? Buffer.from(params.body, "base64") : void 0 }; data.result.attachments.push(attachment); if (params.stepId) { const step = data.steps.get(params.stepId); if (step) step.attachments.push(attachment); else this._testRun.reporter.onStdErr?.("Internal error: step id not found: " + params.stepId); } } _failTestWithErrors(test, errors) { const runData = this._dataByTestId.get(test.id); let result; if (runData) { result = runData.result; } else { result = test._appendTestResult(); this._testRun.reporter.onTestBegin?.(test, result); } result.errors = [...errors]; result.error = result.errors[0]; result.status = errors.length ? "failed" : "skipped"; this._reportTestEnd(test, result); this._failedTests.add(test); } _massSkipTestsFromRemaining(testIds, errors) { for (const test of this._remainingByTestId.values()) { if (!testIds.has(test.id)) continue; if (!this._testRun.hasReachedMaxFailures()) { this._failTestWithErrors(test, errors); errors = []; } this._remainingByTestId.delete(test.id); } if (errors.length) { this._testRun.hasWorkerErrors = true; for (const error of errors) this._testRun.reporter.onError?.(error); } } _onDone(params) { if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError && !params.stoppedDueToUnhandledErrorInTestFail) { this._finished({ didFail: false }); return; } for (const testId of params.fatalUnknownTestIds || []) { const test = this._remainingByTestId.get(testId); if (test) { this._remainingByTestId.delete(testId); this._failTestWithErrors(test, [{ message: `Test not found in the worker process. Make sure test title does not change.` }]); } } if (params.fatalErrors.length) { this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), params.fatalErrors); } this._massSkipTestsFromRemaining(new Set(params.skipTestsDueToSetupFailure), []); if (params.unexpectedExitError) { if (this._currentlyRunning) this._massSkipTestsFromRemaining(/* @__PURE__ */ new Set([this._currentlyRunning.test.id]), [params.unexpectedExitError]); else this._massSkipTestsFromRemaining(new Set(this._remainingByTestId.keys()), [params.unexpectedExitError]); } const retryCandidates = /* @__PURE__ */ new Set(); const serialSuitesWithFailures = /* @__PURE__ */ new Set(); for (const failedTest of this._failedTests) { if (this._failedWithNonRetriableError.has(failedTest)) continue; retryCandidates.add(failedTest); let outermostSerialSuite; for (let parent = failedTest.parent; parent; parent = parent.parent) { if (parent._parallelMode === "serial") outermostSerialSuite = parent; } if (outermostSerialSuite && !this._failedWithNonRetriableError.has(outermostSerialSuite)) serialSuitesWithFailures.add(outermostSerialSuite); } const testsBelongingToSomeSerialSuiteWithFailures = [...this._remainingByTestId.values()].filter((test) => { let parent = test.parent; while (parent && !serialSuitesWithFailures.has(parent)) parent = parent.parent; return !!parent; }); this._massSkipTestsFromRemaining(new Set(testsBelongingToSomeSerialSuiteWithFailures.map((test) => test.id)), []); for (const serialSuite of serialSuitesWithFailures) { serialSuite.allTests().forEach((test) => retryCandidates.add(test)); } const remaining = [...this._remainingByTestId.values()]; for (const test of retryCandidates) { if (test.results.length < test.retries + 1) remaining.push(test); } const newJob = remaining.length ? { ...this.job, tests: remaining } : void 0; this._finished({ didFail: true, newJob }); } onExit(data) { const unexpectedExitError = data.unexpectedly ? { message: `Error: worker process exited unexpectedly (code=${data.code}, signal=${data.signal})` } : void 0; this._onDone({ skipTestsDueToSetupFailure: [], fatalErrors: [], unexpectedExitError }); } _finished(result) { eventsHelper.removeEventListeners(this._listeners); this.jobResult.resolve(result); } runInWorker(worker) { this._parallelIndex = worker.parallelIndex; this._workerIndex = worker.workerIndex; const runPayload = { file: this.job.requireFile, entries: this.job.tests.map((test) => { return { testId: test.id, retry: test.results.length }; }) }; worker.runTestGroup(runPayload); this._listeners = [ eventsHelper.addEventListener(worker, "testBegin", this._onTestBegin.bind(this)), eventsHelper.addEventListener(worker, "testEnd", this._onTestEnd.bind(this)), eventsHelper.addEventListener(worker, "stepBegin", this._onStepBegin.bind(this)), eventsHelper.addEventListener(worker, "stepEnd", this._onStepEnd.bind(this)), eventsHelper.addEventListener(worker, "attach", this._onAttach.bind(this)), eventsHelper.addEventListener(worker, "testPaused", this._onTestPaused.bind(this, worker)), eventsHelper.addEventListener(worker, "done", this._onDone.bind(this)), eventsHelper.addEventListener(worker, "exit", this.onExit.bind(this)) ]; } _onTestPaused(worker, params) { const data = this._dataByTestId.get(params.testId); if (!data) return; const { result, test } = data; const sendMessage = async (message) => { try { if (this.jobResult.isDone()) throw new Error("Test has already stopped"); const response = await worker.sendCustomMessage({ testId: test.id, request: message.request }); if (response.error) addLocationAndSnippetToError(this._testRun.config.config, response.error); return response; } catch (e) { const error = (0, import_util9.serializeError)(e); addLocationAndSnippetToError(this._testRun.config.config, error); return { response: void 0, error }; } }; result.status = params.status; result.errors = params.errors; result.error = result.errors[0]; void this._testRun.reporter.onTestPaused?.(test, result).then(() => { worker.sendResume({}); }); this._testRun.onTestPaused({ ...params, sendMessage }); } skipWholeJob() { const allTestsSkipped = this.job.tests.every((test) => test.expectedStatus === "skipped"); if (allTestsSkipped && !this._testRun.hasReachedMaxFailures()) { for (const test of this.job.tests) { const result = test._appendTestResult(); this._testRun.reporter.onTestBegin?.(test, result); result.status = "skipped"; result.annotations = [...test.annotations]; this._reportTestEnd(test, result); } return true; } return false; } currentlyRunning() { return this._currentlyRunning; } _reportTestEnd(test, result) { this._testRun.reporter.onTestEnd?.(test, result); const hadMaxFailures = this._testRun.hasReachedMaxFailures(); if (test.outcome() === "unexpected" && test.results.length > test.retries) ++this._testRun.failedTestCount; if (!hadMaxFailures && this._testRun.hasReachedMaxFailures()) { this._stopCallback(); this._testRun.reporter.onError?.({ message: colors4.red(`Testing stopped early after ${this._testRun.config.config.maxFailures} maximum allowed failures.`) }); } } }; function chunkFromParams(params) { if (typeof params.text === "string") return params.text; return Buffer.from(params.buffer, "base64"); } // packages/playwright/src/runner/sigIntWatcher.ts var SigIntWatcher = class { constructor() { this._hadSignal = false; let sigintCallback; this._sigintPromise = new Promise((f) => sigintCallback = f); this._sigintHandler = () => { FixedNodeSIGINTHandler.off(this._sigintHandler); this._hadSignal = true; sigintCallback(); }; FixedNodeSIGINTHandler.on(this._sigintHandler); } promise() { return this._sigintPromise; } hadSignal() { return this._hadSignal; } disarm() { FixedNodeSIGINTHandler.off(this._sigintHandler); } }; var FixedNodeSIGINTHandler = class { static { this._handlers = []; } static { this._ignoreNextSIGINTs = false; } static { this._handlerInstalled = false; } static { this._dispatch = () => { if (this._ignoreNextSIGINTs) return; this._ignoreNextSIGINTs = true; setTimeout(() => { this._ignoreNextSIGINTs = false; if (!this._handlers.length) this._uninstall(); }, 1e3); for (const handler of this._handlers) handler(); }; } static _install() { if (!this._handlerInstalled) { this._handlerInstalled = true; process.on("SIGINT", this._dispatch); } } static _uninstall() { if (this._handlerInstalled) { this._handlerInstalled = false; process.off("SIGINT", this._dispatch); } } static on(handler) { this._handlers.push(handler); if (this._handlers.length === 1) this._install(); } static off(handler) { this._handlers = this._handlers.filter((h) => h !== handler); if (!this._ignoreNextSIGINTs && !this._handlers.length) this._uninstall(); } }; // packages/playwright/src/runner/taskRunner.ts var import_util10 = require("../util"); var colors5 = require("playwright-core/lib/utilsBundle").colors; var debug4 = require("playwright-core/lib/utilsBundle").debug; var { ManualPromise: ManualPromise4 } = require("playwright-core/lib/coreBundle").iso; var { monotonicTime: monotonicTime5 } = require("playwright-core/lib/coreBundle").iso; var TaskRunner = class _TaskRunner { constructor(reporter, globalTimeoutForError) { this._tasks = []; this._hasErrors = false; this._interrupted = false; this._isTearDown = false; this._reporter = reporter; this._globalTimeoutForError = globalTimeoutForError; } addTask(task) { this._tasks.push(task); } async run(context, deadline, cancelPromise) { const { status, cleanup } = await this.runDeferCleanup(context, deadline, cancelPromise); const teardownStatus = await cleanup(); return status === "passed" ? teardownStatus : status; } async runDeferCleanup(context, deadline, cancelPromise = new ManualPromise4()) { const sigintWatcher = new SigIntWatcher(); const timeoutWatcher = new TimeoutWatcher(deadline); const teardownRunner = new _TaskRunner(this._reporter, this._globalTimeoutForError); teardownRunner._isTearDown = true; let currentTaskName; const taskLoop = async () => { for (const task of this._tasks) { currentTaskName = task.title; if (this._interrupted) break; debug4("pw:test:task")(`"${task.title}" started`); const errors = []; const softErrors = []; try { teardownRunner._tasks.unshift({ title: `teardown for ${task.title}`, setup: task.teardown }); await task.setup?.(context, errors, softErrors); } catch (e) { debug4("pw:test:task")(`error in "${task.title}": `, e); errors.push((0, import_util10.serializeError)(e)); } finally { for (const error of [...softErrors, ...errors]) this._reporter.onError?.(error); if (errors.length) { if (!this._isTearDown) this._interrupted = true; this._hasErrors = true; } } debug4("pw:test:task")(`"${task.title}" finished`); } }; await Promise.race([ taskLoop(), cancelPromise, sigintWatcher.promise(), timeoutWatcher.promise ]); sigintWatcher.disarm(); timeoutWatcher.disarm(); this._interrupted = true; let status = "passed"; if (sigintWatcher.hadSignal() || cancelPromise?.isDone()) { status = "interrupted"; } else if (timeoutWatcher.timedOut()) { this._reporter.onError?.({ message: colors5.red(`Timed out waiting ${this._globalTimeoutForError / 1e3}s for the ${currentTaskName} to run`) }); status = "timedout"; } else if (this._hasErrors) { status = "failed"; } cancelPromise?.resolve(); const cleanup = () => teardownRunner.runDeferCleanup(context, deadline).then((r) => r.status); return { status, cleanup }; } }; var TimeoutWatcher = class { constructor(deadline) { this._timedOut = false; this.promise = new ManualPromise4(); if (!deadline) return; if (deadline - monotonicTime5() <= 0) { this._timedOut = true; this.promise.resolve(); return; } this._timer = setTimeout(() => { this._timedOut = true; this.promise.resolve(); }, deadline - monotonicTime5()); } timedOut() { return this._timedOut; } disarm() { clearTimeout(this._timer); } }; // packages/playwright/src/runner/vcs.ts var import_child_process2 = __toESM(require("child_process")); var import_path14 = __toESM(require("path")); var import_common7 = require("../common"); async function detectChangedTestFiles(baseCommit, configDir) { function gitFileList(args) { try { return import_child_process2.default.execFileSync( "git", args, { encoding: "utf-8", stdio: "pipe", cwd: configDir } ).split("\n").filter(Boolean); } catch (_error) { const error = _error; const unknownRevision = error.output.some((line) => line?.includes("unknown revision")); if (unknownRevision) { const isShallowClone = import_child_process2.default.execFileSync("git", ["rev-parse", "--is-shallow-repository"], { encoding: "utf-8", stdio: "pipe", cwd: configDir }).trim() === "true"; if (isShallowClone) { throw new Error([ `The repository is a shallow clone and does not have '${baseCommit}' available locally.`, `Note that GitHub Actions checkout is shallow by default: https://github.com/actions/checkout` ].join("\n")); } } throw new Error([ `Cannot detect changed files for --only-changed mode:`, `git ${args.join(" ")}`, "", ...error.output ].join("\n")); } } const untrackedFiles = gitFileList(["ls-files", "--others", "--exclude-standard"]).map((file) => import_path14.default.join(configDir, file)); const [gitRoot] = gitFileList(["rev-parse", "--show-toplevel"]); const trackedFilesWithChanges = gitFileList(["diff", baseCommit, "--name-only"]).map((file) => import_path14.default.join(gitRoot, file)); return new Set(import_common7.cc.affectedTestFiles([...untrackedFiles, ...trackedFilesWithChanges])); } // packages/playwright/src/runner/tasks.ts var import_common8 = require("../common"); var import_util12 = require("../util"); var debug5 = require("playwright-core/lib/utilsBundle").debug; var { ManualPromise: ManualPromise5 } = require("playwright-core/lib/coreBundle").iso; var { monotonicTime: monotonicTime6 } = require("playwright-core/lib/coreBundle").iso; var { removeFolders: removeFolders4 } = require("playwright-core/lib/coreBundle").utils; var readDirAsync2 = (0, import_util11.promisify)(import_fs11.default.readdir); var TestRun = class { constructor(config2, reporter, options) { this.rootSuite = void 0; this.phases = []; this.projectFiles = /* @__PURE__ */ new Map(); this.projectSuites = /* @__PURE__ */ new Map(); this.topLevelProjects = []; this.hasWorkerErrors = false; this.failedTestCount = 0; this.loadFileFilters = []; this.preOnlyTestFilters = []; this.postShardTestFilters = []; this.config = config2; this.options = options ?? {}; this.reporter = reporter; this.filteredProjects = filterProjects(config2.projects, this.options.projectFilter); } onTestPaused(params) { this.options.onTestPaused?.(params); } hasReachedMaxFailures() { const max = this.config.config.maxFailures; return max > 0 && this.failedTestCount >= max; } result() { const hasFailedTests = this.rootSuite?.allTests().some((test) => !test.ok()); const hasFlakyTests = this.rootSuite?.allTests().some((test) => test.outcome() === "flaky"); return this.hasWorkerErrors || this.hasReachedMaxFailures() || hasFailedTests || this.config.failOnFlakyTests && hasFlakyTests ? "failed" : "passed"; } }; async function runTasks(testRun, tasks, globalTimeout, cancelPromise) { const deadline = globalTimeout ? monotonicTime6() + globalTimeout : 0; const taskRunner = new TaskRunner(testRun.reporter, globalTimeout || 0); for (const task of tasks) taskRunner.addTask(task); testRun.reporter.onConfigure(testRun.config.config); const status = await taskRunner.run(testRun, deadline, cancelPromise); return await finishTaskRun(testRun, status); } async function runTasksDeferCleanup(testRun, tasks) { const taskRunner = new TaskRunner(testRun.reporter, 0); for (const task of tasks) taskRunner.addTask(task); testRun.reporter.onConfigure(testRun.config.config); const { status, cleanup } = await taskRunner.runDeferCleanup(testRun, 0); return { status: await finishTaskRun(testRun, status), cleanup }; } async function finishTaskRun(testRun, status) { if (status === "passed") status = testRun.result(); const modifiedResult = await testRun.reporter.onEnd({ status }); if (modifiedResult && modifiedResult.status) status = modifiedResult.status; await testRun.reporter.onExit(); return status; } function createGlobalSetupTasks(config2) { return [ createRemoveOutputDirsTask(), ...createPluginSetupTasks(config2), ...config2.globalTeardowns.map((file) => createGlobalTeardownTask(file, config2)).reverse(), ...config2.globalSetups.map((file) => createGlobalSetupTask(file, config2)) ]; } function createRunTestsTasks(config2) { return [ createPhasesTask(), createReportBeginTask(), ...config2.plugins.map((plugin) => createPluginBeginTask(plugin)), createRunTestsTask() ]; } function createClearCacheTask(config2) { return { title: "clear cache", setup: async () => { await (0, import_util12.removeDirAndLogToConsole)(import_common8.cc.cacheDir); for (const plugin of config2.plugins) await plugin.instance?.clearCache?.(); } }; } function createReportBeginTask() { return { title: "report begin", setup: async (testRun) => { testRun.reporter.onBegin?.(testRun.rootSuite); }, teardown: async ({}) => { } }; } function createPluginSetupTasks(config2) { return config2.plugins.map((plugin) => ({ title: "plugin setup", setup: async ({ reporter }) => { if (typeof plugin.factory === "function") plugin.instance = await plugin.factory(); else plugin.instance = plugin.factory; await plugin.instance?.setup?.(config2.config, config2.configDir, reporter); }, teardown: async () => { await plugin.instance?.teardown?.(); } })); } function createPluginBeginTask(plugin) { return { title: "plugin begin", setup: async (testRun) => { await plugin.instance?.begin?.(testRun.rootSuite); }, teardown: async () => { await plugin.instance?.end?.(); } }; } function createGlobalSetupTask(file, config2) { let title = "global setup"; if (config2.globalSetups.length > 1) title += ` (${file})`; let globalSetupResult; return { title, setup: async ({ config: config3 }) => { const setupHook = await loadGlobalHook(config3, file); globalSetupResult = await setupHook(config3.config); }, teardown: async () => { if (typeof globalSetupResult === "function") await globalSetupResult(); } }; } function createGlobalTeardownTask(file, config2) { let title = "global teardown"; if (config2.globalTeardowns.length > 1) title += ` (${file})`; return { title, teardown: async ({ config: config3 }) => { const teardownHook = await loadGlobalHook(config3, file); await teardownHook(config3.config); } }; } function createRemoveOutputDirsTask() { return { title: "clear output", setup: async (testRun) => { if (testRun.options.preserveOutputDir) return; const outputDirs = /* @__PURE__ */ new Set(); testRun.filteredProjects.forEach((p) => outputDirs.add(p.project.outputDir)); await Promise.all(Array.from(outputDirs).map((outputDir) => removeFolders4([outputDir]).then(async ([error]) => { if (!error) return; if (error.code === "EBUSY") { const entries = await readDirAsync2(outputDir).catch((e) => []); await Promise.all(entries.map((entry) => removeFolders4([import_path15.default.join(outputDir, entry)]))); } else { throw error; } }))); } }; } function createListFilesTask() { return { title: "load tests", setup: async (testRun, errors) => { await createRootSuite(testRun, errors, false); await collectProjectsAndTestFiles(testRun, false); for (const [project, files] of testRun.projectFiles) { const projectSuite = new import_common8.test.Suite(project.project.name, "project"); projectSuite._fullProject = project; testRun.rootSuite._addSuite(projectSuite); const suites = files.map((file) => { const title = import_path15.default.relative(testRun.config.config.rootDir, file); const suite = new import_common8.test.Suite(title, "file"); suite.location = { file, line: 0, column: 0 }; projectSuite._addSuite(suite); return suite; }); testRun.projectSuites.set(project, suites); } } }; } function createLoadTask(mode, options) { return { title: "load tests", setup: async (testRun, errors, softErrors) => { if (testRun.options.locations?.length) { const { testFilter, fileFilter } = import_common8.suiteUtils.createFiltersFromArguments(testRun.options.locations); testRun.loadFileFilters.push(fileFilter); testRun.preOnlyTestFilters.push(testFilter); } if (testRun.options.testList) { const { testFilter, fileFilter } = await loadTestList(testRun.config, testRun.options.testList); testRun.preOnlyTestFilters.push(testFilter); testRun.loadFileFilters.push(fileFilter); } if (testRun.options.testListInvert) { const { testFilter } = await loadTestList(testRun.config, testRun.options.testListInvert); testRun.preOnlyTestFilters.push((test) => !testFilter(test)); } if (testRun.options.grep || testRun.options.grepInvert) { const grepMatcher = testRun.options.grep ? (0, import_util12.createTitleMatcher)((0, import_util12.forceRegExp)(testRun.options.grep)) : () => true; const grepInvertMatcher = testRun.options.grepInvert ? (0, import_util12.createTitleMatcher)((0, import_util12.forceRegExp)(testRun.options.grepInvert)) : () => false; testRun.preOnlyTestFilters.push((test) => { const grepTitle = test._grepTitleWithTags(); return !grepInvertMatcher(grepTitle) && grepMatcher(grepTitle); }); } if (testRun.options.lastFailedTestIds?.length) { const failedTestIds = new Set(testRun.options.lastFailedTestIds); testRun.postShardTestFilters.push((test) => failedTestIds.has(test.id)); } await collectProjectsAndTestFiles(testRun, !!options.doNotRunDepsOutsideProjectFilter); await loadFileSuites(testRun, mode, options.failOnLoadErrors ? errors : softErrors); if (testRun.options.onlyChanged || options.populateDependencies) { for (const plugin of testRun.config.plugins) await plugin.instance?.populateDependencies?.(); } if (testRun.options.onlyChanged) { const changedFiles = await detectChangedTestFiles(testRun.options.onlyChanged, testRun.config.configDir); testRun.preOnlyTestFilters.push((test) => changedFiles.has(test.location.file)); } await createRootSuite(testRun, options.failOnLoadErrors ? errors : softErrors, !!options.filterOnly); if (options.failOnLoadErrors && !testRun.rootSuite?.allTests().length && !testRun.options.passWithNoTests && !testRun.config.config.shard && !testRun.options.onlyChanged && !testRun.options.testList && !testRun.options.testListInvert) { if (testRun.options.locations?.length) { throw new Error([ `No tests found.`, `Make sure that arguments are regular expressions matching test files.`, `You may need to escape symbols like "$" or "*" and quote the arguments.` ].join("\n")); } throw new Error(`No tests found`); } } }; } function createApplyRebaselinesTask() { return { title: "apply rebaselines", setup: async () => { clearSuggestedRebaselines(); }, teardown: async (testRun) => { await applySuggestedRebaselines(testRun.config, testRun.reporter, testRun.filteredProjects); } }; } function createPhasesTask() { return { title: "create phases", setup: async (testRun) => { let maxConcurrentTestGroups = 0; const processed = /* @__PURE__ */ new Set(); const projectToSuite = new Map(testRun.rootSuite.suites.map((suite) => [suite._fullProject, suite])); const allProjects = [...projectToSuite.keys()]; const teardownToSetups = buildTeardownToSetupsMap(allProjects); const teardownToSetupsDependents = /* @__PURE__ */ new Map(); for (const [teardown, setups] of teardownToSetups) { const closure = buildDependentProjects(setups, allProjects); closure.delete(teardown); teardownToSetupsDependents.set(teardown, [...closure]); } for (let i = 0; i < projectToSuite.size; i++) { const phaseProjects = []; for (const project of projectToSuite.keys()) { if (processed.has(project)) continue; const projectsThatShouldFinishFirst = [...project.deps, ...teardownToSetupsDependents.get(project) || []]; if (projectsThatShouldFinishFirst.find((p) => !processed.has(p))) continue; phaseProjects.push(project); } for (const project of phaseProjects) processed.add(project); if (phaseProjects.length) { let testGroupsInPhase = 0; const phase = { dispatcher: new Dispatcher(testRun), projects: [] }; testRun.phases.push(phase); for (const project of phaseProjects) { const projectSuite = projectToSuite.get(project); const testGroups = createTestGroups(projectSuite, testRun.config.config.workers); phase.projects.push({ project, projectSuite, testGroups }); testGroupsInPhase += Math.min(project.workers ?? Number.MAX_SAFE_INTEGER, testGroups.length); } debug5("pw:test:task")(`created phase #${testRun.phases.length} with ${phase.projects.map((p) => p.project.project.name).sort()} projects, ${testGroupsInPhase} testGroups`); maxConcurrentTestGroups = Math.max(maxConcurrentTestGroups, testGroupsInPhase); } } testRun.config.config.metadata.actualWorkers = Math.min(testRun.config.config.workers, maxConcurrentTestGroups); } }; } function createRunTestsTask() { return { title: "test suite", setup: async (testRun) => { const successfulProjects = /* @__PURE__ */ new Set(); const extraEnvByProjectId = /* @__PURE__ */ new Map(); const teardownToSetups = buildTeardownToSetupsMap(testRun.phases.map((phase) => phase.projects.map((p) => p.project)).flat()); for (const { dispatcher, projects } of testRun.phases) { const phaseTestGroups = []; for (const { project, testGroups } of projects) { let extraEnv = {}; for (const dep of project.deps) extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep.id) }; for (const setup of teardownToSetups.get(project) || []) extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(setup.id) }; extraEnvByProjectId.set(project.id, extraEnv); const hasFailedDeps = project.deps.some((p) => !successfulProjects.has(p)); if (!hasFailedDeps) phaseTestGroups.push(...testGroups); } if (phaseTestGroups.length) { await dispatcher.run(phaseTestGroups, extraEnvByProjectId); await dispatcher.stop(); for (const [projectId, envProduced] of dispatcher.producedEnvByProjectId()) { const extraEnv = extraEnvByProjectId.get(projectId) || {}; extraEnvByProjectId.set(projectId, { ...extraEnv, ...envProduced }); } } if (!testRun.hasWorkerErrors) { for (const { project, projectSuite } of projects) { const hasFailedDeps = project.deps.some((p) => !successfulProjects.has(p)); if (!hasFailedDeps && !projectSuite.allTests().some((test) => !test.ok())) successfulProjects.add(project); } } } }, teardown: async ({ phases }) => { for (const { dispatcher } of phases.reverse()) await dispatcher.stop(); } }; } // packages/playwright/src/runner/lastRun.ts var import_fs12 = __toESM(require("fs")); var import_path16 = __toESM(require("path")); var LastRunReporter = class { constructor(filteredProjects, listMode) { this._listMode = !!listMode; const [project] = filteredProjects; if (project) this._lastRunFile = import_path16.default.join(project.project.outputDir, ".last-run.json"); } async filterLastFailed() { if (!this._lastRunFile) return []; try { const lastRunInfo = JSON.parse(await import_fs12.default.promises.readFile(this._lastRunFile, "utf8")); return lastRunInfo.failedTests; } catch { return []; } } version() { return "v2"; } printsToStdio() { return false; } onBegin(suite) { this._suite = suite; } async onEnd(result) { if (!this._lastRunFile || this._listMode) return; const lastRunInfo = { status: result.status, failedTests: this._suite?.allTests().filter((t2) => !t2.ok()).map((t2) => t2.id) || [] }; await import_fs12.default.promises.mkdir(import_path16.default.dirname(this._lastRunFile), { recursive: true }); await import_fs12.default.promises.writeFile(this._lastRunFile, JSON.stringify(lastRunInfo, void 0, 2)); } }; // packages/playwright/src/runner/testRunner.ts var { ManualPromise: ManualPromise6 } = require("playwright-core/lib/coreBundle").iso; var { setPlaywrightTestProcessEnv } = require("playwright-core/lib/coreBundle").utils; var { gracefullyProcessExitDoNotHang: gracefullyProcessExitDoNotHang2 } = require("playwright-core/lib/coreBundle").utils; var TestRunnerEvent = { TestFilesChanged: "testFilesChanged", TestPaused: "testPaused" }; var TestRunner = class extends import_events2.default { constructor(configLocation, configCLIOverrides) { super(); this._watchedProjectDirs = /* @__PURE__ */ new Set(); this._ignoredProjectOutputs = /* @__PURE__ */ new Set(); this._watchedTestDependencies = /* @__PURE__ */ new Set(); this._queue = Promise.resolve(); this._watchTestDirs = false; this._populateDependenciesOnList = false; this._startingEnv = {}; this.configLocation = configLocation; this._configCLIOverrides = configCLIOverrides; this._watcher = new FSWatcher((events) => { const collector = /* @__PURE__ */ new Set(); events.forEach((f) => import_common9.cc.collectAffectedTestFiles(f.file, collector)); this.emit(TestRunnerEvent.TestFilesChanged, [...collector]); }); } async initialize(params) { setPlaywrightTestProcessEnv(); this._watchTestDirs = !!params.watchTestDirs; this._populateDependenciesOnList = !!params.populateDependenciesOnList; this._startingEnv = { ...process.env }; } resizeTerminal(params) { process.stdout.columns = params.cols; process.stdout.rows = params.rows; process.stderr.columns = params.cols; process.stderr.rows = params.rows; } hasSomeBrowsers() { for (const browserName of ["chromium", "webkit", "firefox"]) { try { import_coreBundle2.registry.registry.findExecutable(browserName).executablePathOrDie("javascript"); return true; } catch { } } return false; } async installBrowsers() { const executables = import_coreBundle2.registry.registry.defaultExecutables(); await import_coreBundle2.registry.registry.install(executables); } async loadConfig() { const { config: config2, error } = await this._loadConfig(this._configCLIOverrides); if (config2) return config2; throw new Error("Failed to load config: " + (error ? error.message : "Unknown error")); } async runGlobalSetup(userReporters) { await this.runGlobalTeardown(); const reporter = new InternalReporter(userReporters); const config2 = await this._loadConfigOrReportError(reporter, this._configCLIOverrides); if (!config2) return { status: "failed", env: [] }; const { status, cleanup } = await runTasksDeferCleanup(new TestRun(config2, reporter), [ ...createGlobalSetupTasks(config2) ]); const env = []; for (const key of /* @__PURE__ */ new Set([...Object.keys(process.env), ...Object.keys(this._startingEnv)])) { if (this._startingEnv[key] !== process.env[key]) env.push([key, process.env[key] ?? null]); } if (status !== "passed") await cleanup(); else this._globalSetup = { cleanup }; return { status, env }; } async runGlobalTeardown() { const globalSetup = this._globalSetup; const status = await globalSetup?.cleanup(); this._globalSetup = void 0; return { status }; } async clearCache(userReporter) { const reporter = new InternalReporter(userReporter ? [userReporter] : []); const config2 = await this._loadConfigOrReportError(reporter); if (!config2) return { status: "failed" }; const status = await runTasks(new TestRun(config2, reporter), [ ...createPluginSetupTasks(config2), createClearCacheTask(config2) ]); return { status }; } async listFiles(userReporter, projects) { const reporter = new InternalReporter([userReporter]); const config2 = await this._loadConfigOrReportError(reporter); if (!config2) return { status: "failed" }; const options = { projectFilter: projects?.length ? projects : void 0 }; const status = await runTasks(new TestRun(config2, reporter, options), [ createListFilesTask(), createReportBeginTask() ]); return { status }; } async listTests(userReporter, params) { let result; this._queue = this._queue.then(async () => { const { config: config2, status } = await this._innerListTests(userReporter, params); if (config2) await this._updateWatchedDirs(config2); result = { status }; }).catch(printInternalError); await this._queue; return result; } async _innerListTests(userReporter, params) { const overrides = { ...this._configCLIOverrides, repeatEach: 1, retries: 0 }; const reporter = new InternalReporter([userReporter]); const config2 = await this._loadConfigOrReportError(reporter, overrides); if (!config2) return { status: "failed" }; const options = { locations: params.locations?.length ? params.locations : void 0, grep: params.grep, grepInvert: params.grepInvert, projectFilter: params.projects?.length ? params.projects : void 0, onlyChanged: params.onlyChanged ? "HEAD" : void 0, listMode: true }; const status = await runTasks(new TestRun(config2, reporter, options), [ createLoadTask("out-of-process", { failOnLoadErrors: false, filterOnly: false, populateDependencies: this._populateDependenciesOnList }), createReportBeginTask() ]); return { config: config2, status }; } async _updateWatchedDirs(config2) { this._watchedProjectDirs = /* @__PURE__ */ new Set(); this._ignoredProjectOutputs = /* @__PURE__ */ new Set(); for (const p of config2.projects) { this._watchedProjectDirs.add(p.project.testDir); this._ignoredProjectOutputs.add(p.project.outputDir); } const result = await resolveCtDirs(config2); if (result) { this._watchedProjectDirs.add(result.templateDir); this._ignoredProjectOutputs.add(result.outDir); } if (this._watchTestDirs) await this._updateWatcher(false); } async _updateWatcher(reportPending) { await this._watcher.update([...this._watchedProjectDirs, ...this._watchedTestDependencies], [...this._ignoredProjectOutputs], reportPending); } async runTests(userReporter, params) { let result = { status: "passed" }; this._queue = this._queue.then(async () => { result = await this._innerRunTests(userReporter, params).catch((e) => { printInternalError(e); return { status: "failed" }; }); }); await this._queue; return result; } async _innerRunTests(userReporter, params) { await this.stopTests(); const overrides = { ...this._configCLIOverrides, repeatEach: 1, retries: 0, timeout: params.timeout, reporter: params.reporters ? params.reporters.map((r) => [r]) : void 0, use: { ...this._configCLIOverrides.use, ...params.trace === "on" ? { trace: { mode: "on", sources: false, live: true } } : {}, ...params.trace === "off" ? { trace: "off" } : {}, ...params.video === "on" || params.video === "off" ? { video: params.video } : {}, ...params.headed !== void 0 ? { headless: !params.headed } : {}, _optionContextReuseMode: params.reuseContext ? "when-possible" : void 0, _optionConnectOptions: params.connectWsEndpoint ? { wsEndpoint: params.connectWsEndpoint } : void 0, actionTimeout: params.actionTimeout }, ...params.updateSnapshots ? { updateSnapshots: params.updateSnapshots } : {}, ...params.updateSourceMethod ? { updateSourceMethod: params.updateSourceMethod } : {}, ...params.workers ? { workers: params.workers } : {} }; const config2 = await this._loadConfigOrReportError(new InternalReporter([userReporter]), overrides); if (!config2) return { status: "failed" }; const options = { passWithNoTests: true, locations: params.locations?.length ? params.locations : void 0, grep: params.grep, grepInvert: params.grepInvert, projectFilter: params.projects?.length ? params.projects : void 0, pauseOnError: params.pauseOnError, pauseAtEnd: params.pauseAtEnd, preserveOutputDir: true, onTestPaused: (params2) => this.emit(TestRunnerEvent.TestPaused, params2) }; const configReporters = params.disableConfigReporters ? [] : await createReporters(config2, "test", void 0, options); const reporter = new InternalReporter([...configReporters, userReporter]); const stop = new ManualPromise6(); const testRun = new TestRun(config2, reporter, options); if (params.testIds) { const testIdSet = new Set(params.testIds); testRun.preOnlyTestFilters.push((test) => testIdSet.has(test.id)); } const tasks = [ createApplyRebaselinesTask(), createLoadTask("out-of-process", { filterOnly: true, failOnLoadErrors: !!params.failOnLoadErrors, doNotRunDepsOutsideProjectFilter: params.doNotRunDepsOutsideProjectFilter }), ...createRunTestsTasks(config2) ]; const run = runTasks(testRun, tasks, 0, stop).then(async (status) => { this._testRun = void 0; return status; }); this._testRun = { run, stop }; return { status: await run }; } async watch(fileNames) { this._watchedTestDependencies = /* @__PURE__ */ new Set(); for (const fileName of fileNames) { this._watchedTestDependencies.add(fileName); import_common9.cc.dependenciesForTestFile(fileName).forEach((file) => this._watchedTestDependencies.add(file)); } await this._updateWatcher(true); } async findRelatedTestFiles(files, userReporter) { const errorReporter = createErrorCollectingReporter(internalScreen); const reporter = new InternalReporter(userReporter ? [userReporter, errorReporter] : [errorReporter]); const config2 = await this._loadConfigOrReportError(reporter); if (!config2) return { errors: errorReporter.errors(), testFiles: [] }; const status = await runTasks(new TestRun(config2, reporter), [ ...createPluginSetupTasks(config2), createLoadTask("out-of-process", { failOnLoadErrors: true, filterOnly: false, populateDependencies: true }) ]); if (status !== "passed") return { errors: errorReporter.errors(), testFiles: [] }; return { testFiles: import_common9.cc.affectedTestFiles(files) }; } async stopTests() { this._testRun?.stop?.resolve(); await this._testRun?.run; } async closeGracefully() { gracefullyProcessExitDoNotHang2(0); } async stop() { await this.runGlobalTeardown(); } async _loadConfig(overrides) { try { const config2 = await import_common9.configLoader.loadConfig(this.configLocation, overrides); if (!this._plugins) { webServerPluginsForConfig(config2).forEach((p) => config2.plugins.push({ factory: p })); addGitCommitInfoPlugin(config2); this._plugins = config2.plugins || []; } else { config2.plugins.splice(0, config2.plugins.length, ...this._plugins); } return { config: config2 }; } catch (e) { return { config: null, error: (0, import_util13.serializeError)(e) }; } } async _loadConfigOrReportError(reporter, overrides) { const { config: config2, error } = await this._loadConfig(overrides); if (config2) return config2; reporter.onConfigure(baseFullConfig); reporter.onError(error); await reporter.onEnd({ status: "failed" }); await reporter.onExit(); return null; } }; function printInternalError(e) { console.error("Internal error:", e); } async function resolveCtDirs(config2) { const use = config2.config.projects[0].use; const relativeTemplateDir = use.ctTemplateDir || "playwright"; const templateDir = await import_fs13.default.promises.realpath(import_path17.default.normalize(import_path17.default.join(config2.configDir, relativeTemplateDir))).catch(() => void 0); if (!templateDir) return null; const outDir = use.ctCacheDir ? import_path17.default.resolve(config2.configDir, use.ctCacheDir) : import_path17.default.resolve(templateDir, ".cache"); return { outDir, templateDir }; } async function runAllTestsWithConfig(config2, options) { setPlaywrightTestProcessEnv(); addGitCommitInfoPlugin(config2); webServerPluginsForConfig(config2).forEach((p) => config2.plugins.push({ factory: p })); const filteredProjects = filterProjects(config2.projects, options.projectFilter); const reporters = await createReporters(config2, options.listMode ? "list" : "test", void 0, options); const lastRun = new LastRunReporter(filteredProjects, options.listMode); if (options.lastFailed) { const lastFailedTestIds = await lastRun.filterLastFailed(); if (lastFailedTestIds.length) options = { ...options, lastFailedTestIds }; } const reporter = new InternalReporter([...reporters, lastRun]); const tasks = options.listMode ? [ createLoadTask("in-process", { failOnLoadErrors: true, filterOnly: false }), createReportBeginTask() ] : [ createApplyRebaselinesTask(), ...createGlobalSetupTasks(config2), createLoadTask("in-process", { filterOnly: true, failOnLoadErrors: true }), ...createRunTestsTasks(config2) ]; const testRun = new TestRun(config2, reporter, { ...options, pauseAtEnd: config2.configCLIOverrides.pause, pauseOnError: config2.configCLIOverrides.pause }); const status = await runTasks(testRun, tasks, config2.config.globalTimeout); await new Promise((resolve) => process.stdout.write("", () => resolve())); await new Promise((resolve) => process.stderr.write("", () => resolve())); return status; } // packages/playwright/src/runner/testServer.ts var testServer_exports = {}; __export(testServer_exports, { TestServerDispatcher: () => TestServerDispatcher, runTestServer: () => runTestServer, runUIMode: () => runUIMode }); var import_util14 = __toESM(require("util")); var import_coreBundle3 = require("playwright-core/lib/coreBundle"); var import_common10 = require("../common"); // packages/playwright/src/runner/uiModeReporter.ts var UIModeReporter = class extends TeleReporterEmitter { constructor(options) { super(options._send, { omitBuffers: true }); } }; var uiModeReporter_default = UIModeReporter; // packages/playwright/src/runner/testServer.ts var debug6 = require("playwright-core/lib/utilsBundle").debug; var open2 = require("playwright-core/lib/utilsBundle").open; var { ManualPromise: ManualPromise7 } = require("playwright-core/lib/coreBundle").iso; var { isUnderTest } = require("playwright-core/lib/coreBundle").utils; var { HttpServer: HttpServer2 } = require("playwright-core/lib/coreBundle").utils; var { gracefullyProcessExitDoNotHang: gracefullyProcessExitDoNotHang3 } = require("playwright-core/lib/coreBundle").utils; var originalDebugLog = debug6.log; var originalStdoutWrite = process.stdout.write; var originalStderrWrite = process.stderr.write; var originalStdinIsTTY = process.stdin.isTTY; var TestServer = class { constructor(configLocation, configCLIOverrides) { this._configLocation = configLocation; this._configCLIOverrides = configCLIOverrides; } async start(options) { this._dispatcher = new TestServerDispatcher(this._configLocation, this._configCLIOverrides); return await import_coreBundle3.server.startTraceViewerServer({ ...options, transport: this._dispatcher.transport }); } async stop() { await this._dispatcher?.stop(); } }; var TestServerDispatcher = class { constructor(configLocation, configCLIOverrides) { this._closeOnDisconnect = false; this._testRunner = new TestRunner(configLocation, configCLIOverrides); this.transport = { onconnect: () => { }, dispatch: (method, params) => this[method](params), onclose: () => { if (this._closeOnDisconnect) gracefullyProcessExitDoNotHang3(0); } }; this._dispatchEvent = (method, params) => this.transport.sendEvent?.(method, params); this._testRunner.on(TestRunnerEvent.TestFilesChanged, (testFiles) => this._dispatchEvent("testFilesChanged", { testFiles })); this._testRunner.on(TestRunnerEvent.TestPaused, (params) => this._dispatchEvent("testPaused", { errors: params.errors })); } async _wireReporter(messageSink) { return await createReporterForTestServer(this._serializer, messageSink); } async _collectingReporter() { const report = []; return { reporter: await createReporterForTestServer(this._serializer, (e) => report.push(e)), report }; } async initialize(params) { this._serializer = params.serializer; this._closeOnDisconnect = !!params.closeOnDisconnect; await this._testRunner.initialize({ ...params }); this._setInterceptStdio(!!params.interceptStdio); } async ping() { } async open(params) { if (isUnderTest()) return; open2("vscode://file/" + params.location.file + ":" + params.location.line).catch((e) => console.error(e)); } async resizeTerminal(params) { this._testRunner.resizeTerminal(params); } async checkBrowsers() { return { hasBrowsers: this._testRunner.hasSomeBrowsers() }; } async installBrowsers() { await this._testRunner.installBrowsers(); } async runGlobalSetup(params) { const { reporter, report } = await this._collectingReporter(); this._globalSetupReport = report; const { status, env } = await this._testRunner.runGlobalSetup([reporter, new list_default()]); return { report, status, env }; } async runGlobalTeardown() { const { status } = await this._testRunner.runGlobalTeardown(); const report = this._globalSetupReport || []; this._globalSetupReport = void 0; return { status, report }; } async clearCache(params) { await this._testRunner.clearCache(); } async listFiles(params) { const { reporter, report } = await this._collectingReporter(); const { status } = await this._testRunner.listFiles(reporter, params.projects); return { report, status }; } async listTests(params) { const { reporter, report } = await this._collectingReporter(); const { status } = await this._testRunner.listTests(reporter, params); return { report, status }; } async runTests(params) { const wireReporter = await this._wireReporter((e) => this._dispatchEvent("report", e)); const { status } = await this._testRunner.runTests(wireReporter, { ...params, doNotRunDepsOutsideProjectFilter: true, pauseAtEnd: params.pauseAtEnd, pauseOnError: params.pauseOnError }); return { status }; } async watch(params) { await this._testRunner.watch(params.fileNames); } async findRelatedTestFiles(params) { return this._testRunner.findRelatedTestFiles(params.files); } async stopTests() { await this._testRunner.stopTests(); } async stop() { this._setInterceptStdio(false); await this._testRunner.stop(); } async closeGracefully() { await this._testRunner.closeGracefully(); } _setInterceptStdio(interceptStdio) { if (process.env.PWTEST_DEBUG) return; if (interceptStdio) { if (debug6.log === originalDebugLog) { debug6.log = (...args) => { const string = import_util14.default.format(...args) + "\n"; return originalStderrWrite.apply(process.stderr, [string]); }; } const stdoutWrite = (chunk) => { this._dispatchEvent("stdio", chunkToPayload("stdout", chunk)); return true; }; const stderrWrite = (chunk) => { this._dispatchEvent("stdio", chunkToPayload("stderr", chunk)); return true; }; process.stdout.write = stdoutWrite; process.stderr.write = stderrWrite; process.stdin.isTTY = void 0; } else { debug6.log = originalDebugLog; process.stdout.write = originalStdoutWrite; process.stderr.write = originalStderrWrite; process.stdin.isTTY = originalStdinIsTTY; } } }; async function runUIMode(configFile, configCLIOverrides, options) { const configLocation = import_common10.configLoader.resolveConfigLocation(configFile); return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server, cancelPromise) => { await import_coreBundle3.server.installRootRedirect(server, void 0, { ...options, webApp: "uiMode.html" }); if (options.host !== void 0 || options.port !== void 0) { await import_coreBundle3.server.openTraceInBrowser(server.urlPrefix("human-readable")); } else { const channel = await installedChromiumChannelForUI(configLocation, configCLIOverrides); const page = await import_coreBundle3.server.openTraceViewerApp(server.urlPrefix("precise"), "chromium", { headless: isUnderTest() && process.env.PWTEST_HEADED_FOR_TEST !== "1", persistentContextOptions: { handleSIGINT: false, channel } }); page.on("close", () => cancelPromise.resolve()); } }); } async function installedChromiumChannelForUI(configLocation, configCLIOverrides) { const config2 = await import_common10.configLoader.loadConfig(configLocation, configCLIOverrides).catch((e) => null); if (!config2) return void 0; if (config2.projects.some((p) => (!p.project.use.browserName || p.project.use.browserName === "chromium") && !p.project.use.channel)) return void 0; for (const channel of ["chromium", "chrome", "msedge"]) { if (config2.projects.some((p) => p.project.use.channel === channel)) return channel; } return void 0; } async function runTestServer(configFile, configCLIOverrides, options) { const configLocation = import_common10.configLoader.resolveConfigLocation(configFile); return await innerRunTestServer(configLocation, configCLIOverrides, options, async (server) => { console.log("Listening on " + server.urlPrefix("precise").replace("http:", "ws:") + "/" + server.wsGuid()); }); } async function innerRunTestServer(configLocation, configCLIOverrides, options, openUI) { const testServer = new TestServer(configLocation, configCLIOverrides); const cancelPromise = new ManualPromise7(); const sigintWatcher = new SigIntWatcher(); process.stdin.on("close", () => gracefullyProcessExitDoNotHang3(0)); void sigintWatcher.promise().then(() => cancelPromise.resolve()); try { const server = await testServer.start(options); await openUI(server, cancelPromise); await cancelPromise; } finally { await testServer.stop(); sigintWatcher.disarm(); } return sigintWatcher.hadSignal() ? "interrupted" : "passed"; } function chunkToPayload(type, chunk) { if (chunk instanceof Uint8Array) return { type, buffer: chunk.toString("base64") }; return { type, text: chunk }; } async function createReporterForTestServer(file, messageSink) { const reporterConstructor = file ? await loadReporter(null, file) : uiModeReporter_default; return wrapReporterAsV2(new reporterConstructor({ _send: messageSink })); } // packages/playwright/src/runner/watchMode.ts var watchMode_exports = {}; __export(watchMode_exports, { runWatchModeLoop: () => runWatchModeLoop }); var import_path18 = __toESM(require("path")); var import_readline = __toESM(require("readline")); var import_stream3 = require("stream"); var import_coreBundle4 = require("playwright-core/lib/coreBundle"); // packages/playwright/src/isomorphic/testTree.ts var statusEx = Symbol("statusEx"); // packages/playwright/src/isomorphic/teleSuiteUpdater.ts var TeleSuiteUpdater = class { constructor(options) { this.loadErrors = []; this.progress = { total: 0, passed: 0, failed: 0, skipped: 0 }; this._lastRunTestCount = 0; this._receiver = new TeleReporterReceiver(this._createReporter(), { mergeProjects: true, mergeTestCases: true, resolvePath: createPathResolve(options.pathSeparator), clearPreviousResultsWhenTestBegins: true }); this._options = options; } _createReporter() { return { version: () => "v2", onConfigure: (config2) => { this.config = config2; this._lastRunReceiver = new TeleReporterReceiver({ version: () => "v2", onBegin: (suite) => { this._lastRunTestCount = suite.allTests().length; this._lastRunReceiver = void 0; } }, { mergeProjects: true, mergeTestCases: false, resolvePath: createPathResolve(this._options.pathSeparator) }); void this._lastRunReceiver.dispatch({ method: "onConfigure", params: { config: config2 } }); }, onBegin: (suite) => { if (!this.rootSuite) this.rootSuite = suite; if (this._testResultsSnapshot) { for (const test of this.rootSuite.allTests()) test.results = this._testResultsSnapshot?.get(test.id) || test.results; this._testResultsSnapshot = void 0; } this.progress.total = this._lastRunTestCount; this.progress.passed = 0; this.progress.failed = 0; this.progress.skipped = 0; this._options.onUpdate(true); }, onEnd: () => { this._options.onUpdate(true); }, onTestBegin: (test, testResult) => { testResult[statusEx] = "running"; this._options.onUpdate(); }, onTestEnd: (test, testResult) => { if (test.outcome() === "skipped") ++this.progress.skipped; else if (test.outcome() === "unexpected") ++this.progress.failed; else ++this.progress.passed; testResult[statusEx] = testResult.status; this._options.onUpdate(); }, onError: (error) => this._handleOnError(error), printsToStdio: () => false }; } processGlobalReport(report) { const receiver = new TeleReporterReceiver({ version: () => "v2", onConfigure: (c) => { this.config = c; }, onError: (error) => this._handleOnError(error) }); for (const message of report) void receiver.dispatch(message); } processListReport(report) { const tests = this.rootSuite?.allTests() || []; this._testResultsSnapshot = new Map(tests.map((test) => [test.id, test.results])); this._receiver.reset(); for (const message of report) void this._receiver.dispatch(message); } processTestReportEvent(message) { this._lastRunReceiver?.dispatch(message)?.catch(() => { }); this._receiver.dispatch(message)?.catch(() => { }); } _handleOnError(error) { this.loadErrors.push(error); this._options.onError?.(error); this._options.onUpdate(); } asModel() { return { rootSuite: this.rootSuite || new TeleSuite("", "root"), config: this.config, loadErrors: this.loadErrors, progress: this.progress }; } }; function createPathResolve(pathSeparator) { return (rootDir, relativePath) => { const segments = []; for (const segment of [...rootDir.split(pathSeparator), ...relativePath.split(pathSeparator)]) { const isAfterDrive = pathSeparator === "\\" && segments.length === 1 && segments[0].endsWith(":"); const isFirst = !segments.length; if (!segment && !isFirst && !isAfterDrive) continue; if (segment === ".") continue; if (segment === "..") { segments.pop(); continue; } segments.push(segment); } return segments.join(pathSeparator); }; } // packages/playwright/src/isomorphic/events.ts var Disposable; ((Disposable2) => { function disposeAll(disposables) { for (const disposable of disposables.splice(0)) disposable.dispose(); } Disposable2.disposeAll = disposeAll; })(Disposable || (Disposable = {})); var EventEmitter3 = class { constructor() { this._listeners = /* @__PURE__ */ new Set(); this.event = (listener, disposables) => { this._listeners.add(listener); let disposed = false; const self = this; const result = { dispose() { if (!disposed) { disposed = true; self._listeners.delete(listener); } } }; if (disposables) disposables.push(result); return result; }; } fire(event) { const dispatch = !this._deliveryQueue; if (!this._deliveryQueue) this._deliveryQueue = []; for (const listener of this._listeners) this._deliveryQueue.push({ listener, event }); if (!dispatch) return; for (let index = 0; index < this._deliveryQueue.length; index++) { const { listener, event: event2 } = this._deliveryQueue[index]; listener.call(null, event2); } this._deliveryQueue = void 0; } dispose() { this._listeners.clear(); if (this._deliveryQueue) this._deliveryQueue = []; } }; // packages/playwright/src/isomorphic/testServerConnection.ts var TestServerConnectionClosedError = class extends Error { }; var TestServerConnection = class { constructor(transport) { this._onCloseEmitter = new EventEmitter3(); this._onReportEmitter = new EventEmitter3(); this._onStdioEmitter = new EventEmitter3(); this._onTestFilesChangedEmitter = new EventEmitter3(); this._onLoadTraceRequestedEmitter = new EventEmitter3(); this._onTestPausedEmitter = new EventEmitter3(); this._lastId = 0; this._callbacks = /* @__PURE__ */ new Map(); this._isClosed = false; this.onClose = this._onCloseEmitter.event; this.onReport = this._onReportEmitter.event; this.onStdio = this._onStdioEmitter.event; this.onTestFilesChanged = this._onTestFilesChangedEmitter.event; this.onLoadTraceRequested = this._onLoadTraceRequestedEmitter.event; this.onTestPaused = this._onTestPausedEmitter.event; this._transport = transport; this._transport.onmessage((data) => { const message = JSON.parse(data); const { id, result, error, method, params } = message; if (id) { const callback = this._callbacks.get(id); if (!callback) return; this._callbacks.delete(id); if (error) callback.reject(new Error(error)); else callback.resolve(result); } else { this._dispatchEvent(method, params); } }); const pingInterval = setInterval(() => this._sendMessage("ping").catch(() => { }), 3e4); this._connectedPromise = new Promise((f, r) => { this._transport.onopen(f); this._transport.onerror(r); }); this._transport.onclose(() => { this._isClosed = true; this._onCloseEmitter.fire(); clearInterval(pingInterval); for (const callback of this._callbacks.values()) callback.reject(callback.error); this._callbacks.clear(); }); } isClosed() { return this._isClosed; } async _sendMessage(method, params) { const logForTest = globalThis.__logForTest; logForTest?.({ method, params }); await this._connectedPromise; const id = ++this._lastId; const message = { id, method, params }; const error = new TestServerConnectionClosedError(`${method}: test server connection closed`); this._transport.send(JSON.stringify(message)); return new Promise((resolve, reject) => { this._callbacks.set(id, { resolve, reject, error }); }); } _sendMessageNoReply(method, params) { this._sendMessage(method, params).catch(() => { }); } _dispatchEvent(method, params) { if (method === "report") this._onReportEmitter.fire(params); else if (method === "stdio") this._onStdioEmitter.fire(params); else if (method === "testFilesChanged") this._onTestFilesChangedEmitter.fire(params); else if (method === "loadTraceRequested") this._onLoadTraceRequestedEmitter.fire(params); else if (method === "testPaused") this._onTestPausedEmitter.fire(params); } async initialize(params) { await this._sendMessage("initialize", params); } async ping(params) { await this._sendMessage("ping", params); } async pingNoReply(params) { this._sendMessageNoReply("ping", params); } async watch(params) { await this._sendMessage("watch", params); } watchNoReply(params) { this._sendMessageNoReply("watch", params); } async open(params) { await this._sendMessage("open", params); } openNoReply(params) { this._sendMessageNoReply("open", params); } async resizeTerminal(params) { await this._sendMessage("resizeTerminal", params); } resizeTerminalNoReply(params) { this._sendMessageNoReply("resizeTerminal", params); } async checkBrowsers(params) { return await this._sendMessage("checkBrowsers", params); } async installBrowsers(params) { await this._sendMessage("installBrowsers", params); } async runGlobalSetup(params) { return await this._sendMessage("runGlobalSetup", params); } async runGlobalTeardown(params) { return await this._sendMessage("runGlobalTeardown", params); } async clearCache(params) { return await this._sendMessage("clearCache", params); } async listFiles(params) { return await this._sendMessage("listFiles", params); } async listTests(params) { return await this._sendMessage("listTests", params); } async runTests(params) { return await this._sendMessage("runTests", params); } async findRelatedTestFiles(params) { return await this._sendMessage("findRelatedTestFiles", params); } async stopTests(params) { await this._sendMessage("stopTests", params); } stopTestsNoReply(params) { this._sendMessageNoReply("stopTests", params); } async closeGracefully(params) { await this._sendMessage("closeGracefully", params); } close() { try { this._transport.close(); } catch { } } }; // packages/playwright/src/runner/watchMode.ts var colors6 = require("playwright-core/lib/utilsBundle").colors; var enquirer = require("playwright-core/lib/utilsBundle").enquirer; var { ManualPromise: ManualPromise8 } = require("playwright-core/lib/coreBundle").iso; var { createGuid: createGuid3 } = require("playwright-core/lib/coreBundle").utils; var { getPackageManagerExecCommand: getPackageManagerExecCommand3 } = require("playwright-core/lib/coreBundle").utils; var { eventsHelper: eventsHelper2 } = require("playwright-core/lib/coreBundle").utils; var InMemoryTransport = class extends import_stream3.EventEmitter { constructor(send) { super(); this._send = send; } close() { this.emit("close"); } onclose(listener) { this.on("close", listener); } onerror(listener) { } onmessage(listener) { this.on("message", listener); } onopen(listener) { this.on("open", listener); } send(data) { this._send(data); } }; async function runWatchModeLoop(configLocation, initialOptions) { const options = { ...initialOptions }; let bufferMode = false; const testServerDispatcher = new TestServerDispatcher(configLocation, {}); const transport = new InMemoryTransport( async (data) => { const { id, method, params } = JSON.parse(data); try { const result2 = await testServerDispatcher.transport.dispatch(method, params); transport.emit("message", JSON.stringify({ id, result: result2 })); } catch (e) { transport.emit("message", JSON.stringify({ id, error: String(e) })); } } ); testServerDispatcher.transport.sendEvent = (method, params) => { transport.emit("message", JSON.stringify({ method, params })); }; const testServerConnection = new TestServerConnection(transport); transport.emit("open"); const teleSuiteUpdater = new TeleSuiteUpdater({ pathSeparator: import_path18.default.sep, onUpdate() { } }); const dirtyTestFiles = /* @__PURE__ */ new Set(); const dirtyTestIds = /* @__PURE__ */ new Set(); let onDirtyTests = new ManualPromise8(); let queue = Promise.resolve(); const changedFiles = /* @__PURE__ */ new Set(); testServerConnection.onTestFilesChanged(({ testFiles }) => { testFiles.forEach((file) => changedFiles.add(file)); queue = queue.then(async () => { if (changedFiles.size === 0) return; const { report: report2 } = await testServerConnection.listTests({ locations: options.files, projects: options.projects, grep: options.grep }); teleSuiteUpdater.processListReport(report2); for (const test of teleSuiteUpdater.rootSuite.allTests()) { if (changedFiles.has(test.location.file)) { dirtyTestFiles.add(test.location.file); dirtyTestIds.add(test.id); } } changedFiles.clear(); if (dirtyTestIds.size > 0) { onDirtyTests.resolve("changed"); onDirtyTests = new ManualPromise8(); } }); }); testServerConnection.onReport((report2) => teleSuiteUpdater.processTestReportEvent(report2)); await testServerConnection.initialize({ interceptStdio: false, watchTestDirs: true, populateDependenciesOnList: true }); await testServerConnection.runGlobalSetup({}); const { report } = await testServerConnection.listTests({}); teleSuiteUpdater.processListReport(report); const projectNames = teleSuiteUpdater.rootSuite.suites.map((s) => s.title); let lastRun = { type: "regular" }; let result = "passed"; while (true) { if (bufferMode) printBufferPrompt(dirtyTestFiles, teleSuiteUpdater.config.rootDir); else printPrompt(); const waitForCommand = readCommand(); const command = await Promise.race([ onDirtyTests, waitForCommand.result ]); if (command === "changed") waitForCommand.dispose(); if (bufferMode && command === "changed") continue; const shouldRunChangedFiles = bufferMode ? command === "run" : command === "changed"; if (shouldRunChangedFiles) { if (dirtyTestIds.size === 0) continue; const testIds = [...dirtyTestIds]; dirtyTestIds.clear(); dirtyTestFiles.clear(); await runTests(options, testServerConnection, { testIds, title: "files changed" }); lastRun = { type: "changed", dirtyTestIds: testIds }; continue; } if (command === "run") { await runTests(options, testServerConnection); lastRun = { type: "regular" }; continue; } if (command === "project") { const { selectedProjects } = await enquirer.prompt({ type: "multiselect", name: "selectedProjects", message: "Select projects", choices: projectNames }).catch(() => ({ selectedProjects: null })); if (!selectedProjects) continue; options.projects = selectedProjects.length ? selectedProjects : void 0; await runTests(options, testServerConnection); lastRun = { type: "regular" }; continue; } if (command === "file") { const { filePattern } = await enquirer.prompt({ type: "text", name: "filePattern", message: "Input filename pattern (regex)" }).catch(() => ({ filePattern: null })); if (filePattern === null) continue; if (filePattern.trim()) options.files = filePattern.split(" "); else options.files = void 0; await runTests(options, testServerConnection); lastRun = { type: "regular" }; continue; } if (command === "grep") { const { testPattern } = await enquirer.prompt({ type: "text", name: "testPattern", message: "Input test name pattern (regex)" }).catch(() => ({ testPattern: null })); if (testPattern === null) continue; if (testPattern.trim()) options.grep = testPattern; else options.grep = void 0; await runTests(options, testServerConnection); lastRun = { type: "regular" }; continue; } if (command === "failed") { const failedTestIds = teleSuiteUpdater.rootSuite.allTests().filter((t2) => !t2.ok()).map((t2) => t2.id); await runTests({}, testServerConnection, { title: "running failed tests", testIds: failedTestIds }); lastRun = { type: "failed", failedTestIds }; continue; } if (command === "repeat") { if (lastRun.type === "regular") { await runTests(options, testServerConnection, { title: "re-running tests" }); continue; } else if (lastRun.type === "changed") { await runTests(options, testServerConnection, { title: "re-running tests", testIds: lastRun.dirtyTestIds }); } else if (lastRun.type === "failed") { await runTests({}, testServerConnection, { title: "re-running tests", testIds: lastRun.failedTestIds }); } continue; } if (command === "toggle-show-browser") { await toggleShowBrowser(); continue; } if (command === "toggle-buffer-mode") { bufferMode = !bufferMode; continue; } if (command === "exit") break; if (command === "interrupted") { result = "interrupted"; break; } } const teardown = await testServerConnection.runGlobalTeardown({}); return result === "passed" ? teardown.status : result; } function readKeyPress(handler) { const promise = new ManualPromise8(); const rl = import_readline.default.createInterface({ input: process.stdin, escapeCodeTimeout: 50 }); import_readline.default.emitKeypressEvents(process.stdin, rl); if (process.stdin.isTTY) process.stdin.setRawMode(true); const listener = eventsHelper2.addEventListener(process.stdin, "keypress", (text, key) => { const result = handler(text, key); if (result) promise.resolve(result); }); const dispose = () => { eventsHelper2.removeEventListeners([listener]); rl.close(); if (process.stdin.isTTY) process.stdin.setRawMode(false); }; void promise.finally(dispose); return { result: promise, dispose }; } var isInterrupt = (text, key) => text === "" || text === "\x1B" || key && key.name === "escape" || key && key.ctrl && key.name === "c"; async function runTests(watchOptions, testServerConnection, options) { printConfiguration(watchOptions, options?.title); const waitForDone = readKeyPress((text, key) => { if (isInterrupt(text, key)) { testServerConnection.stopTestsNoReply({}); return "done"; } }); await testServerConnection.runTests({ grep: watchOptions.grep, testIds: options?.testIds, locations: watchOptions?.files ?? [], // TODO: always collect locations based on knowledge about tree, so that we don't have to load all tests projects: watchOptions.projects, connectWsEndpoint, reuseContext: connectWsEndpoint ? true : void 0, workers: connectWsEndpoint ? 1 : void 0, headed: connectWsEndpoint ? true : void 0 }).finally(() => waitForDone.dispose()); } function readCommand() { return readKeyPress((text, key) => { if (isInterrupt(text, key)) return "interrupted"; if (process.platform !== "win32" && key && key.ctrl && key.name === "z") { process.kill(process.ppid, "SIGTSTP"); process.kill(process.pid, "SIGTSTP"); } const name = key?.name; if (name === "q") return "exit"; if (name === "h") { process.stdout.write(`${separator(terminalScreen)} Run tests ${colors6.bold("enter")} ${colors6.dim("run tests")} ${colors6.bold("f")} ${colors6.dim("run failed tests")} ${colors6.bold("r")} ${colors6.dim("repeat last run")} ${colors6.bold("q")} ${colors6.dim("quit")} Change settings ${colors6.bold("c")} ${colors6.dim("set project")} ${colors6.bold("p")} ${colors6.dim("set file filter")} ${colors6.bold("t")} ${colors6.dim("set title filter")} ${colors6.bold("s")} ${colors6.dim("toggle show & reuse the browser")} ${colors6.bold("b")} ${colors6.dim("toggle buffer mode")} `); return; } switch (name) { case "return": return "run"; case "r": return "repeat"; case "c": return "project"; case "p": return "file"; case "t": return "grep"; case "f": return "failed"; case "s": return "toggle-show-browser"; case "b": return "toggle-buffer-mode"; } }); } var showBrowserServer; var connectWsEndpoint = void 0; var seq = 1; function printConfiguration(options, title) { const packageManagerCommand = getPackageManagerExecCommand3(); const tokens = []; tokens.push(`${packageManagerCommand} playwright test`); if (options.projects) tokens.push(...options.projects.map((p) => colors6.blue(`--project ${p}`))); if (options.grep) tokens.push(colors6.red(`--grep ${options.grep}`)); if (options.files) tokens.push(...options.files.map((a) => colors6.bold(a))); if (title) tokens.push(colors6.dim(`(${title})`)); tokens.push(colors6.dim(`#${seq++}`)); const lines = []; const sep = separator(terminalScreen); lines.push("\x1Bc" + sep); lines.push(`${tokens.join(" ")}`); lines.push(`${colors6.dim("Show & reuse browser:")} ${colors6.bold(showBrowserServer ? "on" : "off")}`); process.stdout.write(lines.join("\n")); } function printBufferPrompt(dirtyTestFiles, rootDir) { const sep = separator(terminalScreen); process.stdout.write("\x1Bc"); process.stdout.write(`${sep} `); if (dirtyTestFiles.size === 0) { process.stdout.write(`${colors6.dim("Waiting for file changes. Press")} ${colors6.bold("q")} ${colors6.dim("to quit or")} ${colors6.bold("h")} ${colors6.dim("for more options.")} `); return; } process.stdout.write(`${colors6.dim(`${dirtyTestFiles.size} test ${dirtyTestFiles.size === 1 ? "file" : "files"} changed:`)} `); for (const file of dirtyTestFiles) process.stdout.write(` \xB7 ${import_path18.default.relative(rootDir, file)} `); process.stdout.write(` ${colors6.dim(`Press`)} ${colors6.bold("enter")} ${colors6.dim("to run")}, ${colors6.bold("q")} ${colors6.dim("to quit or")} ${colors6.bold("h")} ${colors6.dim("for more options.")} `); } function printPrompt() { const sep = separator(terminalScreen); process.stdout.write(` ${sep} ${colors6.dim("Waiting for file changes. Press")} ${colors6.bold("enter")} ${colors6.dim("to run tests")}, ${colors6.bold("q")} ${colors6.dim("to quit or")} ${colors6.bold("h")} ${colors6.dim("for more options.")} `); } async function toggleShowBrowser() { if (!showBrowserServer) { showBrowserServer = new import_coreBundle4.remote.PlaywrightServer({ mode: "extension", path: "/" + createGuid3(), maxConnections: 1 }); connectWsEndpoint = await showBrowserServer.listen(); process.stdout.write(`${colors6.dim("Show & reuse browser:")} ${colors6.bold("on")} `); } else { await showBrowserServer?.close(); showBrowserServer = void 0; connectWsEndpoint = void 0; process.stdout.write(`${colors6.dim("Show & reuse browser:")} ${colors6.bold("off")} `); } } // packages/playwright/src/reporters/merge.ts var merge_exports = {}; __export(merge_exports, { createMergedReport: () => createMergedReport }); var import_fs14 = __toESM(require("fs")); var import_path19 = __toESM(require("path")); // packages/playwright/src/isomorphic/stringInternPool.ts var StringInternPool = class { constructor() { this._stringCache = /* @__PURE__ */ new Map(); } internString(s) { let result = this._stringCache.get(s); if (!result) { this._stringCache.set(s, s); result = s; } return result; } }; var JsonStringInternalizer = class { constructor(pool) { this._pool = pool; } traverse(value) { if (typeof value !== "object") return; if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { if (typeof value[i] === "string") value[i] = this.intern(value[i]); else this.traverse(value[i]); } } else { for (const name in value) { if (typeof value[name] === "string") value[name] = this.intern(value[name]); else this.traverse(value[name]); } } } intern(value) { return this._pool.internString(value); } }; // packages/playwright/src/reporters/merge.ts var import_util15 = require("../util"); var { isPathInside } = require("playwright-core/lib/coreBundle").utils; var { ZipFile } = require("playwright-core/lib/coreBundle").utils; async function createMergedReport(config2, dir, reporterDescriptions, rootDirOverride) { const reporters = await createReporters(config2, "merge", reporterDescriptions); const multiplexer = new Multiplexer(reporters); const stringPool = new StringInternPool(); let printStatus = () => { }; if (!multiplexer.printsToStdio()) { printStatus = printStatusToStdout; printStatus(`merging reports from ${dir}`); } const shardFiles = await sortedShardFiles(dir); if (shardFiles.length === 0) throw new Error(`No report files found in ${dir}`); const eventData = await mergeEvents(dir, shardFiles, stringPool, printStatus, rootDirOverride); const pathSeparator = rootDirOverride ? import_path19.default.sep : eventData.pathSeparatorFromMetadata ?? import_path19.default.sep; const pathPackage = pathSeparator === "/" ? import_path19.default.posix : import_path19.default.win32; const receiver = new TeleReporterReceiver(multiplexer, { mergeProjects: false, mergeTestCases: false, // When merging on a different OS, an absolute path like `C:\foo\bar` from win may look like // a relative path on posix, and vice versa. // Therefore, we cannot use `path.resolve()` here - it will resolve relative-looking paths // against `process.cwd()`, while we just want to normalize ".." and "." segments. resolvePath: (rootDir, relativePath) => stringPool.internString(pathPackage.normalize(pathPackage.join(rootDir, relativePath))), configOverrides: config2.config }); printStatus(`processing test events`); const dispatchEvents = async (events) => { for (const event of events) { if (event.method === "onEnd") printStatus(`building final report`); await receiver.dispatch(event); if (event.method === "onEnd") printStatus(`finished building report`); } }; await dispatchEvents(eventData.prologue); let usedWorkers = 0; for (const { reportFile, zipFile, eventPatchers, metadata, config: config3, fullResult } of eventData.reports) { multiplexer.onReportConfigure({ reportPath: zipFile, config: asFullConfig(config3) }); const reportJsonl = await import_fs14.default.promises.readFile(reportFile); const events = parseTestEvents(reportJsonl); new JsonStringInternalizer(stringPool).traverse(events); eventPatchers.patchers.push(new AttachmentPathPatcher(dir)); if (metadata.name) eventPatchers.patchers.push(new GlobalErrorPatcher(metadata.name)); if (config3?.tags?.length) eventPatchers.patchers.push(new GlobalErrorPatcher(config3.tags.join(" "))); const workerIndexPatcher = new WorkerIndexPatcher(usedWorkers); eventPatchers.patchers.push(workerIndexPatcher); eventPatchers.patchEvents(events); usedWorkers += workerIndexPatcher.usedWorkers(); await dispatchEvents(events); multiplexer.onReportEnd({ reportPath: zipFile, result: asFullResult(fullResult) }); } await dispatchEvents(eventData.epilogue); } var commonEventNames = ["onBlobReportMetadata", "onConfigure", "onProject", "onBegin", "onEnd"]; var commonEvents = new Set(commonEventNames); var commonEventRegex = new RegExp(`${commonEventNames.join("|")}`); function parseCommonEvents(reportJsonl) { return splitBufferLines(reportJsonl).map((line) => line.toString("utf8")).filter((line) => commonEventRegex.test(line)).map((line) => JSON.parse(line)).filter((event) => commonEvents.has(event.method)); } function parseTestEvents(reportJsonl) { return splitBufferLines(reportJsonl).map((line) => line.toString("utf8")).filter((line) => line.length).map((line) => JSON.parse(line)).filter((event) => !commonEvents.has(event.method)); } function splitBufferLines(buffer) { const lines = []; let start = 0; while (start < buffer.length) { const end = buffer.indexOf(10, start); if (end === -1) { lines.push(buffer.slice(start)); break; } lines.push(buffer.slice(start, end)); start = end + 1; } return lines; } async function extractAndParseReports(dir, shardFiles, internalizer, printStatus) { const shardEvents = []; await import_fs14.default.promises.mkdir(import_path19.default.join(dir, "resources"), { recursive: true }); const reportNames = new UniqueFileNameGenerator(); for (const file of shardFiles) { const absolutePath = import_path19.default.join(dir, file); printStatus(`extracting: ${(0, import_util15.relativeFilePath)(absolutePath)}`); const zipFile = new ZipFile(absolutePath); const entryNames = await zipFile.entries(); for (const entryName of entryNames.sort()) { let reportFile = import_path19.default.join(dir, entryName); const content = await zipFile.read(entryName); if (entryName.endsWith(".jsonl")) { reportFile = reportNames.makeUnique(reportFile); let parsedEvents = parseCommonEvents(content); internalizer.traverse(parsedEvents); const metadata = findMetadata(parsedEvents, file); parsedEvents = modernizer.modernize(metadata.version, parsedEvents); shardEvents.push({ zipFile: absolutePath, reportFile, metadata, parsedEvents }); } await import_fs14.default.promises.writeFile(reportFile, content); } zipFile.close(); } return shardEvents; } function findMetadata(events, file) { if (events[0]?.method !== "onBlobReportMetadata") throw new Error(`No metadata event found in ${file}`); const metadata = events[0].params; if (metadata.version > currentBlobReportVersion) throw new Error(`Blob report ${file} was created with a newer version of Playwright.`); return metadata; } async function mergeEvents(dir, shardReportFiles, stringPool, printStatus, rootDirOverride) { const internalizer = new JsonStringInternalizer(stringPool); const configureEvents = []; const projectEvents = []; const endEvents = []; const blobs = await extractAndParseReports(dir, shardReportFiles, internalizer, printStatus); blobs.sort((a, b) => { const nameA = a.metadata.name ?? ""; const nameB = b.metadata.name ?? ""; if (nameA !== nameB) return nameA.localeCompare(nameB); const shardA = a.metadata.shard?.current ?? 0; const shardB = b.metadata.shard?.current ?? 0; if (shardA !== shardB) return shardA - shardB; return a.zipFile.localeCompare(b.zipFile); }); printStatus(`merging events`); const reports = []; const globalTestIdSet = /* @__PURE__ */ new Set(); for (let i = 0; i < blobs.length; ++i) { const { parsedEvents, metadata, reportFile, zipFile } = blobs[i]; const eventPatchers = new JsonEventPatchers(); eventPatchers.patchers.push(new IdsPatcher( stringPool, metadata.name, String(i), globalTestIdSet )); if (rootDirOverride) eventPatchers.patchers.push(new PathSeparatorPatcher(metadata.pathSeparator)); eventPatchers.patchEvents(parsedEvents); let config2; let fullResult; for (const event of parsedEvents) { if (event.method === "onConfigure") { configureEvents.push(event); config2 = event.params.config; } else if (event.method === "onProject") { projectEvents.push(event); } else if (event.method === "onEnd") { fullResult = event.params.result; endEvents.push({ event, metadata }); } } reports.push({ eventPatchers, reportFile, zipFile, metadata, config: config2, fullResult }); } return { prologue: [ mergeConfigureEvents(configureEvents, rootDirOverride), ...projectEvents, { method: "onBegin", params: void 0 } ], reports, epilogue: [ mergeEndEvents(endEvents), { method: "onExit", params: void 0 } ], pathSeparatorFromMetadata: blobs[0]?.metadata.pathSeparator }; } function mergeConfigureEvents(configureEvents, rootDirOverride) { if (!configureEvents.length) throw new Error("No configure events found"); let config2 = { configFile: void 0, globalTimeout: 0, maxFailures: 0, metadata: {}, shard: null, rootDir: "", version: "", workers: 0, globalSetup: null, globalTeardown: null }; for (const event of configureEvents) config2 = mergeConfigs(config2, event.params.config); if (rootDirOverride) { config2.rootDir = rootDirOverride; } else { const rootDirs = new Set(configureEvents.map((e) => e.params.config.rootDir)); if (rootDirs.size > 1) { throw new Error([ `Blob reports being merged were recorded with different test directories, and`, `merging cannot proceed. This may happen if you are merging reports from`, `machines with different environments, like different operating systems or`, `if the tests ran with different playwright configs.`, ``, `You can force merge by specifying a merge config file with "-c" option. If`, `you'd like all test paths to be correct, make sure 'testDir' in the merge config`, `file points to the actual tests location.`, ``, `Found directories:`, ...rootDirs ].join("\n")); } } return { method: "onConfigure", params: { config: config2 } }; } function mergeConfigs(to, from) { return { ...to, ...from, metadata: { ...to.metadata, ...from.metadata, actualWorkers: (to.metadata.actualWorkers || 0) + (from.metadata.actualWorkers || 0) }, shard: null, workers: to.workers + from.workers }; } function mergeEndEvents(endEvents) { let startTime = endEvents.length ? 1e13 : Date.now(); let status = "passed"; let endTime = 0; for (const { event } of endEvents) { const shardResult = event.params.result; if (shardResult.status === "failed") status = "failed"; else if (shardResult.status === "timedout" && status !== "failed") status = "timedout"; else if (shardResult.status === "interrupted" && status !== "failed" && status !== "timedout") status = "interrupted"; startTime = Math.min(startTime, shardResult.startTime); endTime = Math.max(endTime, shardResult.startTime + shardResult.duration); } const result = { status, startTime, duration: endTime - startTime }; return { method: "onEnd", params: { result } }; } async function sortedShardFiles(dir) { const files = await import_fs14.default.promises.readdir(dir); return files.filter((file) => file.endsWith(".zip")).sort(); } function printStatusToStdout(message) { process.stdout.write(`${message} `); } var UniqueFileNameGenerator = class { constructor() { this._usedNames = /* @__PURE__ */ new Set(); } makeUnique(name) { if (!this._usedNames.has(name)) { this._usedNames.add(name); return name; } const extension = import_path19.default.extname(name); name = name.substring(0, name.length - extension.length); let index = 0; while (true) { const candidate = `${name}-${++index}${extension}`; if (!this._usedNames.has(candidate)) { this._usedNames.add(candidate); return candidate; } } } }; var IdsPatcher = class { constructor(stringPool, botName, salt, globalTestIdSet) { this._stringPool = stringPool; this._botName = botName; this._salt = salt; this._testIdsMap = /* @__PURE__ */ new Map(); this._globalTestIdSet = globalTestIdSet; } patchEvent(event) { const { method, params } = event; switch (method) { case "onProject": this._onProject(params.project); return; case "onAttach": case "onTestBegin": case "onStepBegin": case "onStepEnd": case "onStdIO": params.testId = params.testId ? this._mapTestId(params.testId) : void 0; return; case "onTestEnd": params.test.testId = this._mapTestId(params.test.testId); return; } } _onProject(project) { project.metadata ??= {}; project.suites.forEach((suite) => this._updateTestIds(suite)); } _updateTestIds(suite) { suite.entries.forEach((entry) => { if ("testId" in entry) this._updateTestId(entry); else this._updateTestIds(entry); }); } _updateTestId(test) { test.testId = this._mapTestId(test.testId); if (this._botName) { test.tags = test.tags || []; test.tags.unshift("@" + this._botName); } } _mapTestId(testId) { const t1 = this._stringPool.internString(testId); if (this._testIdsMap.has(t1)) return this._testIdsMap.get(t1); if (this._globalTestIdSet.has(t1)) { const t2 = this._stringPool.internString(testId + this._salt); this._globalTestIdSet.add(t2); this._testIdsMap.set(t1, t2); return t2; } this._globalTestIdSet.add(t1); this._testIdsMap.set(t1, t1); return t1; } }; var AttachmentPathPatcher = class { constructor(_resourceDir) { this._resourceDir = _resourceDir; } patchEvent(event) { if (event.method === "onAttach") this._patchAttachments(event.params.attachments); else if (event.method === "onTestEnd") this._patchAttachments(event.params.result.attachments ?? []); } _patchAttachments(attachments) { const resourceRoot = import_path19.default.resolve(this._resourceDir); for (const attachment of attachments) { if (!attachment.path) continue; const joined = import_path19.default.resolve(resourceRoot, attachment.path); if (!isPathInside(resourceRoot, joined)) { attachment.path = void 0; continue; } attachment.path = joined; } } }; var PathSeparatorPatcher = class { constructor(from) { this._from = from ?? (import_path19.default.sep === "/" ? "\\" : "/"); this._to = import_path19.default.sep; } patchEvent(jsonEvent) { if (this._from === this._to) return; if (jsonEvent.method === "onProject") { this._updateProject(jsonEvent.params.project); return; } if (jsonEvent.method === "onTestEnd") { const test = jsonEvent.params.test; test.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation)); const testResult = jsonEvent.params.result; testResult.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation)); testResult.errors.forEach((error) => this._updateErrorLocations(error)); (testResult.attachments ?? []).forEach((attachment) => { if (attachment.path) attachment.path = this._updatePath(attachment.path); }); return; } if (jsonEvent.method === "onStepBegin") { const step = jsonEvent.params.step; this._updateLocation(step.location); return; } if (jsonEvent.method === "onStepEnd") { const step = jsonEvent.params.step; this._updateErrorLocations(step.error); step.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation)); return; } if (jsonEvent.method === "onAttach") { const attach = jsonEvent.params; attach.attachments.forEach((attachment) => { if (attachment.path) attachment.path = this._updatePath(attachment.path); }); return; } } _updateProject(project) { project.outputDir = this._updatePath(project.outputDir); project.testDir = this._updatePath(project.testDir); project.snapshotDir = this._updatePath(project.snapshotDir); project.suites.forEach((suite) => this._updateSuite(suite, true)); } _updateSuite(suite, isFileSuite = false) { this._updateLocation(suite.location); if (isFileSuite) suite.title = this._updatePath(suite.title); for (const entry of suite.entries) { if ("testId" in entry) { this._updateLocation(entry.location); entry.annotations?.forEach((annotation) => this._updateAnnotationLocation(annotation)); } else { this._updateSuite(entry); } } } _updateErrorLocations(error) { while (error) { this._updateLocation(error.location); error = error.cause; } } _updateAnnotationLocation(annotation) { this._updateLocation(annotation.location); } _updateLocation(location) { if (location) location.file = this._updatePath(location.file); } _updatePath(text) { return text.split(this._from).join(this._to); } }; var GlobalErrorPatcher = class { constructor(botName) { this._prefix = `(${botName}) `; } patchEvent(event) { if (event.method !== "onError") return; const error = event.params.error; if (error.message !== void 0) error.message = this._prefix + error.message; if (error.stack !== void 0) error.stack = this._prefix + error.stack; } }; var WorkerIndexPatcher = class { constructor(baseWorkerIndex) { this._maxWorkerIndex = 0; this._baseWorkerIndex = baseWorkerIndex; } patchEvent(event) { if (event.method === "onTestBegin") { this._maxWorkerIndex = Math.max(this._maxWorkerIndex, event.params.result.workerIndex); event.params.result.workerIndex += this._baseWorkerIndex; } } usedWorkers() { return this._maxWorkerIndex + 1; } }; var JsonEventPatchers = class { constructor() { this.patchers = []; } patchEvents(events) { for (const event of events) { for (const patcher of this.patchers) patcher.patchEvent(event); } } }; var BlobModernizer = class { modernize(fromVersion, events) { const result = []; for (const event of events) result.push(...this._modernize(fromVersion, event)); return result; } _modernize(fromVersion, event) { let events = [event]; for (let version = fromVersion; version < currentBlobReportVersion; ++version) events = this[`_modernize_${version}_to_${version + 1}`].call(this, events); return events; } _modernize_1_to_2(events) { return events.map((event) => { if (event.method === "onProject") { const modernizeSuite = (suite) => { const newSuites = suite.suites.map(modernizeSuite); const { suites, tests, ...remainder } = suite; return { entries: [...newSuites, ...tests], ...remainder }; }; const project = event.params.project; project.suites = project.suites.map(modernizeSuite); } return event; }); } }; var modernizer = new BlobModernizer(); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { ListModeReporter, ListReporter, TestServerConnection, base, html, merge, projectUtils, runnerReporters, testRunner, testServer, watchMode, webServer });