patchright-bun 1.58.2 → 1.59.0
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/ThirdPartyNotices.txt +10 -1133
- package/lib/agents/generateAgents.js +2 -4
- package/lib/common/config.js +4 -5
- package/lib/common/expectBundleImpl.js +221 -221
- package/lib/common/process.js +1 -0
- package/lib/common/test.js +10 -1
- package/lib/common/testType.js +3 -0
- package/lib/common/validators.js +38 -38
- package/lib/errorContext.js +121 -0
- package/lib/index.js +98 -60
- package/lib/isomorphic/teleReceiver.js +26 -9
- package/lib/isomorphic/testServerConnection.js +3 -5
- package/lib/matchers/matchers.js +2 -0
- package/lib/matchers/toMatchAriaSnapshot.js +5 -1
- package/lib/matchers/toMatchSnapshot.js +42 -35
- package/lib/mcp/test/browserBackend.js +45 -22
- package/lib/mcp/test/generatorTools.js +9 -9
- package/lib/mcp/test/plannerTools.js +17 -17
- package/lib/mcp/test/testBackend.js +27 -27
- package/lib/mcp/test/testContext.js +6 -8
- package/lib/mcp/test/testTools.js +9 -9
- package/lib/plugins/webServerPlugin.js +2 -1
- package/lib/program.js +34 -212
- package/lib/reportActions.js +80 -0
- package/lib/reporters/base.js +4 -5
- package/lib/reporters/blob.js +2 -2
- package/lib/reporters/github.js +1 -2
- package/lib/reporters/html.js +64 -31
- package/lib/reporters/junit.js +104 -15
- package/lib/reporters/line.js +1 -1
- package/lib/reporters/list.js +2 -3
- package/lib/reporters/merge.js +47 -26
- package/lib/reporters/multiplexer.js +6 -2
- package/lib/reporters/teleEmitter.js +2 -0
- package/lib/runner/dispatcher.js +6 -14
- package/lib/runner/loadUtils.js +11 -5
- package/lib/runner/projectUtils.js +1 -1
- package/lib/runner/reporters.js +5 -0
- package/lib/runner/tasks.js +11 -8
- package/lib/runner/testRunner.js +2 -2
- package/lib/runner/workerHost.js +0 -3
- package/lib/testActions.js +220 -0
- package/lib/transform/babelBundle.js +0 -3
- package/lib/transform/babelBundleImpl.js +134 -134
- package/lib/transform/compilationCache.js +0 -2
- package/lib/transform/esmLoader.js +8 -6
- package/lib/transform/transform.js +3 -10
- package/lib/utilsBundle.js +0 -7
- package/lib/utilsBundleImpl.js +48 -51
- package/lib/worker/fixtureRunner.js +2 -2
- package/lib/worker/testInfo.js +10 -14
- package/lib/worker/testTracing.js +12 -6
- package/lib/worker/timeoutManager.js +14 -3
- package/lib/worker/workerMain.js +24 -21
- package/package.json +2 -6
- package/types/test.d.ts +83 -12
- package/lib/mcp/browser/browserContextFactory.js +0 -329
- package/lib/mcp/browser/browserServerBackend.js +0 -84
- package/lib/mcp/browser/config.js +0 -421
- package/lib/mcp/browser/context.js +0 -244
- package/lib/mcp/browser/response.js +0 -278
- package/lib/mcp/browser/sessionLog.js +0 -75
- package/lib/mcp/browser/tab.js +0 -343
- package/lib/mcp/browser/tools/common.js +0 -65
- package/lib/mcp/browser/tools/console.js +0 -46
- package/lib/mcp/browser/tools/dialogs.js +0 -60
- package/lib/mcp/browser/tools/evaluate.js +0 -61
- package/lib/mcp/browser/tools/files.js +0 -58
- package/lib/mcp/browser/tools/form.js +0 -63
- package/lib/mcp/browser/tools/install.js +0 -72
- package/lib/mcp/browser/tools/keyboard.js +0 -107
- package/lib/mcp/browser/tools/mouse.js +0 -107
- package/lib/mcp/browser/tools/navigate.js +0 -71
- package/lib/mcp/browser/tools/network.js +0 -63
- package/lib/mcp/browser/tools/open.js +0 -57
- package/lib/mcp/browser/tools/pdf.js +0 -49
- package/lib/mcp/browser/tools/runCode.js +0 -78
- package/lib/mcp/browser/tools/screenshot.js +0 -93
- package/lib/mcp/browser/tools/snapshot.js +0 -173
- package/lib/mcp/browser/tools/tabs.js +0 -67
- package/lib/mcp/browser/tools/tool.js +0 -47
- package/lib/mcp/browser/tools/tracing.js +0 -74
- package/lib/mcp/browser/tools/utils.js +0 -94
- package/lib/mcp/browser/tools/verify.js +0 -143
- package/lib/mcp/browser/tools/wait.js +0 -63
- package/lib/mcp/browser/tools.js +0 -84
- package/lib/mcp/browser/watchdog.js +0 -44
- package/lib/mcp/config.d.js +0 -16
- package/lib/mcp/extension/cdpRelay.js +0 -351
- package/lib/mcp/extension/extensionContextFactory.js +0 -76
- package/lib/mcp/extension/protocol.js +0 -28
- package/lib/mcp/index.js +0 -61
- package/lib/mcp/log.js +0 -35
- package/lib/mcp/program.js +0 -111
- package/lib/mcp/sdk/exports.js +0 -28
- package/lib/mcp/sdk/http.js +0 -152
- package/lib/mcp/sdk/inProcessTransport.js +0 -71
- package/lib/mcp/sdk/server.js +0 -223
- package/lib/mcp/sdk/tool.js +0 -47
- package/lib/mcp/terminal/cli.js +0 -296
- package/lib/mcp/terminal/command.js +0 -56
- package/lib/mcp/terminal/commands.js +0 -333
- package/lib/mcp/terminal/daemon.js +0 -129
- package/lib/mcp/terminal/help.json +0 -32
- package/lib/mcp/terminal/helpGenerator.js +0 -88
- package/lib/mcp/terminal/socketConnection.js +0 -80
- package/lib/runner/storage.js +0 -91
- package/lib/transform/md.js +0 -221
package/lib/common/process.js
CHANGED
|
@@ -21,6 +21,7 @@ __export(process_exports, {
|
|
|
21
21
|
ProcessRunner: () => ProcessRunner
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(process_exports);
|
|
24
|
+
var import_bootstrap = require("patchright-bun-core/lib/bootstrap");
|
|
24
25
|
var import_utils = require("patchright-bun-core/lib/utils");
|
|
25
26
|
var import_util = require("../util");
|
|
26
27
|
class ProcessRunner {
|
package/lib/common/test.js
CHANGED
|
@@ -110,6 +110,12 @@ class Suite extends Base {
|
|
|
110
110
|
path.push(this.title);
|
|
111
111
|
path.push(...this._tags);
|
|
112
112
|
}
|
|
113
|
+
_collectTagTitlePath(path) {
|
|
114
|
+
this.parent?._collectTagTitlePath(path);
|
|
115
|
+
if (this._type === "describe")
|
|
116
|
+
path.push(this.title);
|
|
117
|
+
path.push(...this._tags);
|
|
118
|
+
}
|
|
113
119
|
_getOnlyItems() {
|
|
114
120
|
const items = [];
|
|
115
121
|
if (this._only)
|
|
@@ -237,7 +243,10 @@ class TestCase extends Base {
|
|
|
237
243
|
return status === "expected" || status === "flaky" || status === "skipped";
|
|
238
244
|
}
|
|
239
245
|
get tags() {
|
|
240
|
-
const
|
|
246
|
+
const path = [];
|
|
247
|
+
this.parent._collectTagTitlePath(path);
|
|
248
|
+
path.push(this.title);
|
|
249
|
+
const titleTags = path.join(" ").match(/@[\S]+/g) || [];
|
|
241
250
|
return [
|
|
242
251
|
...titleTags,
|
|
243
252
|
...this._tags
|
package/lib/common/testType.js
CHANGED
|
@@ -236,6 +236,7 @@ class TestTypeImpl {
|
|
|
236
236
|
const testInfo = (0, import_globals.currentTestInfo)();
|
|
237
237
|
if (!testInfo)
|
|
238
238
|
throw new Error(`test.step() can only be called from a test`);
|
|
239
|
+
await testInfo._onUserStepBegin?.(title);
|
|
239
240
|
const step = testInfo._addStep({ category: "test.step", title, location: options.location, box: options.box });
|
|
240
241
|
return await (0, import_utils.currentZone)().with("stepZone", step).run(async () => {
|
|
241
242
|
try {
|
|
@@ -256,6 +257,8 @@ class TestTypeImpl {
|
|
|
256
257
|
} catch (error) {
|
|
257
258
|
step.complete({ error });
|
|
258
259
|
throw error;
|
|
260
|
+
} finally {
|
|
261
|
+
await testInfo._onUserStepEnd?.();
|
|
259
262
|
}
|
|
260
263
|
});
|
|
261
264
|
}
|
package/lib/common/validators.js
CHANGED
|
@@ -18,51 +18,51 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var validators_exports = {};
|
|
20
20
|
__export(validators_exports, {
|
|
21
|
-
validateTestAnnotation: () => validateTestAnnotation,
|
|
22
21
|
validateTestDetails: () => validateTestDetails
|
|
23
22
|
});
|
|
24
23
|
module.exports = __toCommonJS(validators_exports);
|
|
25
|
-
var
|
|
26
|
-
const testAnnotationSchema =
|
|
27
|
-
type:
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
24
|
+
var import_utils = require("patchright-bun-core/lib/utils");
|
|
25
|
+
const testAnnotationSchema = {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
type: { type: "string" },
|
|
29
|
+
description: { type: "string" }
|
|
30
|
+
},
|
|
31
|
+
required: ["type"]
|
|
32
|
+
};
|
|
33
|
+
const testDetailsSchema = {
|
|
34
|
+
type: "object",
|
|
35
|
+
properties: {
|
|
36
|
+
tag: {
|
|
37
|
+
oneOf: [
|
|
38
|
+
{ type: "string", pattern: "^@", patternError: "Tag must start with '@'" },
|
|
39
|
+
{ type: "array", items: { type: "string", pattern: "^@", patternError: "Tag must start with '@'" } }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
annotation: {
|
|
43
|
+
oneOf: [
|
|
44
|
+
testAnnotationSchema,
|
|
45
|
+
{ type: "array", items: testAnnotationSchema }
|
|
46
|
+
]
|
|
47
|
+
}
|
|
47
48
|
}
|
|
48
|
-
}
|
|
49
|
+
};
|
|
49
50
|
function validateTestDetails(details, location) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
const errors = (0, import_utils.validate)(details, testDetailsSchema, "details");
|
|
52
|
+
if (errors.length)
|
|
53
|
+
throw new Error(errors.join("\n"));
|
|
54
|
+
const obj = details;
|
|
55
|
+
const tag = obj.tag;
|
|
56
|
+
const tags = tag === void 0 ? [] : typeof tag === "string" ? [tag] : tag;
|
|
57
|
+
const annotation = obj.annotation;
|
|
58
|
+
const annotations = annotation === void 0 ? [] : Array.isArray(annotation) ? annotation : [annotation];
|
|
59
|
+
return {
|
|
60
|
+
annotations: annotations.map((a) => ({ ...a, location })),
|
|
61
|
+
tags,
|
|
62
|
+
location
|
|
63
|
+
};
|
|
63
64
|
}
|
|
64
65
|
// Annotate the CommonJS export names for ESM import in node:
|
|
65
66
|
0 && (module.exports = {
|
|
66
|
-
validateTestAnnotation,
|
|
67
67
|
validateTestDetails
|
|
68
68
|
});
|
|
@@ -0,0 +1,121 @@
|
|
|
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 errorContext_exports = {};
|
|
30
|
+
__export(errorContext_exports, {
|
|
31
|
+
buildErrorContext: () => buildErrorContext
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(errorContext_exports);
|
|
34
|
+
var import_fs = __toESM(require("fs"));
|
|
35
|
+
var import_path = __toESM(require("path"));
|
|
36
|
+
var import_utils = require("patchright-bun-core/lib/utils");
|
|
37
|
+
var import_util = require("./util");
|
|
38
|
+
const fixTestInstructions = `# Instructions
|
|
39
|
+
|
|
40
|
+
- Following Playwright test failed.
|
|
41
|
+
- Explain why, be concise, respect Playwright best practices.
|
|
42
|
+
- Provide a snippet of code with the fix, if possible.
|
|
43
|
+
`;
|
|
44
|
+
function buildErrorContext(options) {
|
|
45
|
+
const { titlePath, location, errors, pageSnapshot } = options;
|
|
46
|
+
const meaningfulErrors = errors.filter((e) => !!e.message);
|
|
47
|
+
if (!meaningfulErrors.length && !pageSnapshot)
|
|
48
|
+
return void 0;
|
|
49
|
+
const lines = [
|
|
50
|
+
fixTestInstructions,
|
|
51
|
+
"# Test info",
|
|
52
|
+
"",
|
|
53
|
+
`- Name: ${titlePath.join(" >> ")}`,
|
|
54
|
+
`- Location: ${(0, import_util.relativeFilePath)(location.file)}:${location.line}:${location.column}`
|
|
55
|
+
];
|
|
56
|
+
if (meaningfulErrors.length) {
|
|
57
|
+
lines.push("", "# Error details");
|
|
58
|
+
for (const error of meaningfulErrors) {
|
|
59
|
+
lines.push(
|
|
60
|
+
"",
|
|
61
|
+
"```",
|
|
62
|
+
(0, import_utils.stripAnsiEscapes)(error.message || ""),
|
|
63
|
+
"```"
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (pageSnapshot) {
|
|
68
|
+
lines.push(
|
|
69
|
+
"",
|
|
70
|
+
"# Page snapshot",
|
|
71
|
+
"",
|
|
72
|
+
"```yaml",
|
|
73
|
+
pageSnapshot,
|
|
74
|
+
"```"
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
const lastError = meaningfulErrors[meaningfulErrors.length - 1];
|
|
78
|
+
const codeFrame = lastError ? buildCodeFrame(lastError, location) : void 0;
|
|
79
|
+
if (codeFrame) {
|
|
80
|
+
lines.push(
|
|
81
|
+
"",
|
|
82
|
+
"# Test source",
|
|
83
|
+
"",
|
|
84
|
+
"```ts",
|
|
85
|
+
codeFrame,
|
|
86
|
+
"```"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return lines.join("\n");
|
|
90
|
+
}
|
|
91
|
+
function buildCodeFrame(error, testLocation) {
|
|
92
|
+
const stack = error.stack;
|
|
93
|
+
if (!stack)
|
|
94
|
+
return void 0;
|
|
95
|
+
const parsed = (0, import_utils.parseErrorStack)(stack, import_path.default.sep);
|
|
96
|
+
const errorLocation = parsed.location;
|
|
97
|
+
if (!errorLocation)
|
|
98
|
+
return void 0;
|
|
99
|
+
let source;
|
|
100
|
+
try {
|
|
101
|
+
source = import_fs.default.readFileSync(errorLocation.file, "utf8");
|
|
102
|
+
} catch {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
const sourceLines = source.split("\n");
|
|
106
|
+
const linesAbove = 100;
|
|
107
|
+
const linesBelow = 100;
|
|
108
|
+
const start = Math.max(0, errorLocation.line - linesAbove - 1);
|
|
109
|
+
const end = Math.min(sourceLines.length, errorLocation.line + linesBelow);
|
|
110
|
+
const scope = sourceLines.slice(start, end);
|
|
111
|
+
const lineNumberWidth = String(end).length;
|
|
112
|
+
const message = (0, import_utils.stripAnsiEscapes)(error.message || "").split("\n")[0] || void 0;
|
|
113
|
+
const frame = scope.map((line, index) => `${start + index + 1 === errorLocation.line ? "> " : " "}${(start + index + 1).toString().padEnd(lineNumberWidth, " ")} | ${line}`);
|
|
114
|
+
if (message)
|
|
115
|
+
frame.splice(errorLocation.line - start, 0, `${" ".repeat(lineNumberWidth + 2)} | ${" ".repeat(Math.max(0, errorLocation.column - 2))} ^ ${message}`);
|
|
116
|
+
return frame.join("\n");
|
|
117
|
+
}
|
|
118
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
119
|
+
0 && (module.exports = {
|
|
120
|
+
buildErrorContext
|
|
121
|
+
});
|
package/lib/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var import_fs = __toESM(require("fs"));
|
|
|
40
40
|
var import_path = __toESM(require("path"));
|
|
41
41
|
var playwrightLibrary = __toESM(require("patchright-bun-core"));
|
|
42
42
|
var import_utils = require("patchright-bun-core/lib/utils");
|
|
43
|
+
var import_errorContext = require("./errorContext");
|
|
43
44
|
var import_globals = require("./common/globals");
|
|
44
45
|
var import_testType = require("./common/testType");
|
|
45
46
|
var import_browserBackend = require("./mcp/test/browserBackend");
|
|
@@ -79,7 +80,8 @@ const playwrightFixtures = {
|
|
|
79
80
|
const options = {
|
|
80
81
|
handleSIGINT: false,
|
|
81
82
|
...launchOptions,
|
|
82
|
-
tracesDir: tracing().tracesDir()
|
|
83
|
+
tracesDir: tracing().tracesDir(),
|
|
84
|
+
artifactsDir: tracing().artifactsDir()
|
|
83
85
|
};
|
|
84
86
|
if (headless !== void 0)
|
|
85
87
|
options.headless = headless;
|
|
@@ -89,13 +91,13 @@ const playwrightFixtures = {
|
|
|
89
91
|
await use(options);
|
|
90
92
|
playwright._defaultLaunchOptions = void 0;
|
|
91
93
|
}, { scope: "worker", auto: true, box: true }],
|
|
92
|
-
browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use) => {
|
|
94
|
+
browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, workerInfo) => {
|
|
93
95
|
if (!["chromium", "firefox", "webkit"].includes(browserName))
|
|
94
96
|
throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
|
|
95
97
|
if (connectOptions) {
|
|
96
|
-
const browser2 = await playwright[browserName].connect({
|
|
98
|
+
const browser2 = await playwright[browserName].connect(connectOptions.wsEndpoint, {
|
|
97
99
|
...connectOptions,
|
|
98
|
-
exposeNetwork: connectOptions.exposeNetwork
|
|
100
|
+
exposeNetwork: connectOptions.exposeNetwork,
|
|
99
101
|
headers: {
|
|
100
102
|
// HTTP headers are ASCII only (not UTF-8).
|
|
101
103
|
"x-playwright-launch-options": (0, import_utils.jsonStringifyForceASCII)(_browserOptions),
|
|
@@ -107,6 +109,8 @@ const playwrightFixtures = {
|
|
|
107
109
|
return;
|
|
108
110
|
}
|
|
109
111
|
const browser = await playwright[browserName].launch();
|
|
112
|
+
if (process.env.PLAYWRIGHT_DASHBOARD)
|
|
113
|
+
await browser.bind(`worker-${workerInfo.parallelIndex}`);
|
|
110
114
|
await use(browser);
|
|
111
115
|
await browser.close({ reason: "Test ended." });
|
|
112
116
|
}, { scope: "worker", timeout: 0 }],
|
|
@@ -138,7 +142,6 @@ const playwrightFixtures = {
|
|
|
138
142
|
}, { option: true, box: true }],
|
|
139
143
|
serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
|
|
140
144
|
contextOptions: [{}, { option: true, box: true }],
|
|
141
|
-
agentOptions: [void 0, { option: true, box: true }],
|
|
142
145
|
_combinedContextOptions: [async ({
|
|
143
146
|
acceptDownloads,
|
|
144
147
|
bypassCSP,
|
|
@@ -214,12 +217,14 @@ const playwrightFixtures = {
|
|
|
214
217
|
...options
|
|
215
218
|
});
|
|
216
219
|
}, { box: true }],
|
|
217
|
-
_setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use,
|
|
220
|
+
_setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, _testInfo) => {
|
|
221
|
+
const testInfo = _testInfo;
|
|
218
222
|
if (testIdAttribute)
|
|
219
223
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
|
220
224
|
testInfo.snapshotSuffix = process.platform;
|
|
225
|
+
testInfo._onCustomMessageCallback = () => Promise.reject(new Error("Only tests that use default Playwright context or page fixture support test_debug"));
|
|
221
226
|
if ((0, import_utils.debugMode)() === "inspector")
|
|
222
|
-
testInfo.
|
|
227
|
+
testInfo._setIgnoreTimeouts(true);
|
|
223
228
|
playwright._defaultContextTimeout = actionTimeout || 0;
|
|
224
229
|
playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
|
|
225
230
|
await use();
|
|
@@ -231,6 +236,7 @@ const playwrightFixtures = {
|
|
|
231
236
|
const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot);
|
|
232
237
|
await artifactsRecorder.willStartTest(testInfo);
|
|
233
238
|
const tracingGroupSteps = [];
|
|
239
|
+
const pausedContexts = /* @__PURE__ */ new Set();
|
|
234
240
|
const csiListener = {
|
|
235
241
|
onApiCallBegin: (data, channel) => {
|
|
236
242
|
const testInfo2 = (0, import_globals.currentTestInfo)();
|
|
@@ -272,7 +278,7 @@ const playwrightFixtures = {
|
|
|
272
278
|
},
|
|
273
279
|
onWillPause: ({ keepTestTimeout }) => {
|
|
274
280
|
if (!keepTestTimeout)
|
|
275
|
-
(0, import_globals.currentTestInfo)()?.
|
|
281
|
+
(0, import_globals.currentTestInfo)()?._setIgnoreTimeouts(true);
|
|
276
282
|
},
|
|
277
283
|
runBeforeCreateBrowserContext: async (options) => {
|
|
278
284
|
for (const [key, value] of Object.entries(_combinedContextOptions)) {
|
|
@@ -287,6 +293,16 @@ const playwrightFixtures = {
|
|
|
287
293
|
}
|
|
288
294
|
},
|
|
289
295
|
runAfterCreateBrowserContext: async (context) => {
|
|
296
|
+
context.debugger.on("pausedstatechanged", () => {
|
|
297
|
+
const paused = !!context.debugger.pausedDetails();
|
|
298
|
+
if (pausedContexts.has(context) && !paused) {
|
|
299
|
+
pausedContexts.delete(context);
|
|
300
|
+
testInfo2._setIgnoreTimeouts(false);
|
|
301
|
+
} else if (!pausedContexts.has(context) && paused) {
|
|
302
|
+
pausedContexts.add(context);
|
|
303
|
+
testInfo2._setIgnoreTimeouts(true);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
290
306
|
await artifactsRecorder.didCreateBrowserContext(context);
|
|
291
307
|
const testInfo2 = (0, import_globals.currentTestInfo)();
|
|
292
308
|
if (testInfo2)
|
|
@@ -329,10 +345,12 @@ const playwrightFixtures = {
|
|
|
329
345
|
`If you would like to configure your page before each test, do that in beforeEach hook instead.`
|
|
330
346
|
].join("\n"));
|
|
331
347
|
}
|
|
348
|
+
const show = typeof video === "string" ? void 0 : video.show;
|
|
332
349
|
const videoOptions = captureVideo ? {
|
|
333
350
|
recordVideo: {
|
|
334
351
|
dir: tracing().artifactsDir(),
|
|
335
|
-
size: typeof video === "string" ? void 0 : video.size
|
|
352
|
+
size: typeof video === "string" ? void 0 : video.size,
|
|
353
|
+
showActions: show?.actions
|
|
336
354
|
}
|
|
337
355
|
} : {};
|
|
338
356
|
const context = await browser.newContext({ ...videoOptions, ...options });
|
|
@@ -386,18 +404,24 @@ const playwrightFixtures = {
|
|
|
386
404
|
const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
|
|
387
405
|
await use(reuse);
|
|
388
406
|
}, { scope: "worker", title: "context", box: true }],
|
|
389
|
-
context: async ({ browser, _reuseContext, _contextFactory }, use,
|
|
407
|
+
context: async ({ browser, video, _reuseContext, _contextFactory }, use, testInfoPublic) => {
|
|
390
408
|
const browserImpl = browser;
|
|
409
|
+
const testInfo = testInfoPublic;
|
|
410
|
+
const show = typeof video === "string" ? void 0 : video.show;
|
|
391
411
|
attachConnectedHeaderIfNeeded(testInfo, browserImpl);
|
|
392
412
|
if (!_reuseContext) {
|
|
393
413
|
const { context: context2, close } = await _contextFactory();
|
|
394
414
|
testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context2);
|
|
415
|
+
await (0, import_browserBackend.runDaemonForContext)(testInfo, context2);
|
|
416
|
+
await installScreencastTitleUpdater(testInfo, context2, show?.test);
|
|
395
417
|
await use(context2);
|
|
396
418
|
await close();
|
|
397
419
|
return;
|
|
398
420
|
}
|
|
399
421
|
const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
|
|
400
422
|
testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context);
|
|
423
|
+
await (0, import_browserBackend.runDaemonForContext)(testInfo, context);
|
|
424
|
+
await installScreencastTitleUpdater(testInfo, context, show?.test);
|
|
401
425
|
await use(context);
|
|
402
426
|
const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
|
|
403
427
|
await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
|
|
@@ -412,39 +436,6 @@ const playwrightFixtures = {
|
|
|
412
436
|
page = await context.newPage();
|
|
413
437
|
await use(page);
|
|
414
438
|
},
|
|
415
|
-
agent: async ({ page, agentOptions }, use, testInfo) => {
|
|
416
|
-
const testInfoImpl = testInfo;
|
|
417
|
-
const cachePathTemplate = agentOptions?.cachePathTemplate ?? "{testDir}/{testFilePath}-cache.json";
|
|
418
|
-
const resolvedCacheFile = testInfoImpl._applyPathTemplate(cachePathTemplate, "", ".json");
|
|
419
|
-
const cacheFile = testInfoImpl.config.runAgents === "all" ? void 0 : await testInfoImpl._cloneStorage(resolvedCacheFile);
|
|
420
|
-
const cacheOutFile = import_path.default.join(testInfoImpl.artifactsDir(), "agent-cache-" + (0, import_utils.createGuid)() + ".json");
|
|
421
|
-
const provider = agentOptions?.provider && testInfo.config.runAgents !== "none" ? agentOptions.provider : void 0;
|
|
422
|
-
if (provider)
|
|
423
|
-
testInfo.setTimeout(0);
|
|
424
|
-
const cache = {
|
|
425
|
-
cacheFile,
|
|
426
|
-
cacheOutFile
|
|
427
|
-
};
|
|
428
|
-
const agent = await page.agent({
|
|
429
|
-
provider,
|
|
430
|
-
cache,
|
|
431
|
-
limits: agentOptions?.limits,
|
|
432
|
-
secrets: agentOptions?.secrets,
|
|
433
|
-
systemPrompt: agentOptions?.systemPrompt,
|
|
434
|
-
expect: {
|
|
435
|
-
timeout: testInfoImpl._projectInternal.expect?.timeout
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
await use(agent);
|
|
439
|
-
const usage = await agent.usage();
|
|
440
|
-
if (usage.turns > 0)
|
|
441
|
-
await testInfoImpl.attach("agent-usage", { contentType: "application/json", body: Buffer.from(JSON.stringify(usage, null, 2)) });
|
|
442
|
-
if (!resolvedCacheFile || !cacheOutFile)
|
|
443
|
-
return;
|
|
444
|
-
if (testInfo.status !== "passed")
|
|
445
|
-
return;
|
|
446
|
-
await testInfoImpl._upstreamStorage(resolvedCacheFile, cacheOutFile);
|
|
447
|
-
},
|
|
448
439
|
request: async ({ playwright }, use) => {
|
|
449
440
|
const request = await playwright.request.newContext();
|
|
450
441
|
await use(request);
|
|
@@ -604,7 +595,7 @@ class ArtifactsRecorder {
|
|
|
604
595
|
}
|
|
605
596
|
async willStartTest(testInfo) {
|
|
606
597
|
this._testInfo = testInfo;
|
|
607
|
-
testInfo.
|
|
598
|
+
testInfo._onDidFinishTestFunctionCallbacks.add(() => this.didFinishTestFunction());
|
|
608
599
|
this._screenshotRecorder.fixOrdinal();
|
|
609
600
|
await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
|
|
610
601
|
const existingApiRequests = Array.from(this._playwright.request._contexts);
|
|
@@ -630,7 +621,7 @@ class ArtifactsRecorder {
|
|
|
630
621
|
return;
|
|
631
622
|
try {
|
|
632
623
|
await page._wrapApiCall(async () => {
|
|
633
|
-
this._pageSnapshot =
|
|
624
|
+
this._pageSnapshot = await page.ariaSnapshot({ mode: "ai", timeout: 5e3 });
|
|
634
625
|
}, { internal: true });
|
|
635
626
|
} catch {
|
|
636
627
|
}
|
|
@@ -657,21 +648,22 @@ class ArtifactsRecorder {
|
|
|
657
648
|
const context = leftoverContexts[0];
|
|
658
649
|
if (context)
|
|
659
650
|
await this._takePageSnapshot(context);
|
|
660
|
-
if (this.
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
this._pageSnapshot
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
651
|
+
if (this._testInfo.errors.length > 0) {
|
|
652
|
+
const errorContextContent = (0, import_errorContext.buildErrorContext)({
|
|
653
|
+
titlePath: this._testInfo.titlePath,
|
|
654
|
+
location: { file: this._testInfo.file, line: this._testInfo.line, column: this._testInfo.column },
|
|
655
|
+
errors: this._testInfo.errors,
|
|
656
|
+
pageSnapshot: this._pageSnapshot
|
|
657
|
+
});
|
|
658
|
+
if (errorContextContent) {
|
|
659
|
+
const filePath = this._testInfo.outputPath("error-context.md");
|
|
660
|
+
await import_fs.default.promises.writeFile(filePath, errorContextContent, "utf8");
|
|
661
|
+
this._testInfo._attach({
|
|
662
|
+
name: "error-context",
|
|
663
|
+
contentType: "text/markdown",
|
|
664
|
+
path: filePath
|
|
665
|
+
}, void 0);
|
|
666
|
+
}
|
|
675
667
|
}
|
|
676
668
|
}
|
|
677
669
|
async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
|
|
@@ -704,6 +696,52 @@ class ArtifactsRecorder {
|
|
|
704
696
|
}, { internal: true });
|
|
705
697
|
}
|
|
706
698
|
}
|
|
699
|
+
async function installScreencastTitleUpdater(testInfo, context, testAnnotate) {
|
|
700
|
+
if (!testAnnotate)
|
|
701
|
+
return;
|
|
702
|
+
const testTitle = testAnnotate.level === "file" ? [testInfo.titlePath[0]] : testInfo.titlePath;
|
|
703
|
+
const stepStack = [];
|
|
704
|
+
const overlays = /* @__PURE__ */ new Map();
|
|
705
|
+
const position = testAnnotate.position ?? "top-left";
|
|
706
|
+
const fontSize = testAnnotate.fontSize ?? 14;
|
|
707
|
+
const level = testAnnotate.level ?? "step";
|
|
708
|
+
const updateOverlay = async () => {
|
|
709
|
+
const parts = level === "step" ? [...testTitle, ...stepStack] : testTitle;
|
|
710
|
+
const html = createTestOverlay(parts, position, fontSize);
|
|
711
|
+
for (const page of context.pages()) {
|
|
712
|
+
await overlays.get(page)?.dispose();
|
|
713
|
+
overlays.delete(page);
|
|
714
|
+
const disposable = await page.screencast.showOverlay(html);
|
|
715
|
+
overlays.set(page, disposable);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
testInfo._onUserStepBegin = async (title) => {
|
|
719
|
+
stepStack.push(title);
|
|
720
|
+
await updateOverlay();
|
|
721
|
+
};
|
|
722
|
+
testInfo._onUserStepEnd = async () => {
|
|
723
|
+
stepStack.pop();
|
|
724
|
+
await updateOverlay();
|
|
725
|
+
};
|
|
726
|
+
context.on("page", async () => {
|
|
727
|
+
void updateOverlay();
|
|
728
|
+
});
|
|
729
|
+
await updateOverlay();
|
|
730
|
+
}
|
|
731
|
+
function createTestOverlay(parts, position, fontSize) {
|
|
732
|
+
const positionStyles = {
|
|
733
|
+
"top-left": "top: 6px; left: 6px;",
|
|
734
|
+
"top": "top: 6px; left: 50%; transform: translateX(-50%);",
|
|
735
|
+
"top-right": "top: 6px; right: 6px;",
|
|
736
|
+
"bottom-left": "bottom: 6px; left: 6px;",
|
|
737
|
+
"bottom": "bottom: 6px; left: 50%; transform: translateX(-50%);",
|
|
738
|
+
"bottom-right": "bottom: 6px; right: 6px;"
|
|
739
|
+
};
|
|
740
|
+
const posStyle = positionStyles[position] ?? positionStyles["top-left"];
|
|
741
|
+
return `<div style="white-space: nowrap; font-size: ${fontSize}px; padding: 3px 6px; background: rgba(0,0,0,0.5); color: white; border-radius: 4px; position: absolute; ${posStyle}">
|
|
742
|
+
${parts.map((p) => `<div>${(0, import_utils.escapeHTML)(p)}</div>`).join("")}
|
|
743
|
+
</div>`;
|
|
744
|
+
}
|
|
707
745
|
function renderTitle(type, method, params, title) {
|
|
708
746
|
const prefix = (0, import_utils.renderTitleForCall)({ title, type, method, params });
|
|
709
747
|
let selector;
|
|
@@ -22,6 +22,8 @@ __export(teleReceiver_exports, {
|
|
|
22
22
|
TeleSuite: () => TeleSuite,
|
|
23
23
|
TeleTestCase: () => TeleTestCase,
|
|
24
24
|
TeleTestResult: () => TeleTestResult,
|
|
25
|
+
asFullConfig: () => asFullConfig,
|
|
26
|
+
asFullResult: () => asFullResult,
|
|
25
27
|
baseFullConfig: () => baseFullConfig,
|
|
26
28
|
computeTestCaseOutcome: () => computeTestCaseOutcome,
|
|
27
29
|
parseRegexPatterns: () => parseRegexPatterns,
|
|
@@ -102,7 +104,15 @@ class TeleReporterReceiver {
|
|
|
102
104
|
projectSuite = new TeleSuite(project.name, "project");
|
|
103
105
|
this._rootSuite._addSuite(projectSuite);
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
const parsed = this._parseProject(project);
|
|
108
|
+
projectSuite._project = parsed;
|
|
109
|
+
let index = -1;
|
|
110
|
+
if (this._options.mergeProjects)
|
|
111
|
+
index = this._config.projects.findIndex((p) => p.name === project.name);
|
|
112
|
+
if (index === -1)
|
|
113
|
+
this._config.projects.push(parsed);
|
|
114
|
+
else
|
|
115
|
+
this._config.projects[index] = parsed;
|
|
106
116
|
for (const suite of project.suites)
|
|
107
117
|
this._mergeSuiteInto(suite, projectSuite);
|
|
108
118
|
}
|
|
@@ -194,17 +204,13 @@ class TeleReporterReceiver {
|
|
|
194
204
|
}
|
|
195
205
|
}
|
|
196
206
|
async _onEnd(result) {
|
|
197
|
-
await this._reporter.onEnd?.(
|
|
198
|
-
status: result.status,
|
|
199
|
-
startTime: new Date(result.startTime),
|
|
200
|
-
duration: result.duration
|
|
201
|
-
});
|
|
207
|
+
await this._reporter.onEnd?.(asFullResult(result));
|
|
202
208
|
}
|
|
203
209
|
_onExit() {
|
|
204
210
|
return this._reporter.onExit?.();
|
|
205
211
|
}
|
|
206
212
|
_parseConfig(config) {
|
|
207
|
-
const result =
|
|
213
|
+
const result = asFullConfig(config);
|
|
208
214
|
if (this._options.configOverrides) {
|
|
209
215
|
result.configFile = this._options.configOverrides.configFile;
|
|
210
216
|
result.reportSlowTests = this._options.configOverrides.reportSlowTests;
|
|
@@ -229,6 +235,7 @@ class TeleReporterReceiver {
|
|
|
229
235
|
dependencies: project.dependencies,
|
|
230
236
|
teardown: project.teardown,
|
|
231
237
|
snapshotDir: this._absolutePath(project.snapshotDir),
|
|
238
|
+
ignoreSnapshots: project.ignoreSnapshots ?? false,
|
|
232
239
|
use: project.use
|
|
233
240
|
};
|
|
234
241
|
}
|
|
@@ -459,8 +466,6 @@ const baseFullConfig = {
|
|
|
459
466
|
tags: [],
|
|
460
467
|
updateSnapshots: "missing",
|
|
461
468
|
updateSourceMethod: "patch",
|
|
462
|
-
// @ts-expect-error runAgents is hidden
|
|
463
|
-
runAgents: "none",
|
|
464
469
|
version: "",
|
|
465
470
|
workers: 0,
|
|
466
471
|
webServer: null
|
|
@@ -508,12 +513,24 @@ function computeTestCaseOutcome(test) {
|
|
|
508
513
|
return "unexpected";
|
|
509
514
|
return "flaky";
|
|
510
515
|
}
|
|
516
|
+
function asFullResult(result) {
|
|
517
|
+
return {
|
|
518
|
+
status: result.status,
|
|
519
|
+
startTime: new Date(result.startTime),
|
|
520
|
+
duration: result.duration
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
function asFullConfig(config) {
|
|
524
|
+
return { ...baseFullConfig, ...config };
|
|
525
|
+
}
|
|
511
526
|
// Annotate the CommonJS export names for ESM import in node:
|
|
512
527
|
0 && (module.exports = {
|
|
513
528
|
TeleReporterReceiver,
|
|
514
529
|
TeleSuite,
|
|
515
530
|
TeleTestCase,
|
|
516
531
|
TeleTestResult,
|
|
532
|
+
asFullConfig,
|
|
533
|
+
asFullResult,
|
|
517
534
|
baseFullConfig,
|
|
518
535
|
computeTestCaseOutcome,
|
|
519
536
|
parseRegexPatterns,
|
|
@@ -35,9 +35,6 @@ __export(testServerConnection_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(testServerConnection_exports);
|
|
36
36
|
var events = __toESM(require("./events"));
|
|
37
37
|
class TestServerConnectionClosedError extends Error {
|
|
38
|
-
constructor() {
|
|
39
|
-
super("Test server connection closed");
|
|
40
|
-
}
|
|
41
38
|
}
|
|
42
39
|
class WebSocketTestServerTransport {
|
|
43
40
|
constructor(url) {
|
|
@@ -107,7 +104,7 @@ class TestServerConnection {
|
|
|
107
104
|
this._onCloseEmitter.fire();
|
|
108
105
|
clearInterval(pingInterval);
|
|
109
106
|
for (const callback of this._callbacks.values())
|
|
110
|
-
callback.reject(
|
|
107
|
+
callback.reject(callback.error);
|
|
111
108
|
this._callbacks.clear();
|
|
112
109
|
});
|
|
113
110
|
}
|
|
@@ -120,9 +117,10 @@ class TestServerConnection {
|
|
|
120
117
|
await this._connectedPromise;
|
|
121
118
|
const id = ++this._lastId;
|
|
122
119
|
const message = { id, method, params };
|
|
120
|
+
const error = new TestServerConnectionClosedError(`${method}: test server connection closed`);
|
|
123
121
|
this._transport.send(JSON.stringify(message));
|
|
124
122
|
return new Promise((resolve, reject) => {
|
|
125
|
-
this._callbacks.set(id, { resolve, reject });
|
|
123
|
+
this._callbacks.set(id, { resolve, reject, error });
|
|
126
124
|
});
|
|
127
125
|
}
|
|
128
126
|
_sendMessageNoReply(method, params) {
|