From 4bec6309df2c45ecbbb2767201a98f89f1cf8f5e Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Fri, 22 Jul 2022 19:44:02 -0700 Subject: [PATCH] fix(lifecycle): recalculate lifecycle on iframe detach (#15812) It could be that iframe was blocking some event, like load or networkidle, and we never updated the lifecycle after the iframe was detached. This lead to goto and other navigation commands to never resolve. --- packages/playwright-core/src/server/frames.ts | 7 ++-- tests/page/page-goto.spec.ts | 32 +++++++++++++++++++ tests/page/page-network-idle.spec.ts | 15 +++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index 1d708bf0da..ef8356ca63 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -277,8 +277,11 @@ export class FrameManager { frameDetached(frameId: string) { const frame = this._frames.get(frameId); - if (frame) + if (frame) { this._removeFramesRecursively(frame); + // Recalculate subtree lifecycle for the whole tree - it should not be that big. + this._page.mainFrame()._recalculateLifecycle(); + } } frameStoppedLoading(frameId: string) { @@ -590,7 +593,7 @@ export class Frame extends SdkObject { }); } - private _recalculateLifecycle() { + _recalculateLifecycle() { const events = new Set(this._firedLifecycleEvents); for (const child of this._childFrames) { child._recalculateLifecycle(); diff --git a/tests/page/page-goto.spec.ts b/tests/page/page-goto.spec.ts index bfb2493e0e..d7673be7c1 100644 --- a/tests/page/page-goto.spec.ts +++ b/tests/page/page-goto.spec.ts @@ -663,3 +663,35 @@ it('should return when navigation is committed if commit is specified', async ({ const response = await page.goto(server.EMPTY_PAGE, { waitUntil: 'commit' }); expect(response.status()).toBe(200); }); + +it('should wait for load when iframe attaches and detaches', async ({ page, server }) => { + server.setRoute('/empty.html', (req, res) => { + res.writeHead(200, { 'content-type': 'text/html' }); + res.end(` + + + + `); + }); + + server.setRoute('/iframe.html', (req, res) => { + res.writeHead(200, { 'content-type': 'text/html' }); + res.end(` + + `); + }); + + // Stall the css so that 'load' does not fire. + server.setRoute('/style2.css', () => {}); + + const frameDetached = page.waitForEvent('framedetached'); + const done = page.goto(server.EMPTY_PAGE, { waitUntil: 'load' }); + await frameDetached; // Make sure that iframe is gone. + await done; + expect(await page.$('iframe')).toBe(null); +}); diff --git a/tests/page/page-network-idle.spec.ts b/tests/page/page-network-idle.spec.ts index a93d7ba3e9..1447d156e0 100644 --- a/tests/page/page-network-idle.spec.ts +++ b/tests/page/page-network-idle.spec.ts @@ -157,3 +157,18 @@ it('should wait for networkidle from the popup', async ({ page, server, isAndroi await popup.waitForLoadState('networkidle'); } }); + +it('should wait for networkidle when iframe attaches and detaches', async ({ page }) => { + await page.setContent(` + + + + `, { waitUntil: 'networkidle' }); + expect(await page.$('iframe')).toBe(null); +});