Skip to content

Commit bc7102d

Browse files
DavertMikDavertMikclaude
authored
fix(Playwright): switchTo resolves nested iframes relative to current frame (#5717)
switchTo() always built the frame locator from the page, so switching into an iframe nested inside the current frame silently resolved to nothing: the step passed, but every following action timed out and no element on any level was reachable. The existence check ahead of it runs against the current frame, which is why the bad switch was never reported. Chain the frame locator from the current frame when there is one, as 3.x did. frameLocator() is synchronous, so the Promise.race wrapper never raced anything and left a dangling 5s timer behind on every switchTo call. Fixes #5688 Claude-Session: https://claude.ai/code/session_01M93h8jjpyUt1A7Px3kwpxC Co-authored-by: DavertMik <davert@testomat.io> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent fcf3440 commit bc7102d

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

lib/helper/Playwright.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3667,8 +3667,7 @@ class Playwright extends Helper {
36673667
}
36683668

36693669
try {
3670-
// Always create frame locator from page to avoid nested frame paths
3671-
this.frame = await Promise.race([this.page.frameLocator(locator), new Promise((_, reject) => setTimeout(() => reject(new Error('Frame locator timeout')), 5000))])
3670+
this.frame = this.frame ? this.frame.frameLocator(locator) : this.page.frameLocator(locator)
36723671
} catch (e) {
36733672
console.warn('Warning during frame locator creation:', e.message)
36743673
throw new Error(`Frame ${JSON.stringify(locator)} could not be accessed`)

test/helper/webapi.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,38 @@ export function tests() {
19631963
})
19641964
})
19651965

1966+
describe('#switchTo', () => {
1967+
beforeEach(function () {
1968+
if (isHelper('CDPBrowser')) this.skip() // switchTo/iframes are not implemented in CDPBrowser
1969+
})
1970+
1971+
it('should switch to nested iframes one by one', async () => {
1972+
await I.amOnPage('/iframe_nested')
1973+
await I.switchTo('[name=wrapper]')
1974+
await I.see('Iframe test')
1975+
await I.switchTo('[name=content]')
1976+
await I.see('Information')
1977+
await I.see('Lots of valuable data here')
1978+
})
1979+
1980+
it('should return to the top level context from a nested iframe', async () => {
1981+
await I.amOnPage('/iframe_nested')
1982+
await I.switchTo('[name=wrapper]')
1983+
await I.switchTo('[name=content]')
1984+
await I.see('Information')
1985+
await I.switchTo(null)
1986+
await I.see('Nested Iframe test')
1987+
})
1988+
1989+
it('should not find a nested iframe from the top level context', async () => {
1990+
await I.amOnPage('/iframe_nested')
1991+
await I.switchTo('[name=content]').then(
1992+
() => assert.fail('switched to an iframe which is not in the current context'),
1993+
err => assert.include(err.message, 'was not found'),
1994+
)
1995+
})
1996+
})
1997+
19661998
describe('scroll: #scrollTo, #scrollPageToTop, #scrollPageToBottom', () => {
19671999
beforeEach(function () {
19682000
if (I.capabilities?.layout === 'none') this.skip() // scrolling requires a real layout engine

0 commit comments

Comments
 (0)