playwright-core 1.58.0-alpha-2025-12-09 → 1.58.0-alpha-2025-12-11

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 (37) hide show
  1. package/ThirdPartyNotices.txt +3 -3
  2. package/browsers.json +6 -6
  3. package/lib/client/browser.js +3 -5
  4. package/lib/client/browserType.js +2 -2
  5. package/lib/client/fetch.js +2 -4
  6. package/lib/mcpBundleImpl/index.js +29 -29
  7. package/lib/protocol/serializers.js +5 -0
  8. package/lib/server/agent/actionRunner.js +33 -2
  9. package/lib/server/agent/agent.js +18 -12
  10. package/lib/server/agent/context.js +26 -11
  11. package/lib/server/agent/tools.js +67 -5
  12. package/lib/server/artifact.js +1 -1
  13. package/lib/server/bidi/bidiBrowser.js +13 -4
  14. package/lib/server/bidi/bidiPage.js +9 -1
  15. package/lib/server/bidi/third_party/bidiProtocolCore.js +1 -0
  16. package/lib/server/chromium/crPage.js +5 -5
  17. package/lib/server/dispatchers/dispatcher.js +5 -8
  18. package/lib/server/firefox/ffBrowser.js +1 -1
  19. package/lib/server/firefox/ffPage.js +1 -1
  20. package/lib/server/instrumentation.js +3 -0
  21. package/lib/server/page.js +2 -2
  22. package/lib/server/progress.js +2 -0
  23. package/lib/server/screencast.js +24 -25
  24. package/lib/server/utils/network.js +18 -26
  25. package/lib/server/videoRecorder.js +20 -11
  26. package/lib/server/webkit/wkBrowser.js +1 -1
  27. package/lib/server/webkit/wkPage.js +7 -7
  28. package/lib/vite/traceViewer/index.BVu7tZDe.css +1 -0
  29. package/lib/vite/traceViewer/index.html +2 -2
  30. package/lib/vite/traceViewer/index.zFV_GQE-.js +2 -0
  31. package/lib/vite/traceViewer/sw.bundle.js +3 -1
  32. package/lib/vite/traceViewer/uiMode.DiEbKRwa.js +5 -0
  33. package/lib/vite/traceViewer/uiMode.html +1 -1
  34. package/package.json +1 -1
  35. package/lib/vite/traceViewer/index.C4Y3Aw8n.css +0 -1
  36. package/lib/vite/traceViewer/index.YskCIlQ-.js +0 -2
  37. package/lib/vite/traceViewer/uiMode.CmFFBCQb.js +0 -5
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var serializers_exports = {};
20
20
  __export(serializers_exports, {
21
21
  parseSerializedValue: () => parseSerializedValue,
22
+ serializePlainValue: () => serializePlainValue,
22
23
  serializeValue: () => serializeValue
23
24
  });
24
25
  module.exports = __toCommonJS(serializers_exports);
@@ -90,6 +91,9 @@ function innerParseSerializedValue(value, handles, refs, accessChain) {
90
91
  function serializeValue(value, handleSerializer) {
91
92
  return innerSerializeValue(value, handleSerializer, { lastId: 0, visited: /* @__PURE__ */ new Map() }, []);
92
93
  }
94
+ function serializePlainValue(arg) {
95
+ return serializeValue(arg, (value) => ({ fallThrough: value }));
96
+ }
93
97
  function innerSerializeValue(value, handleSerializer, visitorInfo, accessChain) {
94
98
  const handle = handleSerializer(value);
95
99
  if ("fallThrough" in handle)
@@ -188,5 +192,6 @@ const constructorToTypedArrayKind = new Map(Object.entries(typedArrayKindToConst
188
192
  // Annotate the CommonJS export names for ESM import in node:
189
193
  0 && (module.exports = {
190
194
  parseSerializedValue,
195
+ serializePlainValue,
191
196
  serializeValue
192
197
  });
@@ -18,9 +18,12 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var actionRunner_exports = {};
20
20
  __export(actionRunner_exports, {
21
- runAction: () => runAction
21
+ runAction: () => runAction,
22
+ serializeArgument: () => serializeArgument
22
23
  });
23
24
  module.exports = __toCommonJS(actionRunner_exports);
25
+ var import_expectUtils = require("../utils/expectUtils");
26
+ var import_serializers = require("../../protocol/serializers");
24
27
  async function runAction(progress, page, action, secrets) {
25
28
  const frame = page.mainFrame();
26
29
  switch (action.method) {
@@ -59,10 +62,38 @@ async function runAction(progress, page, action, secrets) {
59
62
  else
60
63
  await frame.uncheck(progress, action.selector, { ...strictTrue });
61
64
  break;
65
+ case "expectVisible": {
66
+ const result = await frame.expect(progress, action.selector, { expression: "to.be.visible", isNot: false }, 5e3);
67
+ if (result.errorMessage)
68
+ throw new Error(result.errorMessage);
69
+ break;
70
+ }
71
+ case "expectValue": {
72
+ let result;
73
+ if (action.type === "textbox" || action.type === "combobox" || action.type === "slider") {
74
+ const expectedText = (0, import_expectUtils.serializeExpectedTextValues)([action.value]);
75
+ result = await frame.expect(progress, action.selector, { expression: "to.have.value", expectedText, isNot: false }, 5e3);
76
+ } else if (action.type === "checkbox" || action.type === "radio") {
77
+ const expectedValue = serializeArgument({ checked: true });
78
+ result = await frame.expect(progress, action.selector, { expression: "to.be.checked", expectedValue, isNot: false }, 5e3);
79
+ } else {
80
+ throw new Error(`Unsupported element type: ${action.type}`);
81
+ }
82
+ if (result.errorMessage)
83
+ throw new Error(result.errorMessage);
84
+ break;
85
+ }
62
86
  }
63
87
  }
88
+ function serializeArgument(arg) {
89
+ return {
90
+ value: (0, import_serializers.serializePlainValue)(arg),
91
+ handles: []
92
+ };
93
+ }
64
94
  const strictTrue = { strict: true };
65
95
  // Annotate the CommonJS export names for ESM import in node:
66
96
  0 && (module.exports = {
67
- runAction
97
+ runAction,
98
+ serializeArgument
68
99
  });
@@ -42,9 +42,7 @@ async function pagePerform(progress, page, options) {
42
42
  const context = new import_context.Context(progress, page);
43
43
  if (await cachedPerform(context, options))
44
44
  return;
45
- await perform(context, options.task, (0, import_mcpBundle.zodToJsonSchema)(import_mcpBundle.z.object({
46
- error: import_mcpBundle.z.string().optional().describe("An error message if the task could not be completed successfully")
47
- })), options);
45
+ await perform(context, options.task, void 0, options);
48
46
  await updateCache(context, options);
49
47
  }
50
48
  async function pageExtract(progress, page, options) {
@@ -70,6 +68,13 @@ async function perform(context, userTask, resultSchema, options = {}) {
70
68
  debug: import_utilsBundle.debug,
71
69
  callTool,
72
70
  tools,
71
+ beforeTurn: (params) => {
72
+ const lastReply = params.conversation.messages.findLast((m) => m.role === "assistant");
73
+ const toolCall = lastReply?.content.find((c) => c.type === "tool_call");
74
+ if (!resultSchema && toolCall && toolCall.arguments.thatShouldBeIt)
75
+ return "break";
76
+ return "continue";
77
+ },
73
78
  ...options
74
79
  });
75
80
  const task = `${userTask}
@@ -77,9 +82,8 @@ async function perform(context, userTask, resultSchema, options = {}) {
77
82
  ### Page snapshot
78
83
  ${full}
79
84
  `;
80
- return await loop.run(task, {
81
- resultSchema
82
- });
85
+ const { result } = await loop.run(task, { resultSchema });
86
+ return result;
83
87
  }
84
88
  const allCaches = /* @__PURE__ */ new Map();
85
89
  async function cachedPerform(context, options) {
@@ -87,13 +91,13 @@ async function cachedPerform(context, options) {
87
91
  return false;
88
92
  const cache = await cachedActions(context.options.cacheFile);
89
93
  const cacheKey = options.key ?? options.task;
90
- const actions = cache[cacheKey];
91
- if (!actions) {
94
+ const entry = cache[cacheKey];
95
+ if (!entry) {
92
96
  if (context.options.cacheMode === "force")
93
97
  throw new Error(`No cached actions for key "${cacheKey}", but cache mode is set to "force"`);
94
98
  return false;
95
99
  }
96
- for (const action of actions)
100
+ for (const action of entry.actions)
97
101
  await (0, import_actionRunner.runAction)(context.progress, context.page, action, context.options.secrets ?? []);
98
102
  return true;
99
103
  }
@@ -103,14 +107,16 @@ async function updateCache(context, options) {
103
107
  return;
104
108
  const cache = await cachedActions(cacheFile);
105
109
  const cacheKey = options.key ?? options.task;
106
- cache[cacheKey] = context.actions;
110
+ cache[cacheKey] = {
111
+ timestamp: Date.now(),
112
+ actions: context.actions
113
+ };
107
114
  await import_fs.default.promises.writeFile(cacheFile, JSON.stringify(cache, void 0, 2));
108
115
  }
109
116
  async function cachedActions(cacheFile) {
110
117
  let cache = allCaches.get(cacheFile);
111
118
  if (!cache) {
112
- const text = await import_fs.default.promises.readFile(cacheFile, "utf-8").catch(() => "{}");
113
- cache = JSON.parse(text);
119
+ cache = await import_fs.default.promises.readFile(cacheFile, "utf-8").then((text) => JSON.parse(text)).catch(() => ({}));
114
120
  allCaches.set(cacheFile, cache);
115
121
  }
116
122
  return cache;
@@ -34,13 +34,17 @@ class Context {
34
34
  return await this.runActionsAndWait([action]);
35
35
  }
36
36
  async runActionsAndWait(action) {
37
- await this.waitForCompletion(async () => {
38
- for (const a of action) {
39
- await (0, import_actionRunner.runAction)(this.progress, this.page, a, this.options?.secrets ?? []);
40
- this.actions.push(a);
41
- }
42
- });
43
- return await this.snapshotResult();
37
+ try {
38
+ await this.waitForCompletion(async () => {
39
+ for (const a of action) {
40
+ await (0, import_actionRunner.runAction)(this.progress, this.page, a, this.options?.secrets ?? []);
41
+ this.actions.push(a);
42
+ }
43
+ });
44
+ return await this.snapshotResult();
45
+ } catch (e) {
46
+ return await this.snapshotResult(e);
47
+ }
44
48
  }
45
49
  async waitForCompletion(callback) {
46
50
  const requests = [];
@@ -73,17 +77,28 @@ class Context {
73
77
  await this.progress.wait(500);
74
78
  return result;
75
79
  }
76
- async snapshotResult() {
80
+ async snapshotResult(error) {
77
81
  let { full } = await this.page.snapshotForAI(this.progress);
78
82
  full = this._redactText(full);
79
- const text = [`# Page snapshot
80
- ${full}`];
83
+ const text = [];
84
+ if (error)
85
+ text.push(`# Error
86
+ ${error.message}`);
87
+ else
88
+ text.push(`# Success`);
89
+ text.push(`# Page snapshot
90
+ ${full}`);
81
91
  return {
82
92
  _meta: {
83
93
  "dev.lowire/state": {
84
94
  "Page snapshot": full
85
- }
95
+ },
96
+ "dev.lowire/history": error ? [{
97
+ category: "error",
98
+ content: error.message
99
+ }] : []
86
100
  },
101
+ isError: !!error,
87
102
  content: [{ type: "text", text: text.join("\n\n") }]
88
103
  };
89
104
  }
@@ -22,21 +22,25 @@ __export(tools_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(tools_exports);
24
24
  var import_mcpBundle = require("../../mcpBundle");
25
+ var import_locatorUtils = require("../../utils/isomorphic/locatorUtils");
25
26
  function defineTool(tool) {
26
27
  return tool;
27
28
  }
29
+ const baseSchema = import_mcpBundle.z.object({
30
+ thatShouldBeIt: import_mcpBundle.z.boolean().describe("Indicates that this tool call is sufficient to complete the task. If false, the task will continue with the next tool call")
31
+ });
28
32
  const snapshot = defineTool({
29
33
  schema: {
30
34
  name: "browser_snapshot",
31
35
  title: "Page snapshot",
32
36
  description: "Capture accessibility snapshot of the current page, this is better than screenshot",
33
- inputSchema: import_mcpBundle.z.object({})
37
+ inputSchema: baseSchema
34
38
  },
35
39
  handle: async (context, params) => {
36
40
  return await context.snapshotResult();
37
41
  }
38
42
  });
39
- const elementSchema = import_mcpBundle.z.object({
43
+ const elementSchema = baseSchema.extend({
40
44
  element: import_mcpBundle.z.string().describe("Human-readable element description used to obtain permission to interact with the element"),
41
45
  ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot")
42
46
  });
@@ -70,7 +74,7 @@ const drag = defineTool({
70
74
  name: "browser_drag",
71
75
  title: "Drag mouse",
72
76
  description: "Perform drag and drop between two elements",
73
- inputSchema: import_mcpBundle.z.object({
77
+ inputSchema: baseSchema.extend({
74
78
  startElement: import_mcpBundle.z.string().describe("Human-readable source element description used to obtain the permission to interact with the element"),
75
79
  startRef: import_mcpBundle.z.string().describe("Exact source element reference from the page snapshot"),
76
80
  endElement: import_mcpBundle.z.string().describe("Human-readable target element description used to obtain the permission to interact with the element"),
@@ -181,7 +185,7 @@ const fillForm = defineTool({
181
185
  name: "browser_fill_form",
182
186
  title: "Fill form",
183
187
  description: "Fill multiple form fields",
184
- inputSchema: import_mcpBundle.z.object({
188
+ inputSchema: baseSchema.extend({
185
189
  fields: import_mcpBundle.z.array(import_mcpBundle.z.object({
186
190
  name: import_mcpBundle.z.string().describe("Human-readable field name"),
187
191
  type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the field"),
@@ -217,6 +221,61 @@ const fillForm = defineTool({
217
221
  return await context.runActionsAndWait(actions);
218
222
  }
219
223
  });
224
+ const expectVisible = defineTool({
225
+ schema: {
226
+ name: "browser_expect_visible",
227
+ title: "Expect element visible",
228
+ description: "Expect element is visible on the page",
229
+ inputSchema: baseSchema.extend({
230
+ role: import_mcpBundle.z.string().describe('ROLE of the element. Can be found in the snapshot like this: `- {ROLE} "Accessible Name":`'),
231
+ accessibleName: import_mcpBundle.z.string().describe('ACCESSIBLE_NAME of the element. Can be found in the snapshot like this: `- role "{ACCESSIBLE_NAME}"`')
232
+ })
233
+ },
234
+ handle: async (context, params) => {
235
+ return await context.runActionAndWait({
236
+ method: "expectVisible",
237
+ selector: (0, import_locatorUtils.getByRoleSelector)(params.role, { name: params.accessibleName })
238
+ });
239
+ }
240
+ });
241
+ const expectVisibleText = defineTool({
242
+ schema: {
243
+ name: "browser_expect_visible_text",
244
+ title: "Expect text visible",
245
+ description: `Expect text is visible on the page. Prefer ${expectVisible.schema.name} if possible.`,
246
+ inputSchema: baseSchema.extend({
247
+ text: import_mcpBundle.z.string().describe('TEXT to expect. Can be found in the snapshot like this: `- role "Accessible Name": {TEXT}` or like this: `- text: {TEXT}`')
248
+ })
249
+ },
250
+ handle: async (context, params) => {
251
+ return await context.runActionAndWait({
252
+ method: "expectVisible",
253
+ selector: (0, import_locatorUtils.getByTextSelector)(params.text)
254
+ });
255
+ }
256
+ });
257
+ const expectValue = defineTool({
258
+ schema: {
259
+ name: "browser_expect_value",
260
+ title: "Expect value",
261
+ description: "Expect element value",
262
+ inputSchema: baseSchema.extend({
263
+ type: import_mcpBundle.z.enum(["textbox", "checkbox", "radio", "combobox", "slider"]).describe("Type of the element"),
264
+ element: import_mcpBundle.z.string().describe("Human-readable element description"),
265
+ ref: import_mcpBundle.z.string().describe("Exact target element reference from the page snapshot"),
266
+ value: import_mcpBundle.z.string().describe('Value to expect. For checkbox, use "true" or "false".')
267
+ })
268
+ },
269
+ handle: async (context, params) => {
270
+ const [selector] = await context.refSelectors([{ ref: params.ref, element: params.element }]);
271
+ return await context.runActionAndWait({
272
+ method: "expectValue",
273
+ selector,
274
+ type: params.type,
275
+ value: params.value
276
+ });
277
+ }
278
+ });
220
279
  var tools_default = [
221
280
  snapshot,
222
281
  click,
@@ -225,5 +284,8 @@ var tools_default = [
225
284
  selectOption,
226
285
  pressKey,
227
286
  type,
228
- fillForm
287
+ fillForm,
288
+ expectVisible,
289
+ expectVisibleText,
290
+ expectValue
229
291
  ];
@@ -103,7 +103,7 @@ class Artifact extends import_instrumentation.SdkObject {
103
103
  if (!this._unaccessibleErrorMessage)
104
104
  await import_fs.default.promises.unlink(this._localPath).catch((e) => {
105
105
  });
106
- await this.reportFinished(new import_errors.TargetClosedError());
106
+ await this.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
107
107
  }
108
108
  async reportFinished(error) {
109
109
  if (this._finished)
@@ -366,19 +366,28 @@ class BidiBrowserContext extends import_browserContext.BrowserContext {
366
366
  }
367
367
  }
368
368
  async doUpdateDefaultViewport() {
369
- if (!this._options.viewport)
369
+ if (!this._options.viewport && !this._options.screen)
370
370
  return;
371
+ const screenSize = this._options.screen || this._options.viewport;
372
+ const viewportSize = this._options.viewport || this._options.screen;
371
373
  await Promise.all([
372
374
  this._browser._browserSession.send("browsingContext.setViewport", {
373
375
  viewport: {
374
- width: this._options.viewport.width,
375
- height: this._options.viewport.height
376
+ width: viewportSize.width,
377
+ height: viewportSize.height
376
378
  },
377
379
  devicePixelRatio: this._options.deviceScaleFactor || 1,
378
380
  userContexts: [this._userContextId()]
379
381
  }),
380
382
  this._browser._browserSession.send("emulation.setScreenOrientationOverride", {
381
- screenOrientation: getScreenOrientation(!!this._options.isMobile, this._options.viewport),
383
+ screenOrientation: getScreenOrientation(!!this._options.isMobile, screenSize),
384
+ userContexts: [this._userContextId()]
385
+ }),
386
+ this._browser._browserSession.send("emulation.setScreenSettingsOverride", {
387
+ screenArea: {
388
+ width: screenSize.width,
389
+ height: screenSize.height
390
+ },
382
391
  userContexts: [this._userContextId()]
383
392
  })
384
393
  ]);
@@ -306,6 +306,7 @@ ${params.stackTrace?.callFrames.map((f) => {
306
306
  const emulatedSize = this._page.emulatedSize();
307
307
  if (!emulatedSize)
308
308
  return;
309
+ const screenSize = emulatedSize.screen;
309
310
  const viewportSize = emulatedSize.viewport;
310
311
  await Promise.all([
311
312
  this._session.send("browsingContext.setViewport", {
@@ -318,7 +319,14 @@ ${params.stackTrace?.callFrames.map((f) => {
318
319
  }),
319
320
  this._session.send("emulation.setScreenOrientationOverride", {
320
321
  contexts: [this._session.sessionId],
321
- screenOrientation: (0, import_bidiBrowser.getScreenOrientation)(!!options.isMobile, viewportSize)
322
+ screenOrientation: (0, import_bidiBrowser.getScreenOrientation)(!!options.isMobile, screenSize)
323
+ }),
324
+ this._session.send("emulation.setScreenSettingsOverride", {
325
+ contexts: [this._session.sessionId],
326
+ screenArea: {
327
+ width: screenSize.width,
328
+ height: screenSize.height
329
+ }
322
330
  })
323
331
  ]);
324
332
  }
@@ -128,6 +128,7 @@ var Network;
128
128
  ((Network2) => {
129
129
  let DataType;
130
130
  ((DataType2) => {
131
+ DataType2["Request"] = "request";
131
132
  DataType2["Response"] = "response";
132
133
  })(DataType = Network2.DataType || (Network2.DataType = {}));
133
134
  })(Network || (Network = {}));
@@ -357,9 +357,9 @@ class FrameSession {
357
357
  const { windowId } = await this._client.send("Browser.getWindowForTarget");
358
358
  this._windowId = windowId;
359
359
  }
360
- let screencastOptions;
360
+ let videoOptions;
361
361
  if (!this._page.isStorageStatePage && this._isMainFrame() && hasUIWindow)
362
- screencastOptions = await this._crPage._page.screencast.initializeVideoRecorder();
362
+ videoOptions = this._crPage._page.screencast.launchVideoRecorder();
363
363
  let lifecycleEventsEnabled;
364
364
  if (!this._isMainFrame())
365
365
  this._addRendererListeners();
@@ -439,15 +439,15 @@ class FrameSession {
439
439
  true
440
440
  /* runImmediately */
441
441
  ));
442
- if (screencastOptions)
443
- promises.push(this._crPage._page.screencast.startVideoRecording(screencastOptions));
442
+ if (videoOptions)
443
+ promises.push(this._crPage._page.screencast.startVideoRecording(videoOptions));
444
444
  }
445
445
  promises.push(this._client.send("Runtime.runIfWaitingForDebugger"));
446
446
  promises.push(this._firstNonInitialNavigationCommittedPromise);
447
447
  await Promise.all(promises);
448
448
  }
449
449
  dispose() {
450
- this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError());
450
+ this._firstNonInitialNavigationCommittedReject(new import_errors.TargetClosedError(this._page.closeReason()));
451
451
  for (const childSession of this._childSessions)
452
452
  childSession.dispose();
453
453
  if (this._parentSession)
@@ -106,7 +106,7 @@ class Dispatcher extends import_events.EventEmitter {
106
106
  this.connection.sendEvent(this, method, params);
107
107
  }
108
108
  _dispose(reason) {
109
- this._disposeRecursively(new import_errors.TargetClosedError());
109
+ this._disposeRecursively(new import_errors.TargetClosedError(this._object.closeReason()));
110
110
  this.connection.sendDispose(this, reason);
111
111
  }
112
112
  _onDispose() {
@@ -257,7 +257,7 @@ class DispatcherConnection {
257
257
  const { id, guid, method, params, metadata } = message;
258
258
  const dispatcher = this._dispatcherByGuid.get(guid);
259
259
  if (!dispatcher) {
260
- this.onmessage({ id, error: (0, import_errors.serializeError)(new import_errors.TargetClosedError()) });
260
+ this.onmessage({ id, error: (0, import_errors.serializeError)(new import_errors.TargetClosedError(void 0)) });
261
261
  return;
262
262
  }
263
263
  let validParams;
@@ -325,19 +325,19 @@ class DispatcherConnection {
325
325
  const response = { id };
326
326
  try {
327
327
  if (this._dispatcherByGuid.get(guid) !== dispatcher)
328
- throw new import_errors.TargetClosedError(closeReason(sdkObject));
328
+ throw new import_errors.TargetClosedError(sdkObject.closeReason());
329
329
  const result = await dispatcher._runCommand(callMetadata, method, validParams);
330
330
  const validator = (0, import_validator.findValidator)(dispatcher._type, method, "Result");
331
331
  response.result = validator(result, "", this._validatorToWireContext());
332
332
  callMetadata.result = result;
333
333
  } catch (e) {
334
334
  if ((0, import_errors.isTargetClosedError)(e)) {
335
- const reason = closeReason(sdkObject);
335
+ const reason = sdkObject.closeReason();
336
336
  if (reason)
337
337
  (0, import_utils.rewriteErrorMessage)(e, reason);
338
338
  } else if ((0, import_protocolError.isProtocolError)(e)) {
339
339
  if (e.type === "closed")
340
- e = new import_errors.TargetClosedError(closeReason(sdkObject), e.browserLogMessage());
340
+ e = new import_errors.TargetClosedError(sdkObject.closeReason(), e.browserLogMessage());
341
341
  else if (e.type === "crashed")
342
342
  (0, import_utils.rewriteErrorMessage)(e, "Target crashed " + e.browserLogMessage());
343
343
  }
@@ -359,9 +359,6 @@ class DispatcherConnection {
359
359
  await new Promise((f) => setTimeout(f, slowMo));
360
360
  }
361
361
  }
362
- function closeReason(sdkObject) {
363
- return sdkObject.attribution.page?.closeReason || sdkObject.attribution.context?._closeReason || sdkObject.attribution.browser?._closeReason;
364
- }
365
362
  // Annotate the CommonJS export names for ESM import in node:
366
363
  0 && (module.exports = {
367
364
  Dispatcher,
@@ -146,7 +146,7 @@ class FFBrowser extends import_browser.Browser {
146
146
  }
147
147
  _onDisconnect() {
148
148
  for (const video of this._idToVideo.values())
149
- video.artifact.reportFinished(new import_errors.TargetClosedError());
149
+ video.artifact.reportFinished(new import_errors.TargetClosedError(this.closeReason()));
150
150
  this._idToVideo.clear();
151
151
  for (const ffPage of this._ffPages.values())
152
152
  ffPage.didClose();
@@ -276,7 +276,7 @@ class FFPage {
276
276
  this._browserContext._browser._videoStarted(this._browserContext, event.screencastId, event.file, this._page.waitForInitializedOrError());
277
277
  }
278
278
  didClose() {
279
- this._markAsError(new import_errors.TargetClosedError());
279
+ this._markAsError(new import_errors.TargetClosedError(this._page.closeReason()));
280
280
  this._session.dispose();
281
281
  import_eventsHelper.eventsHelper.removeEventListeners(this._eventListeners);
282
282
  this._networkManager.dispose();
@@ -33,6 +33,9 @@ class SdkObject extends import_events.EventEmitter {
33
33
  this.attribution = { ...parent.attribution };
34
34
  this.instrumentation = parent.instrumentation;
35
35
  }
36
+ closeReason() {
37
+ return this.attribution.page?._closeReason || this.attribution.context?._closeReason || this.attribution.browser?._closeReason;
38
+ }
36
39
  }
37
40
  function createRootSdkObject() {
38
41
  const fakeParent = { attribution: {}, instrumentation: createInstrumentation() };
@@ -163,7 +163,7 @@ class Page extends import_instrumentation.SdkObject {
163
163
  this.emit(Page.Events.Close);
164
164
  this._closedPromise.resolve();
165
165
  this.instrumentation.onPageClose(this);
166
- this.openScope.close(new import_errors.TargetClosedError());
166
+ this.openScope.close(new import_errors.TargetClosedError(this.closeReason()));
167
167
  }
168
168
  _didCrash() {
169
169
  this.frameManager.dispose();
@@ -577,7 +577,7 @@ class Page extends import_instrumentation.SdkObject {
577
577
  if (this._closedState === "closed")
578
578
  return;
579
579
  if (options.reason)
580
- this.closeReason = options.reason;
580
+ this._closeReason = options.reason;
581
581
  const runBeforeUnload = !!options.runBeforeUnload;
582
582
  if (this._closedState !== "closing") {
583
583
  if (!runBeforeUnload)
@@ -68,6 +68,8 @@ class ProgressController {
68
68
  if (timeout) {
69
69
  const timeoutError = new import_errors.TimeoutError(`Timeout ${timeout}ms exceeded.`);
70
70
  timer = setTimeout(() => {
71
+ if (this.metadata.pauseStartTime && !this.metadata.pauseEndTime)
72
+ return;
71
73
  if (this._state === "running") {
72
74
  timeoutError[kAbortErrorSymbol] = true;
73
75
  this._state = { error: timeoutError };
@@ -40,11 +40,12 @@ var import_registry = require("./registry");
40
40
  class Screencast {
41
41
  constructor(page) {
42
42
  this._videoRecorder = null;
43
- this._screencastId = null;
43
+ this._videoId = null;
44
44
  this._screencastClients = /* @__PURE__ */ new Set();
45
45
  // Aiming at 25 fps by default - each frame is 40ms, but we give some slack with 35ms.
46
46
  // When throttling for tracing, 200ms between frames, except for 10 frames around the action.
47
47
  this._frameThrottler = new FrameThrottler(10, 35, 200);
48
+ this._frameListener = null;
48
49
  this._page = page;
49
50
  }
50
51
  stopFrameThrottler() {
@@ -60,29 +61,31 @@ class Screencast {
60
61
  temporarilyDisableThrottling() {
61
62
  this._frameThrottler.recharge();
62
63
  }
63
- async initializeVideoRecorder() {
64
+ launchVideoRecorder() {
64
65
  const recordVideo = this._page.browserContext._options.recordVideo;
65
66
  if (!recordVideo)
66
67
  return void 0;
67
- const screencastId = (0, import_utils.createGuid)();
68
- const outputFile = import_path.default.join(recordVideo.dir, screencastId + ".webm");
69
- const screencastOptions = {
68
+ (0, import_utils.assert)(!this._videoId);
69
+ this._videoId = (0, import_utils.createGuid)();
70
+ const outputFile = import_path.default.join(recordVideo.dir, this._videoId + ".webm");
71
+ const videoOptions = {
70
72
  // validateBrowserContextOptions ensures correct video size.
71
73
  ...recordVideo.size,
72
74
  outputFile
73
75
  };
74
- await (0, import_utils.mkdirIfNeeded)(import_path.default.join(recordVideo.dir, "dummy"));
75
- await this._createVideoRecorder(screencastId, screencastOptions);
76
+ const ffmpegPath = import_registry.registry.findExecutable("ffmpeg").executablePathOrDie(this._page.browserContext._browser.sdkLanguage());
77
+ this._videoRecorder = new import_videoRecorder.VideoRecorder(ffmpegPath, videoOptions);
78
+ this._frameListener = import_utils.eventsHelper.addEventListener(this._page, import_page.Page.Events.ScreencastFrame, (frame) => this._videoRecorder.writeFrame(frame.buffer, frame.frameSwapWallTime / 1e3));
76
79
  this._page.waitForInitializedOrError().then((p) => {
77
80
  if (p instanceof Error)
78
81
  this.stopVideoRecording().catch(() => {
79
82
  });
80
83
  });
81
- return screencastOptions;
84
+ return videoOptions;
82
85
  }
83
86
  async startVideoRecording(options) {
84
- const screencastId = this._screencastId;
85
- (0, import_utils.assert)(screencastId);
87
+ const videoId = this._videoId;
88
+ (0, import_utils.assert)(videoId);
86
89
  this._page.once(import_page.Page.Events.Close, () => this.stopVideoRecording().catch(() => {
87
90
  }));
88
91
  const gotFirstFrame = new Promise((f) => this._page.once(import_page.Page.Events.ScreencastFrame, f));
@@ -92,20 +95,22 @@ class Screencast {
92
95
  height: options.height
93
96
  });
94
97
  gotFirstFrame.then(() => {
95
- this._page.browserContext._browser._videoStarted(this._page.browserContext, screencastId, options.outputFile, this._page.waitForInitializedOrError());
98
+ this._page.browserContext._browser._videoStarted(this._page.browserContext, videoId, options.outputFile, this._page.waitForInitializedOrError());
96
99
  });
97
100
  }
98
101
  async stopVideoRecording() {
99
- if (!this._screencastId)
102
+ if (!this._videoId)
100
103
  return;
101
- const screencastId = this._screencastId;
102
- this._screencastId = null;
103
- const recorder = this._videoRecorder;
104
+ if (this._frameListener)
105
+ import_utils.eventsHelper.removeEventListeners([this._frameListener]);
106
+ this._frameListener = null;
107
+ const videoId = this._videoId;
108
+ this._videoId = null;
109
+ const videoRecorder = this._videoRecorder;
104
110
  this._videoRecorder = null;
105
- await this._stopScreencast(recorder);
106
- await recorder.stop().catch(() => {
107
- });
108
- const video = this._page.browserContext._browser._takeVideo(screencastId);
111
+ await this._stopScreencast(videoRecorder);
112
+ await videoRecorder.stop();
113
+ const video = this._page.browserContext._browser._takeVideo(videoId);
109
114
  video?.reportFinished();
110
115
  }
111
116
  async _setOptions(options) {
@@ -129,12 +134,6 @@ class Screencast {
129
134
  if (!this._screencastClients.size)
130
135
  await this._page.delegate.stopScreencast();
131
136
  }
132
- async _createVideoRecorder(screencastId, options) {
133
- (0, import_utils.assert)(!this._screencastId);
134
- const ffmpegPath = import_registry.registry.findExecutable("ffmpeg").executablePathOrDie(this._page.browserContext._browser.sdkLanguage());
135
- this._videoRecorder = await import_videoRecorder.VideoRecorder.launch(this._page, ffmpegPath, options);
136
- this._screencastId = screencastId;
137
- }
138
137
  }
139
138
  class FrameThrottler {
140
139
  constructor(nonThrottledFrames, defaultInterval, throttlingInterval) {