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.
Files changed (108) hide show
  1. package/ThirdPartyNotices.txt +10 -1133
  2. package/lib/agents/generateAgents.js +2 -4
  3. package/lib/common/config.js +4 -5
  4. package/lib/common/expectBundleImpl.js +221 -221
  5. package/lib/common/process.js +1 -0
  6. package/lib/common/test.js +10 -1
  7. package/lib/common/testType.js +3 -0
  8. package/lib/common/validators.js +38 -38
  9. package/lib/errorContext.js +121 -0
  10. package/lib/index.js +98 -60
  11. package/lib/isomorphic/teleReceiver.js +26 -9
  12. package/lib/isomorphic/testServerConnection.js +3 -5
  13. package/lib/matchers/matchers.js +2 -0
  14. package/lib/matchers/toMatchAriaSnapshot.js +5 -1
  15. package/lib/matchers/toMatchSnapshot.js +42 -35
  16. package/lib/mcp/test/browserBackend.js +45 -22
  17. package/lib/mcp/test/generatorTools.js +9 -9
  18. package/lib/mcp/test/plannerTools.js +17 -17
  19. package/lib/mcp/test/testBackend.js +27 -27
  20. package/lib/mcp/test/testContext.js +6 -8
  21. package/lib/mcp/test/testTools.js +9 -9
  22. package/lib/plugins/webServerPlugin.js +2 -1
  23. package/lib/program.js +34 -212
  24. package/lib/reportActions.js +80 -0
  25. package/lib/reporters/base.js +4 -5
  26. package/lib/reporters/blob.js +2 -2
  27. package/lib/reporters/github.js +1 -2
  28. package/lib/reporters/html.js +64 -31
  29. package/lib/reporters/junit.js +104 -15
  30. package/lib/reporters/line.js +1 -1
  31. package/lib/reporters/list.js +2 -3
  32. package/lib/reporters/merge.js +47 -26
  33. package/lib/reporters/multiplexer.js +6 -2
  34. package/lib/reporters/teleEmitter.js +2 -0
  35. package/lib/runner/dispatcher.js +6 -14
  36. package/lib/runner/loadUtils.js +11 -5
  37. package/lib/runner/projectUtils.js +1 -1
  38. package/lib/runner/reporters.js +5 -0
  39. package/lib/runner/tasks.js +11 -8
  40. package/lib/runner/testRunner.js +2 -2
  41. package/lib/runner/workerHost.js +0 -3
  42. package/lib/testActions.js +220 -0
  43. package/lib/transform/babelBundle.js +0 -3
  44. package/lib/transform/babelBundleImpl.js +134 -134
  45. package/lib/transform/compilationCache.js +0 -2
  46. package/lib/transform/esmLoader.js +8 -6
  47. package/lib/transform/transform.js +3 -10
  48. package/lib/utilsBundle.js +0 -7
  49. package/lib/utilsBundleImpl.js +48 -51
  50. package/lib/worker/fixtureRunner.js +2 -2
  51. package/lib/worker/testInfo.js +10 -14
  52. package/lib/worker/testTracing.js +12 -6
  53. package/lib/worker/timeoutManager.js +14 -3
  54. package/lib/worker/workerMain.js +24 -21
  55. package/package.json +2 -6
  56. package/types/test.d.ts +83 -12
  57. package/lib/mcp/browser/browserContextFactory.js +0 -329
  58. package/lib/mcp/browser/browserServerBackend.js +0 -84
  59. package/lib/mcp/browser/config.js +0 -421
  60. package/lib/mcp/browser/context.js +0 -244
  61. package/lib/mcp/browser/response.js +0 -278
  62. package/lib/mcp/browser/sessionLog.js +0 -75
  63. package/lib/mcp/browser/tab.js +0 -343
  64. package/lib/mcp/browser/tools/common.js +0 -65
  65. package/lib/mcp/browser/tools/console.js +0 -46
  66. package/lib/mcp/browser/tools/dialogs.js +0 -60
  67. package/lib/mcp/browser/tools/evaluate.js +0 -61
  68. package/lib/mcp/browser/tools/files.js +0 -58
  69. package/lib/mcp/browser/tools/form.js +0 -63
  70. package/lib/mcp/browser/tools/install.js +0 -72
  71. package/lib/mcp/browser/tools/keyboard.js +0 -107
  72. package/lib/mcp/browser/tools/mouse.js +0 -107
  73. package/lib/mcp/browser/tools/navigate.js +0 -71
  74. package/lib/mcp/browser/tools/network.js +0 -63
  75. package/lib/mcp/browser/tools/open.js +0 -57
  76. package/lib/mcp/browser/tools/pdf.js +0 -49
  77. package/lib/mcp/browser/tools/runCode.js +0 -78
  78. package/lib/mcp/browser/tools/screenshot.js +0 -93
  79. package/lib/mcp/browser/tools/snapshot.js +0 -173
  80. package/lib/mcp/browser/tools/tabs.js +0 -67
  81. package/lib/mcp/browser/tools/tool.js +0 -47
  82. package/lib/mcp/browser/tools/tracing.js +0 -74
  83. package/lib/mcp/browser/tools/utils.js +0 -94
  84. package/lib/mcp/browser/tools/verify.js +0 -143
  85. package/lib/mcp/browser/tools/wait.js +0 -63
  86. package/lib/mcp/browser/tools.js +0 -84
  87. package/lib/mcp/browser/watchdog.js +0 -44
  88. package/lib/mcp/config.d.js +0 -16
  89. package/lib/mcp/extension/cdpRelay.js +0 -351
  90. package/lib/mcp/extension/extensionContextFactory.js +0 -76
  91. package/lib/mcp/extension/protocol.js +0 -28
  92. package/lib/mcp/index.js +0 -61
  93. package/lib/mcp/log.js +0 -35
  94. package/lib/mcp/program.js +0 -111
  95. package/lib/mcp/sdk/exports.js +0 -28
  96. package/lib/mcp/sdk/http.js +0 -152
  97. package/lib/mcp/sdk/inProcessTransport.js +0 -71
  98. package/lib/mcp/sdk/server.js +0 -223
  99. package/lib/mcp/sdk/tool.js +0 -47
  100. package/lib/mcp/terminal/cli.js +0 -296
  101. package/lib/mcp/terminal/command.js +0 -56
  102. package/lib/mcp/terminal/commands.js +0 -333
  103. package/lib/mcp/terminal/daemon.js +0 -129
  104. package/lib/mcp/terminal/help.json +0 -32
  105. package/lib/mcp/terminal/helpGenerator.js +0 -88
  106. package/lib/mcp/terminal/socketConnection.js +0 -80
  107. package/lib/runner/storage.js +0 -91
  108. package/lib/transform/md.js +0 -221
@@ -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 {
@@ -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 titleTags = this._grepBaseTitlePath().join(" ").match(/@[\S]+/g) || [];
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
@@ -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
  }
@@ -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 import_mcpBundle = require("patchright-bun-core/lib/mcpBundle");
26
- const testAnnotationSchema = import_mcpBundle.z.object({
27
- type: import_mcpBundle.z.string(),
28
- description: import_mcpBundle.z.string().optional()
29
- });
30
- const testDetailsSchema = import_mcpBundle.z.object({
31
- tag: import_mcpBundle.z.union([
32
- import_mcpBundle.z.string().optional(),
33
- import_mcpBundle.z.array(import_mcpBundle.z.string())
34
- ]).transform((val) => Array.isArray(val) ? val : val !== void 0 ? [val] : []).refine((val) => val.every((v) => v.startsWith("@")), {
35
- message: "Tag must start with '@'"
36
- }),
37
- annotation: import_mcpBundle.z.union([
38
- testAnnotationSchema,
39
- import_mcpBundle.z.array(testAnnotationSchema).optional()
40
- ]).transform((val) => Array.isArray(val) ? val : val !== void 0 ? [val] : [])
41
- });
42
- function validateTestAnnotation(annotation) {
43
- try {
44
- return testAnnotationSchema.parse(annotation);
45
- } catch (error) {
46
- throwZodError(error);
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
- try {
51
- const parsedDetails = testDetailsSchema.parse(details);
52
- return {
53
- annotations: parsedDetails.annotation.map((a) => ({ ...a, location })),
54
- tags: parsedDetails.tag,
55
- location
56
- };
57
- } catch (error) {
58
- throwZodError(error);
59
- }
60
- }
61
- function throwZodError(error) {
62
- throw new Error(error.issues.map((i) => i.message).join("\n"));
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 ?? 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, testInfo) => {
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._setDebugMode();
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)()?._setDebugMode();
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, testInfo) => {
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._onDidFinishTestFunctionCallback = () => this.didFinishTestFunction();
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 = (await page._snapshotForAI({ timeout: 5e3 })).full;
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._pageSnapshot && this._testInfo.errors.length > 0 && !this._testInfo.attachments.some((a) => a.name === "error-context")) {
661
- const lines = [
662
- "# Page snapshot",
663
- "",
664
- "```yaml",
665
- this._pageSnapshot,
666
- "```"
667
- ];
668
- const filePath = this._testInfo.outputPath("error-context.md");
669
- await import_fs.default.promises.writeFile(filePath, lines.join("\n"), "utf8");
670
- this._testInfo._attach({
671
- name: "error-context",
672
- contentType: "text/markdown",
673
- path: filePath
674
- }, void 0);
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
- projectSuite._project = this._parseProject(project);
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 = { ...baseFullConfig, ...config };
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(new TestServerConnectionClosedError());
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) {