8036 lines
289 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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(/<script type="module"[^>]*><\/script>/, () => `<script type="module">${js}</script>`);
html = html.replace(/<link rel="stylesheet"[^>]*>/, () => `<style type='text/css'>${css}</style>`);
await import_fs5.default.promises.writeFile(reportIndexFile, html);
}
return reportIndexFile;
}
async _writeReportData(filePath) {
import_fs5.default.appendFileSync(filePath, '<template id="playwrightReportBase64">data:application/zip;base64,');
await new Promise((f) => {
this._dataZipFile.end(void 0, () => {
this._dataZipFile.outputStream.pipe(new Base64Encoder()).pipe(import_fs5.default.createWriteStream(filePath, { flags: "a" })).on("close", f);
});
});
import_fs5.default.appendFileSync(filePath, "</template>");
}
_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(`</${entry.name}>`);
}
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 = "<![CDATA[" + text.replace(/]]>/g, "]]&gt;") + "]]>";
} else {
const escapeRe = /[&"'<>]/g;
text = text.replace(escapeRe, (c) => ({ "&": "&amp;", '"': "&quot;", "'": "&apos;", "<": "&lt;", ">": "&gt;" })[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
});