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