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.
- package/README.md +3 -3
- package/ThirdPartyNotices.txt +2727 -434
- package/lib/agents/generateAgents.js +263 -0
- package/lib/agents/generator.md +102 -0
- package/lib/agents/healer.md +78 -0
- package/lib/agents/planner.md +135 -0
- package/lib/common/config.js +3 -1
- package/lib/common/configLoader.js +2 -1
- package/lib/common/expectBundle.js +3 -0
- package/lib/common/expectBundleImpl.js +51 -51
- package/lib/common/fixtures.js +1 -1
- package/lib/common/suiteUtils.js +0 -9
- package/lib/index.js +127 -115
- package/lib/isomorphic/testTree.js +35 -8
- package/lib/matchers/expect.js +6 -7
- package/lib/matchers/matcherHint.js +43 -15
- package/lib/matchers/matchers.js +10 -4
- package/lib/matchers/toBeTruthy.js +16 -14
- package/lib/matchers/toEqual.js +18 -13
- package/lib/matchers/toHaveURL.js +12 -27
- package/lib/matchers/toMatchAriaSnapshot.js +26 -31
- package/lib/matchers/toMatchSnapshot.js +15 -12
- package/lib/matchers/toMatchText.js +29 -35
- package/lib/mcp/browser/actions.d.js +16 -0
- package/lib/mcp/browser/browserContextFactory.js +296 -0
- package/lib/mcp/browser/browserServerBackend.js +76 -0
- package/lib/mcp/browser/codegen.js +66 -0
- package/lib/mcp/browser/config.js +383 -0
- package/lib/mcp/browser/context.js +284 -0
- package/lib/mcp/browser/response.js +228 -0
- package/lib/mcp/browser/sessionLog.js +160 -0
- package/lib/mcp/browser/tab.js +277 -0
- package/lib/mcp/browser/tools/common.js +63 -0
- package/lib/mcp/browser/tools/console.js +44 -0
- package/lib/mcp/browser/tools/dialogs.js +60 -0
- package/lib/mcp/browser/tools/evaluate.js +70 -0
- package/lib/mcp/browser/tools/files.js +58 -0
- package/lib/mcp/browser/tools/form.js +74 -0
- package/lib/mcp/browser/tools/install.js +69 -0
- package/lib/mcp/browser/tools/keyboard.js +85 -0
- package/lib/mcp/browser/tools/mouse.js +107 -0
- package/lib/mcp/browser/tools/navigate.js +62 -0
- package/lib/mcp/browser/tools/network.js +54 -0
- package/lib/mcp/browser/tools/pdf.js +59 -0
- package/lib/mcp/browser/tools/screenshot.js +88 -0
- package/lib/mcp/browser/tools/snapshot.js +182 -0
- package/lib/mcp/browser/tools/tabs.js +67 -0
- package/lib/mcp/browser/tools/tool.js +49 -0
- package/lib/mcp/browser/tools/tracing.js +74 -0
- package/lib/mcp/browser/tools/utils.js +100 -0
- package/lib/mcp/browser/tools/verify.js +154 -0
- package/lib/mcp/browser/tools/wait.js +63 -0
- package/lib/mcp/browser/tools.js +80 -0
- package/lib/mcp/browser/watchdog.js +44 -0
- package/lib/mcp/config.d.js +16 -0
- package/lib/mcp/extension/cdpRelay.js +351 -0
- package/lib/mcp/extension/extensionContextFactory.js +75 -0
- package/lib/mcp/extension/protocol.js +28 -0
- package/lib/mcp/index.js +61 -0
- package/lib/mcp/log.js +35 -0
- package/lib/mcp/program.js +96 -0
- package/lib/mcp/sdk/bundle.js +81 -0
- package/lib/mcp/sdk/exports.js +32 -0
- package/lib/mcp/sdk/http.js +180 -0
- package/lib/mcp/sdk/inProcessTransport.js +71 -0
- package/lib/mcp/sdk/mdb.js +208 -0
- package/lib/mcp/sdk/proxyBackend.js +128 -0
- package/lib/mcp/sdk/server.js +190 -0
- package/lib/mcp/sdk/tool.js +51 -0
- package/lib/mcp/test/browserBackend.js +98 -0
- package/lib/mcp/test/generatorTools.js +122 -0
- package/lib/mcp/test/plannerTools.js +46 -0
- package/lib/mcp/test/seed.js +72 -0
- package/lib/mcp/test/streams.js +39 -0
- package/lib/mcp/test/testBackend.js +97 -0
- package/lib/mcp/test/testContext.js +176 -0
- package/lib/mcp/test/testTool.js +30 -0
- package/lib/mcp/test/testTools.js +115 -0
- package/lib/mcpBundleImpl.js +41 -0
- package/lib/plugins/webServerPlugin.js +2 -0
- package/lib/program.js +77 -57
- package/lib/reporters/base.js +34 -29
- package/lib/reporters/dot.js +11 -11
- package/lib/reporters/github.js +2 -1
- package/lib/reporters/html.js +58 -41
- package/lib/reporters/internalReporter.js +2 -1
- package/lib/reporters/line.js +15 -15
- package/lib/reporters/list.js +24 -19
- package/lib/reporters/listModeReporter.js +69 -0
- package/lib/reporters/markdown.js +3 -3
- package/lib/reporters/merge.js +3 -1
- package/lib/reporters/teleEmitter.js +3 -1
- package/lib/runner/dispatcher.js +9 -2
- package/lib/runner/failureTracker.js +12 -2
- package/lib/runner/lastRun.js +7 -4
- package/lib/runner/loadUtils.js +46 -12
- package/lib/runner/projectUtils.js +8 -2
- package/lib/runner/reporters.js +7 -32
- package/lib/runner/tasks.js +20 -10
- package/lib/runner/testRunner.js +390 -0
- package/lib/runner/testServer.js +57 -276
- package/lib/runner/watchMode.js +5 -1
- package/lib/runner/workerHost.js +8 -6
- package/lib/transform/babelBundleImpl.js +179 -195
- package/lib/transform/compilationCache.js +22 -5
- package/lib/transform/transform.js +1 -1
- package/lib/util.js +12 -35
- package/lib/utilsBundleImpl.js +1 -1
- package/lib/worker/fixtureRunner.js +7 -2
- package/lib/worker/testInfo.js +76 -45
- package/lib/worker/testTracing.js +8 -7
- package/lib/worker/workerMain.js +12 -3
- package/package.json +10 -2
- package/types/test.d.ts +63 -44
- package/types/testReporter.d.ts +1 -1
- 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";
|
package/lib/reporters/line.js
CHANGED
|
@@ -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
|
-
|
|
37
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
92
|
+
this.screen.stdout.write(`${prefix + title}
|
|
93
93
|
`);
|
|
94
94
|
else
|
|
95
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
108
|
+
this.screen.stdout.write(`\x1B[1A\x1B[2K`);
|
|
109
109
|
await super.onEnd(result);
|
|
110
110
|
this.epilogue(false);
|
|
111
111
|
}
|
package/lib/reporters/list.js
CHANGED
|
@@ -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
|
-
|
|
48
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
190
|
+
this.screen.stdout.write("#" + this._lastRow + " : " + line + "\n");
|
|
187
191
|
} else {
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
+
this.screen.stdout.write(message);
|
|
222
227
|
}
|
|
223
228
|
async onEnd(result) {
|
|
224
229
|
await super.onEnd(result);
|
|
225
|
-
|
|
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}
|
|
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
|
-
|
|
141
|
+
const formattedTags = extraTags.map((t) => `\`${t}\``).join(" ");
|
|
142
|
+
return `${testTitle}${extraTags.length ? " " + formattedTags : ""}`;
|
|
143
143
|
}
|
|
144
144
|
var markdown_default = MarkdownReporter;
|
package/lib/reporters/merge.js
CHANGED
|
@@ -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) {
|
package/lib/runner/dispatcher.js
CHANGED
|
@@ -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,
|
|
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
|
}
|
package/lib/runner/lastRun.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
package/lib/runner/loadUtils.js
CHANGED
|
@@ -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
|
|
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,
|
|
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.
|
|
170
|
-
(0, import_suiteUtils.
|
|
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.
|
|
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.
|
|
207
|
+
if (!options.testFilters.every((filter) => filter(test)))
|
|
204
208
|
return false;
|
|
205
|
-
if (options.
|
|
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
|
});
|
package/lib/runner/reporters.js
CHANGED
|
@@ -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" ?
|
|
54
|
-
line: mode === "list" ?
|
|
55
|
-
list: mode === "list" ?
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|