playwright 1.54.1 → 1.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +2727 -434
  3. package/lib/agents/generateAgents.js +263 -0
  4. package/lib/agents/generator.md +102 -0
  5. package/lib/agents/healer.md +78 -0
  6. package/lib/agents/planner.md +135 -0
  7. package/lib/common/config.js +3 -1
  8. package/lib/common/configLoader.js +2 -1
  9. package/lib/common/expectBundle.js +3 -0
  10. package/lib/common/expectBundleImpl.js +51 -51
  11. package/lib/common/fixtures.js +1 -1
  12. package/lib/common/suiteUtils.js +0 -9
  13. package/lib/index.js +127 -115
  14. package/lib/isomorphic/testTree.js +35 -8
  15. package/lib/matchers/expect.js +6 -7
  16. package/lib/matchers/matcherHint.js +43 -15
  17. package/lib/matchers/matchers.js +10 -4
  18. package/lib/matchers/toBeTruthy.js +16 -14
  19. package/lib/matchers/toEqual.js +18 -13
  20. package/lib/matchers/toHaveURL.js +12 -27
  21. package/lib/matchers/toMatchAriaSnapshot.js +26 -31
  22. package/lib/matchers/toMatchSnapshot.js +15 -12
  23. package/lib/matchers/toMatchText.js +29 -35
  24. package/lib/mcp/browser/actions.d.js +16 -0
  25. package/lib/mcp/browser/browserContextFactory.js +296 -0
  26. package/lib/mcp/browser/browserServerBackend.js +76 -0
  27. package/lib/mcp/browser/codegen.js +66 -0
  28. package/lib/mcp/browser/config.js +383 -0
  29. package/lib/mcp/browser/context.js +284 -0
  30. package/lib/mcp/browser/response.js +228 -0
  31. package/lib/mcp/browser/sessionLog.js +160 -0
  32. package/lib/mcp/browser/tab.js +277 -0
  33. package/lib/mcp/browser/tools/common.js +63 -0
  34. package/lib/mcp/browser/tools/console.js +44 -0
  35. package/lib/mcp/browser/tools/dialogs.js +60 -0
  36. package/lib/mcp/browser/tools/evaluate.js +70 -0
  37. package/lib/mcp/browser/tools/files.js +58 -0
  38. package/lib/mcp/browser/tools/form.js +74 -0
  39. package/lib/mcp/browser/tools/install.js +69 -0
  40. package/lib/mcp/browser/tools/keyboard.js +85 -0
  41. package/lib/mcp/browser/tools/mouse.js +107 -0
  42. package/lib/mcp/browser/tools/navigate.js +62 -0
  43. package/lib/mcp/browser/tools/network.js +54 -0
  44. package/lib/mcp/browser/tools/pdf.js +59 -0
  45. package/lib/mcp/browser/tools/screenshot.js +88 -0
  46. package/lib/mcp/browser/tools/snapshot.js +182 -0
  47. package/lib/mcp/browser/tools/tabs.js +67 -0
  48. package/lib/mcp/browser/tools/tool.js +49 -0
  49. package/lib/mcp/browser/tools/tracing.js +74 -0
  50. package/lib/mcp/browser/tools/utils.js +100 -0
  51. package/lib/mcp/browser/tools/verify.js +154 -0
  52. package/lib/mcp/browser/tools/wait.js +63 -0
  53. package/lib/mcp/browser/tools.js +80 -0
  54. package/lib/mcp/browser/watchdog.js +44 -0
  55. package/lib/mcp/config.d.js +16 -0
  56. package/lib/mcp/extension/cdpRelay.js +351 -0
  57. package/lib/mcp/extension/extensionContextFactory.js +75 -0
  58. package/lib/mcp/extension/protocol.js +28 -0
  59. package/lib/mcp/index.js +61 -0
  60. package/lib/mcp/log.js +35 -0
  61. package/lib/mcp/program.js +96 -0
  62. package/lib/mcp/sdk/bundle.js +81 -0
  63. package/lib/mcp/sdk/exports.js +32 -0
  64. package/lib/mcp/sdk/http.js +180 -0
  65. package/lib/mcp/sdk/inProcessTransport.js +71 -0
  66. package/lib/mcp/sdk/mdb.js +208 -0
  67. package/lib/mcp/sdk/proxyBackend.js +128 -0
  68. package/lib/mcp/sdk/server.js +190 -0
  69. package/lib/mcp/sdk/tool.js +51 -0
  70. package/lib/mcp/test/browserBackend.js +98 -0
  71. package/lib/mcp/test/generatorTools.js +122 -0
  72. package/lib/mcp/test/plannerTools.js +46 -0
  73. package/lib/mcp/test/seed.js +72 -0
  74. package/lib/mcp/test/streams.js +39 -0
  75. package/lib/mcp/test/testBackend.js +97 -0
  76. package/lib/mcp/test/testContext.js +176 -0
  77. package/lib/mcp/test/testTool.js +30 -0
  78. package/lib/mcp/test/testTools.js +115 -0
  79. package/lib/mcpBundleImpl.js +41 -0
  80. package/lib/plugins/webServerPlugin.js +2 -0
  81. package/lib/program.js +77 -57
  82. package/lib/reporters/base.js +34 -29
  83. package/lib/reporters/dot.js +11 -11
  84. package/lib/reporters/github.js +2 -1
  85. package/lib/reporters/html.js +58 -41
  86. package/lib/reporters/internalReporter.js +2 -1
  87. package/lib/reporters/line.js +15 -15
  88. package/lib/reporters/list.js +24 -19
  89. package/lib/reporters/listModeReporter.js +69 -0
  90. package/lib/reporters/markdown.js +3 -3
  91. package/lib/reporters/merge.js +3 -1
  92. package/lib/reporters/teleEmitter.js +3 -1
  93. package/lib/runner/dispatcher.js +9 -2
  94. package/lib/runner/failureTracker.js +12 -2
  95. package/lib/runner/lastRun.js +7 -4
  96. package/lib/runner/loadUtils.js +46 -12
  97. package/lib/runner/projectUtils.js +8 -2
  98. package/lib/runner/reporters.js +7 -32
  99. package/lib/runner/tasks.js +20 -10
  100. package/lib/runner/testRunner.js +390 -0
  101. package/lib/runner/testServer.js +57 -276
  102. package/lib/runner/watchMode.js +5 -1
  103. package/lib/runner/workerHost.js +8 -6
  104. package/lib/transform/babelBundleImpl.js +179 -195
  105. package/lib/transform/compilationCache.js +22 -5
  106. package/lib/transform/transform.js +1 -1
  107. package/lib/util.js +12 -35
  108. package/lib/utilsBundleImpl.js +1 -1
  109. package/lib/worker/fixtureRunner.js +7 -2
  110. package/lib/worker/testInfo.js +76 -45
  111. package/lib/worker/testTracing.js +8 -7
  112. package/lib/worker/workerMain.js +12 -3
  113. package/package.json +10 -2
  114. package/types/test.d.ts +63 -44
  115. package/types/testReporter.d.ts +1 -1
  116. package/lib/runner/runner.js +0 -110
@@ -37,10 +37,11 @@ var import_base = require("./base");
37
37
  var import_multiplexer = require("./multiplexer");
38
38
  var import_test = require("../common/test");
39
39
  var import_babelBundle = require("../transform/babelBundle");
40
+ var import_reporterV2 = require("./reporterV2");
40
41
  class InternalReporter {
41
42
  constructor(reporters) {
42
43
  this._didBegin = false;
43
- this._reporter = new import_multiplexer.Multiplexer(reporters);
44
+ this._reporter = new import_multiplexer.Multiplexer(reporters.map(import_reporterV2.wrapReporterAsV2));
44
45
  }
45
46
  version() {
46
47
  return "v2";
@@ -33,18 +33,18 @@ class LineReporter extends import_base.TerminalReporter {
33
33
  super.onBegin(suite);
34
34
  const startingMessage = this.generateStartingMessage();
35
35
  if (startingMessage) {
36
- console.log(startingMessage);
37
- console.log();
36
+ this.writeLine(startingMessage);
37
+ this.writeLine();
38
38
  }
39
39
  this._didBegin = true;
40
40
  }
41
41
  onStdOut(chunk, test, result) {
42
42
  super.onStdOut(chunk, test, result);
43
- this._dumpToStdio(test, chunk, process.stdout);
43
+ this._dumpToStdio(test, chunk, this.screen.stdout);
44
44
  }
45
45
  onStdErr(chunk, test, result) {
46
46
  super.onStdErr(chunk, test, result);
47
- this._dumpToStdio(test, chunk, process.stderr);
47
+ this._dumpToStdio(test, chunk, this.screen.stderr);
48
48
  }
49
49
  _dumpToStdio(test, chunk, stream) {
50
50
  if (this.config.quiet)
@@ -59,8 +59,8 @@ class LineReporter extends import_base.TerminalReporter {
59
59
  }
60
60
  stream.write(chunk);
61
61
  if (chunk[chunk.length - 1] !== "\n")
62
- console.log();
63
- console.log();
62
+ this.writeLine();
63
+ this.writeLine();
64
64
  }
65
65
  onTestBegin(test, result) {
66
66
  ++this._current;
@@ -78,9 +78,9 @@ class LineReporter extends import_base.TerminalReporter {
78
78
  super.onTestEnd(test, result);
79
79
  if (!this.willRetry(test) && (test.outcome() === "flaky" || test.outcome() === "unexpected" || result.status === "interrupted")) {
80
80
  if (!process.env.PW_TEST_DEBUG_REPORTERS)
81
- process.stdout.write(`\x1B[1A\x1B[2K`);
82
- console.log(this.formatFailure(test, ++this._failures));
83
- console.log();
81
+ this.screen.stdout.write(`\x1B[1A\x1B[2K`);
82
+ this.writeLine(this.formatFailure(test, ++this._failures));
83
+ this.writeLine();
84
84
  }
85
85
  }
86
86
  _updateLine(test, result, step) {
@@ -89,23 +89,23 @@ class LineReporter extends import_base.TerminalReporter {
89
89
  const currentRetrySuffix = result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : "";
90
90
  const title = this.formatTestTitle(test, step) + currentRetrySuffix;
91
91
  if (process.env.PW_TEST_DEBUG_REPORTERS)
92
- process.stdout.write(`${prefix + title}
92
+ this.screen.stdout.write(`${prefix + title}
93
93
  `);
94
94
  else
95
- process.stdout.write(`\x1B[1A\x1B[2K${prefix + this.fitToScreen(title, prefix)}
95
+ this.screen.stdout.write(`\x1B[1A\x1B[2K${prefix + this.fitToScreen(title, prefix)}
96
96
  `);
97
97
  }
98
98
  onError(error) {
99
99
  super.onError(error);
100
100
  const message = this.formatError(error).message + "\n";
101
101
  if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin)
102
- process.stdout.write(`\x1B[1A\x1B[2K`);
103
- process.stdout.write(message);
104
- console.log();
102
+ this.screen.stdout.write(`\x1B[1A\x1B[2K`);
103
+ this.screen.stdout.write(message);
104
+ this.writeLine();
105
105
  }
106
106
  async onEnd(result) {
107
107
  if (!process.env.PW_TEST_DEBUG_REPORTERS && this._didBegin)
108
- process.stdout.write(`\x1B[1A\x1B[2K`);
108
+ this.screen.stdout.write(`\x1B[1A\x1B[2K`);
109
109
  await super.onEnd(result);
110
110
  this.epilogue(false);
111
111
  }
@@ -30,7 +30,7 @@ const POSITIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "ok" : "\u2713"
30
30
  const NEGATIVE_STATUS_MARK = DOES_NOT_SUPPORT_UTF8_IN_TERMINAL ? "x" : "\u2718";
31
31
  class ListReporter extends import_base.TerminalReporter {
32
32
  constructor(options) {
33
- super();
33
+ super(options);
34
34
  this._lastRow = 0;
35
35
  this._lastColumn = 0;
36
36
  this._testRows = /* @__PURE__ */ new Map();
@@ -39,13 +39,14 @@ class ListReporter extends import_base.TerminalReporter {
39
39
  this._stepIndex = /* @__PURE__ */ new Map();
40
40
  this._needNewLine = false;
41
41
  this._printSteps = (0, import_utils.getAsBooleanFromENV)("PLAYWRIGHT_LIST_PRINT_STEPS", options?.printSteps);
42
+ this._prefixStdio = options?.prefixStdio;
42
43
  }
43
44
  onBegin(suite) {
44
45
  super.onBegin(suite);
45
46
  const startingMessage = this.generateStartingMessage();
46
47
  if (startingMessage) {
47
- console.log(startingMessage);
48
- console.log();
48
+ this.writeLine(startingMessage);
49
+ this.writeLine("");
49
50
  }
50
51
  }
51
52
  onTestBegin(test, result) {
@@ -61,11 +62,11 @@ class ListReporter extends import_base.TerminalReporter {
61
62
  }
62
63
  onStdOut(chunk, test, result) {
63
64
  super.onStdOut(chunk, test, result);
64
- this._dumpToStdio(test, chunk, process.stdout);
65
+ this._dumpToStdio(test, chunk, this.screen.stdout, "out");
65
66
  }
66
67
  onStdErr(chunk, test, result) {
67
68
  super.onStdErr(chunk, test, result);
68
- this._dumpToStdio(test, chunk, process.stderr);
69
+ this._dumpToStdio(test, chunk, this.screen.stderr, "err");
69
70
  }
70
71
  getStepIndex(testIndex, result, step) {
71
72
  if (this._stepIndex.has(step))
@@ -115,7 +116,7 @@ class ListReporter extends import_base.TerminalReporter {
115
116
  _maybeWriteNewLine() {
116
117
  if (this._needNewLine) {
117
118
  this._needNewLine = false;
118
- process.stdout.write("\n");
119
+ this.screen.stdout.write("\n");
119
120
  ++this._lastRow;
120
121
  this._lastColumn = 0;
121
122
  }
@@ -137,12 +138,15 @@ class ListReporter extends import_base.TerminalReporter {
137
138
  }
138
139
  }
139
140
  }
140
- _dumpToStdio(test, chunk, stream) {
141
+ _dumpToStdio(test, chunk, stream, stdio) {
141
142
  if (this.config.quiet)
142
143
  return;
143
144
  const text = chunk.toString("utf-8");
144
145
  this._updateLineCountAndNewLineFlagForOutput(text);
145
- stream.write(chunk);
146
+ if (this._prefixStdio)
147
+ stream.write(`[${stdio}] ${chunk}`);
148
+ else
149
+ stream.write(chunk);
146
150
  }
147
151
  onTestEnd(test, result) {
148
152
  super.onTestEnd(test, result);
@@ -183,10 +187,10 @@ class ListReporter extends import_base.TerminalReporter {
183
187
  _appendLine(text, prefix) {
184
188
  const line = prefix + this.fitToScreen(text, prefix);
185
189
  if (process.env.PW_TEST_DEBUG_REPORTERS) {
186
- process.stdout.write("#" + this._lastRow + " : " + line + "\n");
190
+ this.screen.stdout.write("#" + this._lastRow + " : " + line + "\n");
187
191
  } else {
188
- process.stdout.write(line);
189
- process.stdout.write("\n");
192
+ this.screen.stdout.write(line);
193
+ this.screen.stdout.write("\n");
190
194
  }
191
195
  ++this._lastRow;
192
196
  this._lastColumn = 0;
@@ -194,21 +198,22 @@ class ListReporter extends import_base.TerminalReporter {
194
198
  _updateLine(row, text, prefix) {
195
199
  const line = prefix + this.fitToScreen(text, prefix);
196
200
  if (process.env.PW_TEST_DEBUG_REPORTERS)
197
- process.stdout.write("#" + row + " : " + line + "\n");
201
+ this.screen.stdout.write("#" + row + " : " + line + "\n");
198
202
  else
199
203
  this._updateLineForTTY(row, line);
200
204
  }
201
205
  _updateLineForTTY(row, line) {
202
206
  if (row !== this._lastRow)
203
- process.stdout.write(`\x1B[${this._lastRow - row}A`);
204
- process.stdout.write("\x1B[2K\x1B[0G");
205
- process.stdout.write(line);
207
+ this.screen.stdout.write(`\x1B[${this._lastRow - row}A`);
208
+ this.screen.stdout.write("\x1B[2K\x1B[0G");
209
+ this.screen.stdout.write(line);
206
210
  if (row !== this._lastRow)
207
- process.stdout.write(`\x1B[${this._lastRow - row}E`);
211
+ this.screen.stdout.write(`\x1B[${this._lastRow - row}E`);
208
212
  }
209
213
  _testPrefix(index, statusMark) {
210
214
  const statusMarkLength = (0, import_util.stripAnsiEscapes)(statusMark).length;
211
- return " " + statusMark + " ".repeat(3 - statusMarkLength) + this.screen.colors.dim(index + " ");
215
+ const indexLength = Math.ceil(Math.log10(this.totalTestCount + 1));
216
+ return " " + statusMark + " ".repeat(3 - statusMarkLength) + this.screen.colors.dim(index.padStart(indexLength) + " ");
212
217
  }
213
218
  _retrySuffix(result) {
214
219
  return result.retry ? this.screen.colors.yellow(` (retry #${result.retry})`) : "";
@@ -218,11 +223,11 @@ class ListReporter extends import_base.TerminalReporter {
218
223
  this._maybeWriteNewLine();
219
224
  const message = this.formatError(error).message + "\n";
220
225
  this._updateLineCountAndNewLineFlagForOutput(message);
221
- process.stdout.write(message);
226
+ this.screen.stdout.write(message);
222
227
  }
223
228
  async onEnd(result) {
224
229
  await super.onEnd(result);
225
- process.stdout.write("\n");
230
+ this.screen.stdout.write("\n");
226
231
  this.epilogue(true);
227
232
  }
228
233
  }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var listModeReporter_exports = {};
30
+ __export(listModeReporter_exports, {
31
+ default: () => listModeReporter_default
32
+ });
33
+ module.exports = __toCommonJS(listModeReporter_exports);
34
+ var import_path = __toESM(require("path"));
35
+ var import_base = require("./base");
36
+ class ListModeReporter {
37
+ constructor(options = {}) {
38
+ this._options = options;
39
+ this.screen = options?.screen ?? import_base.terminalScreen;
40
+ }
41
+ version() {
42
+ return "v2";
43
+ }
44
+ onConfigure(config) {
45
+ this.config = config;
46
+ }
47
+ onBegin(suite) {
48
+ this._writeLine(`Listing tests:`);
49
+ const tests = suite.allTests();
50
+ const files = /* @__PURE__ */ new Set();
51
+ for (const test of tests) {
52
+ const [, projectName, , ...titles] = test.titlePath();
53
+ const location = `${import_path.default.relative(this.config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
54
+ const testId = this._options.includeTestId ? `[id=${test.id}] ` : "";
55
+ const projectLabel = this._options.includeTestId ? `project=` : "";
56
+ const projectTitle = projectName ? `[${projectLabel}${projectName}] \u203A ` : "";
57
+ this._writeLine(` ${testId}${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`);
58
+ files.add(test.location.file);
59
+ }
60
+ this._writeLine(`Total: ${tests.length} ${tests.length === 1 ? "test" : "tests"} in ${files.size} ${files.size === 1 ? "file" : "files"}`);
61
+ }
62
+ onError(error) {
63
+ this.screen.stderr.write("\n" + (0, import_base.formatError)(import_base.terminalScreen, error).message + "\n");
64
+ }
65
+ _writeLine(line) {
66
+ this.screen.stdout.write(line + "\n");
67
+ }
68
+ }
69
+ var listModeReporter_default = ListModeReporter;
@@ -74,7 +74,6 @@ class MarkdownReporter {
74
74
  const skipped = summary.skipped ? `, ${summary.skipped} skipped` : "";
75
75
  const didNotRun = summary.didNotRun ? `, ${summary.didNotRun} did not run` : "";
76
76
  lines.push(`**${summary.expected} passed${skipped}${didNotRun}**`);
77
- lines.push(`:heavy_check_mark::heavy_check_mark::heavy_check_mark:`);
78
77
  lines.push(``);
79
78
  await this.publishReport(lines.join("\n"));
80
79
  }
@@ -135,10 +134,11 @@ class MarkdownReporter {
135
134
  function formatTestTitle(rootDir, test) {
136
135
  const [, projectName, , ...titles] = test.titlePath();
137
136
  const relativeTestPath = import_path.default.relative(rootDir, test.location.file);
138
- const location = `${relativeTestPath}:${test.location.line}:${test.location.column}`;
137
+ const location = `${relativeTestPath}:${test.location.line}`;
139
138
  const projectTitle = projectName ? `[${projectName}] \u203A ` : "";
140
139
  const testTitle = `${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`;
141
140
  const extraTags = test.tags.filter((t) => !testTitle.includes(t));
142
- return `${testTitle}${extraTags.length ? " " + extraTags.join(" ") : ""}`;
141
+ const formattedTags = extraTags.map((t) => `\`${t}\``).join(" ");
142
+ return `${testTitle}${extraTags.length ? " " + formattedTags : ""}`;
143
143
  }
144
144
  var markdown_default = MarkdownReporter;
@@ -216,7 +216,9 @@ function mergeConfigureEvents(configureEvents, rootDirOverride) {
216
216
  metadata: {},
217
217
  rootDir: "",
218
218
  version: "",
219
- workers: 0
219
+ workers: 0,
220
+ globalSetup: null,
221
+ globalTeardown: null
220
222
  };
221
223
  for (const event of configureEvents)
222
224
  config = mergeConfigs(config, event.params.config);
@@ -152,7 +152,9 @@ class TeleReporterEmitter {
152
152
  metadata: config.metadata,
153
153
  rootDir: config.rootDir,
154
154
  version: config.version,
155
- workers: config.workers
155
+ workers: config.workers,
156
+ globalSetup: config.globalSetup,
157
+ globalTeardown: config.globalTeardown
156
158
  };
157
159
  }
158
160
  _serializeProject(suite) {
@@ -158,7 +158,14 @@ class Dispatcher {
158
158
  _createWorker(testGroup, parallelIndex, loaderData) {
159
159
  const projectConfig = this._config.projects.find((p) => p.id === testGroup.projectId);
160
160
  const outputDir = projectConfig.project.outputDir;
161
- const worker = new import_workerHost.WorkerHost(testGroup, parallelIndex, loaderData, this._extraEnvByProjectId.get(testGroup.projectId) || {}, outputDir);
161
+ const worker = new import_workerHost.WorkerHost(testGroup, {
162
+ parallelIndex,
163
+ config: loaderData,
164
+ extraEnv: this._extraEnvByProjectId.get(testGroup.projectId) || {},
165
+ outputDir,
166
+ pauseOnError: this._failureTracker.pauseOnError(),
167
+ pauseAtEnd: this._failureTracker.pauseAtEnd(projectConfig)
168
+ });
162
169
  const handleOutput = (params) => {
163
170
  const chunk = chunkFromParams(params);
164
171
  if (worker.didFail()) {
@@ -363,7 +370,7 @@ class JobDispatcher {
363
370
  }
364
371
  }
365
372
  _onDone(params) {
366
- if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError) {
373
+ if (!this._remainingByTestId.size && !this._failedTests.size && !params.fatalErrors.length && !params.skipTestsDueToSetupFailure.length && !params.fatalUnknownTestIds && !params.unexpectedExitError && !params.stoppedDueToUnhandledErrorInTestFail) {
367
374
  this._finished({ didFail: false });
368
375
  return;
369
376
  }
@@ -22,13 +22,17 @@ __export(failureTracker_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(failureTracker_exports);
24
24
  class FailureTracker {
25
- constructor(_config) {
25
+ constructor(_config, options) {
26
26
  this._config = _config;
27
27
  this._failureCount = 0;
28
28
  this._hasWorkerErrors = false;
29
+ this._topLevelProjects = [];
30
+ this._pauseOnError = options?.pauseOnError ?? false;
31
+ this._pauseAtEnd = options?.pauseAtEnd ?? false;
29
32
  }
30
- onRootSuite(rootSuite) {
33
+ onRootSuite(rootSuite, topLevelProjects) {
31
34
  this._rootSuite = rootSuite;
35
+ this._topLevelProjects = topLevelProjects;
32
36
  }
33
37
  onTestEnd(test, result) {
34
38
  if (test.outcome() === "unexpected" && test.results.length > test.retries)
@@ -37,6 +41,12 @@ class FailureTracker {
37
41
  onWorkerError() {
38
42
  this._hasWorkerErrors = true;
39
43
  }
44
+ pauseOnError() {
45
+ return this._pauseOnError;
46
+ }
47
+ pauseAtEnd(inProject) {
48
+ return this._pauseAtEnd && this._topLevelProjects.includes(inProject);
49
+ }
40
50
  hasReachedMaxFailures() {
41
51
  return this.maxFailures() > 0 && this._failureCount >= this.maxFailures();
42
52
  }
@@ -46,7 +46,8 @@ class LastRunReporter {
46
46
  return;
47
47
  try {
48
48
  const lastRunInfo = JSON.parse(await import_fs.default.promises.readFile(this._lastRunFile, "utf8"));
49
- this._config.lastFailedTestIdMatcher = (id) => lastRunInfo.failedTests.includes(id);
49
+ const failedTestIds = new Set(lastRunInfo.failedTests);
50
+ this._config.postShardTestFilters.push((test) => failedTestIds.has(test.id));
50
51
  } catch {
51
52
  }
52
53
  }
@@ -62,10 +63,12 @@ class LastRunReporter {
62
63
  async onEnd(result) {
63
64
  if (!this._lastRunFile || this._config.cliListOnly)
64
65
  return;
66
+ const lastRunInfo = {
67
+ status: result.status,
68
+ failedTests: this._suite?.allTests().filter((t) => !t.ok()).map((t) => t.id) || []
69
+ };
65
70
  await import_fs.default.promises.mkdir(import_path.default.dirname(this._lastRunFile), { recursive: true });
66
- const failedTests = this._suite?.allTests().filter((t) => !t.ok()).map((t) => t.id);
67
- const lastRunReport = JSON.stringify({ status: result.status, failedTests }, void 0, 2);
68
- await import_fs.default.promises.writeFile(this._lastRunFile, lastRunReport);
71
+ await import_fs.default.promises.writeFile(this._lastRunFile, JSON.stringify(lastRunInfo, void 0, 2));
69
72
  }
70
73
  }
71
74
  // Annotate the CommonJS export names for ESM import in node:
@@ -32,10 +32,13 @@ __export(loadUtils_exports, {
32
32
  createRootSuite: () => createRootSuite,
33
33
  loadFileSuites: () => loadFileSuites,
34
34
  loadGlobalHook: () => loadGlobalHook,
35
- loadReporter: () => loadReporter
35
+ loadReporter: () => loadReporter,
36
+ loadTestList: () => loadTestList
36
37
  });
37
38
  module.exports = __toCommonJS(loadUtils_exports);
38
39
  var import_path = __toESM(require("path"));
40
+ var import_fs = __toESM(require("fs"));
41
+ var import_utils = require("playwright-core/lib/utils");
39
42
  var import_loaderHost = require("./loaderHost");
40
43
  var import_util = require("../util");
41
44
  var import_projectUtils = require("./projectUtils");
@@ -112,7 +115,7 @@ async function loadFileSuites(testRun, mode, errors) {
112
115
  testRun.projectSuites.set(project, suites);
113
116
  }
114
117
  }
115
- async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFileMatcher) {
118
+ async function createRootSuite(testRun, errors, shouldFilterOnly) {
116
119
  const config = testRun.config;
117
120
  const rootSuite = new import_test.Suite("", "root");
118
121
  const projectSuites = /* @__PURE__ */ new Map();
@@ -125,7 +128,7 @@ async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFile
125
128
  for (const [project, fileSuites] of testRun.projectSuites) {
126
129
  const projectSuite = createProjectSuite(project, fileSuites);
127
130
  projectSuites.set(project, projectSuite);
128
- const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testIdMatcher: config.testIdMatcher, additionalFileMatcher });
131
+ const filteredProjectSuite = filterProjectSuite(projectSuite, { cliFileFilters, cliTitleMatcher, testFilters: config.preOnlyTestFilters });
129
132
  filteredProjectSuites.set(project, filteredProjectSuite);
130
133
  }
131
134
  }
@@ -166,16 +169,19 @@ async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFile
166
169
  }
167
170
  (0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => testsInThisShard.has(test));
168
171
  }
169
- if (config.lastFailedTestIdMatcher)
170
- (0, import_suiteUtils.filterByTestIds)(rootSuite, config.lastFailedTestIdMatcher);
172
+ if (config.postShardTestFilters.length)
173
+ (0, import_suiteUtils.filterTestsRemoveEmptySuites)(rootSuite, (test) => config.postShardTestFilters.every((filter) => filter(test)));
174
+ const topLevelProjects = [];
171
175
  {
172
176
  const projectClosure2 = new Map((0, import_projectUtils.buildProjectsClosure)(rootSuite.suites.map((suite) => suite._fullProject)));
173
177
  for (const [project, level] of projectClosure2.entries()) {
174
178
  if (level === "dependency")
175
179
  rootSuite._prependSuite(buildProjectSuite(project, projectSuites.get(project)));
180
+ else
181
+ topLevelProjects.push(project);
176
182
  }
177
183
  }
178
- return rootSuite;
184
+ return { rootSuite, topLevelProjects };
179
185
  }
180
186
  function createProjectSuite(project, fileSuites) {
181
187
  const projectSuite = new import_test.Suite(project.project.name, "project");
@@ -192,17 +198,15 @@ function createProjectSuite(project, fileSuites) {
192
198
  return projectSuite;
193
199
  }
194
200
  function filterProjectSuite(projectSuite, options) {
195
- if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testIdMatcher && !options.additionalFileMatcher)
201
+ if (!options.cliFileFilters.length && !options.cliTitleMatcher && !options.testFilters.length)
196
202
  return projectSuite;
197
203
  const result = projectSuite._deepClone();
198
204
  if (options.cliFileFilters.length)
199
205
  (0, import_suiteUtils.filterByFocusedLine)(result, options.cliFileFilters);
200
- if (options.testIdMatcher)
201
- (0, import_suiteUtils.filterByTestIds)(result, options.testIdMatcher);
202
206
  (0, import_suiteUtils.filterTestsRemoveEmptySuites)(result, (test) => {
203
- if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
207
+ if (!options.testFilters.every((filter) => filter(test)))
204
208
  return false;
205
- if (options.additionalFileMatcher && !options.additionalFileMatcher(test.location.file))
209
+ if (options.cliTitleMatcher && !options.cliTitleMatcher(test._grepTitleWithTags()))
206
210
  return false;
207
211
  return true;
208
212
  });
@@ -289,11 +293,41 @@ function sourceMapSources(file, cache) {
289
293
  return sources;
290
294
  }
291
295
  }
296
+ async function loadTestList(config, filePath) {
297
+ try {
298
+ const content = await import_fs.default.promises.readFile(filePath, "utf-8");
299
+ const lines = content.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
300
+ const descriptions = lines.map((line) => {
301
+ const delimiter = line.includes("\u203A") ? "\u203A" : ">";
302
+ const tokens = line.split(delimiter).map((token) => token.trim());
303
+ let project;
304
+ if (tokens[0].startsWith("[")) {
305
+ if (!tokens[0].endsWith("]"))
306
+ throw new Error(`Malformed test description: ${line}`);
307
+ project = tokens[0].substring(1, tokens[0].length - 1);
308
+ tokens.shift();
309
+ }
310
+ return { project, file: (0, import_utils.toPosixPath)((0, import_util.parseLocationArg)(tokens[0]).file), titlePath: tokens.slice(1) };
311
+ });
312
+ return (test) => descriptions.some((d) => {
313
+ const [projectName, , ...titles] = test.titlePath();
314
+ if (d.project !== void 0 && d.project !== projectName)
315
+ return false;
316
+ const relativeFile = (0, import_utils.toPosixPath)(import_path.default.relative(config.config.rootDir, test.location.file));
317
+ if (relativeFile !== d.file)
318
+ return false;
319
+ return d.titlePath.length === titles.length && d.titlePath.every((_, index) => titles[index] === d.titlePath[index]);
320
+ });
321
+ } catch (e) {
322
+ throw (0, import_util.errorWithFile)(filePath, "Cannot read test list file: " + e.message);
323
+ }
324
+ }
292
325
  // Annotate the CommonJS export names for ESM import in node:
293
326
  0 && (module.exports = {
294
327
  collectProjectsAndTestFiles,
295
328
  createRootSuite,
296
329
  loadFileSuites,
297
330
  loadGlobalHook,
298
- loadReporter
331
+ loadReporter,
332
+ loadTestList
299
333
  });
@@ -32,7 +32,8 @@ __export(projectUtils_exports, {
32
32
  buildProjectsClosure: () => buildProjectsClosure,
33
33
  buildTeardownToSetupsMap: () => buildTeardownToSetupsMap,
34
34
  collectFilesForProject: () => collectFilesForProject,
35
- filterProjects: () => filterProjects
35
+ filterProjects: () => filterProjects,
36
+ findTopLevelProjects: () => findTopLevelProjects
36
37
  });
37
38
  module.exports = __toCommonJS(projectUtils_exports);
38
39
  var import_fs = __toESM(require("fs"));
@@ -116,6 +117,10 @@ function buildProjectsClosure(projects, hasTests) {
116
117
  visit(0, p);
117
118
  return result;
118
119
  }
120
+ function findTopLevelProjects(config) {
121
+ const closure = buildProjectsClosure(config.projects);
122
+ return [...closure].filter((entry) => entry[1] === "top-level").map((entry) => entry[0]);
123
+ }
119
124
  function buildDependentProjects(forProjects, projects) {
120
125
  const reverseDeps = new Map(projects.map((p) => [p, []]));
121
126
  for (const project of projects) {
@@ -231,5 +236,6 @@ async function collectFiles(testDir, respectGitIgnore) {
231
236
  buildProjectsClosure,
232
237
  buildTeardownToSetupsMap,
233
238
  collectFilesForProject,
234
- filterProjects
239
+ filterProjects,
240
+ findTopLevelProjects
235
241
  });
@@ -33,7 +33,6 @@ __export(reporters_exports, {
33
33
  createReporters: () => createReporters
34
34
  });
35
35
  module.exports = __toCommonJS(reporters_exports);
36
- var import_path = __toESM(require("path"));
37
36
  var import_utils = require("playwright-core/lib/utils");
38
37
  var import_loadUtils = require("./loadUtils");
39
38
  var import_base = require("../reporters/base");
@@ -46,13 +45,14 @@ var import_json = __toESM(require("../reporters/json"));
46
45
  var import_junit = __toESM(require("../reporters/junit"));
47
46
  var import_line = __toESM(require("../reporters/line"));
48
47
  var import_list = __toESM(require("../reporters/list"));
48
+ var import_listModeReporter = __toESM(require("../reporters/listModeReporter"));
49
49
  var import_reporterV2 = require("../reporters/reporterV2");
50
50
  async function createReporters(config, mode, isTestServer, descriptions) {
51
51
  const defaultReporters = {
52
52
  blob: import_blob.BlobReporter,
53
- dot: mode === "list" ? ListModeReporter : import_dot.default,
54
- line: mode === "list" ? ListModeReporter : import_line.default,
55
- list: mode === "list" ? ListModeReporter : import_list.default,
53
+ dot: mode === "list" ? import_listModeReporter.default : import_dot.default,
54
+ line: mode === "list" ? import_listModeReporter.default : import_line.default,
55
+ list: mode === "list" ? import_listModeReporter.default : import_list.default,
56
56
  github: import_github.default,
57
57
  json: import_json.default,
58
58
  junit: import_junit.default,
@@ -81,7 +81,7 @@ async function createReporters(config, mode, isTestServer, descriptions) {
81
81
  const someReporterPrintsToStdio = reporters.some((r) => r.printsToStdio ? r.printsToStdio() : true);
82
82
  if (reporters.length && !someReporterPrintsToStdio) {
83
83
  if (mode === "list")
84
- reporters.unshift(new ListModeReporter());
84
+ reporters.unshift(new import_listModeReporter.default());
85
85
  else if (mode !== "merge")
86
86
  reporters.unshift(!process.env.CI ? new import_line.default() : new import_dot.default());
87
87
  }
@@ -93,14 +93,13 @@ async function createReporterForTestServer(file, messageSink) {
93
93
  _send: messageSink
94
94
  }));
95
95
  }
96
- function createErrorCollectingReporter(screen, writeToConsole) {
96
+ function createErrorCollectingReporter(screen) {
97
97
  const errors = [];
98
98
  return {
99
99
  version: () => "v2",
100
100
  onError(error) {
101
101
  errors.push(error);
102
- if (writeToConsole)
103
- process.stdout.write((0, import_base.formatError)(screen, error).message + "\n");
102
+ screen.stderr?.write((0, import_base.formatError)(screen, error).message + "\n");
104
103
  },
105
104
  errors: () => errors
106
105
  };
@@ -130,30 +129,6 @@ function computeCommandHash(config) {
130
129
  parts.push((0, import_utils.calculateSha1)(JSON.stringify(command)).substring(0, 7));
131
130
  return parts.join("-");
132
131
  }
133
- class ListModeReporter {
134
- version() {
135
- return "v2";
136
- }
137
- onConfigure(config) {
138
- this.config = config;
139
- }
140
- onBegin(suite) {
141
- console.log(`Listing tests:`);
142
- const tests = suite.allTests();
143
- const files = /* @__PURE__ */ new Set();
144
- for (const test of tests) {
145
- const [, projectName, , ...titles] = test.titlePath();
146
- const location = `${import_path.default.relative(this.config.rootDir, test.location.file)}:${test.location.line}:${test.location.column}`;
147
- const projectTitle = projectName ? `[${projectName}] \u203A ` : "";
148
- console.log(` ${projectTitle}${location} \u203A ${titles.join(" \u203A ")}`);
149
- files.add(test.location.file);
150
- }
151
- console.log(`Total: ${tests.length} ${tests.length === 1 ? "test" : "tests"} in ${files.size} ${files.size === 1 ? "file" : "files"}`);
152
- }
153
- onError(error) {
154
- console.error("\n" + (0, import_base.formatError)(import_base.terminalScreen, error).message);
155
- }
156
- }
157
132
  // Annotate the CommonJS export names for ESM import in node:
158
133
  0 && (module.exports = {
159
134
  createErrorCollectingReporter,