patchright-core 1.49.1 → 1.50.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 (120) hide show
  1. package/ThirdPartyNotices.txt +9 -9
  2. package/bin/reinstall_msedge_beta_linux.sh +6 -0
  3. package/bin/reinstall_msedge_dev_linux.sh +6 -0
  4. package/bin/reinstall_msedge_stable_linux.sh +6 -0
  5. package/browsers.json +17 -16
  6. package/lib/androidServerImpl.js +1 -1
  7. package/lib/cli/program.js +6 -30
  8. package/lib/client/channelOwner.js +35 -55
  9. package/lib/client/clientInstrumentation.js +2 -0
  10. package/lib/client/connection.js +3 -3
  11. package/lib/client/network.js +3 -1
  12. package/lib/client/waiter.js +1 -1
  13. package/lib/generated/consoleApiSource.js +1 -1
  14. package/lib/generated/injectedScriptSource.js +1 -1
  15. package/lib/generated/pollingRecorderSource.js +1 -1
  16. package/lib/inProcessFactory.js +2 -0
  17. package/lib/protocol/debug.js +1 -1
  18. package/lib/protocol/validator.js +2 -2
  19. package/lib/remote/playwrightConnection.js +4 -3
  20. package/lib/remote/playwrightServer.js +2 -1
  21. package/lib/server/bidi/bidiBrowser.js +9 -6
  22. package/lib/server/bidi/bidiExecutionContext.js +20 -1
  23. package/lib/server/bidi/bidiInput.js +7 -5
  24. package/lib/server/bidi/bidiNetworkManager.js +8 -9
  25. package/lib/server/bidi/bidiPage.js +9 -20
  26. package/lib/server/bidi/third_party/bidiKeyboard.js +9 -7
  27. package/lib/server/browserContext.js +24 -16
  28. package/lib/server/chromium/crBrowser.js +10 -10
  29. package/lib/server/chromium/crExecutionContext.js +1 -5
  30. package/lib/server/chromium/crInput.js +15 -4
  31. package/lib/server/chromium/crPage.js +19 -31
  32. package/lib/server/codegen/csharp.js +12 -2
  33. package/lib/server/codegen/java.js +14 -3
  34. package/lib/server/codegen/javascript.js +10 -2
  35. package/lib/server/codegen/jsonl.js +1 -1
  36. package/lib/server/codegen/python.js +5 -4
  37. package/lib/server/debugController.js +15 -40
  38. package/lib/server/debugger.js +1 -1
  39. package/lib/server/deviceDescriptorsSource.json +50 -50
  40. package/lib/server/dispatchers/browserContextDispatcher.js +2 -13
  41. package/lib/server/dispatchers/debugControllerDispatcher.js +4 -2
  42. package/lib/server/dispatchers/frameDispatcher.js +3 -2
  43. package/lib/server/dispatchers/pageDispatcher.js +1 -1
  44. package/lib/server/dispatchers/webSocketRouteDispatcher.js +10 -11
  45. package/lib/server/dom.js +7 -2
  46. package/lib/server/firefox/ffBrowser.js +6 -6
  47. package/lib/server/firefox/ffInput.js +15 -4
  48. package/lib/server/firefox/ffPage.js +13 -28
  49. package/lib/server/frames.js +30 -39
  50. package/lib/server/har/harTracer.js +1 -1
  51. package/lib/server/input.js +2 -3
  52. package/lib/server/launchApp.js +5 -5
  53. package/lib/server/network.js +2 -2
  54. package/lib/server/page.js +23 -16
  55. package/lib/server/recorder/chat.js +177 -0
  56. package/lib/server/recorder/contextRecorder.js +6 -15
  57. package/lib/server/recorder/recorderApp.js +1 -1
  58. package/lib/server/recorder/recorderCollection.js +5 -17
  59. package/lib/server/recorder/recorderRunner.js +7 -3
  60. package/lib/server/recorder/recorderUtils.js +5 -29
  61. package/lib/server/recorder.js +12 -9
  62. package/lib/server/registry/browserFetcher.js +1 -1
  63. package/lib/server/registry/dependencies.js +5 -5
  64. package/lib/server/registry/index.js +118 -5
  65. package/lib/server/registry/nativeDeps.js +7 -4
  66. package/lib/server/trace/recorder/snapshotterInjected.js +12 -5
  67. package/lib/server/trace/viewer/traceViewer.js +6 -1
  68. package/lib/server/transport.js +1 -0
  69. package/lib/server/webkit/webkit.js +1 -1
  70. package/lib/server/webkit/wkBrowser.js +6 -6
  71. package/lib/server/webkit/wkExecutionContext.js +1 -0
  72. package/lib/server/webkit/wkInput.js +15 -5
  73. package/lib/server/webkit/wkPage.js +9 -25
  74. package/lib/utils/comparators.js +16 -10
  75. package/lib/utils/debugLogger.js +3 -1
  76. package/lib/utils/hostPlatform.js +14 -8
  77. package/lib/utils/isomorphic/ariaSnapshot.js +176 -52
  78. package/lib/utils/isomorphic/cssParser.js +4 -4
  79. package/lib/utils/isomorphic/locatorGenerators.js +2 -2
  80. package/lib/utils/isomorphic/locatorParser.js +18 -12
  81. package/lib/utils/isomorphic/urlMatch.js +2 -4
  82. package/lib/utils/processLauncher.js +1 -1
  83. package/lib/utils/wsServer.js +1 -0
  84. package/lib/utils/zones.js +18 -20
  85. package/lib/utilsBundleImpl/index.js +95 -95
  86. package/lib/vite/htmlReport/index.html +14 -14
  87. package/lib/vite/recorder/assets/{codeMirrorModule-AFvV6hAs.js → codeMirrorModule-3Qn3tPnZ.js} +1 -1
  88. package/lib/vite/recorder/assets/{index-_cTWgVuJ.js → index-Bek6JFv8.js} +78 -78
  89. package/lib/vite/recorder/assets/{index-iA1aAGZg.css → index-CAQewHss.css} +1 -1
  90. package/lib/vite/recorder/index.html +2 -2
  91. package/lib/vite/traceViewer/assets/{codeMirrorModule-BWCrdKft.js → codeMirrorModule-aLkSUGpW.js} +1 -1
  92. package/lib/vite/traceViewer/assets/defaultSettingsView-CxUo6zd3.js +243 -0
  93. package/lib/vite/traceViewer/defaultSettingsView.DtIkrKWn.css +1 -0
  94. package/lib/vite/traceViewer/index.Bhu5cv5R.js +2 -0
  95. package/lib/vite/traceViewer/index.html +3 -6
  96. package/lib/vite/traceViewer/sw.bundle.js +3 -3
  97. package/lib/vite/traceViewer/uiMode.BBy7FOVd.js +5 -0
  98. package/lib/vite/traceViewer/{uiMode.voC1ZiOQ.css → uiMode.Be_ME-Go.css} +1 -1
  99. package/lib/vite/traceViewer/uiMode.html +4 -7
  100. package/package.json +1 -1
  101. package/types/protocol.d.ts +269 -20
  102. package/types/types.d.ts +59 -29
  103. package/bin/PrintDeps.exe +0 -0
  104. package/bin/README.md +0 -2
  105. package/lib/server/ariaSnapshot.js +0 -33
  106. package/lib/server/recorder/recorderInTraceViewer.js +0 -144
  107. package/lib/utils/isomorphic/recorderUtils.js +0 -227
  108. package/lib/vite/traceViewer/assets/inspectorTab-C_9qyxv5.js +0 -68
  109. package/lib/vite/traceViewer/assets/testServerConnection-DeE2kSzz.js +0 -1
  110. package/lib/vite/traceViewer/assets/workbench-DsQEOQud.js +0 -9
  111. package/lib/vite/traceViewer/embedded.D4x_-tXl.js +0 -2
  112. package/lib/vite/traceViewer/embedded.html +0 -18
  113. package/lib/vite/traceViewer/embedded.w7WN2u1R.css +0 -1
  114. package/lib/vite/traceViewer/index.BskMikzx.js +0 -2
  115. package/lib/vite/traceViewer/inspectorTab.DEOUW62d.css +0 -1
  116. package/lib/vite/traceViewer/recorder.B_SY1GJM.css +0 -0
  117. package/lib/vite/traceViewer/recorder.Dsk1wX5k.js +0 -2
  118. package/lib/vite/traceViewer/recorder.html +0 -17
  119. package/lib/vite/traceViewer/uiMode.CzKr-TMc.js +0 -5
  120. package/lib/vite/traceViewer/workbench.C-zR9ysA.css +0 -1
@@ -41,7 +41,6 @@ class BidiPage {
41
41
  this.rawKeyboard = void 0;
42
42
  this.rawTouchscreen = void 0;
43
43
  this._page = void 0;
44
- this._pagePromise = void 0;
45
44
  this._session = void 0;
46
45
  this._opener = void 0;
47
46
  this._realmToContext = void 0;
@@ -49,7 +48,6 @@ class BidiPage {
49
48
  this._browserContext = void 0;
50
49
  this._networkManager = void 0;
51
50
  this._pdf = void 0;
52
- this._initializedPage = null;
53
51
  this._initScriptIds = [];
54
52
  this._session = bidiSession;
55
53
  this._opener = opener;
@@ -65,15 +63,13 @@ class BidiPage {
65
63
  this._sessionListeners = [_eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.realmCreated', this._onRealmCreated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'script.message', this._onScriptMessage.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.contextDestroyed', this._onBrowsingContextDestroyed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationStarted', this._onNavigationStarted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationAborted', this._onNavigationAborted.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.navigationFailed', this._onNavigationFailed.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.fragmentNavigated', this._onFragmentNavigated.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.domContentLoaded', this._onDomContentLoaded.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.load', this._onLoad.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'browsingContext.userPromptOpened', this._onUserPromptOpened.bind(this)), _eventsHelper.eventsHelper.addEventListener(bidiSession, 'log.entryAdded', this._onLogEntryAdded.bind(this))];
66
64
 
67
65
  // Initialize main frame.
68
- this._pagePromise = this._initialize().finally(async () => {
69
- await this._page.initOpener(this._opener);
70
- }).then(() => {
71
- this._initializedPage = this._page;
72
- this._page.reportAsNew();
73
- return this._page;
74
- }).catch(e => {
75
- this._page.reportAsNew(e);
76
- return e;
66
+ // TODO: Wait for first execution context to be created and maybe about:blank navigated.
67
+ this._initialize().then(() => {
68
+ var _this$_opener;
69
+ return this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page);
70
+ }, error => {
71
+ var _this$_opener2;
72
+ return this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error);
77
73
  });
78
74
  }
79
75
  async _initialize() {
@@ -84,18 +80,11 @@ class BidiPage {
84
80
  async _addAllInitScripts() {
85
81
  return Promise.all(this._page.allInitScripts().map(initScript => this.addInitScript(initScript)));
86
82
  }
87
- potentiallyUninitializedPage() {
88
- return this._page;
89
- }
90
83
  didClose() {
91
84
  this._session.dispose();
92
85
  _eventsHelper.eventsHelper.removeEventListeners(this._sessionListeners);
93
86
  this._page._didClose();
94
87
  }
95
- async pageOrError() {
96
- // TODO: Wait for first execution context to be created and maybe about:blank navigated.
97
- return this._pagePromise;
98
- }
99
88
  _onFrameAttached(frameId, parentFrameId) {
100
89
  return this._page._frameManager.frameAttached(frameId, parentFrameId);
101
90
  }
@@ -311,7 +300,7 @@ class BidiPage {
311
300
  }
312
301
  async _onScriptMessage(event) {
313
302
  if (event.channel !== kPlaywrightBindingChannel) return;
314
- const pageOrError = await this.pageOrError();
303
+ const pageOrError = await this._page.waitForInitializedOrError();
315
304
  if (pageOrError instanceof Error) return;
316
305
  const context = this._realmToContext.get(event.source.realm);
317
306
  if (!context) return;
@@ -351,7 +340,7 @@ class BidiPage {
351
340
  context: this._session.sessionId,
352
341
  format: {
353
342
  type: `image/${format === 'png' ? 'png' : 'jpeg'}`,
354
- quality: quality || 80
343
+ quality: quality ? quality / 100 : 0.8
355
344
  },
356
345
  origin: documentRect ? 'document' : 'viewport',
357
346
  clip: {
@@ -13,18 +13,18 @@ exports.getBidiKeyValue = void 0;
13
13
 
14
14
  /* eslint-disable curly */
15
15
 
16
- const getBidiKeyValue = key => {
17
- switch (key) {
16
+ const getBidiKeyValue = keyName => {
17
+ switch (keyName) {
18
18
  case '\r':
19
19
  case '\n':
20
- key = 'Enter';
20
+ keyName = 'Enter';
21
21
  break;
22
22
  }
23
23
  // Measures the number of code points rather than UTF-16 code units.
24
- if ([...key].length === 1) {
25
- return key;
24
+ if ([...keyName].length === 1) {
25
+ return keyName;
26
26
  }
27
- switch (key) {
27
+ switch (keyName) {
28
28
  case 'Cancel':
29
29
  return '\uE001';
30
30
  case 'Help':
@@ -137,6 +137,8 @@ const getBidiKeyValue = key => {
137
137
  return '\uE052';
138
138
  case 'MetaRight':
139
139
  return '\uE053';
140
+ case 'Space':
141
+ return ' ';
140
142
  case 'Digit0':
141
143
  return '0';
142
144
  case 'Digit1':
@@ -232,7 +234,7 @@ const getBidiKeyValue = key => {
232
234
  case 'Quote':
233
235
  return '"';
234
236
  default:
235
- throw new Error(`Unknown key: "${key}"`);
237
+ throw new Error(`Unknown key: "${keyName}"`);
236
238
  }
237
239
  };
238
240
  exports.getBidiKeyValue = getBidiKeyValue;
@@ -102,7 +102,7 @@ class BrowserContext extends _instrumentation.SdkObject {
102
102
  this._debugger = new _debugger.Debugger(this);
103
103
 
104
104
  // When PWDEBUG=1, show inspector for each context.
105
- if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show('actions', this, _recorderApp.RecorderApp.factory(this), {
105
+ if ((0, _utils.debugMode)() === 'inspector') await _recorder.Recorder.show(this, _recorderApp.RecorderApp.factory(this), {
106
106
  pauseOnNextStatement: true
107
107
  });
108
108
 
@@ -201,6 +201,9 @@ class BrowserContext extends _instrumentation.SdkObject {
201
201
  this._closePromiseFulfill(new Error('Context closed'));
202
202
  this.emit(BrowserContext.Events.Close);
203
203
  }
204
+ pages() {
205
+ return this.possiblyUninitializedPages().filter(page => page.initializedOrUndefined());
206
+ }
204
207
 
205
208
  // BrowserContext methods.
206
209
 
@@ -227,6 +230,9 @@ class BrowserContext extends _instrumentation.SdkObject {
227
230
  setHTTPCredentials(httpCredentials) {
228
231
  return this.doSetHTTPCredentials(httpCredentials);
229
232
  }
233
+ hasBinding(name) {
234
+ return this._pageBindings.has(name);
235
+ }
230
236
  async exposeBinding(name, needsHandle, playwrightBinding) {
231
237
  if (this._pageBindings.has(name)) throw new Error(`Function "${name}" has been already registered`);
232
238
  for (const page of this.pages()) {
@@ -265,27 +271,29 @@ class BrowserContext extends _instrumentation.SdkObject {
265
271
  this._timeoutSettings.setDefaultTimeout(timeout);
266
272
  }
267
273
  async _loadDefaultContextAsIs(progress) {
268
- if (!this.pages().length) {
274
+ if (!this.possiblyUninitializedPages().length) {
269
275
  const waitForEvent = _helper.helper.waitForEvent(progress, this, BrowserContext.Events.Page);
270
276
  progress.cleanupWhenAborted(() => waitForEvent.dispose);
271
- const page = await waitForEvent.promise;
272
- if (page._pageIsError) throw page._pageIsError;
277
+ // Race against BrowserContext.close
278
+ await Promise.race([waitForEvent.promise, this._closePromise]);
273
279
  }
274
- const pages = this.pages();
275
- if (pages[0]._pageIsError) throw pages[0]._pageIsError;
276
- await pages[0].mainFrame()._waitForLoadState(progress, 'load');
277
- return pages;
280
+ const page = this.possiblyUninitializedPages()[0];
281
+ if (!page) return;
282
+ const pageOrError = await page.waitForInitializedOrError();
283
+ if (pageOrError instanceof Error) throw pageOrError;
284
+ await page.mainFrame()._waitForLoadState(progress, 'load');
285
+ return page;
278
286
  }
279
287
  async _loadDefaultContext(progress) {
280
- const pages = await this._loadDefaultContextAsIs(progress);
288
+ const defaultPage = await this._loadDefaultContextAsIs(progress);
289
+ if (!defaultPage) return;
281
290
  const browserName = this._browser.options.name;
282
291
  if (this._options.isMobile && browserName === 'chromium' || this._options.locale && browserName === 'webkit') {
283
292
  // Workaround for:
284
293
  // - chromium fails to change isMobile for existing page;
285
294
  // - webkit fails to change locale for existing page.
286
- const oldPage = pages[0];
287
295
  await this.newPage(progress.metadata);
288
- await oldPage.close(progress.metadata);
296
+ await defaultPage.close(progress.metadata);
289
297
  }
290
298
  }
291
299
  _authenticateProxyViaHeader() {
@@ -318,8 +326,8 @@ class BrowserContext extends _instrumentation.SdkObject {
318
326
  password: password || ''
319
327
  };
320
328
  }
321
- async addInitScript(source) {
322
- const initScript = new _page6.InitScript(source);
329
+ async addInitScript(source, name) {
330
+ const initScript = new _page6.InitScript(source, false /* internal */, name);
323
331
  this.initScripts.push(initScript);
324
332
  await this.doAddInitScript(initScript);
325
333
  }
@@ -379,9 +387,9 @@ class BrowserContext extends _instrumentation.SdkObject {
379
387
  await this._closePromise;
380
388
  }
381
389
  async newPage(metadata) {
382
- const pageDelegate = await this.newPageDelegate();
383
- if (metadata.isServerSide) pageDelegate.potentiallyUninitializedPage().markAsServerSideOnly();
384
- const pageOrError = await pageDelegate.pageOrError();
390
+ const page = await this.doCreateNewPage();
391
+ if (metadata.isServerSide) page.markAsServerSideOnly();
392
+ const pageOrError = await page.waitForInitializedOrError();
385
393
  if (pageOrError instanceof _page6.Page) {
386
394
  if (pageOrError.isClosed()) throw new Error('Page has been closed.');
387
395
  return pageOrError;
@@ -134,7 +134,7 @@ class CRBrowser extends _browser.Browser {
134
134
  return this.options.name === 'clank';
135
135
  }
136
136
  async _waitForAllPagesToBeInitialized() {
137
- await Promise.all([...this._crPages.values()].map(page => page.pageOrError()));
137
+ await Promise.all([...this._crPages.values()].map(crPage => crPage._page.waitForInitializedOrError()));
138
138
  }
139
139
  _onAttachedToTarget({
140
140
  targetInfo,
@@ -239,9 +239,9 @@ class CRBrowser extends _browser.Browser {
239
239
  return;
240
240
  }
241
241
  page.willBeginDownload();
242
- let originPage = page._initializedPage;
242
+ let originPage = page._page.initializedOrUndefined();
243
243
  // If it's a new window download, report it on the opener page.
244
- if (!originPage && page._opener) originPage = page._opener._initializedPage;
244
+ if (!originPage && page._opener) originPage = page._opener._page.initializedOrUndefined();
245
245
  if (!originPage) return;
246
246
  this._downloadCreated(originPage, payload.guid, payload.url, payload.suggestedFilename);
247
247
  }
@@ -312,10 +312,10 @@ class CRBrowserContext extends _browserContext.BrowserContext {
312
312
  _crPages() {
313
313
  return [...this._browser._crPages.values()].filter(crPage => crPage._browserContext === this);
314
314
  }
315
- pages() {
316
- return this._crPages().map(crPage => crPage._initializedPage).filter(Boolean);
315
+ possiblyUninitializedPages() {
316
+ return this._crPages().map(crPage => crPage._page);
317
317
  }
318
- async newPageDelegate() {
318
+ async doCreateNewPage() {
319
319
  (0, _browserContext.assertBrowserContextIsNotOwned)(this);
320
320
  const oldKeys = this._browser.isClank() ? new Set(this._browser._crPages.keys()) : undefined;
321
321
  let {
@@ -338,7 +338,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
338
338
  (0, _utils.assert)(newKeys.size === 1);
339
339
  [targetId] = [...newKeys];
340
340
  }
341
- return this._browser._crPages.get(targetId);
341
+ return this._browser._crPages.get(targetId)._page;
342
342
  }
343
343
  async doGetCookies(urls) {
344
344
  const {
@@ -372,7 +372,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
372
372
  });
373
373
  }
374
374
  async doGrantPermissions(origin, permissions) {
375
- const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['accessibility-events', 'accessibilityEvents'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
375
+ const webPermissionToProtocol = new Map([['geolocation', 'geolocation'], ['midi', 'midi'], ['notifications', 'notifications'], ['camera', 'videoCapture'], ['microphone', 'audioCapture'], ['background-sync', 'backgroundSync'], ['ambient-light-sensor', 'sensors'], ['accelerometer', 'sensors'], ['gyroscope', 'sensors'], ['magnetometer', 'sensors'], ['clipboard-read', 'clipboardReadWrite'], ['clipboard-write', 'clipboardSanitizedWrite'], ['payment-handler', 'paymentHandler'],
376
376
  // chrome-specific permissions we have.
377
377
  ['midi-sysex', 'midiSysex'], ['storage-access', 'storageAccess']]);
378
378
  const filtered = permissions.map(permission => {
@@ -463,7 +463,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
463
463
  // When persistent context is closed, we do not necessary get Target.detachedFromTarget
464
464
  // for all the background pages.
465
465
  for (const [targetId, backgroundPage] of this._browser._backgroundPages.entries()) {
466
- if (backgroundPage._browserContext === this && backgroundPage._initializedPage) {
466
+ if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) {
467
467
  backgroundPage.didClose();
468
468
  this._browser._backgroundPages.delete(targetId);
469
469
  }
@@ -484,7 +484,7 @@ class CRBrowserContext extends _browserContext.BrowserContext {
484
484
  backgroundPages() {
485
485
  const result = [];
486
486
  for (const backgroundPage of this._browser._backgroundPages.values()) {
487
- if (backgroundPage._browserContext === this && backgroundPage._initializedPage) result.push(backgroundPage._initializedPage);
487
+ if (backgroundPage._browserContext === this && backgroundPage._page.initializedOrUndefined()) result.push(backgroundPage._page);
488
488
  }
489
489
  return result;
490
490
  }
@@ -100,11 +100,7 @@ class CRExecutionContext {
100
100
  }
101
101
  exports.CRExecutionContext = CRExecutionContext;
102
102
  function rewriteError(error) {
103
- if (error.message.includes('Object reference chain is too long')) return {
104
- result: {
105
- type: 'undefined'
106
- }
107
- };
103
+ if (error.message.includes('Object reference chain is too long')) throw new Error('Cannot serialize result: object reference chain is too long.');
108
104
  if (error.message.includes('Object couldn\'t be returned by value')) return {
109
105
  result: {
110
106
  type: 'undefined'
@@ -48,13 +48,19 @@ class RawKeyboardImpl {
48
48
  // remove the trailing : to match the Chromium command names.
49
49
  return commands.map(c => c.substring(0, c.length - 1));
50
50
  }
51
- async keydown(modifiers, code, keyCode, keyCodeWithoutLocation, key, location, autoRepeat, text) {
51
+ async keydown(modifiers, keyName, description, autoRepeat) {
52
+ const {
53
+ code,
54
+ key,
55
+ location,
56
+ text
57
+ } = description;
52
58
  if (code === 'Escape' && (await this._dragManger.cancelDrag())) return;
53
59
  const commands = this._commandsForCode(code, modifiers);
54
60
  await this._client.send('Input.dispatchKeyEvent', {
55
61
  type: text ? 'keyDown' : 'rawKeyDown',
56
62
  modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
57
- windowsVirtualKeyCode: keyCodeWithoutLocation,
63
+ windowsVirtualKeyCode: description.keyCodeWithoutLocation,
58
64
  code,
59
65
  commands,
60
66
  key,
@@ -65,12 +71,17 @@ class RawKeyboardImpl {
65
71
  isKeypad: location === input.keypadLocation
66
72
  });
67
73
  }
68
- async keyup(modifiers, code, keyCode, keyCodeWithoutLocation, key, location) {
74
+ async keyup(modifiers, keyName, description) {
75
+ const {
76
+ code,
77
+ key,
78
+ location
79
+ } = description;
69
80
  await this._client.send('Input.dispatchKeyEvent', {
70
81
  type: 'keyUp',
71
82
  modifiers: (0, _crProtocolHelper.toModifiersMask)(modifiers),
72
83
  key,
73
- windowsVirtualKeyCode: keyCodeWithoutLocation,
84
+ windowsVirtualKeyCode: description.keyCodeWithoutLocation,
74
85
  code,
75
86
  location
76
87
  });
@@ -71,8 +71,6 @@ class CRPage {
71
71
  this._pdf = void 0;
72
72
  this._coverage = void 0;
73
73
  this._browserContext = void 0;
74
- this._pagePromise = void 0;
75
- this._initializedPage = null;
76
74
  this._isBackgroundPage = void 0;
77
75
  // Holds window features for the next popup being opened via window.open,
78
76
  // until the popup target arrives. This could be racy if two oopifs
@@ -109,29 +107,15 @@ class CRPage {
109
107
  screen: viewportSize
110
108
  };
111
109
  }
112
- // Note: it is important to call |reportAsNew| before resolving pageOrError promise,
113
- // so that anyone who awaits pageOrError got a ready and reported page.
114
- this._pagePromise = this._mainFrameSession._initialize(bits.hasUIWindow).then(async r => {
115
- await this._page.initOpener(this._opener);
116
- return r;
117
- }).catch(async e => {
118
- await this._page.initOpener(this._opener);
119
- throw e;
120
- }).then(() => {
121
- this._initializedPage = this._page;
122
- this._reportAsNew();
123
- return this._page;
124
- }).catch(e => {
125
- this._reportAsNew(e);
126
- return e;
110
+ const createdEvent = this._isBackgroundPage ? _crBrowser.CRBrowserContext.CREvents.BackgroundPage : _browserContext.BrowserContext.Events.Page;
111
+ this._mainFrameSession._initialize(bits.hasUIWindow).then(() => {
112
+ var _this$_opener;
113
+ return this._page.reportAsNew((_this$_opener = this._opener) === null || _this$_opener === void 0 ? void 0 : _this$_opener._page, undefined, createdEvent);
114
+ }, error => {
115
+ var _this$_opener2;
116
+ return this._page.reportAsNew((_this$_opener2 = this._opener) === null || _this$_opener2 === void 0 ? void 0 : _this$_opener2._page, error, createdEvent);
127
117
  });
128
118
  }
129
- potentiallyUninitializedPage() {
130
- return this._page;
131
- }
132
- _reportAsNew(error) {
133
- this._page.reportAsNew(error, this._isBackgroundPage ? _crBrowser.CRBrowserContext.CREvents.BackgroundPage : _browserContext.BrowserContext.Events.Page);
134
- }
135
119
  async _forAllFrameSessions(cb) {
136
120
  const frameSessions = Array.from(this._sessions.values());
137
121
  await Promise.all(frameSessions.map(frameSession => {
@@ -159,9 +143,6 @@ class CRPage {
159
143
  willBeginDownload() {
160
144
  this._mainFrameSession._willBeginDownload();
161
145
  }
162
- async pageOrError() {
163
- return this._pagePromise;
164
- }
165
146
  didClose() {
166
147
  for (const session of this._sessions.values()) session.dispose();
167
148
  this._page._didClose();
@@ -398,6 +379,9 @@ class FrameSession {
398
379
  this._firstNonInitialNavigationCommittedFulfill = f;
399
380
  this._firstNonInitialNavigationCommittedReject = r;
400
381
  });
382
+ // The Promise is not always awaited (e.g. FrameSession._initialize can throw)
383
+ // so we catch errors here to prevent unhandled promise rejection.
384
+ this._firstNonInitialNavigationCommittedPromise.catch(() => {});
401
385
  }
402
386
  _isMainFrame() {
403
387
  return this._targetId === this._crPage._targetId;
@@ -429,7 +413,7 @@ class FrameSession {
429
413
  // Note: it is important to start video recorder before sending Page.startScreencast,
430
414
  // and it is equally important to send Page.startScreencast before sending Runtime.runIfWaitingForDebugger.
431
415
  await this._createVideoRecorder(screencastId, screencastOptions);
432
- this._crPage.pageOrError().then(p => {
416
+ this._crPage._page.waitForInitializedOrError().then(p => {
433
417
  if (p instanceof Error) this._stopVideoRecording().catch(() => {});
434
418
  });
435
419
  }
@@ -671,6 +655,9 @@ class FrameSession {
671
655
  const frame = this._page._frameManager.frame(targetId);
672
656
  if (!frame) return; // Subtree may be already gone due to renderer/browser race.
673
657
  this._page._frameManager.removeChildFramesRecursively(frame);
658
+ for (const [contextId, context] of this._contextIdToContext) {
659
+ if (context.frame === frame) this._onExecutionContextDestroyed(contextId);
660
+ }
674
661
  const frameSession = new FrameSession(this._crPage, session, targetId, this);
675
662
  this._crPage._sessions.set(targetId, frameSession);
676
663
  frameSession._initialize(false).catch(e => e);
@@ -773,7 +760,7 @@ class FrameSession {
773
760
  this._page._addConsoleMessage(event.type, values, (0, _crProtocolHelper.toConsoleMessageLocation)(event.stackTrace));
774
761
  }
775
762
  async _onBindingCalled(event) {
776
- const pageOrError = await this._crPage.pageOrError();
763
+ const pageOrError = await this._crPage._page.waitForInitializedOrError();
777
764
  if (!(pageOrError instanceof Error)) {
778
765
  const context = this._contextIdToContext.get(event.executionContextId);
779
766
  if (context) await this._page._onBindingCalled(event.payload, context);else await this._page._onBindingCalled(event.payload, await this._page.mainFrame()._mainContext()); // This might be a bit sketchy but it works for now
@@ -782,6 +769,8 @@ class FrameSession {
782
769
  _onDialog(event) {
783
770
  if (!this._page._frameManager.frame(this._targetId)) return; // Our frame/subtree may be gone already.
784
771
  this._page.emitOnContext(_browserContext.BrowserContext.Events.Dialog, new dialog.Dialog(this._page, event.type, event.message, async (accept, promptText) => {
772
+ // TODO: this should actually be a CDP event that notifies about a cancelled navigation attempt.
773
+ if (this._isMainFrame() && event.type === 'beforeunload' && !accept) this._page._frameManager.frameAbortedNavigation(this._page.mainFrame()._id, 'navigation cancelled by beforeunload dialog');
785
774
  await this._client.send('Page.handleJavaScriptDialog', {
786
775
  accept,
787
776
  promptText
@@ -829,8 +818,7 @@ class FrameSession {
829
818
  await this._page._onFileChooserOpened(handle);
830
819
  }
831
820
  _willBeginDownload() {
832
- const originPage = this._crPage._initializedPage;
833
- if (!originPage) {
821
+ if (!this._crPage._page.initializedOrUndefined()) {
834
822
  // Resume the page creation with an error. The page will automatically close right
835
823
  // after the download begins.
836
824
  this._firstNonInitialNavigationCommittedReject(new Error('Starting new page download'));
@@ -869,7 +857,7 @@ class FrameSession {
869
857
  });
870
858
  // Wait for the first frame before reporting video to the client.
871
859
  gotFirstFrame.then(() => {
872
- this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage.pageOrError());
860
+ this._crPage._browserContext._browser._videoStarted(this._crPage._browserContext, screencastId, options.outputFile, this._crPage._page.waitForInitializedOrError());
873
861
  });
874
862
  }
875
863
  async _stopVideoRecording() {
@@ -151,7 +151,12 @@ class CSharpLanguageGenerator {
151
151
  using var playwright = await Playwright.CreateAsync();
152
152
  await using var browser = await playwright.${toPascal(options.browserName)}.LaunchAsync(${formatObject(options.launchOptions, ' ', 'BrowserTypeLaunchOptions')});
153
153
  var context = await browser.NewContextAsync(${formatContextOptions(options.contextOptions, options.deviceName)});`);
154
- if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
154
+ if (options.contextOptions.recordHar) {
155
+ const url = options.contextOptions.recordHar.urlFilter;
156
+ formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({
157
+ url
158
+ }, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
159
+ }
155
160
  formatter.newLine();
156
161
  return formatter.format();
157
162
  }
@@ -176,7 +181,12 @@ class CSharpLanguageGenerator {
176
181
  formatter.add(` [${this._mode === 'nunit' ? 'Test' : 'TestMethod'}]
177
182
  public async Task MyTest()
178
183
  {`);
179
- if (options.contextOptions.recordHar) formatter.add(` await context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)});`);
184
+ if (options.contextOptions.recordHar) {
185
+ const url = options.contextOptions.recordHar.urlFilter;
186
+ formatter.add(` await Context.RouteFromHARAsync(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatObject({
187
+ url
188
+ }, ' ', 'BrowserContextRouteFromHAROptions')}` : ''});`);
189
+ }
180
190
  return formatter.format();
181
191
  }
182
192
  generateFooter(saveStorage) {
@@ -135,27 +135,38 @@ class JavaLanguageGenerator {
135
135
  import com.microsoft.playwright.Page;
136
136
  import com.microsoft.playwright.options.*;
137
137
 
138
- import org.junit.jupiter.api.*;
138
+ ${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import org.junit.jupiter.api.*;
139
139
  import static com.microsoft.playwright.assertions.PlaywrightAssertions.*;
140
140
 
141
141
  @UsePlaywright
142
142
  public class TestExample {
143
143
  @Test
144
144
  void test(Page page) {`);
145
+ if (options.contextOptions.recordHar) {
146
+ const url = options.contextOptions.recordHar.urlFilter;
147
+ const recordHarOptions = typeof url === 'string' ? `, new Page.RouteFromHAROptions()
148
+ .setUrl(${quote(url)})` : '';
149
+ formatter.add(` page.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
150
+ }
145
151
  return formatter.format();
146
152
  }
147
153
  formatter.add(`
148
154
  import com.microsoft.playwright.*;
149
155
  import com.microsoft.playwright.options.*;
150
156
  import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
151
- import java.util.*;
157
+ ${options.contextOptions.recordHar ? `import java.nio.file.Paths;\n` : ''}import java.util.*;
152
158
 
153
159
  public class Example {
154
160
  public static void main(String[] args) {
155
161
  try (Playwright playwright = Playwright.create()) {
156
162
  Browser browser = playwright.${options.browserName}().launch(${formatLaunchOptions(options.launchOptions)});
157
163
  BrowserContext context = browser.newContext(${formatContextOptions(options.contextOptions, options.deviceName)});`);
158
- if (options.contextOptions.recordHar) formatter.add(` context.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
164
+ if (options.contextOptions.recordHar) {
165
+ const url = options.contextOptions.recordHar.urlFilter;
166
+ const recordHarOptions = typeof url === 'string' ? `, new BrowserContext.RouteFromHAROptions()
167
+ .setUrl(${quote(url)})` : '';
168
+ formatter.add(` context.routeFromHAR(Paths.get(${quote(options.contextOptions.recordHar.path)})${recordHarOptions});`);
169
+ }
159
170
  return formatter.format();
160
171
  }
161
172
  generateFooter(saveStorage) {
@@ -106,7 +106,10 @@ class JavaScriptLanguageGenerator {
106
106
  return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).${assertion};`;
107
107
  }
108
108
  case 'assertSnapshot':
109
- return `${this._isTest ? '' : '// '}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot)});`;
109
+ {
110
+ const commentIfNeeded = this._isTest ? '' : '// ';
111
+ return `${commentIfNeeded}await expect(${subject}.${this._asLocator(action.selector)}).toMatchAriaSnapshot(${quoteMultiline(action.snapshot, `${commentIfNeeded} `)});`;
112
+ }
110
113
  }
111
114
  }
112
115
  _asLocator(selector) {
@@ -127,7 +130,12 @@ class JavaScriptLanguageGenerator {
127
130
  import { test, expect${options.deviceName ? ', devices' : ''} } from '@playwright/test';
128
131
  ${useText ? '\ntest.use(' + useText + ');\n' : ''}
129
132
  test('test', async ({ page }) => {`);
130
- if (options.contextOptions.recordHar) formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)});`);
133
+ if (options.contextOptions.recordHar) {
134
+ const url = options.contextOptions.recordHar.urlFilter;
135
+ formatter.add(` await page.routeFromHAR(${quote(options.contextOptions.recordHar.path)}${url ? `, ${formatOptions({
136
+ url
137
+ }, false)}` : ''});`);
138
+ }
131
139
  return formatter.format();
132
140
  }
133
141
  generateTestFooter(saveStorage) {
@@ -32,7 +32,7 @@ class JsonlLanguageGenerator {
32
32
  const locator = actionInContext.action.selector ? JSON.parse((0, _utils.asLocator)('jsonl', actionInContext.action.selector)) : undefined;
33
33
  const entry = {
34
34
  ...actionInContext.action,
35
- pageAlias: actionInContext.frame.pageAlias,
35
+ ...actionInContext.frame,
36
36
  locator
37
37
  };
38
38
  return JSON.stringify(entry);
@@ -123,6 +123,7 @@ class PythonLanguageGenerator {
123
123
  }
124
124
  generateHeader(options) {
125
125
  const formatter = new PythonFormatter();
126
+ const recordHar = options.contextOptions.recordHar;
126
127
  if (this._isPyTest) {
127
128
  const contextOptions = formatContextOptions(options.contextOptions, options.deviceName, true /* asDict */);
128
129
  const fixture = contextOptions ? `
@@ -132,12 +133,12 @@ def browser_context_args(browser_context_args, playwright) {
132
133
  return {${contextOptions}}
133
134
  }
134
135
  ` : '';
135
- formatter.add(`${options.deviceName ? 'import pytest\n' : ''}import re
136
+ formatter.add(`${options.deviceName || contextOptions ? 'import pytest\n' : ''}import re
136
137
  from playwright.sync_api import Page, expect
137
138
  ${fixture}
138
139
 
139
140
  def test_example(page: Page) -> None {`);
140
- if (options.contextOptions.recordHar) formatter.add(` page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
141
+ if (recordHar) formatter.add(` page.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
141
142
  } else if (this._isAsync) {
142
143
  formatter.add(`
143
144
  import asyncio
@@ -148,7 +149,7 @@ from playwright.async_api import Playwright, async_playwright, expect
148
149
  async def run(playwright: Playwright) -> None {
149
150
  browser = await playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
150
151
  context = await browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
151
- if (options.contextOptions.recordHar) formatter.add(` await page.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
152
+ if (recordHar) formatter.add(` await context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
152
153
  } else {
153
154
  formatter.add(`
154
155
  import re
@@ -158,7 +159,7 @@ from playwright.sync_api import Playwright, sync_playwright, expect
158
159
  def run(playwright: Playwright) -> None {
159
160
  browser = playwright.${options.browserName}.launch(${formatOptions(options.launchOptions, false)})
160
161
  context = browser.new_context(${formatContextOptions(options.contextOptions, options.deviceName)})`);
161
- if (options.contextOptions.recordHar) formatter.add(` context.route_from_har(${quote(options.contextOptions.recordHar.path)})`);
162
+ if (recordHar) formatter.add(` context.route_from_har(${quote(recordHar.path)}${typeof recordHar.urlFilter === 'string' ? `, url=${quote(recordHar.urlFilter)}` : ''})`);
162
163
  }
163
164
  return formatter.format();
164
165
  }