diff --git a/packages/playwright-core/src/server/bidi/bidiBrowser.ts b/packages/playwright-core/src/server/bidi/bidiBrowser.ts index b9d3ce349631b..fe4565cfd6247 100644 --- a/packages/playwright-core/src/server/bidi/bidiBrowser.ts +++ b/packages/playwright-core/src/server/bidi/bidiBrowser.ts @@ -137,10 +137,13 @@ export class BidiBrowser extends Browser { const page = this._findPageForFrame(parentFrameId); if (page) { page._session.addFrameBrowsingContext(event.context); - page._page.frameManager.frameAttached(event.context, parentFrameId); - const frame = page._page.frameManager.frame(event.context); - if (frame) - frame._url = event.url; + const frame = page._page.frameManager.frameAttached(event.context, parentFrameId); + frame._url = event.url; + page._getFrameNode(frame).then(node => { + const attributes = node?.value?.attributes; + frame._name = attributes?.name ?? attributes?.id ?? ''; + }); + return; } return; } diff --git a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts index c7e0a745ebafe..cef9cbdfa9898 100644 --- a/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts +++ b/packages/playwright-core/src/server/bidi/bidiExecutionContext.ts @@ -141,11 +141,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate { }; } - async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise { + async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise { const result = await this._remoteValueForReference(nodeId, true); if (!('handle' in result)) throw new Error('Can\'t get remote object for nodeId'); - return createHandle(context, result); + return createHandle(context, result) as dom.ElementHandle; } async contentFrameIdForFrame(handle: dom.ElementHandle) { diff --git a/packages/playwright-core/src/server/bidi/bidiPage.ts b/packages/playwright-core/src/server/bidi/bidiPage.ts index 9041469d57c99..a068522b97a14 100644 --- a/packages/playwright-core/src/server/bidi/bidiPage.ts +++ b/packages/playwright-core/src/server/bidi/bidiPage.ts @@ -196,7 +196,8 @@ export class BidiPage implements PageDelegate { private _onNavigationCommitted(params: bidi.BrowsingContext.NavigationInfo) { const frameId = params.context; - this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, '', params.navigation!, /* initial */ false); + const frame = this._page.frameManager.frame(frameId)!; + this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, frame._name, params.navigation!, /* initial */ false); } private _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) { @@ -587,24 +588,24 @@ export class BidiPage implements PageDelegate { const parent = frame.parentFrame(); if (!parent) throw new Error('Frame has been detached.'); - const parentContext = await parent._mainContext(); - const list = await parentContext.evaluateHandle(() => { return [...document.querySelectorAll('iframe,frame')]; }); - const length = await list.evaluate(list => list.length); - let foundElement = null; - for (let i = 0; i < length; i++) { - const element = await list.evaluateHandle((list, i) => list[i], i); - const candidate = await element.contentFrame(); - if (frame === candidate) { - foundElement = element; - break; - } else { - element.dispose(); - } - } - list.dispose(); - if (!foundElement) + const node = await this._getFrameNode(frame); + if (!node?.sharedId) throw new Error('Frame has been detached.'); - return foundElement; + const parentFrameExecutionContext = await parent._mainContext(); + return await toBidiExecutionContext(parentFrameExecutionContext).remoteObjectForNodeId(parentFrameExecutionContext, { sharedId: node.sharedId }); + } + + async _getFrameNode(frame: frames.Frame): Promise { + const parent = frame.parentFrame(); + if (!parent) + return undefined; + + const result = await this._session.send('browsingContext.locateNodes', { + context: parent._id, + locator: { type: 'context', value: { context: frame._id } }, + }); + const node = result.nodes[0]; + return node; } shouldToggleStyleSheetToSyncAnimations(): boolean { diff --git a/tests/library/hit-target.spec.ts b/tests/library/hit-target.spec.ts index 6bf4085b6b393..19578b8e62b42 100644 --- a/tests/library/hit-target.spec.ts +++ b/tests/library/hit-target.spec.ts @@ -290,7 +290,7 @@ it('should click into frame inside closed shadow root', async ({ page, server }) `); - const frame = page.frame({ name: 'myframe' }); + const frame = page.frames()[1]; await frame.locator('text=click me').click(); expect(await page.evaluate('window.__clicked')).toBe(true); }); diff --git a/tests/page/elementhandle-bounding-box.spec.ts b/tests/page/elementhandle-bounding-box.spec.ts index c86d516e19ece..edd7f0fd68bdf 100644 --- a/tests/page/elementhandle-bounding-box.spec.ts +++ b/tests/page/elementhandle-bounding-box.spec.ts @@ -30,8 +30,8 @@ it('should work', async ({ page, server, browserName, headless, isLinux }) => { it('should handle nested frames', async ({ page, server }) => { await page.setViewportSize({ width: 616, height: 500 }); await page.goto(server.PREFIX + '/frames/nested-frames.html'); - const nestedFrame = page.frames().find(frame => frame.name() === 'dos'); - const elementHandle = await nestedFrame.$('div'); + const nestedFrame = page.frameLocator('[name="2frames"]').frameLocator('[name=dos]'); + const elementHandle = await nestedFrame.locator('div').elementHandle(); const box = await elementHandle.boundingBox(); expect(box).toEqual({ x: 24, y: 224, width: 268, height: 18 }); }); diff --git a/tests/page/frame-frame-element.spec.ts b/tests/page/frame-frame-element.spec.ts index e44e7deb3ec26..92e330037c56d 100644 --- a/tests/page/frame-frame-element.spec.ts +++ b/tests/page/frame-frame-element.spec.ts @@ -71,7 +71,7 @@ it('should work inside closed shadow root', async ({ page, server, browserName } `); - const frame = page.frame({ name: 'myframe' }); + const frame = page.frames()[1]; const element = await frame.frameElement(); expect(await element.getAttribute('name')).toBe('myframe'); }); @@ -87,7 +87,7 @@ it('should work inside declarative shadow root', async ({ page, server, browserN footer `); - const frame = page.frame({ name: 'myframe' }); + const frame = page.frames()[1]; const element = await frame.frameElement(); expect(await element.getAttribute('name')).toBe('myframe'); }); diff --git a/tests/page/frame-hierarchy.spec.ts b/tests/page/frame-hierarchy.spec.ts index 46e9dace1a4ca..ab1b6af33fd0a 100644 --- a/tests/page/frame-hierarchy.spec.ts +++ b/tests/page/frame-hierarchy.spec.ts @@ -35,8 +35,10 @@ function dumpFrames(frame: Frame, indentation: string = ''): string[] { return result; } -it('should handle nested frames @smoke', async ({ page, server, isAndroid }) => { +it('should handle nested frames @smoke', async ({ page, server, isAndroid, browserName, channel }) => { it.skip(isAndroid, 'No cross-process on Android'); + it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'frame.name() is racy with BiDi'); + it.skip(channel?.startsWith('bidi-chrom'), 'frame.name() is racy with BiDi'); await page.goto(server.PREFIX + '/frames/nested-frames.html'); expect(dumpFrames(page.mainFrame())).toEqual([ @@ -154,7 +156,10 @@ it('should report frame from-inside shadow DOM', async ({ page, server }) => { expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE); }); -it('should report frame.name()', async ({ page, server }) => { +it('should report frame.name()', async ({ page, server, browserName, channel }) => { + it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'frame.name() is racy with BiDi'); + it.skip(channel?.startsWith('bidi-chrom'), 'frame.name() is racy with BiDi'); + await attachFrame(page, 'theFrameId', server.EMPTY_PAGE); await page.evaluate(url => { const frame = document.createElement('iframe'); diff --git a/tests/page/page-basic.spec.ts b/tests/page/page-basic.spec.ts index c93ee813e59b3..f191ed3bce8fb 100644 --- a/tests/page/page-basic.spec.ts +++ b/tests/page/page-basic.spec.ts @@ -94,7 +94,10 @@ it('page.close should work with window.close', async function({ page }) { await closedPromise; }); -it('page.frame should respect name', async function({ page }) { +it('page.frame should respect name', async function({ page, browserName, channel }) { + it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'page.frame({ name }) is racy with BiDi'); + it.skip(channel?.startsWith('bidi-chrom'), 'page.frame({ name }) is racy with BiDi'); + await page.setContent(``); expect(page.frame({ name: 'bogus' })).toBe(null); const frame = page.frame({ name: 'target' });