diff --git a/packages/playwright-core/src/server/frames.ts b/packages/playwright-core/src/server/frames.ts index dd24193f21..6dc412c235 100644 --- a/packages/playwright-core/src/server/frames.ts +++ b/packages/playwright-core/src/server/frames.ts @@ -805,6 +805,8 @@ export class Frame extends SdkObject { return continuePolling; } const result = await resolved.injected.evaluateHandle((injected, { info, root }) => { + if (root && !root.isConnected) + throw injected.createStacklessError('Element is not attached to the DOM'); const elements = injected.querySelectorAll(info.parsed, root || document); const element: Element | undefined = elements[0]; const visible = element ? injected.utils.isElementVisible(element) : false; diff --git a/tests/page/page-wait-for-selector-2.spec.ts b/tests/page/page-wait-for-selector-2.spec.ts index 5a23e581c7..4f473f86f2 100644 --- a/tests/page/page-wait-for-selector-2.spec.ts +++ b/tests/page/page-wait-for-selector-2.spec.ts @@ -328,3 +328,31 @@ it('should fail when navigating while on handle', async ({ page, mode, server }) const error = await body.waitForSelector('div', { __testHookBeforeAdoptNode } as any).catch(e => e); expect(error.message).toContain(`waiting for locator('div') to be visible`); }); + +it('should fail if element handle was detached while waiting', async ({ page, server }) => { + await page.setContent(``); + const button = await page.$('button'); + const promise = button.waitForSelector('something').catch(e => e); + await page.waitForTimeout(100); + await page.evaluate(() => document.body.innerText = ''); + const error = await promise; + expect(error.message).toContain('Element is not attached to the DOM'); +}); + +it('should succeed if element handle was detached while waiting for hidden', async ({ page, server }) => { + await page.setContent(``); + const button = await page.$('button'); + const promise = button.waitForSelector('something', { state: 'hidden' }); + await page.waitForTimeout(100); + await page.evaluate(() => document.body.innerText = ''); + await promise; +}); + +it('should succeed if element handle was detached while waiting for detached', async ({ page, server }) => { + await page.setContent(``); + const button = await page.$('button'); + const promise = button.waitForSelector('something', { state: 'detached' }); + await page.waitForTimeout(100); + await page.evaluate(() => document.body.innerText = ''); + await promise; +}); diff --git a/tests/page/selectors-frame.spec.ts b/tests/page/selectors-frame.spec.ts index c32a00e8e9..fda55e9958 100644 --- a/tests/page/selectors-frame.spec.ts +++ b/tests/page/selectors-frame.spec.ts @@ -308,17 +308,6 @@ it('click should survive navigation', async ({ page, server }) => { await promise; }); -it('should fail if element removed while waiting on element handle', async ({ page, server }) => { - it.fixme(); - await routeIframe(page); - await page.goto(server.PREFIX + '/iframe.html'); - const button = await page.$('button'); - const promise = button.waitForSelector('something'); - await page.waitForTimeout(100); - await page.evaluate(() => document.body.innerText = ''); - await promise; -}); - it('should non work for non-frame', async ({ page, server }) => { await routeIframe(page); await page.setContent('
');