@smoothdeploy/playwright 1.58.4 → 1.61.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 (182) hide show
  1. package/ThirdPartyNotices.txt +8 -5036
  2. package/lib/agents/agentParser.js +2 -2
  3. package/lib/agents/generateAgents.js +69 -22
  4. package/lib/agents/playwright-test-healer.agent.md +1 -0
  5. package/lib/agents/playwright-test-planner.agent.md +2 -1
  6. package/lib/cli/reportActions.js +78 -0
  7. package/lib/cli/testActions.js +213 -0
  8. package/lib/common/index.js +2931 -0
  9. package/lib/common/index.js.txt +35 -0
  10. package/lib/errorContext.js +130 -0
  11. package/lib/index.js +276 -219
  12. package/lib/{isomorphic/testServerConnection.js → isomorphic.js} +70 -35
  13. package/lib/isomorphic.js.txt +9 -0
  14. package/lib/loader/loaderProcessEntry.js +34 -0
  15. package/lib/loader/loaderProcessEntry.js.txt +9 -0
  16. package/lib/matchers/expect.js +12990 -249
  17. package/lib/matchers/expect.js.LICENSE +693 -0
  18. package/lib/matchers/expect.js.txt +72 -0
  19. package/lib/mcp/test/browserBackend.js +49 -22
  20. package/lib/mcp/test/generatorTools.js +16 -16
  21. package/lib/mcp/test/plannerTools.js +25 -20
  22. package/lib/mcp/test/seed.js +7 -7
  23. package/lib/mcp/test/testBackend.js +30 -30
  24. package/lib/mcp/test/testContext.js +54 -33
  25. package/lib/mcp/test/testTools.js +15 -25
  26. package/lib/{internalsForTest.js → package.js} +13 -8
  27. package/lib/program.js +71 -265
  28. package/lib/runner/index.js +8389 -0
  29. package/lib/runner/index.js.txt +60 -0
  30. package/lib/transform/babelBundle.js +71002 -18
  31. package/lib/transform/babelBundle.js.LICENSE +2359 -0
  32. package/lib/transform/babelBundle.js.txt +325 -0
  33. package/lib/transform/esmLoader.js +5994 -33
  34. package/lib/transform/esmLoader.js.LICENSE +335 -0
  35. package/lib/transform/esmLoader.js.txt +57 -0
  36. package/lib/util.js +48 -48
  37. package/lib/worker/workerProcessEntry.js +1868 -0
  38. package/lib/worker/workerProcessEntry.js.txt +20 -0
  39. package/package.json +5 -16
  40. package/test.mjs +1 -0
  41. package/types/test.d.ts +238 -28
  42. package/types/testReporter.d.ts +7 -5
  43. package/lib/common/config.js +0 -282
  44. package/lib/common/configLoader.js +0 -344
  45. package/lib/common/esmLoaderHost.js +0 -104
  46. package/lib/common/expectBundle.js +0 -28
  47. package/lib/common/expectBundleImpl.js +0 -407
  48. package/lib/common/fixtures.js +0 -302
  49. package/lib/common/ipc.js +0 -60
  50. package/lib/common/poolBuilder.js +0 -85
  51. package/lib/common/process.js +0 -132
  52. package/lib/common/suiteUtils.js +0 -140
  53. package/lib/common/test.js +0 -321
  54. package/lib/common/testLoader.js +0 -101
  55. package/lib/common/testType.js +0 -298
  56. package/lib/common/validators.js +0 -68
  57. package/lib/fsWatcher.js +0 -67
  58. package/lib/isomorphic/events.js +0 -77
  59. package/lib/isomorphic/folders.js +0 -30
  60. package/lib/isomorphic/stringInternPool.js +0 -69
  61. package/lib/isomorphic/teleReceiver.js +0 -521
  62. package/lib/isomorphic/teleSuiteUpdater.js +0 -157
  63. package/lib/isomorphic/testServerInterface.js +0 -16
  64. package/lib/isomorphic/testTree.js +0 -329
  65. package/lib/isomorphic/types.d.js +0 -16
  66. package/lib/loader/loaderMain.js +0 -59
  67. package/lib/matchers/matcherHint.js +0 -44
  68. package/lib/matchers/matchers.js +0 -383
  69. package/lib/matchers/toBeTruthy.js +0 -75
  70. package/lib/matchers/toEqual.js +0 -100
  71. package/lib/matchers/toHaveURL.js +0 -101
  72. package/lib/matchers/toMatchAriaSnapshot.js +0 -159
  73. package/lib/matchers/toMatchSnapshot.js +0 -342
  74. package/lib/matchers/toMatchText.js +0 -99
  75. package/lib/mcp/browser/browserContextFactory.js +0 -329
  76. package/lib/mcp/browser/browserServerBackend.js +0 -84
  77. package/lib/mcp/browser/config.js +0 -421
  78. package/lib/mcp/browser/context.js +0 -244
  79. package/lib/mcp/browser/response.js +0 -278
  80. package/lib/mcp/browser/sessionLog.js +0 -75
  81. package/lib/mcp/browser/tab.js +0 -343
  82. package/lib/mcp/browser/tools/common.js +0 -65
  83. package/lib/mcp/browser/tools/console.js +0 -46
  84. package/lib/mcp/browser/tools/dialogs.js +0 -60
  85. package/lib/mcp/browser/tools/evaluate.js +0 -61
  86. package/lib/mcp/browser/tools/files.js +0 -58
  87. package/lib/mcp/browser/tools/form.js +0 -63
  88. package/lib/mcp/browser/tools/install.js +0 -72
  89. package/lib/mcp/browser/tools/keyboard.js +0 -107
  90. package/lib/mcp/browser/tools/mouse.js +0 -107
  91. package/lib/mcp/browser/tools/navigate.js +0 -71
  92. package/lib/mcp/browser/tools/network.js +0 -63
  93. package/lib/mcp/browser/tools/open.js +0 -57
  94. package/lib/mcp/browser/tools/pdf.js +0 -49
  95. package/lib/mcp/browser/tools/runCode.js +0 -78
  96. package/lib/mcp/browser/tools/screenshot.js +0 -93
  97. package/lib/mcp/browser/tools/snapshot.js +0 -173
  98. package/lib/mcp/browser/tools/tabs.js +0 -67
  99. package/lib/mcp/browser/tools/tool.js +0 -47
  100. package/lib/mcp/browser/tools/tracing.js +0 -74
  101. package/lib/mcp/browser/tools/utils.js +0 -94
  102. package/lib/mcp/browser/tools/verify.js +0 -143
  103. package/lib/mcp/browser/tools/wait.js +0 -63
  104. package/lib/mcp/browser/tools.js +0 -84
  105. package/lib/mcp/browser/watchdog.js +0 -44
  106. package/lib/mcp/config.d.js +0 -16
  107. package/lib/mcp/extension/cdpRelay.js +0 -351
  108. package/lib/mcp/extension/extensionContextFactory.js +0 -76
  109. package/lib/mcp/extension/protocol.js +0 -28
  110. package/lib/mcp/index.js +0 -61
  111. package/lib/mcp/log.js +0 -35
  112. package/lib/mcp/program.js +0 -111
  113. package/lib/mcp/sdk/exports.js +0 -28
  114. package/lib/mcp/sdk/http.js +0 -152
  115. package/lib/mcp/sdk/inProcessTransport.js +0 -71
  116. package/lib/mcp/sdk/server.js +0 -223
  117. package/lib/mcp/sdk/tool.js +0 -47
  118. package/lib/mcp/terminal/cli.js +0 -296
  119. package/lib/mcp/terminal/command.js +0 -56
  120. package/lib/mcp/terminal/commands.js +0 -333
  121. package/lib/mcp/terminal/daemon.js +0 -129
  122. package/lib/mcp/terminal/help.json +0 -32
  123. package/lib/mcp/terminal/helpGenerator.js +0 -88
  124. package/lib/mcp/terminal/socketConnection.js +0 -80
  125. package/lib/plugins/gitCommitInfoPlugin.js +0 -198
  126. package/lib/plugins/index.js +0 -28
  127. package/lib/plugins/webServerPlugin.js +0 -237
  128. package/lib/reporters/base.js +0 -634
  129. package/lib/reporters/blob.js +0 -138
  130. package/lib/reporters/dot.js +0 -99
  131. package/lib/reporters/empty.js +0 -32
  132. package/lib/reporters/github.js +0 -128
  133. package/lib/reporters/html.js +0 -633
  134. package/lib/reporters/internalReporter.js +0 -138
  135. package/lib/reporters/json.js +0 -254
  136. package/lib/reporters/junit.js +0 -232
  137. package/lib/reporters/line.js +0 -131
  138. package/lib/reporters/list.js +0 -253
  139. package/lib/reporters/listModeReporter.js +0 -69
  140. package/lib/reporters/markdown.js +0 -144
  141. package/lib/reporters/merge.js +0 -558
  142. package/lib/reporters/multiplexer.js +0 -112
  143. package/lib/reporters/reporterV2.js +0 -102
  144. package/lib/reporters/smoothdeploy.js +0 -333
  145. package/lib/reporters/teleEmitter.js +0 -317
  146. package/lib/reporters/versions/blobV1.js +0 -16
  147. package/lib/runner/dispatcher.js +0 -531
  148. package/lib/runner/failureTracker.js +0 -72
  149. package/lib/runner/lastRun.js +0 -77
  150. package/lib/runner/loadUtils.js +0 -334
  151. package/lib/runner/loaderHost.js +0 -89
  152. package/lib/runner/processHost.js +0 -180
  153. package/lib/runner/projectUtils.js +0 -241
  154. package/lib/runner/rebase.js +0 -189
  155. package/lib/runner/reporters.js +0 -140
  156. package/lib/runner/sigIntWatcher.js +0 -96
  157. package/lib/runner/storage.js +0 -91
  158. package/lib/runner/taskRunner.js +0 -127
  159. package/lib/runner/tasks.js +0 -410
  160. package/lib/runner/testGroups.js +0 -125
  161. package/lib/runner/testRunner.js +0 -398
  162. package/lib/runner/testServer.js +0 -269
  163. package/lib/runner/uiModeReporter.js +0 -30
  164. package/lib/runner/vcs.js +0 -72
  165. package/lib/runner/watchMode.js +0 -396
  166. package/lib/runner/workerHost.js +0 -104
  167. package/lib/third_party/pirates.js +0 -62
  168. package/lib/third_party/tsconfig-loader.js +0 -103
  169. package/lib/transform/babelBundleImpl.js +0 -461
  170. package/lib/transform/compilationCache.js +0 -274
  171. package/lib/transform/md.js +0 -221
  172. package/lib/transform/portTransport.js +0 -67
  173. package/lib/transform/transform.js +0 -303
  174. package/lib/utilsBundle.js +0 -50
  175. package/lib/utilsBundleImpl.js +0 -103
  176. package/lib/worker/fixtureRunner.js +0 -262
  177. package/lib/worker/testInfo.js +0 -536
  178. package/lib/worker/testTracing.js +0 -345
  179. package/lib/worker/timeoutManager.js +0 -174
  180. package/lib/worker/util.js +0 -31
  181. package/lib/worker/workerMain.js +0 -530
  182. /package/lib/{common/globals.js → globals.js} +0 -0
package/lib/index.js CHANGED
@@ -29,28 +29,37 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
29
29
  var index_exports = {};
30
30
  __export(index_exports, {
31
31
  _baseTest: () => _baseTest,
32
- defineConfig: () => import_configLoader.defineConfig,
32
+ _utilityTest: () => _utilityTest,
33
+ defineConfig: () => import_common2.defineConfig,
33
34
  expect: () => import_expect.expect,
34
35
  mergeExpects: () => import_expect2.mergeExpects,
35
- mergeTests: () => import_testType2.mergeTests,
36
+ mergeTests: () => import_common2.mergeTests,
36
37
  test: () => test
37
38
  });
38
39
  module.exports = __toCommonJS(index_exports);
39
40
  var import_fs = __toESM(require("fs"));
40
41
  var import_path = __toESM(require("path"));
41
42
  var playwrightLibrary = __toESM(require("playwright-core"));
42
- var import_utils = require("playwright-core/lib/utils");
43
- var import_screenshotCompositor = require("playwright-core/lib/server/screenshotCompositor");
43
+ var import_coreBundle = require("playwright-core/lib/coreBundle");
44
44
  var import_utilsBundle = require("playwright-core/lib/utilsBundle");
45
- var import_globals = require("./common/globals");
46
- var import_testType = require("./common/testType");
45
+ var import_errorContext = require("./errorContext");
46
+ var import_common = require("./common");
47
+ var globals = __toESM(require("./globals"));
48
+ var import_package = require("./package");
47
49
  var import_browserBackend = require("./mcp/test/browserBackend");
48
50
  var import_expect = require("./matchers/expect");
49
- var import_configLoader = require("./common/configLoader");
50
- var import_testType2 = require("./common/testType");
51
+ var import_common2 = require("./common");
51
52
  var import_expect2 = require("./matchers/expect");
52
- const _baseTest = import_testType.rootTestType.test;
53
- (0, import_utils.setBoxedStackPrefixes)([import_path.default.dirname(require.resolve("../package.json"))]);
53
+ const { asLocatorDescription } = require("playwright-core/lib/coreBundle").iso;
54
+ const { getActionGroup, renderTitleForCall } = require("playwright-core/lib/coreBundle").iso;
55
+ const { escapeHTML } = require("playwright-core/lib/coreBundle").iso;
56
+ const { jsonStringifyForceASCII } = require("playwright-core/lib/coreBundle").utils;
57
+ const { createGuid } = require("playwright-core/lib/coreBundle").utils;
58
+ const { debugMode } = require("playwright-core/lib/coreBundle").utils;
59
+ const { setBoxedStackPrefixes } = require("playwright-core/lib/coreBundle").utils;
60
+ const { currentZone } = require("playwright-core/lib/coreBundle").utils;
61
+ const _baseTest = import_common.testType.rootTestType.test;
62
+ setBoxedStackPrefixes([import_package.packageRoot]);
54
63
  if (process["__pw_initiator__"]) {
55
64
  const originalStackTraceLimit = Error.stackTraceLimit;
56
65
  Error.stackTraceLimit = 200;
@@ -62,28 +71,159 @@ if (process["__pw_initiator__"]) {
62
71
  } else {
63
72
  process["__pw_initiator__"] = new Error().stack;
64
73
  }
65
- const playwrightFixtures = {
66
- defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
67
- browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
74
+ const utilityFixtures = {
68
75
  playwright: [async ({}, use) => {
69
76
  await use(require("playwright-core"));
70
77
  }, { scope: "worker", box: true }],
78
+ screenshot: ["off", { scope: "worker", option: true, box: true }],
79
+ trace: ["off", { scope: "worker", option: true, box: true }],
80
+ launchOptions: [{}, { scope: "worker", option: true, box: true }],
71
81
  headless: [({ launchOptions }, use) => use(launchOptions.headless ?? true), { scope: "worker", option: true, box: true }],
82
+ resultScreenshots: [false, { scope: "worker", option: true, box: true }],
83
+ pauseOnFailure: [false, { scope: "worker", option: true, box: true }],
84
+ testIdAttribute: ["data-testid", { option: true, box: true }],
85
+ _combinedContextOptions: [{}, { box: true }],
86
+ _setupArtifacts: [async ({ playwright, headless, screenshot, resultScreenshots, pauseOnFailure, _combinedContextOptions }, use, testInfo) => {
87
+ testInfo.setTimeout(testInfo.project.timeout);
88
+ const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, resultScreenshots, pauseOnFailure && !headless);
89
+ await artifactsRecorder.willStartTest(testInfo);
90
+ const tracingGroupSteps = [];
91
+ const pausedContexts = /* @__PURE__ */ new Set();
92
+ const csiListener = {
93
+ onApiCallBegin: (data, channel) => {
94
+ const testInfo2 = globals.currentTestInfo();
95
+ if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
96
+ return;
97
+ const zone = currentZone().data("stepZone");
98
+ const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
99
+ if (zone && zone.category === "expect" && isExpectCall) {
100
+ if (zone.apiName)
101
+ data.apiName = zone.apiName;
102
+ if (zone.shortTitle || zone.title)
103
+ data.title = zone.shortTitle ?? zone.title;
104
+ data.stepId = zone.stepId;
105
+ if (resultScreenshots && channel.params?.selector)
106
+ data._channel = channel;
107
+ return;
108
+ }
109
+ const step = testInfo2._addStep({
110
+ location: data.frames[0],
111
+ category: "pw:api",
112
+ title: renderTitle(channel.type, channel.method, channel.params, data.title),
113
+ apiName: data.apiName,
114
+ params: channel.params,
115
+ group: getActionGroup({ type: channel.type, method: channel.method })
116
+ }, tracingGroupSteps[tracingGroupSteps.length - 1]);
117
+ data.userData = step;
118
+ data.stepId = step.stepId;
119
+ if (data.apiName === "tracing.group")
120
+ tracingGroupSteps.push(step);
121
+ if (resultScreenshots && channel.params?.selector)
122
+ data._channel = channel;
123
+ },
124
+ onApiCallEnd: async (data) => {
125
+ if (data.apiName === "tracing.group")
126
+ return;
127
+ if (data.apiName === "tracing.groupEnd") {
128
+ const step2 = tracingGroupSteps.pop();
129
+ step2?.complete({ error: data.error });
130
+ return;
131
+ }
132
+ const channel = data._channel;
133
+ if (channel && !data.error) {
134
+ const testInfo2 = globals.currentTestInfo();
135
+ if (testInfo2) {
136
+ try {
137
+ await captureResultScreenshot(channel, data, testInfo2);
138
+ } catch {
139
+ }
140
+ }
141
+ }
142
+ const step = data.userData;
143
+ step?.complete({ error: data.error });
144
+ },
145
+ onWillPause: ({ keepTestTimeout }) => {
146
+ if (!keepTestTimeout)
147
+ globals.currentTestInfo()?._setIgnoreTimeouts(true);
148
+ },
149
+ runBeforeCreateBrowserContext: async (options) => {
150
+ for (const [key, value] of Object.entries(_combinedContextOptions)) {
151
+ if (!(key in options))
152
+ options[key] = value;
153
+ }
154
+ },
155
+ runBeforeCreateRequestContext: async (options) => {
156
+ for (const [key, value] of Object.entries(_combinedContextOptions)) {
157
+ if (!(key in options))
158
+ options[key] = value;
159
+ }
160
+ },
161
+ runAfterCreateBrowserContext: async (context) => {
162
+ context.debugger.on("pausedstatechanged", () => {
163
+ const paused = !!context.debugger.pausedDetails();
164
+ if (pausedContexts.has(context) && !paused) {
165
+ pausedContexts.delete(context);
166
+ testInfo._setIgnoreTimeouts(false);
167
+ } else if (!pausedContexts.has(context) && paused) {
168
+ pausedContexts.add(context);
169
+ testInfo._setIgnoreTimeouts(true);
170
+ }
171
+ });
172
+ await artifactsRecorder.didCreateBrowserContext(context);
173
+ const currentTestInfo = globals.currentTestInfo();
174
+ if (currentTestInfo) {
175
+ attachConnectedHeaderIfNeeded(currentTestInfo, context.browser());
176
+ currentTestInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(currentTestInfo, context);
177
+ await (0, import_browserBackend.runDaemonForContext)(currentTestInfo, context);
178
+ }
179
+ },
180
+ runAfterCreateRequestContext: async (context) => {
181
+ await artifactsRecorder.didCreateRequestContext(context);
182
+ },
183
+ runBeforeCloseBrowserContext: async (context) => {
184
+ await artifactsRecorder.willCloseBrowserContext(context);
185
+ },
186
+ runBeforeCloseRequestContext: async (context) => {
187
+ await artifactsRecorder.willCloseRequestContext(context);
188
+ }
189
+ };
190
+ const clientInstrumentation = playwright._instrumentation;
191
+ clientInstrumentation.addListener(csiListener);
192
+ await use();
193
+ clientInstrumentation.removeListener(csiListener);
194
+ await artifactsRecorder.didFinishTest();
195
+ }, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }],
196
+ request: async ({ playwright }, use) => {
197
+ const request = await playwright.request.newContext();
198
+ await use(request);
199
+ const hook = test.info()._currentHookType();
200
+ if (hook === "beforeAll") {
201
+ await request.dispose({ reason: [
202
+ `Fixture { request } from beforeAll cannot be reused in a test.`,
203
+ ` - Recommended fix: use a separate { request } in the test.`,
204
+ ` - Alternatively, manually create APIRequestContext in beforeAll and dispose it in afterAll.`,
205
+ `See https://playwright.dev/docs/api-testing#sending-api-requests-from-ui-tests for more details.`
206
+ ].join("\n") });
207
+ } else {
208
+ await request.dispose();
209
+ }
210
+ }
211
+ };
212
+ const _utilityTest = _baseTest.extend(utilityFixtures);
213
+ const playwrightFixtures = {
214
+ defaultBrowserType: ["chromium", { scope: "worker", option: true, box: true }],
215
+ browserName: [({ defaultBrowserType }, use) => use(defaultBrowserType), { scope: "worker", option: true, box: true }],
72
216
  channel: [({ launchOptions }, use) => use(launchOptions.channel), { scope: "worker", option: true, box: true }],
73
- launchOptions: [{}, { scope: "worker", option: true, box: true }],
74
217
  connectOptions: [async ({ _optionConnectOptions }, use) => {
75
218
  await use(connectOptionsFromEnv() || _optionConnectOptions);
76
219
  }, { scope: "worker", option: true, box: true }],
77
- screenshot: ["off", { scope: "worker", option: true, box: true }],
78
- resultScreenshots: [false, { scope: "worker", option: true, box: true }],
79
- pauseOnFailure: [false, { scope: "worker", option: true, box: true }],
80
220
  video: ["off", { scope: "worker", option: true, box: true }],
81
- trace: ["off", { scope: "worker", option: true, box: true }],
82
221
  _browserOptions: [async ({ playwright, headless, channel, launchOptions }, use) => {
83
222
  const options = {
84
223
  handleSIGINT: false,
85
224
  ...launchOptions,
86
- tracesDir: tracing().tracesDir()
225
+ tracesDir: tracing().tracesDir(),
226
+ artifactsDir: tracing().artifactsDir()
87
227
  };
88
228
  if (headless !== void 0)
89
229
  options.headless = headless;
@@ -93,16 +233,16 @@ const playwrightFixtures = {
93
233
  await use(options);
94
234
  playwright._defaultLaunchOptions = void 0;
95
235
  }, { scope: "worker", auto: true, box: true }],
96
- browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use) => {
236
+ browser: [async ({ playwright, browserName, _browserOptions, connectOptions }, use, workerInfo) => {
97
237
  if (!["chromium", "firefox", "webkit"].includes(browserName))
98
238
  throw new Error(`Unexpected browserName "${browserName}", must be one of "chromium", "firefox" or "webkit"`);
99
239
  if (connectOptions) {
100
- const browser2 = await playwright[browserName].connect({
240
+ const browser2 = await playwright[browserName].connect(connectOptions.wsEndpoint, {
101
241
  ...connectOptions,
102
- exposeNetwork: connectOptions.exposeNetwork ?? connectOptions._exposeNetwork,
242
+ exposeNetwork: connectOptions.exposeNetwork,
103
243
  headers: {
104
244
  // HTTP headers are ASCII only (not UTF-8).
105
- "x-playwright-launch-options": (0, import_utils.jsonStringifyForceASCII)(_browserOptions),
245
+ "x-playwright-launch-options": jsonStringifyForceASCII(_browserOptions),
106
246
  ...connectOptions.headers
107
247
  }
108
248
  });
@@ -111,6 +251,8 @@ const playwrightFixtures = {
111
251
  return;
112
252
  }
113
253
  const browser = await playwright[browserName].launch();
254
+ if (process.env.PLAYWRIGHT_DASHBOARD)
255
+ await browser.bind(`worker-${workerInfo.parallelIndex}`);
114
256
  await use(browser);
115
257
  await browser.close({ reason: "Test ended." });
116
258
  }, { scope: "worker", timeout: 0 }],
@@ -135,14 +277,12 @@ const playwrightFixtures = {
135
277
  userAgent: [({ contextOptions }, use) => use(contextOptions.userAgent), { option: true, box: true }],
136
278
  viewport: [({ contextOptions }, use) => use(contextOptions.viewport === void 0 ? null : contextOptions.viewport), { option: true, box: true }],
137
279
  actionTimeout: [0, { option: true, box: true }],
138
- testIdAttribute: ["data-testid", { option: true, box: true }],
139
280
  navigationTimeout: [0, { option: true, box: true }],
140
281
  baseURL: [async ({}, use) => {
141
282
  await use(process.env.PLAYWRIGHT_TEST_BASE_URL);
142
283
  }, { option: true, box: true }],
143
284
  serviceWorkers: [({ contextOptions }, use) => use(contextOptions.serviceWorkers ?? "allow"), { option: true, box: true }],
144
285
  contextOptions: [{}, { option: true, box: true }],
145
- agentOptions: [void 0, { option: true, box: true }],
146
286
  _combinedContextOptions: [async ({
147
287
  acceptDownloads,
148
288
  bypassCSP,
@@ -217,116 +357,21 @@ const playwrightFixtures = {
217
357
  ...contextOptions,
218
358
  ...options
219
359
  });
220
- }, { box: true }],
221
- _setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, testInfo) => {
360
+ }, { scope: "test", box: true }],
361
+ _setupContextOptions: [async ({ playwright, actionTimeout, navigationTimeout, testIdAttribute }, use, _testInfo) => {
362
+ const testInfo = _testInfo;
222
363
  if (testIdAttribute)
223
364
  playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
224
365
  testInfo.snapshotSuffix = process.platform;
225
- if ((0, import_utils.debugMode)() === "inspector")
226
- testInfo._setDebugMode();
366
+ testInfo._onCustomMessageCallback = () => Promise.reject(new Error("Only tests that use default Playwright context or page fixture support test_debug"));
367
+ if (debugMode() === "inspector")
368
+ testInfo._setIgnoreTimeouts(true);
227
369
  playwright._defaultContextTimeout = actionTimeout || 0;
228
370
  playwright._defaultContextNavigationTimeout = navigationTimeout || 0;
229
371
  await use();
230
372
  playwright._defaultContextTimeout = void 0;
231
373
  playwright._defaultContextNavigationTimeout = void 0;
232
374
  }, { auto: "all-hooks-included", title: "context configuration", box: true }],
233
- _setupArtifacts: [async ({ playwright, headless, screenshot, resultScreenshots, pauseOnFailure, _combinedContextOptions }, use, testInfo) => {
234
- testInfo.setTimeout(testInfo.project.timeout);
235
- const artifactsRecorder = new ArtifactsRecorder(playwright, tracing().artifactsDir(), screenshot, resultScreenshots, pauseOnFailure && !headless);
236
- await artifactsRecorder.willStartTest(testInfo);
237
- const tracingGroupSteps = [];
238
- const csiListener = {
239
- onApiCallBegin: (data, channel) => {
240
- const testInfo2 = (0, import_globals.currentTestInfo)();
241
- if (!testInfo2 || data.apiName.includes("setTestIdAttribute") || data.apiName === "tracing.groupEnd")
242
- return;
243
- const zone = (0, import_utils.currentZone)().data("stepZone");
244
- const isExpectCall = data.apiName === "locator._expect" || data.apiName === "frame._expect" || data.apiName === "page._expectScreenshot";
245
- if (zone && zone.category === "expect" && isExpectCall) {
246
- if (zone.apiName)
247
- data.apiName = zone.apiName;
248
- if (zone.shortTitle || zone.title)
249
- data.title = zone.shortTitle ?? zone.title;
250
- data.stepId = zone.stepId;
251
- if (resultScreenshots && channel.params?.selector)
252
- data._channel = channel;
253
- return;
254
- }
255
- const stepTitle = renderTitle(channel.type, channel.method, channel.params, data.title);
256
- const step = testInfo2._addStep({
257
- location: data.frames[0],
258
- category: "pw:api",
259
- title: stepTitle,
260
- apiName: data.apiName,
261
- params: channel.params,
262
- group: (0, import_utils.getActionGroup)({ type: channel.type, method: channel.method })
263
- }, tracingGroupSteps[tracingGroupSteps.length - 1]);
264
- data.userData = step;
265
- data.stepId = step.stepId;
266
- if (data.apiName === "tracing.group")
267
- tracingGroupSteps.push(step);
268
- if (resultScreenshots && channel.params?.selector)
269
- data._channel = channel;
270
- },
271
- onApiCallEnd: async (data) => {
272
- if (data.apiName === "tracing.group")
273
- return;
274
- if (data.apiName === "tracing.groupEnd") {
275
- const step2 = tracingGroupSteps.pop();
276
- step2?.complete({ error: data.error });
277
- return;
278
- }
279
- const channel = data._channel;
280
- if (channel && !data.error) {
281
- const testInfo2 = (0, import_globals.currentTestInfo)();
282
- if (testInfo2) {
283
- try {
284
- await captureResultScreenshot(channel, data, testInfo2);
285
- } catch {
286
- }
287
- }
288
- }
289
- const step = data.userData;
290
- step?.complete({ error: data.error });
291
- },
292
- onWillPause: ({ keepTestTimeout }) => {
293
- if (!keepTestTimeout)
294
- (0, import_globals.currentTestInfo)()?._setDebugMode();
295
- },
296
- runBeforeCreateBrowserContext: async (options) => {
297
- for (const [key, value] of Object.entries(_combinedContextOptions)) {
298
- if (!(key in options))
299
- options[key] = value;
300
- }
301
- },
302
- runBeforeCreateRequestContext: async (options) => {
303
- for (const [key, value] of Object.entries(_combinedContextOptions)) {
304
- if (!(key in options))
305
- options[key] = value;
306
- }
307
- },
308
- runAfterCreateBrowserContext: async (context) => {
309
- await artifactsRecorder.didCreateBrowserContext(context);
310
- const testInfo2 = (0, import_globals.currentTestInfo)();
311
- if (testInfo2)
312
- attachConnectedHeaderIfNeeded(testInfo2, context.browser());
313
- },
314
- runAfterCreateRequestContext: async (context) => {
315
- await artifactsRecorder.didCreateRequestContext(context);
316
- },
317
- runBeforeCloseBrowserContext: async (context) => {
318
- await artifactsRecorder.willCloseBrowserContext(context);
319
- },
320
- runBeforeCloseRequestContext: async (context) => {
321
- await artifactsRecorder.willCloseRequestContext(context);
322
- }
323
- };
324
- const clientInstrumentation = playwright._instrumentation;
325
- clientInstrumentation.addListener(csiListener);
326
- await use();
327
- clientInstrumentation.removeListener(csiListener);
328
- await artifactsRecorder.didFinishTest();
329
- }, { auto: "all-hooks-included", title: "trace recording", box: true, timeout: 0 }],
330
375
  _contextFactory: [async ({
331
376
  browser,
332
377
  video,
@@ -348,23 +393,15 @@ const playwrightFixtures = {
348
393
  `If you would like to configure your page before each test, do that in beforeEach hook instead.`
349
394
  ].join("\n"));
350
395
  }
396
+ const show = typeof video === "string" ? void 0 : video.show;
351
397
  const videoOptions = captureVideo ? {
352
398
  recordVideo: {
353
399
  dir: tracing().artifactsDir(),
354
- size: typeof video === "string" ? void 0 : video.size
400
+ size: typeof video === "string" ? void 0 : video.size,
401
+ showActions: show?.actions
355
402
  }
356
403
  } : {};
357
404
  const context = await browser.newContext({ ...videoOptions, ...options });
358
- if (process.env.PW_CLOCK === "frozen") {
359
- await context._wrapApiCall(async () => {
360
- await context.clock.install({ time: 0 });
361
- await context.clock.pauseAt(1e3);
362
- }, { internal: true });
363
- } else if (process.env.PW_CLOCK === "realtime") {
364
- await context._wrapApiCall(async () => {
365
- await context.clock.install({ time: 0 });
366
- }, { internal: true });
367
- }
368
405
  let closed = false;
369
406
  const close = async () => {
370
407
  if (closed)
@@ -372,8 +409,7 @@ const playwrightFixtures = {
372
409
  closed = true;
373
410
  const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
374
411
  await context.close({ reason: closeReason });
375
- const testFailed = testInfo.status !== testInfo.expectedStatus;
376
- const preserveVideo = captureVideo && (videoMode === "on" || testFailed && videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1);
412
+ const preserveVideo = captureVideo && shouldPreserveVideo(videoMode, testInfo);
377
413
  if (preserveVideo) {
378
414
  const { pagesWithVideo: pagesForVideo } = contexts.get(context);
379
415
  const videos = pagesForVideo.map((p) => p.video()).filter((video2) => !!video2);
@@ -405,18 +441,20 @@ const playwrightFixtures = {
405
441
  const reuse = mode === "when-possible" && normalizeVideoMode(video) === "off";
406
442
  await use(reuse);
407
443
  }, { scope: "worker", title: "context", box: true }],
408
- context: async ({ browser, _reuseContext, _contextFactory }, use, testInfo) => {
444
+ context: async ({ browser, video, _reuseContext, _contextFactory }, use, testInfoPublic) => {
409
445
  const browserImpl = browser;
446
+ const testInfo = testInfoPublic;
447
+ const show = typeof video === "string" ? void 0 : video.show;
410
448
  attachConnectedHeaderIfNeeded(testInfo, browserImpl);
411
449
  if (!_reuseContext) {
412
450
  const { context: context2, close } = await _contextFactory();
413
- testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context2);
451
+ await installScreencastTitleUpdater(testInfo, context2, show?.test);
414
452
  await use(context2);
415
453
  await close();
416
454
  return;
417
455
  }
418
456
  const context = await browserImpl._wrapApiCall(() => browserImpl._newContextForReuse(), { internal: true });
419
- testInfo._onCustomMessageCallback = (0, import_browserBackend.createCustomMessageHandler)(testInfo, context);
457
+ await installScreencastTitleUpdater(testInfo, context, show?.test);
420
458
  await use(context);
421
459
  const closeReason = testInfo.status === "timedOut" ? "Test timeout of " + testInfo.timeout + "ms exceeded." : "Test ended.";
422
460
  await browserImpl._wrapApiCall(() => browserImpl._disconnectFromReusedContext(closeReason), { internal: true });
@@ -430,54 +468,6 @@ const playwrightFixtures = {
430
468
  if (!page)
431
469
  page = await context.newPage();
432
470
  await use(page);
433
- },
434
- agent: async ({ page, agentOptions }, use, testInfo) => {
435
- const testInfoImpl = testInfo;
436
- const cachePathTemplate = agentOptions?.cachePathTemplate ?? "{testDir}/{testFilePath}-cache.json";
437
- const resolvedCacheFile = testInfoImpl._applyPathTemplate(cachePathTemplate, "", ".json");
438
- const cacheFile = testInfoImpl.config.runAgents === "all" ? void 0 : await testInfoImpl._cloneStorage(resolvedCacheFile);
439
- const cacheOutFile = import_path.default.join(testInfoImpl.artifactsDir(), "agent-cache-" + (0, import_utils.createGuid)() + ".json");
440
- const provider = agentOptions?.provider && testInfo.config.runAgents !== "none" ? agentOptions.provider : void 0;
441
- if (provider)
442
- testInfo.setTimeout(0);
443
- const cache = {
444
- cacheFile,
445
- cacheOutFile
446
- };
447
- const agent = await page.agent({
448
- provider,
449
- cache,
450
- limits: agentOptions?.limits,
451
- secrets: agentOptions?.secrets,
452
- systemPrompt: agentOptions?.systemPrompt,
453
- expect: {
454
- timeout: testInfoImpl._projectInternal.expect?.timeout
455
- }
456
- });
457
- await use(agent);
458
- const usage = await agent.usage();
459
- if (usage.turns > 0)
460
- await testInfoImpl.attach("agent-usage", { contentType: "application/json", body: Buffer.from(JSON.stringify(usage, null, 2)) });
461
- if (!resolvedCacheFile || !cacheOutFile)
462
- return;
463
- if (testInfo.status !== "passed")
464
- return;
465
- await testInfoImpl._upstreamStorage(resolvedCacheFile, cacheOutFile);
466
- },
467
- request: async ({ playwright }, use) => {
468
- const request = await playwright.request.newContext();
469
- await use(request);
470
- const hook = test.info()._currentHookType();
471
- if (hook === "beforeAll") {
472
- await request.dispose({ reason: [
473
- `Fixture { request } from beforeAll cannot be reused in a test.`,
474
- ` - Recommended fix: use a separate { request } in the test.`,
475
- ` - Alternatively, manually create APIRequestContext in beforeAll and dispose it in afterAll.`,
476
- `See https://playwright.dev/docs/api-testing#sending-api-requests-from-ui-tests for more details.`
477
- ].join("\n") });
478
- } else {
479
- await request.dispose();
480
- }
481
471
  }
482
472
  };
483
473
  function normalizeVideoMode(video) {
@@ -489,7 +479,23 @@ function normalizeVideoMode(video) {
489
479
  return videoMode;
490
480
  }
491
481
  function shouldCaptureVideo(videoMode, testInfo) {
492
- return videoMode === "on" || videoMode === "retain-on-failure" || videoMode === "on-first-retry" && testInfo.retry === 1;
482
+ return videoMode === "on" || videoMode === "retain-on-failure" || videoMode === "retain-on-failure-and-retries" || videoMode === "on-first-retry" && testInfo.retry === 1 || videoMode === "on-all-retries" && testInfo.retry > 0 || videoMode === "retain-on-first-failure" && testInfo.retry === 0;
483
+ }
484
+ function shouldPreserveVideo(videoMode, testInfo) {
485
+ const testFailed = testInfo.status !== testInfo.expectedStatus;
486
+ switch (videoMode) {
487
+ case "on":
488
+ case "on-first-retry":
489
+ case "on-all-retries":
490
+ return true;
491
+ case "retain-on-failure":
492
+ case "retain-on-first-failure":
493
+ return testFailed;
494
+ case "retain-on-failure-and-retries":
495
+ return testFailed || testInfo.retry > 0;
496
+ default:
497
+ return false;
498
+ }
493
499
  }
494
500
  function normalizeScreenshotMode(screenshot) {
495
501
  if (!screenshot)
@@ -512,12 +518,12 @@ function attachConnectedHeaderIfNeeded(testInfo, browser) {
512
518
  }
513
519
  }
514
520
  function resolveFileToConfig(file) {
515
- const config = test.info().config.configFile;
516
- if (!config || !file)
521
+ const config2 = test.info().config.configFile;
522
+ if (!config2 || !file)
517
523
  return file;
518
524
  if (import_path.default.isAbsolute(file))
519
525
  return file;
520
- return import_path.default.resolve(import_path.default.dirname(config), file);
526
+ return import_path.default.resolve(import_path.default.dirname(config2), file);
521
527
  }
522
528
  function resolveClientCerticates(clientCertificates) {
523
529
  for (const cert of clientCertificates) {
@@ -596,7 +602,7 @@ class SnapshotRecorder {
596
602
  return;
597
603
  page[this.testInfo._uniqueSymbol] = true;
598
604
  try {
599
- const path2 = temporary ? this._createTemporaryArtifact((0, import_utils.createGuid)() + this._extension) : this._createAttachmentPath();
605
+ const path2 = temporary ? this._createTemporaryArtifact(createGuid() + this._extension) : this._createAttachmentPath();
600
606
  await this._doSnapshot(page, path2);
601
607
  if (temporary)
602
608
  this._temporary.push(path2);
@@ -625,7 +631,7 @@ class ArtifactsRecorder {
625
631
  }
626
632
  async willStartTest(testInfo) {
627
633
  this._testInfo = testInfo;
628
- testInfo._onDidFinishTestFunctionCallback = () => this.didFinishTestFunction();
634
+ testInfo._onDidFinishTestFunctionCallbacks.add(() => this.didFinishTestFunction());
629
635
  this._screenshotRecorder.fixOrdinal();
630
636
  await Promise.all(this._playwright._allContexts().map((context) => this.didCreateBrowserContext(context)));
631
637
  const existingApiRequests = Array.from(this._playwright.request._contexts);
@@ -644,6 +650,8 @@ class ArtifactsRecorder {
644
650
  return;
645
651
  if (this._testInfo.errors.length === 0)
646
652
  return;
653
+ if (this._testInfo.errors.some((e) => e.errorContext))
654
+ return;
647
655
  if (this._pageSnapshot)
648
656
  return;
649
657
  const page = context.pages()[0];
@@ -651,16 +659,16 @@ class ArtifactsRecorder {
651
659
  return;
652
660
  try {
653
661
  await page._wrapApiCall(async () => {
654
- this._pageSnapshot = (await page._snapshotForAI({ timeout: 5e3 })).full;
662
+ this._pageSnapshot = await page.ariaSnapshot({ mode: "ai", timeout: 5e3 });
655
663
  }, { internal: true });
656
664
  } catch {
657
665
  }
658
666
  }
659
667
  async didCreateRequestContext(context) {
660
- await this._startTraceChunkOnContextCreation(context, context._tracing);
668
+ await this._startTraceChunkOnContextCreation(context, context.tracing);
661
669
  }
662
670
  async willCloseRequestContext(context) {
663
- await this._stopTracing(context, context._tracing);
671
+ await this._stopTracing(context, context.tracing);
664
672
  }
665
673
  async didFinishTestFunction() {
666
674
  await this._screenshotRecorder.maybeCapture();
@@ -717,27 +725,29 @@ class ArtifactsRecorder {
717
725
  await Promise.all(leftoverContexts.map(async (context2) => {
718
726
  await this._stopTracing(context2, context2.tracing);
719
727
  }).concat(leftoverApiRequests.map(async (context2) => {
720
- await this._stopTracing(context2, context2._tracing);
728
+ await this._stopTracing(context2, context2.tracing);
721
729
  })));
722
730
  await this._screenshotRecorder.persistTemporary();
723
731
  const context = leftoverContexts[0];
724
732
  if (context)
725
733
  await this._takePageSnapshot(context);
726
- if (this._pageSnapshot && this._testInfo.errors.length > 0 && !this._testInfo.attachments.some((a) => a.name === "error-context")) {
727
- const lines = [
728
- "# Page snapshot",
729
- "",
730
- "```yaml",
731
- this._pageSnapshot,
732
- "```"
733
- ];
734
- const filePath = this._testInfo.outputPath("error-context.md");
735
- await import_fs.default.promises.writeFile(filePath, lines.join("\n"), "utf8");
736
- this._testInfo._attach({
737
- name: "error-context",
738
- contentType: "text/markdown",
739
- path: filePath
740
- }, void 0);
734
+ if (this._testInfo.errors.length > 0) {
735
+ const hasMatcherAriaSnapshot = this._testInfo.errors.some((e) => e.errorContext);
736
+ const errorContextContent = (0, import_errorContext.buildErrorContext)({
737
+ titlePath: this._testInfo.titlePath,
738
+ location: { file: this._testInfo.file, line: this._testInfo.line, column: this._testInfo.column },
739
+ errors: this._testInfo.errors,
740
+ pageSnapshot: hasMatcherAriaSnapshot ? void 0 : this._pageSnapshot
741
+ });
742
+ if (errorContextContent) {
743
+ const filePath = this._testInfo.outputPath("error-context.md");
744
+ await import_fs.default.promises.writeFile(filePath, errorContextContent, "utf8");
745
+ this._testInfo._attach({
746
+ name: "error-context",
747
+ contentType: "text/markdown",
748
+ path: filePath
749
+ }, void 0);
750
+ }
741
751
  }
742
752
  }
743
753
  async _startTraceChunkOnContextCreation(channelOwner, tracing2) {
@@ -805,7 +815,7 @@ async function captureResultScreenshot(channel, data, testInfo) {
805
815
  style: "x-pw-glass { display: none !important; }"
806
816
  });
807
817
  }, { internal: true });
808
- const { full, cropped } = (0, import_screenshotCompositor.compositeHighlight)(pngBuffer, box, cssViewport);
818
+ const { full, cropped } = import_coreBundle.server.compositeHighlight(pngBuffer, box, cssViewport);
809
819
  const safeTestId = testInfo.testId.replace(/[^a-zA-Z0-9_-]/g, "-");
810
820
  const safeStepId = (data.stepId ?? "unknown").replace(/[^a-zA-Z0-9_-]/g, "-");
811
821
  const screenshotDir = import_path.default.join(process.cwd(), ".smoothdeploy-output", "screenshots", safeTestId);
@@ -821,20 +831,67 @@ async function captureResultScreenshot(channel, data, testInfo) {
821
831
  _capturingResultScreenshot = false;
822
832
  }
823
833
  }
834
+ async function installScreencastTitleUpdater(testInfo, context, testAnnotate) {
835
+ if (!testAnnotate)
836
+ return;
837
+ const testTitle = testAnnotate.level === "file" ? [testInfo.titlePath[0]] : testInfo.titlePath;
838
+ const stepStack = [];
839
+ const overlays = /* @__PURE__ */ new Map();
840
+ const position = testAnnotate.position ?? "top-left";
841
+ const fontSize = testAnnotate.fontSize ?? 14;
842
+ const level = testAnnotate.level ?? "step";
843
+ const updateOverlay = async () => {
844
+ const parts = level === "step" ? [...testTitle, ...stepStack] : testTitle;
845
+ const html = createTestOverlay(parts, position, fontSize);
846
+ for (const page of context.pages()) {
847
+ await overlays.get(page)?.dispose();
848
+ overlays.delete(page);
849
+ const disposable = await page.screencast.showOverlay(html);
850
+ overlays.set(page, disposable);
851
+ }
852
+ };
853
+ testInfo._onUserStepBegin = async (title) => {
854
+ stepStack.push(title);
855
+ await updateOverlay();
856
+ };
857
+ testInfo._onUserStepEnd = async () => {
858
+ stepStack.pop();
859
+ await updateOverlay();
860
+ };
861
+ context.on("page", async () => {
862
+ void updateOverlay();
863
+ });
864
+ await updateOverlay();
865
+ }
866
+ function createTestOverlay(parts, position, fontSize) {
867
+ const positionStyles = {
868
+ "top-left": "top: 6px; left: 6px;",
869
+ "top": "top: 6px; left: 50%; transform: translateX(-50%);",
870
+ "top-right": "top: 6px; right: 6px;",
871
+ "bottom-left": "bottom: 6px; left: 6px;",
872
+ "bottom": "bottom: 6px; left: 50%; transform: translateX(-50%);",
873
+ "bottom-right": "bottom: 6px; right: 6px;"
874
+ };
875
+ const posStyle = positionStyles[position] ?? positionStyles["top-left"];
876
+ 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}">
877
+ ${parts.map((p) => `<div>${escapeHTML(p)}</div>`).join("")}
878
+ </div>`;
879
+ }
824
880
  function renderTitle(type, method, params, title) {
825
- const prefix = (0, import_utils.renderTitleForCall)({ title, type, method, params });
881
+ const prefix = renderTitleForCall({ title, type, method, params });
826
882
  let selector;
827
883
  if (params?.["selector"] && typeof params.selector === "string")
828
- selector = (0, import_utils.asLocatorDescription)("javascript", params.selector);
884
+ selector = asLocatorDescription("javascript", params.selector);
829
885
  return prefix + (selector ? ` ${selector}` : "");
830
886
  }
831
887
  function tracing() {
832
888
  return test.info()._tracing;
833
889
  }
834
- const test = _baseTest.extend(playwrightFixtures);
890
+ const test = _utilityTest.extend(playwrightFixtures);
835
891
  // Annotate the CommonJS export names for ESM import in node:
836
892
  0 && (module.exports = {
837
893
  _baseTest,
894
+ _utilityTest,
838
895
  defineConfig,
839
896
  expect,
840
897
  mergeExpects,