From 95d1644415dbbcc94e548b0ce97d94081b602b07 Mon Sep 17 00:00:00 2001 From: "Yury Semikhatskii (from Dev Box)" Date: Wed, 3 Jun 2026 12:29:25 -0700 Subject: [PATCH 1/5] fix(chromium): do not hang connectOverCDP on a page stuck loading When connecting over CDP, a pre-existing tab whose first navigation never commits (e.g. stuck behind a hung proxy) left the page on the initial empty document forever, blocking the connection while it waited for a navigation that never arrived. Report such pre-existing pages as-is instead. Fixes: https://github.com/microsoft/playwright/issues/41093 --- .../src/server/chromium/crBrowser.ts | 6 ++- .../src/server/chromium/crPage.ts | 13 +++++-- .../library/chromium/connect-over-cdp.spec.ts | 38 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index ec96fb94966da..9a9c8a0eb1ddc 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -55,6 +55,8 @@ export class CRBrowser extends Browser { private _tracingRecording = false; private _tracingClient: CRSession | undefined; private _userAgent: string = ''; + // True while attaching to targets that already existed when we connected. + private _attachingPreExistingTargets = false; static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { // Make a copy in case we need to update `headful` property below. @@ -84,6 +86,7 @@ export class CRBrowser extends Browser { return browser; } browser._defaultContext = new CRBrowserContext(browser, undefined, options.persistent); + browser._attachingPreExistingTargets = true; await Promise.all([ session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }).then(async () => { // Target.setAutoAttach has a bug where it does not wait for new Targets being attached. @@ -93,6 +96,7 @@ export class CRBrowser extends Browser { }), browser._defaultContext.initialize(), ]); + browser._attachingPreExistingTargets = false; await browser._waitForAllPagesToBeInitialized(); return browser; } @@ -190,7 +194,7 @@ export class CRBrowser extends Browser { if (targetInfo.type === 'page' || treatOtherAsPage) { const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null; - const crPage = new CRPage(session, targetInfo.targetId, context, opener, { hasUIWindow: targetInfo.type === 'page' }); + const crPage = new CRPage(session, targetInfo.targetId, context, opener, { hasUIWindow: targetInfo.type === 'page', isPreExistingTarget: this._attachingPreExistingTargets }); this._crPages.set(targetInfo.targetId, crPage); return; } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 907f4a47907ca..95de9d2f32fe3 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -76,7 +76,7 @@ export class CRPage implements PageDelegate { return crPage._mainFrameSession; } - constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, bits: { hasUIWindow: boolean }) { + constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, bits: { hasUIWindow: boolean, isPreExistingTarget: boolean }) { this._targetId = targetId; this._opener = opener; const dragManager = new DragManager(this); @@ -106,7 +106,7 @@ export class CRPage implements PageDelegate { this._page.setEmulatedSizeFromWindowOpen({ viewport: viewportSize, screen: viewportSize }); } - this._mainFrameSession._initialize(bits.hasUIWindow).then( + this._mainFrameSession._initialize(bits.hasUIWindow, bits.isPreExistingTarget).then( () => this._page.reportAsNew(this._opener?._page, undefined), error => this._page.reportAsNew(this._opener?._page, error)); } @@ -437,7 +437,7 @@ class FrameSession { ]); } - async _initialize(hasUIWindow: boolean) { + async _initialize(hasUIWindow: boolean, isPreExistingTarget: boolean) { if (!this._page.isStorageStatePage && hasUIWindow && !this._crPage._browserContext._browser.isClank() && !this._crPage._browserContext._options.noDefaultViewport) { @@ -496,6 +496,11 @@ class FrameSession { lifecycleEventsEnabled.catch(e => {}).then(() => { this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); }); + // A pre-existing page may sit on the initial empty document forever (e.g. a tab whose + // navigation never commits), so report it instead of blocking the connection. + // See https://github.com/microsoft/playwright/issues/41093. + if (isPreExistingTarget) + this._firstNonInitialNavigationCommittedFulfill(); } else { this._firstNonInitialNavigationCommittedFulfill(); this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); @@ -719,7 +724,7 @@ class FrameSession { } const frameSession = new FrameSession(this._crPage, session, targetId, this); this._crPage._sessions.set(targetId, frameSession); - frameSession._initialize(false).catch(e => e); + frameSession._initialize(false, false).catch(e => e); return; } diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index 5ebca4c902078..7d15bf8dc7077 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -224,6 +224,44 @@ test('should connect to existing page with iframe and navigate', async ({ browse } }); +test('should connect to a browser with a page stuck loading its first navigation', { + annotation: { + type: 'issue', + description: 'https://github.com/microsoft/playwright/issues/41093' + } +}, async ({ browserType, server, childProcess, waitForPort, mode }, testInfo) => { + test.skip(mode !== 'default', 'Spawns the browser executable directly'); + + // Never respond, so the tab's first navigation never commits. + server.setRoute('/hang', () => {}); + + const port = 9339 + testInfo.workerIndex; + // Launch the browser directly (not via Playwright) so that we are the only CDP client. + childProcess({ + command: [browserType.executablePath(), + `--remote-debugging-port=${port}`, + `--user-data-dir=${testInfo.outputPath('cdp-user-data-dir')}`, + '--headless=new', + '--no-first-run', + '--no-default-browser-check', + 'about:blank', + ], + }); + await waitForPort(port); + + // Open a tab whose very first navigation never commits. + await Promise.all([ + server.waitForRequest('/hang'), + fetch(`http://127.0.0.1:${port}/json/new?${server.PREFIX}/hang`, { method: 'PUT' }), + ]); + + // connectOverCDP must not hang waiting for the stuck page to commit a navigation. + const cdpBrowser = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); + expect(cdpBrowser.contexts().length).toBe(1); + expect(cdpBrowser.contexts()[0].pages().length).toBeGreaterThan(0); + await cdpBrowser.close(); +}); + test('should connect to existing service workers', async ({ browserType, mode, server }, testInfo) => { const port = 9339 + testInfo.workerIndex; const browserServer = await browserType.launch({ From b9fee6e9af35f1c972cfd85ce53e960537bde442 Mon Sep 17 00:00:00 2001 From: "Yury Semikhatskii (from Dev Box)" Date: Wed, 3 Jun 2026 12:33:01 -0700 Subject: [PATCH 2/5] test(chromium): assert the previously-stuck page is usable after connect Verify connectOverCDP surfaces both pages and that releasing the held response lets the previously-stuck tab finish navigating to its URL. --- .../library/chromium/connect-over-cdp.spec.ts | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index 7d15bf8dc7077..f9c6befcc3af2 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -232,8 +232,14 @@ test('should connect to a browser with a page stuck loading its first navigation }, async ({ browserType, server, childProcess, waitForPort, mode }, testInfo) => { test.skip(mode !== 'default', 'Spawns the browser executable directly'); - // Never respond, so the tab's first navigation never commits. - server.setRoute('/hang', () => {}); + // Hold the response so the tab's first navigation never commits; release it later. + let releaseHang = () => {}; + server.setRoute('/hang', (req, res) => { + releaseHang = () => { + res.writeHead(200, { 'content-type': 'text/html' }); + res.end(''); + }; + }); const port = 9339 + testInfo.workerIndex; // Launch the browser directly (not via Playwright) so that we are the only CDP client. @@ -257,8 +263,14 @@ test('should connect to a browser with a page stuck loading its first navigation // connectOverCDP must not hang waiting for the stuck page to commit a navigation. const cdpBrowser = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); - expect(cdpBrowser.contexts().length).toBe(1); - expect(cdpBrowser.contexts()[0].pages().length).toBeGreaterThan(0); + const pages = cdpBrowser.contexts()[0].pages(); + expect(pages.length).toBe(2); + // The stuck tab is reported while still on the initial empty document. + const stuckPage = pages.find(page => page.url() === ':'); + expect(stuckPage).toBeTruthy(); + // Releasing the response lets the previously-stuck page finish navigating to /hang. + releaseHang(); + await stuckPage!.waitForURL(server.PREFIX + '/hang'); await cdpBrowser.close(); }); From 222be5d6c0150dc72d9239d91c64d1c102cce5f7 Mon Sep 17 00:00:00 2001 From: "Yury Semikhatskii (from Dev Box)" Date: Wed, 3 Jun 2026 14:12:14 -0700 Subject: [PATCH 3/5] chore(chromium): drop redundant comment on _attachingPreExistingTargets --- packages/playwright-core/src/server/chromium/crBrowser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 9a9c8a0eb1ddc..58d6dce708c0d 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -55,7 +55,6 @@ export class CRBrowser extends Browser { private _tracingRecording = false; private _tracingClient: CRSession | undefined; private _userAgent: string = ''; - // True while attaching to targets that already existed when we connected. private _attachingPreExistingTargets = false; static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { From e28d90bc95c38f23432505e2ac2bfe1bc2f272d5 Mon Sep 17 00:00:00 2001 From: "Yury Semikhatskii (from Dev Box)" Date: Wed, 3 Jun 2026 16:53:38 -0700 Subject: [PATCH 4/5] fix(chromium): skip page-init wait when connecting over CDP Per review feedback, rather than reporting a stuck pre-existing page early, do not wait for all pages to be initialized when connecting over CDP. A tab stuck loading no longer blocks the connection; pages are reported as they initialize. Existing tests that read pages right after connect now wait for them to appear. --- .../src/server/chromium/chromium.ts | 2 +- .../src/server/chromium/crBrowser.ts | 13 +++++++------ .../src/server/chromium/crPage.ts | 13 ++++--------- tests/library/chromium/connect-over-cdp.spec.ts | 16 ++++++++-------- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index d9f1c9da72602..b30b06cbc3f4d 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -157,7 +157,7 @@ export class Chromium extends BrowserType { noDefaults: options.noDefaults, }; validateBrowserContextOptions(persistent, browserOptions); - const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, transport, browserOptions)); + const browser = await progress.race(CRBrowser.connect(this.attribution.playwright, transport, browserOptions, undefined, true /* connectToExistingBrowser */)); if (!options.isLocal) browser._isBrowserCollocatedWithServer = false; browser.on(Browser.Events.Disconnected, doCleanup); diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 58d6dce708c0d..2d20a34202b8d 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -55,9 +55,8 @@ export class CRBrowser extends Browser { private _tracingRecording = false; private _tracingClient: CRSession | undefined; private _userAgent: string = ''; - private _attachingPreExistingTargets = false; - static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools): Promise { + static async connect(parent: SdkObject, transport: ConnectionTransport, options: BrowserOptions, devtools?: CRDevTools, connectToExistingBrowser?: boolean): Promise { // Make a copy in case we need to update `headful` property below. options = { ...options }; const connection = new CRConnection(parent, transport, options.protocolLogger, options.browserLogsCollector); @@ -85,7 +84,6 @@ export class CRBrowser extends Browser { return browser; } browser._defaultContext = new CRBrowserContext(browser, undefined, options.persistent); - browser._attachingPreExistingTargets = true; await Promise.all([ session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }).then(async () => { // Target.setAutoAttach has a bug where it does not wait for new Targets being attached. @@ -95,8 +93,11 @@ export class CRBrowser extends Browser { }), browser._defaultContext.initialize(), ]); - browser._attachingPreExistingTargets = false; - await browser._waitForAllPagesToBeInitialized(); + // When connecting to an existing browser (connectOverCDP), it may have pages that are stuck + // loading and will never finish initializing, so do not block the connection on them. + // See https://github.com/microsoft/playwright/issues/41093. + if (!connectToExistingBrowser) + await browser._waitForAllPagesToBeInitialized(); return browser; } @@ -193,7 +194,7 @@ export class CRBrowser extends Browser { if (targetInfo.type === 'page' || treatOtherAsPage) { const opener = targetInfo.openerId ? this._crPages.get(targetInfo.openerId) || null : null; - const crPage = new CRPage(session, targetInfo.targetId, context, opener, { hasUIWindow: targetInfo.type === 'page', isPreExistingTarget: this._attachingPreExistingTargets }); + const crPage = new CRPage(session, targetInfo.targetId, context, opener, { hasUIWindow: targetInfo.type === 'page' }); this._crPages.set(targetInfo.targetId, crPage); return; } diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 95de9d2f32fe3..907f4a47907ca 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -76,7 +76,7 @@ export class CRPage implements PageDelegate { return crPage._mainFrameSession; } - constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, bits: { hasUIWindow: boolean, isPreExistingTarget: boolean }) { + constructor(client: CRSession, targetId: string, browserContext: CRBrowserContext, opener: CRPage | null, bits: { hasUIWindow: boolean }) { this._targetId = targetId; this._opener = opener; const dragManager = new DragManager(this); @@ -106,7 +106,7 @@ export class CRPage implements PageDelegate { this._page.setEmulatedSizeFromWindowOpen({ viewport: viewportSize, screen: viewportSize }); } - this._mainFrameSession._initialize(bits.hasUIWindow, bits.isPreExistingTarget).then( + this._mainFrameSession._initialize(bits.hasUIWindow).then( () => this._page.reportAsNew(this._opener?._page, undefined), error => this._page.reportAsNew(this._opener?._page, error)); } @@ -437,7 +437,7 @@ class FrameSession { ]); } - async _initialize(hasUIWindow: boolean, isPreExistingTarget: boolean) { + async _initialize(hasUIWindow: boolean) { if (!this._page.isStorageStatePage && hasUIWindow && !this._crPage._browserContext._browser.isClank() && !this._crPage._browserContext._options.noDefaultViewport) { @@ -496,11 +496,6 @@ class FrameSession { lifecycleEventsEnabled.catch(e => {}).then(() => { this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); }); - // A pre-existing page may sit on the initial empty document forever (e.g. a tab whose - // navigation never commits), so report it instead of blocking the connection. - // See https://github.com/microsoft/playwright/issues/41093. - if (isPreExistingTarget) - this._firstNonInitialNavigationCommittedFulfill(); } else { this._firstNonInitialNavigationCommittedFulfill(); this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); @@ -724,7 +719,7 @@ class FrameSession { } const frameSession = new FrameSession(this._crPage, session, targetId, this); this._crPage._sessions.set(targetId, frameSession); - frameSession._initialize(false, false).catch(e => e); + frameSession._initialize(false).catch(e => e); return; } diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index f9c6befcc3af2..d3081d5884de1 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -217,6 +217,7 @@ test('should connect to existing page with iframe and navigate', async ({ browse const cdpBrowser = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); const contexts = cdpBrowser.contexts(); expect(contexts.length).toBe(1); + await expect.poll(() => contexts[0].pages().length).toBe(1); await contexts[0].pages()[0].goto(server.EMPTY_PAGE); await cdpBrowser.close(); } finally { @@ -263,14 +264,11 @@ test('should connect to a browser with a page stuck loading its first navigation // connectOverCDP must not hang waiting for the stuck page to commit a navigation. const cdpBrowser = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); - const pages = cdpBrowser.contexts()[0].pages(); - expect(pages.length).toBe(2); - // The stuck tab is reported while still on the initial empty document. - const stuckPage = pages.find(page => page.url() === ':'); - expect(stuckPage).toBeTruthy(); - // Releasing the response lets the previously-stuck page finish navigating to /hang. + const context = cdpBrowser.contexts()[0]; + // The stuck tab has not committed a navigation yet, so it is not reported. Releasing the + // response lets it finish navigating to /hang and show up in the context. releaseHang(); - await stuckPage!.waitForURL(server.PREFIX + '/hang'); + await expect.poll(() => context.pages().map(page => page.url())).toContain(server.PREFIX + '/hang'); await cdpBrowser.close(); }); @@ -427,7 +425,7 @@ test('should report all pages in an existing browser', async ({ browserType }, t const cdpBrowser2 = await browserType.connectOverCDP({ endpointURL: `http://127.0.0.1:${port}/`, }); - expect(cdpBrowser2.contexts()[0].pages().length).toBe(3); + await expect.poll(() => cdpBrowser2.contexts()[0].pages().length).toBe(3); await cdpBrowser2.close(); } finally { @@ -728,6 +726,7 @@ test('should not reuse utility worlds between two clients', async ({ browserType const browser2 = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); const context2 = browser2.contexts()[0]; + await expect.poll(() => context2.pages().length).toBe(1); const page2 = context2.pages()[0]; const frameImpl2 = toImpl(page2.mainFrame()) as Frame; const result = await frameImpl2.evaluateExpression(nullProgress, 'window.foo', { world: 'utility' }); @@ -766,6 +765,7 @@ test('should get title and URL of existing page', async ({ browserType, mode, se browsers.push(cdpBrowser); const [context] = cdpBrowser.contexts(); + await expect.poll(() => context.pages().length).toBe(1); const [page] = context.pages(); expect(page.url()).toBe(server.EMPTY_PAGE); expect(await page.title()).toBe('my title'); From 3c0437974a44b00f161635090680829821bcfe00 Mon Sep 17 00:00:00 2001 From: "Yury Semikhatskii (from Dev Box)" Date: Wed, 3 Jun 2026 19:21:50 -0700 Subject: [PATCH 5/5] fix(chromium): only wait for committed pages when connecting over CDP Rather than skipping the page-initialization wait entirely, parametrize it: when connecting over CDP, still wait for pages that have committed a navigation, but not for pages stuck on the initial empty document (their first navigation may never commit). This keeps the page list available right after connect for normal pages, while not hanging on a stuck tab. --- .../src/server/chromium/crBrowser.ts | 17 ++++++++++------- .../src/server/chromium/crPage.ts | 4 ++++ tests/library/chromium/connect-over-cdp.spec.ts | 5 +---- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crBrowser.ts b/packages/playwright-core/src/server/chromium/crBrowser.ts index 2d20a34202b8d..3b0fa29be618e 100644 --- a/packages/playwright-core/src/server/chromium/crBrowser.ts +++ b/packages/playwright-core/src/server/chromium/crBrowser.ts @@ -93,11 +93,7 @@ export class CRBrowser extends Browser { }), browser._defaultContext.initialize(), ]); - // When connecting to an existing browser (connectOverCDP), it may have pages that are stuck - // loading and will never finish initializing, so do not block the connection on them. - // See https://github.com/microsoft/playwright/issues/41093. - if (!connectToExistingBrowser) - await browser._waitForAllPagesToBeInitialized(); + await browser._waitForAllPagesToBeInitialized(connectToExistingBrowser); return browser; } @@ -161,8 +157,15 @@ export class CRBrowser extends Browser { return this.options.name === 'clank'; } - async _waitForAllPagesToBeInitialized() { - await Promise.all([...this._crPages.values()].map(crPage => crPage._page.waitForInitializedOrError())); + async _waitForAllPagesToBeInitialized(connectToExistingBrowser?: boolean) { + await Promise.all([...this._crPages.values()].map(crPage => { + // When connecting to an existing browser, do not block on a page that is stuck on the initial + // empty document - its first navigation may never commit. Such a page is reported later, once + // it navigates. See https://github.com/microsoft/playwright/issues/41093. + if (connectToExistingBrowser) + return Promise.race([crPage._page.waitForInitializedOrError(), crPage._initialEmptyPagePromise]); + return crPage._page.waitForInitializedOrError(); + })); } _onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }: Protocol.Target.attachedToTargetPayload) { diff --git a/packages/playwright-core/src/server/chromium/crPage.ts b/packages/playwright-core/src/server/chromium/crPage.ts index 907f4a47907ca..884b07ae7729f 100644 --- a/packages/playwright-core/src/server/chromium/crPage.ts +++ b/packages/playwright-core/src/server/chromium/crPage.ts @@ -16,6 +16,7 @@ */ import { assert } from '@isomorphic/assert'; +import { ManualPromise } from '@isomorphic/manualPromise'; import { rewriteErrorMessage } from '@isomorphic/stackTrace'; import { eventsHelper } from '@utils/eventsHelper'; import * as dialog from '../dialog'; @@ -63,6 +64,7 @@ export class CRPage implements PageDelegate { private readonly _pdf: CRPDF; private readonly _coverage: CRCoverage; readonly _browserContext: CRBrowserContext; + readonly _initialEmptyPagePromise = new ManualPromise(); // Holds window features for the next popup being opened via window.open, // until the popup target arrives. This could be racy if two oopifs @@ -496,6 +498,8 @@ class FrameSession { lifecycleEventsEnabled.catch(e => {}).then(() => { this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); }); + if (!this._crPage._initialEmptyPagePromise.isDone()) + this._crPage._initialEmptyPagePromise.resolve(); } else { this._firstNonInitialNavigationCommittedFulfill(); this._eventListeners.push(eventsHelper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event))); diff --git a/tests/library/chromium/connect-over-cdp.spec.ts b/tests/library/chromium/connect-over-cdp.spec.ts index d3081d5884de1..91697c7d1abca 100644 --- a/tests/library/chromium/connect-over-cdp.spec.ts +++ b/tests/library/chromium/connect-over-cdp.spec.ts @@ -217,7 +217,6 @@ test('should connect to existing page with iframe and navigate', async ({ browse const cdpBrowser = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); const contexts = cdpBrowser.contexts(); expect(contexts.length).toBe(1); - await expect.poll(() => contexts[0].pages().length).toBe(1); await contexts[0].pages()[0].goto(server.EMPTY_PAGE); await cdpBrowser.close(); } finally { @@ -425,7 +424,7 @@ test('should report all pages in an existing browser', async ({ browserType }, t const cdpBrowser2 = await browserType.connectOverCDP({ endpointURL: `http://127.0.0.1:${port}/`, }); - await expect.poll(() => cdpBrowser2.contexts()[0].pages().length).toBe(3); + expect(cdpBrowser2.contexts()[0].pages().length).toBe(3); await cdpBrowser2.close(); } finally { @@ -726,7 +725,6 @@ test('should not reuse utility worlds between two clients', async ({ browserType const browser2 = await browserType.connectOverCDP(`http://127.0.0.1:${port}/`); const context2 = browser2.contexts()[0]; - await expect.poll(() => context2.pages().length).toBe(1); const page2 = context2.pages()[0]; const frameImpl2 = toImpl(page2.mainFrame()) as Frame; const result = await frameImpl2.evaluateExpression(nullProgress, 'window.foo', { world: 'utility' }); @@ -765,7 +763,6 @@ test('should get title and URL of existing page', async ({ browserType, mode, se browsers.push(cdpBrowser); const [context] = cdpBrowser.contexts(); - await expect.poll(() => context.pages().length).toBe(1); const [page] = context.pages(); expect(page.url()).toBe(server.EMPTY_PAGE); expect(await page.title()).toBe('my title');