Skip to content

Commit 7c73c15

Browse files
committed
chore(bidi): add support for context locators
1 parent e7bff52 commit 7c73c15

File tree

10 files changed

+52
-33
lines changed

10 files changed

+52
-33
lines changed

packages/playwright-core/src/server/bidi/bidiBrowser.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,12 @@ export class BidiBrowser extends Browser {
135135
if (!parentFrame)
136136
continue;
137137
page._session.addFrameBrowsingContext(event.context);
138-
page._page.frameManager.frameAttached(event.context, parentFrameId);
139-
const frame = page._page.frameManager.frame(event.context);
140-
if (frame)
141-
frame._url = event.url;
138+
const frame = page._page.frameManager.frameAttached(event.context, parentFrameId);
139+
frame._url = event.url;
140+
page._getFrameNode(frame).then(node => {
141+
const attributes = node?.value?.attributes;
142+
frame._name = attributes?.name ?? attributes?.id ?? '';
143+
});
142144
return;
143145
}
144146
return;

packages/playwright-core/src/server/bidi/bidiExecutionContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,11 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
141141
};
142142
}
143143

144-
async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<js.JSHandle> {
144+
async remoteObjectForNodeId(context: dom.FrameExecutionContext, nodeId: bidi.Script.SharedReference): Promise<dom.ElementHandle> {
145145
const result = await this._remoteValueForReference(nodeId, true);
146146
if (!('handle' in result))
147147
throw new Error('Can\'t get remote object for nodeId');
148-
return createHandle(context, result);
148+
return createHandle(context, result) as dom.ElementHandle;
149149
}
150150

151151
async contentFrameIdForFrame(handle: dom.ElementHandle) {

packages/playwright-core/src/server/bidi/bidiPage.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -582,24 +582,24 @@ export class BidiPage implements PageDelegate {
582582
const parent = frame.parentFrame();
583583
if (!parent)
584584
throw new Error('Frame has been detached.');
585-
const parentContext = await parent._mainContext();
586-
const list = await parentContext.evaluateHandle(() => { return [...document.querySelectorAll('iframe,frame')]; });
587-
const length = await list.evaluate(list => list.length);
588-
let foundElement = null;
589-
for (let i = 0; i < length; i++) {
590-
const element = await list.evaluateHandle((list, i) => list[i], i);
591-
const candidate = await element.contentFrame();
592-
if (frame === candidate) {
593-
foundElement = element;
594-
break;
595-
} else {
596-
element.dispose();
597-
}
598-
}
599-
list.dispose();
600-
if (!foundElement)
585+
const node = await this._getFrameNode(frame);
586+
if (!node?.sharedId)
601587
throw new Error('Frame has been detached.');
602-
return foundElement;
588+
const parentFrameExecutionContext = await parent._mainContext();
589+
return await toBidiExecutionContext(parentFrameExecutionContext).remoteObjectForNodeId(parentFrameExecutionContext, { sharedId: node.sharedId });
590+
}
591+
592+
async _getFrameNode(frame: frames.Frame): Promise<bidi.Script.NodeRemoteValue | undefined> {
593+
const parent = frame.parentFrame();
594+
if (!parent)
595+
return undefined;
596+
597+
const result = await this._session.send('browsingContext.locateNodes', {
598+
context: parent._id,
599+
locator: { type: 'context', value: { context: frame._id } },
600+
});
601+
const node = result.nodes[0];
602+
return node;
603603
}
604604

605605
shouldToggleStyleSheetToSyncAnimations(): boolean {

packages/playwright-core/src/server/frames.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ export class FrameManager {
213213
this.removeChildFramesRecursively(frame);
214214
this.clearWebSockets(frame);
215215
frame._url = url;
216-
frame._name = name;
216+
frame._name = name || frame._name;
217217

218218
let keepPending: DocumentInfo | undefined;
219219
const pendingDocument = frame.pendingDocument();

tests/config/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ export async function detachFrame(page: Page, frameId: string) {
4343
}, frameId);
4444
}
4545

46+
export async function findFrameByName(page: Page, name: string) {
47+
for (const frame of page.frames()) {
48+
if (frame.parentFrame() && await (await frame.frameElement()).getAttribute('name') === name)
49+
return frame;
50+
}
51+
return null;
52+
}
53+
4654
export async function verifyViewport(page: Page, width: number, height: number) {
4755
// `expect` may clash in test runner tests if imported eagerly.
4856
const { expect } = require('@playwright/test');

tests/library/hit-target.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { contextTest as it, expect } from '../config/browserTest';
18+
import { findFrameByName } from '../config/utils';
1819
import type { ElementHandle } from 'playwright-core';
1920

2021
declare const renderComponent;
@@ -290,7 +291,7 @@ it('should click into frame inside closed shadow root', async ({ page, server })
290291
</script>
291292
`);
292293

293-
const frame = page.frame({ name: 'myframe' });
294+
const frame = await findFrameByName(page, 'myframe');
294295
await frame.locator('text=click me').click();
295296
expect(await page.evaluate('window.__clicked')).toBe(true);
296297
});

tests/page/elementhandle-bounding-box.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ it('should work', async ({ page, server, browserName, headless, isLinux }) => {
3030
it('should handle nested frames', async ({ page, server }) => {
3131
await page.setViewportSize({ width: 616, height: 500 });
3232
await page.goto(server.PREFIX + '/frames/nested-frames.html');
33-
const nestedFrame = page.frames().find(frame => frame.name() === 'dos');
34-
const elementHandle = await nestedFrame.$('div');
33+
const nestedFrame = page.frameLocator('[name="2frames"]').frameLocator('[name=dos]');
34+
const elementHandle = await nestedFrame.locator('div').elementHandle();
3535
const box = await elementHandle.boundingBox();
3636
expect(box).toEqual({ x: 24, y: 224, width: 268, height: 18 });
3737
});

tests/page/frame-frame-element.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*/
1717

1818
import { test as it, expect } from './pageTest';
19-
import { attachFrame } from '../config/utils';
19+
import { attachFrame, findFrameByName } from '../config/utils';
2020

2121
it('should work @smoke', async ({ page, server }) => {
2222
await page.goto(server.EMPTY_PAGE);
@@ -71,7 +71,7 @@ it('should work inside closed shadow root', async ({ page, server, browserName }
7171
</script>
7272
`);
7373

74-
const frame = page.frame({ name: 'myframe' });
74+
const frame = await findFrameByName(page, 'myframe');
7575
const element = await frame.frameElement();
7676
expect(await element.getAttribute('name')).toBe('myframe');
7777
});
@@ -87,7 +87,7 @@ it('should work inside declarative shadow root', async ({ page, server, browserN
8787
<span>footer</span>
8888
</div>
8989
`);
90-
const frame = page.frame({ name: 'myframe' });
90+
const frame = await findFrameByName(page, 'myframe');
9191
const element = await frame.frameElement();
9292
expect(await element.getAttribute('name')).toBe('myframe');
9393
});

tests/page/frame-hierarchy.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ function dumpFrames(frame: Frame, indentation: string = ''): string[] {
3535
return result;
3636
}
3737

38-
it('should handle nested frames @smoke', async ({ page, server, isAndroid }) => {
38+
it('should handle nested frames @smoke', async ({ page, server, isAndroid, browserName, channel }) => {
3939
it.skip(isAndroid, 'No cross-process on Android');
40+
it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'frame.name() is racy with BiDi');
41+
it.skip(channel?.startsWith('bidi-chrom'), 'frame.name() is racy with BiDi');
4042

4143
await page.goto(server.PREFIX + '/frames/nested-frames.html');
4244
expect(dumpFrames(page.mainFrame())).toEqual([
@@ -154,7 +156,10 @@ it('should report frame from-inside shadow DOM', async ({ page, server }) => {
154156
expect(page.frames()[1].url()).toBe(server.EMPTY_PAGE);
155157
});
156158

157-
it('should report frame.name()', async ({ page, server }) => {
159+
it('should report frame.name()', async ({ page, server, browserName, channel }) => {
160+
it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'frame.name() is racy with BiDi');
161+
it.skip(channel?.startsWith('bidi-chrom'), 'frame.name() is racy with BiDi');
162+
158163
await attachFrame(page, 'theFrameId', server.EMPTY_PAGE);
159164
await page.evaluate(url => {
160165
const frame = document.createElement('iframe');

tests/page/page-basic.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@ it('page.close should work with window.close', async function({ page }) {
9494
await closedPromise;
9595
});
9696

97-
it('page.frame should respect name', async function({ page }) {
97+
it('page.frame should respect name', async function({ page, browserName, channel }) {
98+
it.skip(browserName === 'firefox' && channel?.startsWith('moz-firefox'), 'page.frame({ name }) is racy with BiDi');
99+
it.skip(channel?.startsWith('bidi-chrom'), 'page.frame({ name }) is racy with BiDi');
100+
98101
await page.setContent(`<iframe name=target></iframe>`);
99102
expect(page.frame({ name: 'bogus' })).toBe(null);
100103
const frame = page.frame({ name: 'target' });

0 commit comments

Comments
 (0)