playwright-core 1.55.0-alpha-2025-07-29 → 1.55.0-alpha-1753913825000

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 (46) hide show
  1. package/browsers.json +8 -8
  2. package/lib/generated/injectedScriptSource.js +1 -1
  3. package/lib/generated/pollingRecorderSource.js +1 -1
  4. package/lib/protocol/validator.js +0 -16
  5. package/lib/server/android/android.js +54 -43
  6. package/lib/server/bidi/bidiPage.js +4 -0
  7. package/lib/server/browser.js +22 -18
  8. package/lib/server/browserContext.js +73 -46
  9. package/lib/server/browserType.js +67 -48
  10. package/lib/server/chromium/chromium.js +34 -28
  11. package/lib/server/chromium/crCoverage.js +3 -4
  12. package/lib/server/chromium/crDragDrop.js +17 -13
  13. package/lib/server/chromium/videoRecorder.js +10 -19
  14. package/lib/server/codegen/jsonl.js +2 -1
  15. package/lib/server/debugController.js +2 -18
  16. package/lib/server/deviceDescriptorsSource.json +2 -2
  17. package/lib/server/dispatchers/debugControllerDispatcher.js +0 -9
  18. package/lib/server/dispatchers/localUtilsDispatcher.js +0 -5
  19. package/lib/server/electron/electron.js +66 -66
  20. package/lib/server/fetch.js +6 -2
  21. package/lib/server/frames.js +6 -6
  22. package/lib/server/helper.js +4 -7
  23. package/lib/server/localUtils.js +23 -16
  24. package/lib/server/playwright.js +0 -4
  25. package/lib/server/progress.js +17 -11
  26. package/lib/server/recorder/recorderUtils.js +5 -0
  27. package/lib/server/recorder.js +61 -1
  28. package/lib/server/screenshotter.js +72 -56
  29. package/lib/server/socksClientCertificatesInterceptor.js +59 -43
  30. package/lib/server/transport.js +19 -15
  31. package/lib/server/utils/network.js +28 -17
  32. package/lib/server/utils/processLauncher.js +4 -1
  33. package/lib/utils/isomorphic/ariaSnapshot.js +58 -4
  34. package/lib/utils/isomorphic/protocolMetainfo.js +1 -5
  35. package/lib/utils/isomorphic/urlMatch.js +0 -2
  36. package/lib/vite/htmlReport/index.html +16 -16
  37. package/lib/vite/recorder/assets/{codeMirrorModule-cgtwtYOb.js → codeMirrorModule-B5Ye5Cij.js} +1 -1
  38. package/lib/vite/recorder/assets/{index-KWWSfrzB.js → index-B5VmX9JT.js} +48 -48
  39. package/lib/vite/recorder/index.html +1 -1
  40. package/lib/vite/traceViewer/assets/{codeMirrorModule-k_7BpzGX.js → codeMirrorModule-3XE5WU2G.js} +1 -1
  41. package/lib/vite/traceViewer/assets/{defaultSettingsView-CKM53V_K.js → defaultSettingsView-u5uZPktL.js} +103 -103
  42. package/lib/vite/traceViewer/{index.CCXGYfh2.js → index.CycYDQ3P.js} +1 -1
  43. package/lib/vite/traceViewer/index.html +2 -2
  44. package/lib/vite/traceViewer/{uiMode.CNa3x5Xj.js → uiMode.DeaA8YiP.js} +1 -1
  45. package/lib/vite/traceViewer/uiMode.html +2 -2
  46. package/package.json +1 -1
@@ -52,12 +52,6 @@ class DebugControllerDispatcher extends import_dispatcher.Dispatcher {
52
52
  async setReportStateChanged(params, progress) {
53
53
  this._object.setReportStateChanged(params.enabled);
54
54
  }
55
- async resetForReuse(params, progress) {
56
- await this._object.resetForReuse(progress);
57
- }
58
- async navigate(params, progress) {
59
- await this._object.navigate(progress, params.url);
60
- }
61
55
  async setRecorderMode(params, progress) {
62
56
  await this._object.setRecorderMode(progress, params);
63
57
  }
@@ -73,9 +67,6 @@ class DebugControllerDispatcher extends import_dispatcher.Dispatcher {
73
67
  async kill(params, progress) {
74
68
  this._object.kill();
75
69
  }
76
- async closeAllBrowsers(params, progress) {
77
- await this._object.closeAllBrowsers();
78
- }
79
70
  _onDispose() {
80
71
  import_utils.eventsHelper.removeEventListeners(this._listeners);
81
72
  this._object.dispose();
@@ -40,7 +40,6 @@ var import_jsonPipeDispatcher = require("../dispatchers/jsonPipeDispatcher");
40
40
  var import_socksInterceptor = require("../socksInterceptor");
41
41
  var import_transport = require("../transport");
42
42
  var import_network = require("../utils/network");
43
- var import_urlMatch = require("../../utils/isomorphic/urlMatch");
44
43
  class LocalUtilsDispatcher extends import_dispatcher.Dispatcher {
45
44
  constructor(scope, playwright) {
46
45
  const localUtils2 = new import_instrumentation.SdkObject(playwright, "localUtils", "localUtils");
@@ -112,10 +111,6 @@ class LocalUtilsDispatcher extends import_dispatcher.Dispatcher {
112
111
  pipe.on("close", () => transport.close());
113
112
  return { pipe, headers: transport.headers };
114
113
  }
115
- async globToRegex(params, progress) {
116
- const regex = (0, import_urlMatch.resolveGlobToRegexPattern)(params.baseURL, params.glob, params.webSocketUrl);
117
- return { regex };
118
- }
119
114
  }
120
115
  async function urlToWSEndpoint(progress, endpointURL) {
121
116
  if (endpointURL.startsWith("ws"))
@@ -136,7 +136,6 @@ class Electron extends import_instrumentation.SdkObject {
136
136
  electronArguments.unshift("--no-sandbox");
137
137
  }
138
138
  const artifactsDir = await progress.race(import_fs.default.promises.mkdtemp(ARTIFACTS_FOLDER));
139
- progress.cleanupWhenAborted(() => (0, import_utils.removeFolders)([artifactsDir]));
140
139
  const browserLogsCollector = new import_debugLogger.RecentLogsCollector();
141
140
  const env = options.env ? (0, import_processLauncher.envArrayToObject)(options.env) : process.env;
142
141
  let command;
@@ -195,73 +194,74 @@ class Electron extends import_instrumentation.SdkObject {
195
194
  const nodeMatchPromise = waitForLine(progress, launchedProcess, /^Debugger listening on (ws:\/\/.*)$/);
196
195
  const chromeMatchPromise = waitForLine(progress, launchedProcess, /^DevTools listening on (ws:\/\/.*)$/);
197
196
  const debuggerDisconnectPromise = waitForLine(progress, launchedProcess, /Waiting for the debugger to disconnect\.\.\./);
198
- const nodeMatch = await nodeMatchPromise;
199
- const nodeTransport = await import_transport.WebSocketTransport.connect(progress, nodeMatch[1]);
200
- progress.cleanupWhenAborted(() => nodeTransport.close());
201
- const nodeConnection = new import_crConnection.CRConnection(this, nodeTransport, import_helper.helper.debugProtocolLogger(), browserLogsCollector);
202
- debuggerDisconnectPromise.then(() => {
203
- nodeTransport.close();
204
- }).catch(() => {
205
- });
206
- const chromeMatch = await Promise.race([
207
- chromeMatchPromise,
208
- waitForXserverError
209
- ]);
210
- const chromeTransport = await import_transport.WebSocketTransport.connect(progress, chromeMatch[1]);
211
- progress.cleanupWhenAborted(() => chromeTransport.close());
212
- const browserProcess = {
213
- onclose: void 0,
214
- process: launchedProcess,
215
- close: gracefullyClose,
216
- kill
217
- };
218
- const contextOptions = {
219
- ...options,
220
- noDefaultViewport: true
221
- };
222
- const browserOptions = {
223
- name: "electron",
224
- isChromium: true,
225
- headful: true,
226
- persistent: contextOptions,
227
- browserProcess,
228
- protocolLogger: import_helper.helper.debugProtocolLogger(),
229
- browserLogsCollector,
230
- artifactsDir,
231
- downloadsPath: artifactsDir,
232
- tracesDir: options.tracesDir || artifactsDir,
233
- originalLaunchOptions: {}
234
- };
235
- (0, import_browserContext.validateBrowserContextOptions)(contextOptions, browserOptions);
236
- const browser = await progress.race(import_crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions));
237
- app = new ElectronApplication(this, browser, nodeConnection, launchedProcess);
238
- await progress.race(app.initialize());
239
- return app;
197
+ try {
198
+ const nodeMatch = await nodeMatchPromise;
199
+ const nodeTransport = await import_transport.WebSocketTransport.connect(progress, nodeMatch[1]);
200
+ const nodeConnection = new import_crConnection.CRConnection(this, nodeTransport, import_helper.helper.debugProtocolLogger(), browserLogsCollector);
201
+ debuggerDisconnectPromise.then(() => {
202
+ nodeTransport.close();
203
+ }).catch(() => {
204
+ });
205
+ const chromeMatch = await Promise.race([
206
+ chromeMatchPromise,
207
+ waitForXserverError
208
+ ]);
209
+ const chromeTransport = await import_transport.WebSocketTransport.connect(progress, chromeMatch[1]);
210
+ const browserProcess = {
211
+ onclose: void 0,
212
+ process: launchedProcess,
213
+ close: gracefullyClose,
214
+ kill
215
+ };
216
+ const contextOptions = {
217
+ ...options,
218
+ noDefaultViewport: true
219
+ };
220
+ const browserOptions = {
221
+ name: "electron",
222
+ isChromium: true,
223
+ headful: true,
224
+ persistent: contextOptions,
225
+ browserProcess,
226
+ protocolLogger: import_helper.helper.debugProtocolLogger(),
227
+ browserLogsCollector,
228
+ artifactsDir,
229
+ downloadsPath: artifactsDir,
230
+ tracesDir: options.tracesDir || artifactsDir,
231
+ originalLaunchOptions: {}
232
+ };
233
+ (0, import_browserContext.validateBrowserContextOptions)(contextOptions, browserOptions);
234
+ const browser = await progress.race(import_crBrowser.CRBrowser.connect(this.attribution.playwright, chromeTransport, browserOptions));
235
+ app = new ElectronApplication(this, browser, nodeConnection, launchedProcess);
236
+ await progress.race(app.initialize());
237
+ return app;
238
+ } catch (error) {
239
+ await kill();
240
+ throw error;
241
+ }
240
242
  }
241
243
  }
242
- function waitForLine(progress, process2, regex) {
243
- return progress.race(new Promise((resolve, reject) => {
244
- const rl = readline.createInterface({ input: process2.stderr });
245
- const failError = new Error("Process failed to launch!");
246
- const listeners = [
247
- import_eventsHelper.eventsHelper.addEventListener(rl, "line", onLine),
248
- import_eventsHelper.eventsHelper.addEventListener(rl, "close", reject.bind(null, failError)),
249
- import_eventsHelper.eventsHelper.addEventListener(process2, "exit", reject.bind(null, failError)),
250
- // It is Ok to remove error handler because we did not create process and there is another listener.
251
- import_eventsHelper.eventsHelper.addEventListener(process2, "error", reject.bind(null, failError))
252
- ];
253
- progress.cleanupWhenAborted(cleanup);
254
- function onLine(line) {
255
- const match = line.match(regex);
256
- if (!match)
257
- return;
258
- cleanup();
259
- resolve(match);
260
- }
261
- function cleanup() {
262
- import_eventsHelper.eventsHelper.removeEventListeners(listeners);
263
- }
264
- }));
244
+ async function waitForLine(progress, process2, regex) {
245
+ const promise = new import_utils.ManualPromise();
246
+ const rl = readline.createInterface({ input: process2.stderr });
247
+ const failError = new Error("Process failed to launch!");
248
+ const listeners = [
249
+ import_eventsHelper.eventsHelper.addEventListener(rl, "line", onLine),
250
+ import_eventsHelper.eventsHelper.addEventListener(rl, "close", () => promise.reject(failError)),
251
+ import_eventsHelper.eventsHelper.addEventListener(process2, "exit", () => promise.reject(failError)),
252
+ // It is Ok to remove error handler because we did not create process and there is another listener.
253
+ import_eventsHelper.eventsHelper.addEventListener(process2, "error", () => promise.reject(failError))
254
+ ];
255
+ function onLine(line) {
256
+ const match = line.match(regex);
257
+ if (match)
258
+ promise.resolve(match);
259
+ }
260
+ try {
261
+ return await progress.race(promise);
262
+ } finally {
263
+ import_eventsHelper.eventsHelper.removeEventListeners(listeners);
264
+ }
265
265
  }
266
266
  // Annotate the CommonJS export names for ESM import in node:
267
267
  0 && (module.exports = {
@@ -221,6 +221,7 @@ ${text}`;
221
221
  postData
222
222
  };
223
223
  this.emit(APIRequestContext.Events.Request, requestEvent);
224
+ let destroyRequest;
224
225
  const resultPromise = new Promise((fulfill, reject) => {
225
226
  const requestConstructor = (url.protocol === "https:" ? import_https.default : import_http.default).request;
226
227
  const agent = options.agent || (url.protocol === "https:" ? import_happyEyeballs.httpsHappyEyeballsAgent : import_happyEyeballs.httpHappyEyeballsAgent);
@@ -379,7 +380,7 @@ ${text}`;
379
380
  body.on("end", notifyBodyFinished);
380
381
  });
381
382
  request.on("error", reject);
382
- progress.cleanupWhenAborted(() => request.destroy());
383
+ destroyRequest = () => request.destroy();
383
384
  listeners.push(
384
385
  import_utils.eventsHelper.addEventListener(this, APIRequestContext.Events.Dispose, () => {
385
386
  reject(new Error("Request context disposed."));
@@ -431,7 +432,10 @@ ${text}`;
431
432
  request.write(postData);
432
433
  request.end();
433
434
  });
434
- return progress.race(resultPromise);
435
+ return progress.race(resultPromise).catch((error) => {
436
+ destroyRequest?.();
437
+ throw error;
438
+ });
435
439
  }
436
440
  _getHttpCredentials(url) {
437
441
  if (!this._defaultOptions().httpCredentials?.origin || url.origin.toLowerCase() === this._defaultOptions().httpCredentials?.origin?.toLowerCase())
@@ -618,7 +618,7 @@ class Frame extends import_instrumentation.SdkObject {
618
618
  return null;
619
619
  return continuePolling;
620
620
  }
621
- const result = await progress.raceWithCleanup(resolved.injected.evaluateHandle((injected, { info, root }) => {
621
+ const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, root }) => {
622
622
  if (root && !root.isConnected)
623
623
  throw injected.createStacklessError("Element is not attached to the DOM");
624
624
  const elements = injected.querySelectorAll(info.parsed, root || document);
@@ -633,7 +633,7 @@ class Frame extends import_instrumentation.SdkObject {
633
633
  log2 = ` locator resolved to ${visible2 ? "visible" : "hidden"} ${injected.previewNode(element2)}`;
634
634
  }
635
635
  return { log: log2, element: element2, visible: visible2, attached: !!element2 };
636
- }, { info: resolved.info, root: resolved.frame === this ? scope : void 0 }), (handle) => handle.dispose());
636
+ }, { info: resolved.info, root: resolved.frame === this ? scope : void 0 }));
637
637
  const { log, visible, attached } = await progress.race(result.evaluate((r) => ({ log: r.log, visible: r.visible, attached: r.attached })));
638
638
  if (log)
639
639
  progress.log(log);
@@ -898,7 +898,7 @@ class Frame extends import_instrumentation.SdkObject {
898
898
  const resolved = await progress.race(this.selectors.resolveInjectedForSelector(selector, { strict }));
899
899
  if (!resolved)
900
900
  return continuePolling;
901
- const result = await progress.raceWithCleanup(resolved.injected.evaluateHandle((injected, { info, callId }) => {
901
+ const result = await progress.race(resolved.injected.evaluateHandle((injected, { info, callId }) => {
902
902
  const elements = injected.querySelectorAll(info.parsed, document);
903
903
  if (callId)
904
904
  injected.markTargetElements(new Set(elements), callId);
@@ -912,7 +912,7 @@ class Frame extends import_instrumentation.SdkObject {
912
912
  log2 = ` locator resolved to ${injected.previewNode(element2)}`;
913
913
  }
914
914
  return { log: log2, success: !!element2, element: element2 };
915
- }, { info: resolved.info, callId: progress.metadata.id }), (handle) => handle.dispose());
915
+ }, { info: resolved.info, callId: progress.metadata.id }));
916
916
  const { log, success } = await progress.race(result.evaluate((r) => ({ log: r.log, success: r.success })));
917
917
  if (log)
918
918
  progress.log(log);
@@ -982,8 +982,8 @@ class Frame extends import_instrumentation.SdkObject {
982
982
  async blur(progress, selector, options) {
983
983
  dom.assertDone(await this._retryWithProgressIfNotConnected(progress, selector, options.strict, true, (handle) => handle._blur(progress)));
984
984
  }
985
- async resolveSelector(progress, selector) {
986
- const element = await progress.race(this.selectors.query(selector));
985
+ async resolveSelector(progress, selector, options = {}) {
986
+ const element = await progress.race(this.selectors.query(selector, options));
987
987
  if (!element)
988
988
  throw new Error(`No element matching ${selector}`);
989
989
  const generated = await progress.race(element.evaluateInUtility(async ([injected, node]) => {
@@ -51,22 +51,19 @@ class Helper {
51
51
  }
52
52
  static waitForEvent(progress, emitter, event, predicate) {
53
53
  const listeners = [];
54
- const promise = new Promise((resolve, reject) => {
54
+ const dispose = () => import_eventsHelper.eventsHelper.removeEventListeners(listeners);
55
+ const promise = progress.race(new Promise((resolve, reject) => {
55
56
  listeners.push(import_eventsHelper.eventsHelper.addEventListener(emitter, event, (eventArg) => {
56
57
  try {
57
58
  if (predicate && !predicate(eventArg))
58
59
  return;
59
- import_eventsHelper.eventsHelper.removeEventListeners(listeners);
60
60
  resolve(eventArg);
61
61
  } catch (e) {
62
- import_eventsHelper.eventsHelper.removeEventListeners(listeners);
63
62
  reject(e);
64
63
  }
65
64
  }));
66
- });
67
- const dispose = () => import_eventsHelper.eventsHelper.removeEventListeners(listeners);
68
- progress.cleanupWhenAborted(dispose);
69
- return { promise: progress.race(promise), dispose };
65
+ })).finally(() => dispose());
66
+ return { promise, dispose };
70
67
  }
71
68
  static secondsToRoundishMillis(value) {
72
69
  return (value * 1e6 | 0) / 1e3;
@@ -135,13 +135,18 @@ async function harOpen(progress, harBackends, params) {
135
135
  let harBackend;
136
136
  if (params.file.endsWith(".zip")) {
137
137
  const zipFile = new import_zipFile.ZipFile(params.file);
138
- const entryNames = await zipFile.entries();
139
- const harEntryName = entryNames.find((e) => e.endsWith(".har"));
140
- if (!harEntryName)
141
- return { error: "Specified archive does not have a .har file" };
142
- const har = await progress.raceWithCleanup(zipFile.read(harEntryName), () => zipFile.close());
143
- const harFile = JSON.parse(har.toString());
144
- harBackend = new import_harBackend.HarBackend(harFile, null, zipFile);
138
+ try {
139
+ const entryNames = await progress.race(zipFile.entries());
140
+ const harEntryName = entryNames.find((e) => e.endsWith(".har"));
141
+ if (!harEntryName)
142
+ return { error: "Specified archive does not have a .har file" };
143
+ const har = await progress.race(zipFile.read(harEntryName));
144
+ const harFile = JSON.parse(har.toString());
145
+ harBackend = new import_harBackend.HarBackend(harFile, null, zipFile);
146
+ } catch (error) {
147
+ zipFile.close();
148
+ throw error;
149
+ }
145
150
  } else {
146
151
  const harFile = JSON.parse(await progress.race(import_fs.default.promises.readFile(params.file, "utf-8")));
147
152
  harBackend = new import_harBackend.HarBackend(harFile, import_path.default.dirname(params.file), null);
@@ -165,16 +170,18 @@ function harClose(harBackends, params) {
165
170
  async function harUnzip(progress, params) {
166
171
  const dir = import_path.default.dirname(params.zipFile);
167
172
  const zipFile = new import_zipFile.ZipFile(params.zipFile);
168
- progress.cleanupWhenAborted(() => zipFile.close());
169
- for (const entry of await progress.race(zipFile.entries())) {
170
- const buffer = await progress.race(zipFile.read(entry));
171
- if (entry === "har.har")
172
- await progress.race(import_fs.default.promises.writeFile(params.harFile, buffer));
173
- else
174
- await progress.race(import_fs.default.promises.writeFile(import_path.default.join(dir, entry), buffer));
173
+ try {
174
+ for (const entry of await progress.race(zipFile.entries())) {
175
+ const buffer = await progress.race(zipFile.read(entry));
176
+ if (entry === "har.har")
177
+ await progress.race(import_fs.default.promises.writeFile(params.harFile, buffer));
178
+ else
179
+ await progress.race(import_fs.default.promises.writeFile(import_path.default.join(dir, entry), buffer));
180
+ }
181
+ await progress.race(import_fs.default.promises.unlink(params.zipFile));
182
+ } finally {
183
+ zipFile.close();
175
184
  }
176
- await progress.race(import_fs.default.promises.unlink(params.zipFile));
177
- zipFile.close();
178
185
  }
179
186
  async function tracingStarted(progress, stackSessions, params) {
180
187
  let tmpDir = void 0;
@@ -58,10 +58,6 @@ class Playwright extends import_instrumentation.SdkObject {
58
58
  this.android = new import_android.Android(this, new import_backendAdb.AdbBackend());
59
59
  this.debugController = new import_debugController.DebugController(this);
60
60
  }
61
- async hideHighlight() {
62
- await Promise.all([...this._allPages].map((p) => p.hideHighlight().catch(() => {
63
- })));
64
- }
65
61
  allBrowsers() {
66
62
  return [...this._allBrowsers];
67
63
  }
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var progress_exports = {};
20
20
  __export(progress_exports, {
21
21
  ProgressController: () => ProgressController,
22
- isAbortError: () => isAbortError
22
+ isAbortError: () => isAbortError,
23
+ raceUncancellableOperationWithCleanup: () => raceUncancellableOperationWithCleanup
23
24
  });
24
25
  module.exports = __toCommonJS(progress_exports);
25
26
  var import_errors = require("./errors");
@@ -67,15 +68,6 @@ class ProgressController {
67
68
  const promises = Array.isArray(promise) ? promise : [promise];
68
69
  return Promise.race([...promises, this._forceAbortPromise]);
69
70
  },
70
- raceWithCleanup: (promise, cleanup) => {
71
- return progress.race(promise.then((result) => {
72
- if (this._state !== "running")
73
- cleanup(result);
74
- else
75
- this._cleanups.push(() => cleanup(result));
76
- return result;
77
- }));
78
- },
79
71
  wait: async (timeout2) => {
80
72
  let timer2;
81
73
  const promise = new Promise((f) => timer2 = setTimeout(f, timeout2));
@@ -117,8 +109,22 @@ const kAbortErrorSymbol = Symbol("kAbortError");
117
109
  function isAbortError(error) {
118
110
  return !!error[kAbortErrorSymbol];
119
111
  }
112
+ async function raceUncancellableOperationWithCleanup(progress, run, cleanup) {
113
+ let aborted = false;
114
+ try {
115
+ return await progress.race(run().then(async (t) => {
116
+ if (aborted)
117
+ await cleanup(t);
118
+ return t;
119
+ }));
120
+ } catch (error) {
121
+ aborted = true;
122
+ throw error;
123
+ }
124
+ }
120
125
  // Annotate the CommonJS export names for ESM import in node:
121
126
  0 && (module.exports = {
122
127
  ProgressController,
123
- isAbortError
128
+ isAbortError,
129
+ raceUncancellableOperationWithCleanup
124
130
  });
@@ -22,6 +22,7 @@ __export(recorderUtils_exports, {
22
22
  collapseActions: () => collapseActions,
23
23
  frameForAction: () => frameForAction,
24
24
  generateFrameSelector: () => generateFrameSelector,
25
+ isAssertAction: () => isAssertAction,
25
26
  mainFrameForAction: () => mainFrameForAction,
26
27
  metadataToCallLog: () => metadataToCallLog,
27
28
  shouldMergeAction: () => shouldMergeAction
@@ -75,6 +76,9 @@ async function frameForAction(pageAliases, actionInContext, action) {
75
76
  throw new Error("Internal error: frame not found");
76
77
  return result.frame;
77
78
  }
79
+ function isAssertAction(action) {
80
+ return action.name.startsWith("assert");
81
+ }
78
82
  function isSameAction(a, b) {
79
83
  return a.action.name === b.action.name && a.frame.pageAlias === b.frame.pageAlias && a.frame.framePath.join("|") === b.frame.framePath.join("|");
80
84
  }
@@ -151,6 +155,7 @@ async function generateFrameSelectorInParent(parent, frame) {
151
155
  collapseActions,
152
156
  frameForAction,
153
157
  generateFrameSelector,
158
+ isAssertAction,
154
159
  mainFrameForAction,
155
160
  metadataToCallLog,
156
161
  shouldMergeAction
@@ -48,6 +48,8 @@ var import_utils2 = require("./../utils");
48
48
  var import_frames = require("./frames");
49
49
  var import_page = require("./page");
50
50
  var import_recorderRunner = require("./recorder/recorderRunner");
51
+ var import_ariaSnapshot = require("../utils/isomorphic/ariaSnapshot");
52
+ var import_utilsBundle = require("../utilsBundle");
51
53
  const recorderSymbol = Symbol("recorderSymbol");
52
54
  const RecorderEvent = {
53
55
  PausedStateChanged: "pausedStateChanged",
@@ -440,15 +442,66 @@ class Recorder extends import_events.default {
440
442
  };
441
443
  return actionInContext;
442
444
  }
445
+ async _maybeGenerateAssertAction(frame, actionInContext) {
446
+ const lastAction = getLastFrameAction(frame);
447
+ if ((0, import_recorderUtils.isAssertAction)(actionInContext.action))
448
+ return;
449
+ if (lastAction && ((0, import_recorderUtils.isAssertAction)(lastAction.action) || (0, import_recorderUtils.shouldMergeAction)(actionInContext, lastAction)))
450
+ return;
451
+ const newSnapshot = actionInContext.action.ariaSnapshot;
452
+ if (!newSnapshot)
453
+ return;
454
+ const lastSnapshot = lastAction?.action.ariaSnapshot || `- document [ref=e1]
455
+ `;
456
+ if (!lastSnapshot)
457
+ return;
458
+ const callMetadata = (0, import_instrumentation.serverSideCallMetadata)();
459
+ const controller = new import_progress.ProgressController(callMetadata, frame);
460
+ const selector = await controller.run(async (progress) => {
461
+ const ref = (0, import_ariaSnapshot.findNewElementRef)(import_utilsBundle.yaml, lastSnapshot, newSnapshot);
462
+ if (!ref)
463
+ return;
464
+ const { resolvedSelector } = await frame.resolveSelector(progress, `aria-ref=${ref}`, { mainWorld: true }).catch(() => ({ resolvedSelector: void 0 }));
465
+ if (!resolvedSelector)
466
+ return;
467
+ const isVisible = await frame._page.mainFrame().isVisible(progress, resolvedSelector, { strict: true }).catch(() => false);
468
+ return isVisible ? resolvedSelector : void 0;
469
+ }).catch(() => void 0);
470
+ if (!selector)
471
+ return;
472
+ if (!actionInContext.frame.framePath.length && "selector" in actionInContext.action && actionInContext.action.selector === selector)
473
+ return;
474
+ const assertActionInContext = {
475
+ frame: {
476
+ pageGuid: actionInContext.frame.pageGuid,
477
+ pageAlias: actionInContext.frame.pageAlias,
478
+ framePath: []
479
+ },
480
+ action: {
481
+ name: "assertVisible",
482
+ selector,
483
+ signals: []
484
+ },
485
+ startTime: actionInContext.startTime,
486
+ endTime: actionInContext.startTime
487
+ };
488
+ return assertActionInContext;
489
+ }
443
490
  async _performAction(frame, action) {
444
491
  const actionInContext = await this._createActionInContext(frame, action);
492
+ const assertActionInContext = await this._maybeGenerateAssertAction(frame, actionInContext);
493
+ if (assertActionInContext)
494
+ this._signalProcessor.addAction(assertActionInContext);
445
495
  this._signalProcessor.addAction(actionInContext);
496
+ setLastFrameAction(frame, actionInContext);
446
497
  if (actionInContext.action.name !== "openPage" && actionInContext.action.name !== "closePage")
447
498
  await (0, import_recorderRunner.performAction)(this._pageAliases, actionInContext);
448
499
  actionInContext.endTime = (0, import_utils2.monotonicTime)();
449
500
  }
450
501
  async _recordAction(frame, action) {
451
- this._signalProcessor.addAction(await this._createActionInContext(frame, action));
502
+ const actionInContext = await this._createActionInContext(frame, action);
503
+ this._signalProcessor.addAction(actionInContext);
504
+ setLastFrameAction(frame, actionInContext);
452
505
  }
453
506
  _onFrameNavigated(frame, page) {
454
507
  const pageAlias = this._pageAliases.get(page);
@@ -482,6 +535,13 @@ function languageForFile(file) {
482
535
  return "csharp";
483
536
  return "javascript";
484
537
  }
538
+ const kLastFrameActionSymbol = Symbol("lastFrameAction");
539
+ function getLastFrameAction(frame) {
540
+ return frame[kLastFrameActionSymbol];
541
+ }
542
+ function setLastFrameAction(frame, action) {
543
+ frame[kLastFrameActionSymbol] = action;
544
+ }
485
545
  // Annotate the CommonJS export names for ESM import in node:
486
546
  0 && (module.exports = {
487
547
  Recorder,