feat(frame): introduce frame.frameElement (#856)

Fixes #839.
This commit is contained in:
Dmitry Gozman 2020-02-05 17:20:23 -08:00 committed by GitHub
parent 4be39f8af0
commit 6318ba6e4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 95 additions and 0 deletions

View File

@ -1676,6 +1676,7 @@ An example of getting text from an iframe element:
- [frame.evaluateHandle(pageFunction[, ...args])](#frameevaluatehandlepagefunction-args)
- [frame.fill(selector, value, options)](#framefillselector-value-options)
- [frame.focus(selector, options)](#framefocusselector-options)
- [frame.frameElement()](#frameframeelement)
- [frame.goto(url[, options])](#framegotourl-options)
- [frame.hover(selector[, options])](#framehoverselector-options)
- [frame.isDetached()](#frameisdetached)
@ -1916,6 +1917,19 @@ If there's no text `<input>`, `<textarea>` or `[contenteditable]` element matchi
This method fetches an element with `selector` and focuses it.
If there's no element matching `selector`, the method throws an error.
#### frame.frameElement()
- returns: <[Promise]<[ElementHandle]>> Promise that resolves with a `frame` or `iframe` element handle which corresponds to this frame.
This is an inverse of [elementHandle.contentFrame()](#elementhandlecontentframe). Note that returned handle actually belongs to the parent frame.
This method throws an error if the frame has been detached before `frameElement()` returns.
```js
const frameElement = await frame.frameElement();
const contentFrame = await frameElement.contentFrame();
console.log(frame === contentFrame); // -> true
```
#### frame.goto(url[, options])
- `url` <[string]> URL to navigate frame to. The url should include scheme, e.g. `https://`.
- `options` <[Object]> Navigation parameters which might have the following properties:

View File

@ -525,6 +525,18 @@ export class CRPage implements PageDelegate {
coverage(): Coverage | undefined {
return this._coverage;
}
async getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle> {
const { backendNodeId } = await this._client.send('DOM.getFrameOwner', { frameId: frame._id }).catch(e => {
if (e instanceof Error && e.message.includes('Frame with the given id was not found.'))
e.message = 'Frame has been detached.';
throw e;
});
const parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
return this.adoptBackendNodeId(backendNodeId, await parent._mainContext());
}
}
function toRemoteObject(handle: js.JSHandle): Protocol.Runtime.RemoteObject {

View File

@ -443,6 +443,23 @@ export class FFPage implements PageDelegate {
coverage(): Coverage | undefined {
return undefined;
}
async getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle> {
const parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
const context = await parent._utilityContext();
const handles = await context._$$('iframe');
const items = await Promise.all(handles.map(async handle => {
const frame = await handle.contentFrame().catch(e => null);
return { handle, frame };
}));
const result = items.find(item => item.frame === frame);
await Promise.all(items.map(item => item === result ? Promise.resolve() : item.handle.dispose()));
if (!result)
throw new Error('Frame has been detached.');
return result.handle;
}
}
export function normalizeWaitUntil(waitUntil: frames.LifecycleEvent | frames.LifecycleEvent[]): frames.LifecycleEvent[] {

View File

@ -437,6 +437,10 @@ export class Frame {
throw error;
}
async frameElement(): Promise<dom.ElementHandle> {
return this._page._delegate.getFrameElement(this);
}
_context(contextType: ContextType): Promise<dom.FrameExecutionContext> {
if (this._detached)
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);

View File

@ -68,6 +68,7 @@ export interface PageDelegate {
layoutViewport(): Promise<{ width: number, height: number }>;
setInputFiles(handle: dom.ElementHandle<HTMLInputElement>, files: types.FilePayload[]): Promise<void>;
getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null>;
getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle>;
getAccessibilityTree(needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}>;
pdf?: (options?: types.PDFOptions) => Promise<platform.BufferType>;

View File

@ -575,6 +575,23 @@ export class WKPage implements PageDelegate {
return undefined;
}
async getFrameElement(frame: frames.Frame): Promise<dom.ElementHandle> {
const parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
const context = await parent._utilityContext();
const handles = await context._$$('iframe');
const items = await Promise.all(handles.map(async handle => {
const frame = await handle.contentFrame().catch(e => null);
return { handle, frame };
}));
const result = items.find(item => item.frame === frame);
await Promise.all(items.map(item => item === result ? Promise.resolve() : item.handle.dispose()));
if (!result)
throw new Error('Frame has been detached.');
return result.handle;
}
_onRequestWillBeSent(session: WKSession, event: Protocol.Network.requestWillBeSentPayload) {
if (event.request.url.startsWith('data:'))
return;

View File

@ -31,6 +31,36 @@ module.exports.describe = function({testRunner, expect, FFOX, CHROMIUM, WEBKIT})
});
});
describe('Frame.frameElement', function() {
it('should work', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const frame2 = await utils.attachFrame(page, 'frame2', server.EMPTY_PAGE);
const frame3 = await utils.attachFrame(page, 'frame3', server.EMPTY_PAGE);
const frame1handle1 = await page.$('#frame1');
const frame1handle2 = await frame1.frameElement();
const frame3handle1 = await page.$('#frame3');
const frame3handle2 = await frame3.frameElement();
expect(await frame1handle1.evaluate((a, b) => a === b, frame1handle2)).toBe(true);
expect(await frame3handle1.evaluate((a, b) => a === b, frame3handle2)).toBe(true);
expect(await frame1handle1.evaluate((a, b) => a === b, frame3handle1)).toBe(false);
});
it('should work with contentFrame', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
const handle = await frame.frameElement();
const contentFrame = await handle.contentFrame();
expect(contentFrame).toBe(frame);
});
it('should throw when detached', async({page, server}) => {
await page.goto(server.EMPTY_PAGE);
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);
await page.$eval('#frame1', e => e.remove());
const error = await frame1.frameElement().catch(e => e);
expect(error.message).toBe('Frame has been detached.');
});
});
describe('Frame.evaluate', function() {
it('should throw for detached frames', async({page, server}) => {
const frame1 = await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);