playwright 1.54.1 → 1.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/README.md +3 -3
  2. package/ThirdPartyNotices.txt +2727 -434
  3. package/lib/agents/generateAgents.js +263 -0
  4. package/lib/agents/generator.md +102 -0
  5. package/lib/agents/healer.md +78 -0
  6. package/lib/agents/planner.md +135 -0
  7. package/lib/common/config.js +3 -1
  8. package/lib/common/configLoader.js +2 -1
  9. package/lib/common/expectBundle.js +3 -0
  10. package/lib/common/expectBundleImpl.js +51 -51
  11. package/lib/common/fixtures.js +1 -1
  12. package/lib/common/suiteUtils.js +0 -9
  13. package/lib/index.js +127 -115
  14. package/lib/isomorphic/testTree.js +35 -8
  15. package/lib/matchers/expect.js +6 -7
  16. package/lib/matchers/matcherHint.js +43 -15
  17. package/lib/matchers/matchers.js +10 -4
  18. package/lib/matchers/toBeTruthy.js +16 -14
  19. package/lib/matchers/toEqual.js +18 -13
  20. package/lib/matchers/toHaveURL.js +12 -27
  21. package/lib/matchers/toMatchAriaSnapshot.js +26 -31
  22. package/lib/matchers/toMatchSnapshot.js +15 -12
  23. package/lib/matchers/toMatchText.js +29 -35
  24. package/lib/mcp/browser/actions.d.js +16 -0
  25. package/lib/mcp/browser/browserContextFactory.js +296 -0
  26. package/lib/mcp/browser/browserServerBackend.js +76 -0
  27. package/lib/mcp/browser/codegen.js +66 -0
  28. package/lib/mcp/browser/config.js +383 -0
  29. package/lib/mcp/browser/context.js +284 -0
  30. package/lib/mcp/browser/response.js +228 -0
  31. package/lib/mcp/browser/sessionLog.js +160 -0
  32. package/lib/mcp/browser/tab.js +277 -0
  33. package/lib/mcp/browser/tools/common.js +63 -0
  34. package/lib/mcp/browser/tools/console.js +44 -0
  35. package/lib/mcp/browser/tools/dialogs.js +60 -0
  36. package/lib/mcp/browser/tools/evaluate.js +70 -0
  37. package/lib/mcp/browser/tools/files.js +58 -0
  38. package/lib/mcp/browser/tools/form.js +74 -0
  39. package/lib/mcp/browser/tools/install.js +69 -0
  40. package/lib/mcp/browser/tools/keyboard.js +85 -0
  41. package/lib/mcp/browser/tools/mouse.js +107 -0
  42. package/lib/mcp/browser/tools/navigate.js +62 -0
  43. package/lib/mcp/browser/tools/network.js +54 -0
  44. package/lib/mcp/browser/tools/pdf.js +59 -0
  45. package/lib/mcp/browser/tools/screenshot.js +88 -0
  46. package/lib/mcp/browser/tools/snapshot.js +182 -0
  47. package/lib/mcp/browser/tools/tabs.js +67 -0
  48. package/lib/mcp/browser/tools/tool.js +49 -0
  49. package/lib/mcp/browser/tools/tracing.js +74 -0
  50. package/lib/mcp/browser/tools/utils.js +100 -0
  51. package/lib/mcp/browser/tools/verify.js +154 -0
  52. package/lib/mcp/browser/tools/wait.js +63 -0
  53. package/lib/mcp/browser/tools.js +80 -0
  54. package/lib/mcp/browser/watchdog.js +44 -0
  55. package/lib/mcp/config.d.js +16 -0
  56. package/lib/mcp/extension/cdpRelay.js +351 -0
  57. package/lib/mcp/extension/extensionContextFactory.js +75 -0
  58. package/lib/mcp/extension/protocol.js +28 -0
  59. package/lib/mcp/index.js +61 -0
  60. package/lib/mcp/log.js +35 -0
  61. package/lib/mcp/program.js +96 -0
  62. package/lib/mcp/sdk/bundle.js +81 -0
  63. package/lib/mcp/sdk/exports.js +32 -0
  64. package/lib/mcp/sdk/http.js +180 -0
  65. package/lib/mcp/sdk/inProcessTransport.js +71 -0
  66. package/lib/mcp/sdk/mdb.js +208 -0
  67. package/lib/mcp/sdk/proxyBackend.js +128 -0
  68. package/lib/mcp/sdk/server.js +190 -0
  69. package/lib/mcp/sdk/tool.js +51 -0
  70. package/lib/mcp/test/browserBackend.js +98 -0
  71. package/lib/mcp/test/generatorTools.js +122 -0
  72. package/lib/mcp/test/plannerTools.js +46 -0
  73. package/lib/mcp/test/seed.js +72 -0
  74. package/lib/mcp/test/streams.js +39 -0
  75. package/lib/mcp/test/testBackend.js +97 -0
  76. package/lib/mcp/test/testContext.js +176 -0
  77. package/lib/mcp/test/testTool.js +30 -0
  78. package/lib/mcp/test/testTools.js +115 -0
  79. package/lib/mcpBundleImpl.js +41 -0
  80. package/lib/plugins/webServerPlugin.js +2 -0
  81. package/lib/program.js +77 -57
  82. package/lib/reporters/base.js +34 -29
  83. package/lib/reporters/dot.js +11 -11
  84. package/lib/reporters/github.js +2 -1
  85. package/lib/reporters/html.js +58 -41
  86. package/lib/reporters/internalReporter.js +2 -1
  87. package/lib/reporters/line.js +15 -15
  88. package/lib/reporters/list.js +24 -19
  89. package/lib/reporters/listModeReporter.js +69 -0
  90. package/lib/reporters/markdown.js +3 -3
  91. package/lib/reporters/merge.js +3 -1
  92. package/lib/reporters/teleEmitter.js +3 -1
  93. package/lib/runner/dispatcher.js +9 -2
  94. package/lib/runner/failureTracker.js +12 -2
  95. package/lib/runner/lastRun.js +7 -4
  96. package/lib/runner/loadUtils.js +46 -12
  97. package/lib/runner/projectUtils.js +8 -2
  98. package/lib/runner/reporters.js +7 -32
  99. package/lib/runner/tasks.js +20 -10
  100. package/lib/runner/testRunner.js +390 -0
  101. package/lib/runner/testServer.js +57 -276
  102. package/lib/runner/watchMode.js +5 -1
  103. package/lib/runner/workerHost.js +8 -6
  104. package/lib/transform/babelBundleImpl.js +179 -195
  105. package/lib/transform/compilationCache.js +22 -5
  106. package/lib/transform/transform.js +1 -1
  107. package/lib/util.js +12 -35
  108. package/lib/utilsBundleImpl.js +1 -1
  109. package/lib/worker/fixtureRunner.js +7 -2
  110. package/lib/worker/testInfo.js +76 -45
  111. package/lib/worker/testTracing.js +8 -7
  112. package/lib/worker/workerMain.js +12 -3
  113. package/package.json +10 -2
  114. package/types/test.d.ts +63 -44
  115. package/types/testReporter.d.ts +1 -1
  116. package/lib/runner/runner.js +0 -110
@@ -90,7 +90,7 @@ class FixturePool {
90
90
  continue;
91
91
  }
92
92
  } else if (previous) {
93
- options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle, box: previous.box };
93
+ options = { auto: previous.auto, scope: previous.scope, option: previous.option, timeout: previous.timeout, customTitle: previous.customTitle };
94
94
  } else if (!options) {
95
95
  options = { auto: false, scope: "test", option: false, timeout: void 0 };
96
96
  }
@@ -31,10 +31,8 @@ __export(suiteUtils_exports, {
31
31
  applyRepeatEachIndex: () => applyRepeatEachIndex,
32
32
  bindFileSuiteToProject: () => bindFileSuiteToProject,
33
33
  filterByFocusedLine: () => filterByFocusedLine,
34
- filterByTestIds: () => filterByTestIds,
35
34
  filterOnly: () => filterOnly,
36
35
  filterSuite: () => filterSuite,
37
- filterSuiteWithOnlySemantics: () => filterSuiteWithOnlySemantics,
38
36
  filterTestsRemoveEmptySuites: () => filterTestsRemoveEmptySuites
39
37
  });
40
38
  module.exports = __toCommonJS(suiteUtils_exports);
@@ -127,11 +125,6 @@ function filterByFocusedLine(suite, focusedTestFileLines) {
127
125
  const testFilter = (test) => testFileLineMatches(test.location.file, test.location.line, test.location.column);
128
126
  return filterSuite(suite, suiteFilter, testFilter);
129
127
  }
130
- function filterByTestIds(suite, testIdMatcher) {
131
- if (!testIdMatcher)
132
- return;
133
- filterTestsRemoveEmptySuites(suite, (test) => testIdMatcher(test.id));
134
- }
135
128
  function createFileMatcherFromFilter(filter) {
136
129
  const fileMatcher = (0, import_util.createFileMatcher)(filter.re || filter.exact || "");
137
130
  return (testFileName, testLine, testColumn) => fileMatcher(testFileName) && (filter.line === testLine || filter.line === null) && (filter.column === testColumn || filter.column === null);
@@ -141,9 +134,7 @@ function createFileMatcherFromFilter(filter) {
141
134
  applyRepeatEachIndex,
142
135
  bindFileSuiteToProject,
143
136
  filterByFocusedLine,
144
- filterByTestIds,
145
137
  filterOnly,
146
138
  filterSuite,
147
- filterSuiteWithOnlySemantics,
148
139
  filterTestsRemoveEmptySuites
149
140
  });
package/lib/index.js CHANGED
@@ -42,7 +42,7 @@ var playwrightLibrary = __toESM(require("playwright-core"));
42
42
  var import_utils = require("playwright-core/lib/utils");
43
43
  var import_globals = require("./common/globals");
44
44
  var import_testType = require("./common/testType");
45
- var import_util = require("./util");
45
+ var import_browserBackend = require("./mcp/test/browserBackend");
46
46
  var import_expect = require("./matchers/expect");
47
47
  var import_configLoader = require("./common/configLoader");
48
48
  var import_testType2 = require("./common/testType");
@@ -61,20 +61,20 @@ if (process["__pw_initiator__"]) {
61
61
  process["__pw_initiator__"] = new Error().stack;
62
62
  }
63
63
  const playwrightFixtures = {
64
- defaultBrowserType: ["chromium", { scope: "worker", option: true }],
65
- browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true }],
64
+ defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
65
+ browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
66
66
  playwright: [async ({}, use) => {
67
67
  await use(require("playwright-core"));
68
68
  }, { scope: "worker", box: true }],
69
- headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true }],
70
- channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true }],
71
- launchOptions: [{}, { scope: "worker", option: true }],
69
+ headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
70
+ channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
71
+ launchOptions: [{}, { scope: "worker", option: true, box: true }],
72
72
  connectOptions: [async ({ _optionConnectOptions }, use) => {
73
73
  await use(connectOptionsFromEnv() || _optionConnectOptions);
74
- }, { scope: "worker", option: true }],
75
- screenshot: ["off", { scope: "worker", option: true }],
76
- video: ["off", { scope: "worker", option: true }],
77
- trace: ["off", { scope: "worker", option: true }],
74
+ }, { scope: "worker", option: true, box: true }],
75
+ screenshot: ["off", { scope: "worker", option: true, box: true }],
76
+ video: ["off", { scope: "worker", option: true, box: true }],
77
+ trace: ["off", { scope: "worker", option: true, box: true }],
78
78
  _browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
79
79
  const options = {
80
80
  handleSIGINT: false,
@@ -103,45 +103,41 @@ const playwrightFixtures = {
103
103
  }
104
104
  });
105
105
  await use(browser2);
106
- await browser2._wrapApiCall(async () => {
107
- await browser2.close({ reason: "Test ended." });
108
- }, { internal: true });
106
+ await browser2.close({ reason: "Test ended." });
109
107
  return;
110
108
  }
111
109
  const browser = await playwright[browserName].launch();
112
110
  await use(browser);
113
- await browser._wrapApiCall(async () => {
114
- await browser.close({ reason: "Test ended." });
115
- }, { internal: true });
111
+ await browser.close({ reason: "Test ended." });
116
112
  }, { scope: "worker", timeout: 0 }],
117
- acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true }],
118
- bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true }],
119
- colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true }],
120
- deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true }],
121
- extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true }],
122
- geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true }],
123
- hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true }],
124
- httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true }],
125
- ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true }],
126
- isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true }],
127
- javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true }],
128
- locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true }],
129
- offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true }],
130
- permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true }],
131
- proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true }],
132
- storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true }],
133
- clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true }],
134
- timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true }],
135
- userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true }],
136
- viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true }],
137
- actionTimeout: [0, { option: true }],
138
- testIdAttribute: ["data-testid", { option: true }],
139
- navigationTimeout: [0, { option: true }],
113
+ acceptDownloads: [({ contextOptions }, use) => use(contextOptions.acceptDownloads ?? true), { option: true, box: true }],
114
+ bypassCSP: [({ contextOptions }, use) => use(contextOptions.bypassCSP ?? false), { option: true, box: true }],
115
+ colorScheme: [({ contextOptions }, use) => use(contextOptions.colorScheme === void 0 ? "light" : contextOptions.colorScheme), { option: true, box: true }],
116
+ deviceScaleFactor: [({ contextOptions }, use) => use(contextOptions.deviceScaleFactor), { option: true, box: true }],
117
+ extraHTTPHeaders: [({ contextOptions }, use) => use(contextOptions.extraHTTPHeaders), { option: true, box: true }],
118
+ geolocation: [({ contextOptions }, use) => use(contextOptions.geolocation), { option: true, box: true }],
119
+ hasTouch: [({ contextOptions }, use) => use(contextOptions.hasTouch ?? false), { option: true, box: true }],
120
+ httpCredentials: [({ contextOptions }, use) => use(contextOptions.httpCredentials), { option: true, box: true }],
121
+ ignoreHTTPSErrors: [({ contextOptions }, use) => use(contextOptions.ignoreHTTPSErrors ?? false), { option: true, box: true }],
122
+ isMobile: [({ contextOptions }, use) => use(contextOptions.isMobile ?? false), { option: true, box: true }],
123
+ javaScriptEnabled: [({ contextOptions }, use) => use(contextOptions.javaScriptEnabled ?? true), { option: true, box: true }],
124
+ locale: [({ contextOptions }, use) => use(contextOptions.locale ?? "en-US"), { option: true, box: true }],
125
+ offline: [({ contextOptions }, use) => use(contextOptions.offline ?? false), { option: true, box: true }],
126
+ permissions: [({ contextOptions }, use) => use(contextOptions.permissions), { option: true, box: true }],
127
+ proxy: [({ contextOptions }, use) => use(contextOptions.proxy), { option: true, box: true }],
128
+ storageState: [({ contextOptions }, use) => use(contextOptions.storageState), { option: true, box: true }],
129
+ clientCertificates: [({ contextOptions }, use) => use(contextOptions.clientCertificates), { option: true, box: true }],
130
+ timezoneId: [({ contextOptions }, use) => use(contextOptions.timezoneId), { option: true, box: true }],
131
+ userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
132
+ viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? { width: 1280, height: 720 } : contextOptions.viewport), { option: true, box: true }],
133
+ actionTimeout: [0, { option: true, box: true }],
134
+ testIdAttribute: ["data-testid", { option: true, box: true }],
135
+ navigationTimeout: [0, { option: true, box: true }],
140
136
  baseURL: [async ({}, use) => {
141
137
  await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
142
- }, { option: true }],
143
- serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true }],
144
- contextOptions: [{}, { option: true }],
138
+ }, { option: true, box: true }],
139
+ serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
140
+ contextOptions: [{}, { option: true, box: true }],
145
141
  _combinedContextOptions: [async ({
146
142
  acceptDownloads,
147
143
  bypassCSP,
@@ -221,10 +217,10 @@ const playwrightFixtures = {
221
217
  if (testIdAttribute)
222
218
  playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
223
219
  testInfo.snapshotSuffix = process.platform;
224
- if ((0, import_utils.debugMode)())
220
+ if ((0, import_utils.debugMode)() === "inspector")
225
221
  testInfo._setDebugMode();
226
222
  playwright._defaultContextOptions = _combinedContextOptions;
227
- playwright._defaultContextTimeout = actionTimeout || 0;
223
+ playwright._defaultContextTimeout = testInfo._pauseOnError() ? 5e3 : actionTimeout || 0;
228
224
  playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
229
225
  await use();
230
226
  playwright._defaultContextOptions = void 0;
@@ -242,11 +238,12 @@ const playwrightFixtures = {
242
238
  if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
243
239
  return;
244
240
  const zone = (0, import_utils.currentZone)().data("stepZone");
245
- if (zone && zone.category === "expect") {
241
+ const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
242
+ if (zone && zone.category === "expect" && isExpectCall) {
246
243
  if (zone.apiName)
247
244
  data.apiName = zone.apiName;
248
245
  if (zone.title)
249
- data.title = (0, import_util.stepTitle)(zone.category, zone.title);
246
+ data.title = zone.title;
250
247
  data.stepId = zone.stepId;
251
248
  return;
252
249
  }
@@ -255,7 +252,8 @@ const playwrightFixtures = {
255
252
  category: "pw:api",
256
253
  title: renderTitle(channel.type, channel.method, channel.params, data.title),
257
254
  apiName: data.apiName,
258
- params: channel.params
255
+ params: channel.params,
256
+ group: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method })
259
257
  }, tracingGroupSteps[tracingGroupSteps.length - 1]);
260
258
  data.userData = step;
261
259
  data.stepId = step.stepId;
@@ -310,6 +308,7 @@ const playwrightFixtures = {
310
308
  const videoMode = normalizeVideoMode(video);
311
309
  const captureVideo = shouldCaptureVideo(videoMode, testInfo) && !_reuseContext;
312
310
  const contexts = /* @__PURE__ */ new Map();
311
+ let counter = 0;
313
312
  await use(async (options) => {
314
313
  const hook = testInfoImpl._currentHookType();
315
314
  if (hook === "beforeAll" || hook === "afterAll") {
@@ -326,10 +325,6 @@ const playwrightFixtures = {
326
325
  }
327
326
  } : {};
328
327
  const context = await browser.newContext({ ...videoOptions, ...options });
329
- const contextData = { pagesWithVideo: [] };
330
- contexts.set(context, contextData);
331
- if (captureVideo)
332
- context.on("page", (page) => contextData.pagesWithVideo.push(page));
333
328
  if (process.env.PW_CLOCK === "frozen") {
334
329
  await context._wrapApiCall(async () => {
335
330
  await context.clock.install({ time: 0 });
@@ -340,33 +335,39 @@ const playwrightFixtures = {
340
335
  await context.clock.install({ time: 0 });
341
336
  }, { internal: true });
342
337
  }
343
- return context;
344
- });
345
- let counter = 0;
346
- const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
347
- await Promise.all([...contexts.keys()].map(async (context) => {
348
- await context._wrapApiCall(async () => {
338
+ let closed = false;
339
+ const close = async () => {
340
+ if (closed)
341
+ return;
342
+ closed = true;
343
+ const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
349
344
  await context.close({ reason: closeReason });
350
- }, { internal: true });
351
- const testFailed = testInfo.status !== testInfo.expectedStatus;
352
- const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
353
- if (preserveVideo) {
354
- const { pagesWithVideo: pagesForVideo } = contexts.get(context);
355
- const videos = pagesForVideo.map((p) => p.video()).filter(Boolean);
356
- await Promise.all(videos.map(async (v) => {
357
- try {
358
- const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
359
- ++counter;
360
- await v.saveAs(savedPath);
361
- testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
362
- } catch (e) {
363
- }
364
- }));
365
- }
366
- }));
345
+ const testFailed = testInfo.status !== testInfo.expectedStatus;
346
+ const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
347
+ if (preserveVideo) {
348
+ const { pagesWithVideo: pagesForVideo } = contexts.get(context);
349
+ const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
350
+ await Promise.all(videos.map(async (v) => {
351
+ try {
352
+ const savedPath = testInfo.outputPath(`video${counter ? "-" + counter : ""}.webm`);
353
+ ++counter;
354
+ await v.saveAs(savedPath);
355
+ testInfo.attachments.push({ name: "video", path: savedPath, contentType: "video/webm" });
356
+ } catch (e) {
357
+ }
358
+ }));
359
+ }
360
+ };
361
+ const contextData = { close, pagesWithVideo: [] };
362
+ if (captureVideo)
363
+ context.on("page", (page) => contextData.pagesWithVideo.push(page));
364
+ contexts.set(context, contextData);
365
+ return { context, close };
366
+ });
367
+ await Promise.all([...contexts.values()].map((data) => data.close()));
367
368
  }, { scope: "test", title: "context", box: true }],
368
- _optionContextReuseMode: ["none", { scope: "worker", option: true }],
369
- _optionConnectOptions: [void 0, { scope: "worker", option: true }],
369
+ _optionContextReuseMode: ["none", { scope: "worker", option: true, box: true }],
370
+ _optionConnectOptions: [void 0, { scope: "worker", option: true, box: true }],
370
371
  _reuseContext: [async ({ video, _optionContextReuseMode }, use) => {
371
372
  let mode = _optionContextReuseMode;
372
373
  if (process.env.PW_TEST_REUSE_CONTEXT)
@@ -374,17 +375,21 @@ const playwrightFixtures = {
374
375
  const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
375
376
  await use(reuse);
376
377
  }, { scope: "worker", title: "context", box: true }],
377
- context: async ({ playwright, browser, _reuseContext, _contextFactory }, use, testInfo) => {
378
- attachConnectedHeaderIfNeeded(testInfo, browser);
378
+ context: async ({ browser, _reuseContext, _contextFactory }, use, testInfo) => {
379
+ const browserImpl = browser;
380
+ attachConnectedHeaderIfNeeded(testInfo, browserImpl);
379
381
  if (!_reuseContext) {
380
- await use(await _contextFactory());
382
+ const { context: context2, close } = await _contextFactory();
383
+ testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context2, testInfo.errors[0]?.message));
384
+ await use(context2);
385
+ await close();
381
386
  return;
382
387
  }
383
- const defaultContextOptions = playwright.chromium._defaultContextOptions;
384
- const context = await browser._newContextForReuse(defaultContextOptions);
388
+ const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
389
+ testInfo._onDidFinishTestFunctions.unshift(() => (0, import_browserBackend.runBrowserBackendAtEnd)(context, testInfo.errors[0]?.message));
385
390
  await use(context);
386
391
  const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
387
- await browser._disconnectFromReusedContext(closeReason);
392
+ await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
388
393
  },
389
394
  page: async ({ context, _reuseContext }, use) => {
390
395
  if (!_reuseContext) {
@@ -548,22 +553,24 @@ class ArtifactsRecorder {
548
553
  const screenshotOptions = typeof screenshot === "string" ? void 0 : screenshot;
549
554
  this._startedCollectingArtifacts = Symbol("startedCollectingArtifacts");
550
555
  this._screenshotRecorder = new SnapshotRecorder(this, normalizeScreenshotMode(screenshot), "screenshot", "image/png", ".png", async (page, path2) => {
551
- await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
556
+ await page._wrapApiCall(async () => {
557
+ await page.screenshot({ ...screenshotOptions, timeout: 5e3, path: path2, caret: "initial" });
558
+ }, { internal: true });
552
559
  });
553
560
  }
554
561
  async willStartTest(testInfo) {
555
562
  this._testInfo = testInfo;
556
- testInfo._onDidFinishTestFunction = () => this.didFinishTestFunction();
563
+ testInfo._onDidFinishTestFunctions.push(() => this.didFinishTestFunction());
557
564
  this._screenshotRecorder.fixOrdinal();
558
565
  await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
559
566
  const existingApiRequests = Array.from(this._playwright.request._contexts);
560
567
  await Promise.all(existingApiRequests.map((c) => this.didCreateRequestContext(c)));
561
568
  }
562
569
  async didCreateBrowserContext(context) {
563
- await this._startTraceChunkOnContextCreation(context.tracing);
570
+ await this._startTraceChunkOnContextCreation(context, context.tracing);
564
571
  }
565
572
  async willCloseBrowserContext(context) {
566
- await this._stopTracing(context.tracing);
573
+ await this._stopTracing(context, context.tracing);
567
574
  await this._screenshotRecorder.captureTemporary(context);
568
575
  await this._takePageSnapshot(context);
569
576
  }
@@ -575,18 +582,20 @@ class ArtifactsRecorder {
575
582
  if (this._pageSnapshot)
576
583
  return;
577
584
  const page = context.pages()[0];
585
+ if (!page)
586
+ return;
578
587
  try {
579
- this._pageSnapshot = await page?.locator("body").ariaSnapshot({ timeout: 5e3 });
588
+ await page._wrapApiCall(async () => {
589
+ this._pageSnapshot = await page._snapshotForAI({ timeout: 5e3 });
590
+ }, { internal: true });
580
591
  } catch {
581
592
  }
582
593
  }
583
594
  async didCreateRequestContext(context) {
584
- const tracing2 = context._tracing;
585
- await this._startTraceChunkOnContextCreation(tracing2);
595
+ await this._startTraceChunkOnContextCreation(context, context._tracing);
586
596
  }
587
597
  async willCloseRequestContext(context) {
588
- const tracing2 = context._tracing;
589
- await this._stopTracing(tracing2);
598
+ await this._stopTracing(context, context._tracing);
590
599
  }
591
600
  async didFinishTestFunction() {
592
601
  await this._screenshotRecorder.maybeCapture();
@@ -596,10 +605,9 @@ class ArtifactsRecorder {
596
605
  const leftoverContexts = this._playwright._allContexts();
597
606
  const leftoverApiRequests = Array.from(this._playwright.request._contexts);
598
607
  await Promise.all(leftoverContexts.map(async (context2) => {
599
- await this._stopTracing(context2.tracing);
608
+ await this._stopTracing(context2, context2.tracing);
600
609
  }).concat(leftoverApiRequests.map(async (context2) => {
601
- const tracing2 = context2._tracing;
602
- await this._stopTracing(tracing2);
610
+ await this._stopTracing(context2, context2._tracing);
603
611
  })));
604
612
  await this._screenshotRecorder.persistTemporary();
605
613
  const context = leftoverContexts[0];
@@ -622,30 +630,34 @@ class ArtifactsRecorder {
622
630
  }, void 0);
623
631
  }
624
632
  }
625
- async _startTraceChunkOnContextCreation(tracing2) {
626
- const options = this._testInfo._tracing.traceOptions();
627
- if (options) {
628
- const title = this._testInfo._tracing.traceTitle();
629
- const name = this._testInfo._tracing.generateNextTraceRecordingName();
630
- if (!tracing2[kTracingStarted]) {
631
- await tracing2.start({ ...options, title, name });
632
- tracing2[kTracingStarted] = true;
633
+ async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
634
+ await channelOwner._wrapApiCall(async () => {
635
+ const options = this._testInfo._tracing.traceOptions();
636
+ if (options) {
637
+ const title = this._testInfo._tracing.traceTitle();
638
+ const name = this._testInfo._tracing.generateNextTraceRecordingName();
639
+ if (!tracing2[kTracingStarted]) {
640
+ await tracing2.start({ ...options, title, name });
641
+ tracing2[kTracingStarted] = true;
642
+ } else {
643
+ await tracing2.startChunk({ title, name });
644
+ }
633
645
  } else {
634
- await tracing2.startChunk({ title, name });
635
- }
636
- } else {
637
- if (tracing2[kTracingStarted]) {
638
- tracing2[kTracingStarted] = false;
639
- await tracing2.stop();
646
+ if (tracing2[kTracingStarted]) {
647
+ tracing2[kTracingStarted] = false;
648
+ await tracing2.stop();
649
+ }
640
650
  }
641
- }
651
+ }, { internal: true });
642
652
  }
643
- async _stopTracing(tracing2) {
644
- if (tracing2[this._startedCollectingArtifacts])
645
- return;
646
- tracing2[this._startedCollectingArtifacts] = true;
647
- if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
648
- await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
653
+ async _stopTracing(channelOwner, tracing2) {
654
+ await channelOwner._wrapApiCall(async () => {
655
+ if (tracing2[this._startedCollectingArtifacts])
656
+ return;
657
+ tracing2[this._startedCollectingArtifacts] = true;
658
+ if (this._testInfo._tracing.traceOptions() && tracing2[kTracingStarted])
659
+ await tracing2.stopChunk({ path: this._testInfo._tracing.maybeGenerateNextTraceRecordingPath() });
660
+ }, { internal: true });
649
661
  }
650
662
  }
651
663
  function renderTitle(type, method, params, title) {
@@ -25,7 +25,7 @@ __export(testTree_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(testTree_exports);
27
27
  class TestTree {
28
- constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator) {
28
+ constructor(rootFolder, rootSuite, loadErrors, projectFilters, pathSeparator, hideFiles) {
29
29
  this._treeItemById = /* @__PURE__ */ new Map();
30
30
  this._treeItemByTestId = /* @__PURE__ */ new Map();
31
31
  const filterProjects = projectFilters && [...projectFilters.values()].some(Boolean);
@@ -43,10 +43,10 @@ class TestTree {
43
43
  hasLoadErrors: false
44
44
  };
45
45
  this._treeItemById.set(rootFolder, this.rootItem);
46
- const visitSuite = (project, parentSuite, parentGroup) => {
47
- for (const suite of parentSuite.suites) {
46
+ const visitSuite = (project, parentSuite, parentGroup, mode) => {
47
+ for (const suite of mode === "tests" ? [] : parentSuite.suites) {
48
48
  if (!suite.title) {
49
- visitSuite(project, suite, parentGroup);
49
+ visitSuite(project, suite, parentGroup, "all");
50
50
  continue;
51
51
  }
52
52
  let group = parentGroup.children.find((item) => item.kind === "group" && item.title === suite.title);
@@ -66,9 +66,9 @@ class TestTree {
66
66
  };
67
67
  this._addChild(parentGroup, group);
68
68
  }
69
- visitSuite(project, suite, group);
69
+ visitSuite(project, suite, group, "all");
70
70
  }
71
- for (const test of parentSuite.tests) {
71
+ for (const test of mode === "suites" ? [] : parentSuite.tests) {
72
72
  const title = test.title;
73
73
  let testCaseItem = parentGroup.children.find((t) => t.kind !== "group" && t.title === title);
74
74
  if (!testCaseItem) {
@@ -124,8 +124,16 @@ class TestTree {
124
124
  if (filterProjects && !projectFilters.get(projectSuite.title))
125
125
  continue;
126
126
  for (const fileSuite of projectSuite.suites) {
127
- const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
128
- visitSuite(projectSuite.project(), fileSuite, fileItem);
127
+ if (hideFiles) {
128
+ visitSuite(projectSuite.project(), fileSuite, this.rootItem, "suites");
129
+ if (fileSuite.tests.length) {
130
+ const defaultDescribeItem = this._defaultDescribeItem();
131
+ visitSuite(projectSuite.project(), fileSuite, defaultDescribeItem, "tests");
132
+ }
133
+ } else {
134
+ const fileItem = this._fileItem(fileSuite.location.file.split(pathSeparator), true);
135
+ visitSuite(projectSuite.project(), fileSuite, fileItem, "all");
136
+ }
129
137
  }
130
138
  }
131
139
  for (const loadError of loadErrors) {
@@ -192,6 +200,25 @@ class TestTree {
192
200
  this._addChild(parentFileItem, fileItem);
193
201
  return fileItem;
194
202
  }
203
+ _defaultDescribeItem() {
204
+ let defaultDescribeItem = this._treeItemById.get("<anonymous>");
205
+ if (!defaultDescribeItem) {
206
+ defaultDescribeItem = {
207
+ kind: "group",
208
+ subKind: "describe",
209
+ id: "<anonymous>",
210
+ title: "<anonymous>",
211
+ location: { file: "", line: 0, column: 0 },
212
+ duration: 0,
213
+ parent: this.rootItem,
214
+ children: [],
215
+ status: "none",
216
+ hasLoadErrors: false
217
+ };
218
+ this._addChild(this.rootItem, defaultDescribeItem);
219
+ }
220
+ return defaultDescribeItem;
221
+ }
195
222
  sortAndPropagateStatus() {
196
223
  sortAndPropagateStatus(this.rootItem);
197
224
  }
@@ -222,26 +222,25 @@ class ExpectMetaInfoProxyHandler {
222
222
  const customMessage = this._info.message || "";
223
223
  const argsSuffix = computeArgsSuffix(matcherName, args);
224
224
  const defaultTitle = `${this._info.poll ? "poll " : ""}${this._info.isSoft ? "soft " : ""}${this._info.isNot ? "not " : ""}${matcherName}${argsSuffix}`;
225
- const title = customMessage || defaultTitle;
225
+ const title = customMessage || `Expect ${(0, import_utils.escapeWithQuotes)(defaultTitle, '"')}`;
226
226
  const apiName = `expect${this._info.poll ? ".poll " : ""}${this._info.isSoft ? ".soft " : ""}${this._info.isNot ? ".not" : ""}.${matcherName}${argsSuffix}`;
227
227
  const stackFrames = (0, import_util.filteredStackTrace)((0, import_utils.captureRawStack)());
228
- const category = matcherName === "toPass" || this._info.poll ? "test.step" : "expect";
229
- const formattedTitle = category === "expect" ? title : `Expect "${title}"`;
230
228
  const stepInfo = {
231
- category,
229
+ category: "expect",
232
230
  apiName,
233
- title: formattedTitle,
231
+ title,
234
232
  params: args[0] ? { expected: args[0] } : void 0,
235
233
  infectParentStepsWithError: this._info.isSoft
236
234
  };
237
235
  const step = testInfo._addStep(stepInfo);
238
236
  const reportStepError = (e) => {
239
237
  const jestError = (0, import_matcherHint.isJestError)(e) ? e : null;
240
- const error = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : e;
238
+ const expectError = jestError ? new import_matcherHint.ExpectError(jestError, customMessage, stackFrames) : void 0;
241
239
  if (jestError?.matcherResult.suggestedRebaseline) {
242
240
  step.complete({ suggestedRebaseline: jestError?.matcherResult.suggestedRebaseline });
243
241
  return;
244
242
  }
243
+ const error = expectError ?? e;
245
244
  step.complete({ error });
246
245
  if (this._info.isSoft)
247
246
  testInfo._failWithError(error);
@@ -260,7 +259,7 @@ class ExpectMetaInfoProxyHandler {
260
259
  finalizer();
261
260
  return result;
262
261
  } catch (e) {
263
- reportStepError(e);
262
+ void reportStepError(e);
264
263
  }
265
264
  };
266
265
  }
@@ -19,22 +19,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var matcherHint_exports = {};
20
20
  __export(matcherHint_exports, {
21
21
  ExpectError: () => ExpectError,
22
- isJestError: () => isJestError,
23
- kNoElementsFoundError: () => kNoElementsFoundError,
24
- matcherHint: () => matcherHint
22
+ callLogText: () => callLogText,
23
+ formatMatcherMessage: () => formatMatcherMessage,
24
+ isJestError: () => isJestError
25
25
  });
26
26
  module.exports = __toCommonJS(matcherHint_exports);
27
27
  var import_utils = require("playwright-core/lib/utils");
28
- var import_utils2 = require("playwright-core/lib/utils");
29
- const kNoElementsFoundError = "<element(s) not found>";
30
- function matcherHint(state, locator, matcherName, expression, actual, matcherOptions, timeout) {
31
- let header = state.utils.matcherHint(matcherName, expression, actual, matcherOptions).replace(/ \/\/ deep equality/, "") + "\n\n";
32
- if (timeout)
33
- header = import_utils2.colors.red(`Timed out ${timeout}ms waiting for `) + header;
34
- if (locator)
35
- header += `Locator: ${String(locator)}
28
+ var import_expectBundle = require("../common/expectBundle");
29
+ function formatMatcherMessage(state, details) {
30
+ const receiver = details.receiver ?? (details.locator ? "locator" : "page");
31
+ let message = (0, import_expectBundle.DIM_COLOR)("expect(") + (0, import_expectBundle.RECEIVED_COLOR)(receiver) + (0, import_expectBundle.DIM_COLOR)(")" + (state.promise ? "." + state.promise : "") + (state.isNot ? ".not" : "") + ".") + details.matcherName + (0, import_expectBundle.DIM_COLOR)("(") + (0, import_expectBundle.EXPECTED_COLOR)(details.expectation) + (0, import_expectBundle.DIM_COLOR)(")") + " failed\n\n";
32
+ const diffLines = details.printedDiff?.split("\n");
33
+ if (diffLines?.length === 2) {
34
+ details.printedExpected = diffLines[0];
35
+ details.printedReceived = diffLines[1];
36
+ details.printedDiff = void 0;
37
+ }
38
+ const align = !details.errorMessage && details.printedExpected?.startsWith("Expected:") && (!details.printedReceived || details.printedReceived.startsWith("Received:"));
39
+ if (details.locator)
40
+ message += `Locator: ${align ? " " : ""}${String(details.locator)}
41
+ `;
42
+ if (details.printedExpected)
43
+ message += details.printedExpected + "\n";
44
+ if (details.printedReceived)
45
+ message += details.printedReceived + "\n";
46
+ if (details.timedOut && details.timeout)
47
+ message += `Timeout: ${align ? " " : ""}${details.timeout}ms
36
48
  `;
37
- return header;
49
+ if (details.printedDiff)
50
+ message += details.printedDiff + "\n";
51
+ if (details.errorMessage) {
52
+ message += details.errorMessage;
53
+ if (!details.errorMessage.endsWith("\n"))
54
+ message += "\n";
55
+ }
56
+ message += callLogText(details.log);
57
+ return message;
38
58
  }
39
59
  class ExpectError extends Error {
40
60
  constructor(jestError, customMessage, stackFrames) {
@@ -50,10 +70,18 @@ class ExpectError extends Error {
50
70
  function isJestError(e) {
51
71
  return e instanceof Error && "matcherResult" in e;
52
72
  }
73
+ const callLogText = (log) => {
74
+ if (!log || !log.some((l) => !!l))
75
+ return "";
76
+ return `
77
+ Call log:
78
+ ${(0, import_expectBundle.DIM_COLOR)(log.join("\n"))}
79
+ `;
80
+ };
53
81
  // Annotate the CommonJS export names for ESM import in node:
54
82
  0 && (module.exports = {
55
83
  ExpectError,
56
- isJestError,
57
- kNoElementsFoundError,
58
- matcherHint
84
+ callLogText,
85
+ formatMatcherMessage,
86
+ isJestError
59
87
  });