diff --git a/packages/injected/src/domUtils.ts b/packages/injected/src/domUtils.ts index c88184506a424..438607f6582ff 100644 --- a/packages/injected/src/domUtils.ts +++ b/packages/injected/src/domUtils.ts @@ -114,6 +114,12 @@ function computeElementStyleVisibilityVisible(element: Element, style?: CSSStyle } if (style.visibility !== 'visible') return false; + try { + const frameElement = element.ownerDocument?.defaultView?.frameElement; + if (frameElement && !isElementStyleVisibilityVisible(frameElement)) + return false; + } catch { + } return true; } diff --git a/tests/page/locator-is-visible.spec.ts b/tests/page/locator-is-visible.spec.ts index 4349f3b3d8625..f601f0e4dc5ff 100644 --- a/tests/page/locator-is-visible.spec.ts +++ b/tests/page/locator-is-visible.spec.ts @@ -103,3 +103,52 @@ it('isVisible with invalid selector should throw', async ({ page }) => { const error = await page.locator('hey=what').isVisible().catch(e => e); expect(error.message).toContain('Unknown engine "hey" while parsing selector hey=what'); }); + +it('isVisible and isHidden should work with iframe visibility', async ({ page }) => { + await page.setContent(` + +
+ + + `); + + const btn1 = page.frameLocator('[name="f1"]').locator('button'); + expect(await btn1.isVisible()).toBe(false); + expect(await btn1.isHidden()).toBe(true); + await expect(btn1).toBeHidden(); + await expect(btn1).not.toBeVisible(); + await btn1.waitFor({ state: 'hidden' }); + + const btn2 = page.frameLocator('[name="f2"]').locator('button'); + expect(await btn2.isVisible()).toBe(false); + expect(await btn2.isHidden()).toBe(true); + await expect(btn2).toBeHidden(); + await expect(btn2).not.toBeVisible(); + + const btn3 = page.frameLocator('[name="f3"]').locator('button'); + expect(await btn3.isVisible()).toBe(true); + expect(await btn3.isHidden()).toBe(false); + await expect(btn3).toBeVisible(); + + const btn4 = page.frameLocator('[name="outer"]').frameLocator('[name="inner"]').locator('button'); + expect(await btn4.isVisible()).toBe(false); + expect(await btn4.isHidden()).toBe(true); + await expect(btn4).toBeHidden(); + + // Dynamically toggling iframe visibility + await page.evaluate(() => { + (document.querySelector('[name="f1"]') as HTMLElement).style.visibility = 'visible'; + }); + expect(await btn1.isVisible()).toBe(true); + await expect(btn1).toBeVisible(); + + await page.evaluate(() => { + (document.querySelector('[name="f1"]') as HTMLElement).style.visibility = 'hidden'; + }); + expect(await btn1.isVisible()).toBe(false); + await expect(btn1).toBeHidden(); +});