From 27636afacf0a57cd1a033b26075ce11f06d42460 Mon Sep 17 00:00:00 2001 From: jrandolf <101637635+jrandolf@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:58:18 +0200 Subject: [PATCH] chore: implement `boxModel`, `contentFrame`, and nested frame `boundingBox` (#10773) --- docs/api/index.md | 1 + docs/api/puppeteer.boxmodel.md | 16 +- .../puppeteer.elementhandle.contentframe.md | 12 +- .../puppeteer.elementhandle.contentframe_1.md | 17 + docs/api/puppeteer.elementhandle.md | 3 +- docs/api/puppeteer.quad.md | 13 + .../puppeteer-core/src/api/ElementHandle.ts | 185 ++++++++++- packages/puppeteer-core/src/api/Frame.ts | 24 +- .../src/common/ElementHandle.ts | 74 +---- .../src/common/bidi/ElementHandle.ts | 42 ++- .../puppeteer-core/src/common/bidi/Frame.ts | 2 +- test/TestExpectations.json | 294 +++++++++++++++--- test/src/elementhandle.spec.ts | 2 +- 13 files changed, 525 insertions(+), 160 deletions(-) create mode 100644 docs/api/puppeteer.elementhandle.contentframe_1.md create mode 100644 docs/api/puppeteer.quad.md diff --git a/docs/api/index.md b/docs/api/index.md index f44728225f2..e2a5bddc1d8 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -182,6 +182,7 @@ sidebar_label: API | [ProtocolLifeCycleEvent](./puppeteer.protocollifecycleevent.md) | | | [PuppeteerLifeCycleEvent](./puppeteer.puppeteerlifecycleevent.md) | | | [PuppeteerNodeLaunchOptions](./puppeteer.puppeteernodelaunchoptions.md) | Utility type exposed to enable users to define options that can be passed to puppeteer.launch without having to list the set of all types. | +| [Quad](./puppeteer.quad.md) | | | [ResourceType](./puppeteer.resourcetype.md) | Resource types for HTTPRequests as perceived by the rendering engine. | | [TargetFilterCallback](./puppeteer.targetfiltercallback.md) | | | [VisibilityOption](./puppeteer.visibilityoption.md) | | diff --git a/docs/api/puppeteer.boxmodel.md b/docs/api/puppeteer.boxmodel.md index dd2abd8dd48..e30accd8225 100644 --- a/docs/api/puppeteer.boxmodel.md +++ b/docs/api/puppeteer.boxmodel.md @@ -12,11 +12,11 @@ export interface BoxModel ## Properties -| Property | Modifiers | Type | Description | Default | -| -------- | --------- | --------------------------------- | ----------- | ------- | -| border | | [Point](./puppeteer.point.md)\[\] | | | -| content | | [Point](./puppeteer.point.md)\[\] | | | -| height | | number | | | -| margin | | [Point](./puppeteer.point.md)\[\] | | | -| padding | | [Point](./puppeteer.point.md)\[\] | | | -| width | | number | | | +| Property | Modifiers | Type | Description | Default | +| -------- | --------- | --------------------------- | ----------- | ------- | +| border | | [Quad](./puppeteer.quad.md) | | | +| content | | [Quad](./puppeteer.quad.md) | | | +| height | | number | | | +| margin | | [Quad](./puppeteer.quad.md) | | | +| padding | | [Quad](./puppeteer.quad.md) | | | +| width | | number | | | diff --git a/docs/api/puppeteer.elementhandle.contentframe.md b/docs/api/puppeteer.elementhandle.contentframe.md index 928b29c8e9b..9a866ce2e49 100644 --- a/docs/api/puppeteer.elementhandle.contentframe.md +++ b/docs/api/puppeteer.elementhandle.contentframe.md @@ -4,16 +4,22 @@ sidebar_label: ElementHandle.contentFrame # ElementHandle.contentFrame() method -Resolves to the content frame for element handles referencing iframe nodes, or null otherwise +Resolves the frame associated with the element. #### Signature: ```typescript class ElementHandle { - contentFrame(): Promise; + contentFrame(this: ElementHandle): Promise; } ``` +## Parameters + +| Parameter | Type | Description | +| --------- | ---------------------------------------------------------------------- | ----------- | +| this | [ElementHandle](./puppeteer.elementhandle.md)<HTMLIFrameElement> | | + **Returns:** -Promise<[Frame](./puppeteer.frame.md) \| null> +Promise<[Frame](./puppeteer.frame.md)> diff --git a/docs/api/puppeteer.elementhandle.contentframe_1.md b/docs/api/puppeteer.elementhandle.contentframe_1.md new file mode 100644 index 00000000000..cca79cec048 --- /dev/null +++ b/docs/api/puppeteer.elementhandle.contentframe_1.md @@ -0,0 +1,17 @@ +--- +sidebar_label: ElementHandle.contentFrame_1 +--- + +# ElementHandle.contentFrame() method + +#### Signature: + +```typescript +class ElementHandle { + contentFrame(): Promise; +} +``` + +**Returns:** + +Promise<[Frame](./puppeteer.frame.md) \| null> diff --git a/docs/api/puppeteer.elementhandle.md b/docs/api/puppeteer.elementhandle.md index fa40388fdac..ff8614347a7 100644 --- a/docs/api/puppeteer.elementhandle.md +++ b/docs/api/puppeteer.elementhandle.md @@ -60,7 +60,8 @@ The constructor for this class is marked as internal. Third-party code should no | [boxModel()](./puppeteer.elementhandle.boxmodel.md) | | This method returns boxes of the element, or null if the element is not visible. | | [click(this, options)](./puppeteer.elementhandle.click.md) | | This method scrolls element into view if needed, and then uses [Page.mouse](./puppeteer.page.md) to click in the center of the element. If the element is detached from DOM, the method throws an error. | | [clickablePoint(offset)](./puppeteer.elementhandle.clickablepoint.md) | | Returns the middle point within an element unless a specific offset is provided. | -| [contentFrame()](./puppeteer.elementhandle.contentframe.md) | | Resolves to the content frame for element handles referencing iframe nodes, or null otherwise | +| [contentFrame(this)](./puppeteer.elementhandle.contentframe.md) | | Resolves the frame associated with the element. | +| [contentFrame()](./puppeteer.elementhandle.contentframe_1.md) | | | | [drag(this, target)](./puppeteer.elementhandle.drag.md) | | This method creates and captures a dragevent from the element. | | [dragAndDrop(this, target, options)](./puppeteer.elementhandle.draganddrop.md) | | This method triggers a dragenter, dragover, and drop on the element. | | [dragEnter(this, data)](./puppeteer.elementhandle.dragenter.md) | | This method creates a dragenter event on the element. | diff --git a/docs/api/puppeteer.quad.md b/docs/api/puppeteer.quad.md new file mode 100644 index 00000000000..b313aca61ad --- /dev/null +++ b/docs/api/puppeteer.quad.md @@ -0,0 +1,13 @@ +--- +sidebar_label: Quad +--- + +# Quad type + +#### Signature: + +```typescript +export type Quad = [Point, Point, Point, Point]; +``` + +**References:** [Point](./puppeteer.point.md) diff --git a/packages/puppeteer-core/src/api/ElementHandle.ts b/packages/puppeteer-core/src/api/ElementHandle.ts index c22ab929df9..7d8a1ef6d1c 100644 --- a/packages/puppeteer-core/src/api/ElementHandle.ts +++ b/packages/puppeteer-core/src/api/ElementHandle.ts @@ -30,7 +30,11 @@ import { NodeFor, } from '../common/types.js'; import {KeyInput} from '../common/USKeyboardLayout.js'; -import {isString, withSourcePuppeteerURLIfNone} from '../common/util.js'; +import { + debugError, + isString, + withSourcePuppeteerURLIfNone, +} from '../common/util.js'; import {assert} from '../util/assert.js'; import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js'; @@ -42,14 +46,19 @@ import { import {JSHandle} from './JSHandle.js'; import {ScreenshotOptions} from './Page.js'; +/** + * @public + */ +export type Quad = [Point, Point, Point, Point]; + /** * @public */ export interface BoxModel { - content: Point[]; - padding: Point[]; - border: Point[]; - margin: Point[]; + content: Quad; + padding: Quad; + border: Quad; + margin: Quad; width: number; height: number; } @@ -620,9 +629,10 @@ export class ElementHandle< } /** - * Resolves to the content frame for element handles referencing - * iframe nodes, or null otherwise + * Resolves the frame associated with the element. */ + async contentFrame(this: ElementHandle): Promise; + async contentFrame(): Promise; async contentFrame(): Promise { throw new Error('Not implemented'); } @@ -885,7 +895,72 @@ export class ElementHandle< * or `null` if the element is not visible. */ async boundingBox(): Promise { - throw new Error('Not implemented'); + const adoptedThis = await this.frame.isolatedRealm().adoptHandle(this); + const box = await adoptedThis.evaluate(element => { + if (!(element instanceof Element)) { + return null; + } + // Element is not visible. + if (element.getClientRects().length === 0) { + return null; + } + const rect = element.getBoundingClientRect(); + return { + x: rect.left, + y: rect.top, + width: rect.width, + height: rect.height, + }; + }); + void adoptedThis.dispose().catch(debugError); + if (!box) { + return null; + } + const offset = await this.#getTopLeftCornerOfFrame(); + if (!offset) { + return null; + } + box.x += offset.x; + box.y += offset.y; + return box; + } + + async #getTopLeftCornerOfFrame() { + const point = {x: 0, y: 0}; + let frame: Frame | null | undefined = this.frame; + let element: HandleFor | null | undefined; + while ((element = await frame?.frameElement())) { + try { + element = await element.frame.isolatedRealm().transferHandle(element); + const parentBox = await element.evaluate(element => { + // Element is not visible. + if (element.getClientRects().length === 0) { + return null; + } + const rect = element.getBoundingClientRect(); + const style = window.getComputedStyle(element); + return { + left: + rect.left + + parseInt(style.paddingLeft, 10) + + parseInt(style.borderLeftWidth, 10), + top: + rect.top + + parseInt(style.paddingTop, 10) + + parseInt(style.borderTopWidth, 10), + }; + }); + if (!parentBox) { + return null; + } + point.x += parentBox.left; + point.y += parentBox.top; + frame = frame?.parentFrame(); + } finally { + void element.dispose().catch(debugError); + } + } + return point; } /** @@ -897,7 +972,99 @@ export class ElementHandle< * Each Point is an object `{x, y}`. Box points are sorted clock-wise. */ async boxModel(): Promise { - throw new Error('Not implemented'); + const adoptedThis = await this.frame.isolatedRealm().adoptHandle(this); + const model = await adoptedThis.evaluate(element => { + if (!(element instanceof Element)) { + return null; + } + // Element is not visible. + if (element.getClientRects().length === 0) { + return null; + } + const rect = element.getBoundingClientRect(); + const style = window.getComputedStyle(element); + const offsets = { + padding: { + left: parseInt(style.paddingLeft, 10), + top: parseInt(style.paddingTop, 10), + right: parseInt(style.paddingRight, 10), + bottom: parseInt(style.paddingBottom, 10), + }, + margin: { + left: -parseInt(style.marginLeft, 10), + top: -parseInt(style.marginTop, 10), + right: -parseInt(style.marginRight, 10), + bottom: -parseInt(style.marginBottom, 10), + }, + border: { + left: parseInt(style.borderLeft, 10), + top: parseInt(style.borderTop, 10), + right: parseInt(style.borderRight, 10), + bottom: parseInt(style.borderBottom, 10), + }, + }; + const border: Quad = [ + {x: rect.left, y: rect.top}, + {x: rect.left + rect.width, y: rect.top}, + {x: rect.left + rect.width, y: rect.top + rect.bottom}, + {x: rect.left, y: rect.top + rect.bottom}, + ]; + const padding = transformQuadWithOffsets(border, offsets.border); + const content = transformQuadWithOffsets(padding, offsets.padding); + const margin = transformQuadWithOffsets(border, offsets.margin); + return { + content, + padding, + border, + margin, + width: rect.width, + height: rect.height, + }; + + function transformQuadWithOffsets( + quad: Quad, + offsets: {top: number; left: number; right: number; bottom: number} + ): Quad { + return [ + { + x: quad[0].x + offsets.left, + y: quad[0].y + offsets.top, + }, + { + x: quad[1].x - offsets.right, + y: quad[1].y + offsets.top, + }, + { + x: quad[2].x - offsets.right, + y: quad[2].y - offsets.bottom, + }, + { + x: quad[3].x + offsets.left, + y: quad[3].y - offsets.bottom, + }, + ]; + } + }); + void adoptedThis.dispose().catch(debugError); + if (!model) { + return null; + } + const offset = await this.#getTopLeftCornerOfFrame(); + if (!offset) { + return null; + } + for (const attribute of [ + 'content', + 'padding', + 'border', + 'margin', + ] as const) { + for (const point of model[attribute]) { + point.x += offset.x; + point.y += offset.y; + } + } + return model; } /** diff --git a/packages/puppeteer-core/src/api/Frame.ts b/packages/puppeteer-core/src/api/Frame.ts index 53a8dea7538..98575c72d08 100644 --- a/packages/puppeteer-core/src/api/Frame.ts +++ b/packages/puppeteer-core/src/api/Frame.ts @@ -22,6 +22,7 @@ import {DeviceRequestPrompt} from '../common/DeviceRequestPrompt.js'; import {EventEmitter} from '../common/EventEmitter.js'; import {ExecutionContext} from '../common/ExecutionContext.js'; import {getQueryHandlerAndSelector} from '../common/GetQueryHandler.js'; +import {transposeIterableHandle} from '../common/HandleIterator.js'; import { IsolatedWorldChart, WaitForSelectorOptions, @@ -36,7 +37,7 @@ import { InnerLazyParams, NodeFor, } from '../common/types.js'; -import {importFSPromises} from '../common/util.js'; +import {debugError, importFSPromises} from '../common/util.js'; import {TaskManager} from '../common/WaitTask.js'; import {KeyboardTypeOptions} from './Input.js'; @@ -380,6 +381,27 @@ export class Frame extends EventEmitter { throw new Error('Not implemented'); } + /** + * @internal + */ + async frameElement(): Promise | null> { + const parentFrame = this.parentFrame(); + if (!parentFrame) { + return null; + } + const list = await parentFrame.isolatedRealm().evaluateHandle(() => { + return document.querySelectorAll('iframe'); + }); + for await (const iframe of transposeIterableHandle(list)) { + const frame = await iframe.contentFrame(); + if (frame._id === this._id) { + return iframe; + } + void iframe.dispose().catch(debugError); + } + return null; + } + /** * Behaves identically to {@link Page.evaluateHandle} except it's run within * the context of this frame. diff --git a/packages/puppeteer-core/src/common/ElementHandle.ts b/packages/puppeteer-core/src/common/ElementHandle.ts index af4d74e5f40..947d65ab986 100644 --- a/packages/puppeteer-core/src/common/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/ElementHandle.ts @@ -18,14 +18,13 @@ import {Protocol} from 'devtools-protocol'; import { AutofillData, - BoundingBox, - BoxModel, ClickOptions, ElementHandle, Offset, Point, + Quad, } from '../api/ElementHandle.js'; -import {KeyPressOptions, KeyboardTypeOptions} from '../api/Input.js'; +import {KeyboardTypeOptions, KeyPressOptions} from '../api/Input.js'; import {Page, ScreenshotOptions} from '../api/Page.js'; import {assert} from '../util/assert.js'; @@ -45,9 +44,11 @@ const applyOffsetsToQuad = ( offsetX: number, offsetY: number ) => { + assert(quad.length === 4); return quad.map(part => { return {x: part.x + offsetX, y: part.y + offsetY}; - }); + // SAFETY: We know this is a quad from the length check. + }) as Quad; }; /** @@ -127,6 +128,9 @@ export class CDPElementHandle< > | null; } + override async contentFrame( + this: ElementHandle + ): Promise; override async contentFrame(): Promise { const nodeInfo = await this.client.send('DOM.describeNode', { objectId: this.id, @@ -251,15 +255,6 @@ export class CDPElementHandle< }; } - #getBoxModel(): Promise { - const params: Protocol.DOM.GetBoxModelRequest = { - objectId: this.id, - }; - return this.client.send('DOM.getBoxModel', params).catch(error => { - return debugError(error); - }); - } - #fromProtocolQuad(quad: number[]): Point[] { return [ {x: quad[0]!, y: quad[1]!}, @@ -462,59 +457,6 @@ export class CDPElementHandle< await this.#page.keyboard.press(key, options); } - override async boundingBox(): Promise { - const result = await this.#getBoxModel(); - - if (!result) { - return null; - } - - const {offsetX, offsetY} = await this.#getOOPIFOffsets(this.#frame); - const quad = result.model.border; - const x = Math.min(quad[0]!, quad[2]!, quad[4]!, quad[6]!); - const y = Math.min(quad[1]!, quad[3]!, quad[5]!, quad[7]!); - const width = Math.max(quad[0]!, quad[2]!, quad[4]!, quad[6]!) - x; - const height = Math.max(quad[1]!, quad[3]!, quad[5]!, quad[7]!) - y; - - return {x: x + offsetX, y: y + offsetY, width, height}; - } - - override async boxModel(): Promise { - const result = await this.#getBoxModel(); - - if (!result) { - return null; - } - - const {offsetX, offsetY} = await this.#getOOPIFOffsets(this.#frame); - - const {content, padding, border, margin, width, height} = result.model; - return { - content: applyOffsetsToQuad( - this.#fromProtocolQuad(content), - offsetX, - offsetY - ), - padding: applyOffsetsToQuad( - this.#fromProtocolQuad(padding), - offsetX, - offsetY - ), - border: applyOffsetsToQuad( - this.#fromProtocolQuad(border), - offsetX, - offsetY - ), - margin: applyOffsetsToQuad( - this.#fromProtocolQuad(margin), - offsetX, - offsetY - ), - width, - height, - }; - } - override async screenshot( this: CDPElementHandle, options: ScreenshotOptions = {} diff --git a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts index 3eb269b0505..8fd4bbc8619 100644 --- a/packages/puppeteer-core/src/common/bidi/ElementHandle.ts +++ b/packages/puppeteer-core/src/common/bidi/ElementHandle.ts @@ -19,15 +19,15 @@ import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import { AutofillData, ElementHandle as BaseElementHandle, - BoundingBox, ClickOptions, } from '../../api/ElementHandle.js'; -import {KeyPressOptions, KeyboardTypeOptions} from '../../api/Input.js'; +import {KeyboardTypeOptions, KeyPressOptions} from '../../api/Input.js'; import {assert} from '../../util/assert.js'; import {KeyInput} from '../USKeyboardLayout.js'; +import {debugError} from '../util.js'; import {Frame} from './Frame.js'; -import {JSHandle} from './JSHandle.js'; +import {JSHandle as BidiJSHandle, JSHandle} from './JSHandle.js'; import {Realm} from './Realm.js'; /** @@ -86,26 +86,24 @@ export class ElementHandle< }); } - override async boundingBox(): Promise { - if (this.frame.parentFrame()) { - throw new Error( - 'Elements within nested iframes are currently not supported.' - ); - } - const box = await this.frame.isolatedRealm().evaluate(element => { - const rect = (element as unknown as Element).getBoundingClientRect(); - if (!rect.left && !rect.top && !rect.width && !rect.height) { - // TODO(jrandolf): Detect if the element is truly not visible. - return null; + override async contentFrame( + this: ElementHandle + ): Promise; + override async contentFrame(): Promise { + const adoptedThis = await this.frame.isolatedRealm().adoptHandle(this); + const handle = (await adoptedThis.evaluateHandle(element => { + if (element instanceof HTMLIFrameElement) { + return element.contentWindow; } - return { - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }; - }, this); - return box; + return; + })) as BidiJSHandle; + void handle.dispose().catch(debugError); + void adoptedThis.dispose().catch(debugError); + const value = handle.remoteValue(); + if (value.type === 'window') { + return this.frame.page().frame(value.value.context); + } + return null; } // /////////////////// diff --git a/packages/puppeteer-core/src/common/bidi/Frame.ts b/packages/puppeteer-core/src/common/bidi/Frame.ts index 768d5f45f49..445abbe3b10 100644 --- a/packages/puppeteer-core/src/common/bidi/Frame.ts +++ b/packages/puppeteer-core/src/common/bidi/Frame.ts @@ -36,8 +36,8 @@ import {Page} from './Page.js'; import { MAIN_SANDBOX, PUPPETEER_SANDBOX, - SandboxChart, Sandbox, + SandboxChart, } from './Sandbox.js'; /** diff --git a/test/TestExpectations.json b/test/TestExpectations.json index e79f87030db..76b54293492 100644 --- a/test/TestExpectations.json +++ b/test/TestExpectations.json @@ -219,7 +219,7 @@ "testIdPattern": "[page.spec] Page Page.Events.PageError *", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["PASS", "TIMEOUT"] + "expectations": ["PASS"] }, { "testIdPattern": "[page.spec] Page Page.pdf *", @@ -605,6 +605,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should handle nested frames", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should return null for invisible elements", "platforms": ["darwin", "linux", "win32"], @@ -623,6 +629,18 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boxModel should return null for invisible elements", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boxModel should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should not work for TextNodes", "platforms": ["darwin", "linux", "win32"], @@ -647,6 +665,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.contentFrame should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.hover should work", "platforms": ["darwin", "linux", "win32"], @@ -666,10 +690,16 @@ "expectations": ["PASS"] }, { - "testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts", + "testIdPattern": "[emulation.spec] Emulation Page.viewport should get the proper viewport size", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] + "expectations": ["PASS"] + }, + { + "testIdPattern": "[emulation.spec] Emulation Page.viewport should support mobile emulation", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] }, { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should throw a nice error after a navigation", @@ -731,12 +761,36 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame from-inside shadow DOM", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame.parent()", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[frame.spec] Frame specs Frame Management should support lazy frames", "platforms": ["darwin", "linux", "win32"], "parameters": ["chrome"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[frame.spec] Frame specs Frame Management should support url fragment", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[frame.spec] Frame specs Frame.client should return the client instance", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[frame.spec] Frame specs Frame.evaluate allows readonly array to be an argument", "platforms": ["darwin", "linux", "win32"], @@ -881,6 +935,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should type emoji into an iframe", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should type into a textarea", "platforms": ["darwin", "linux", "win32"], @@ -1001,6 +1061,24 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[mouse.spec] Mouse should select the text with mouse", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[mouse.spec] Mouse should send mouse wheel events", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[mouse.spec] Mouse should set modifier keys on click", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[mouse.spec] Mouse should trigger hover state", "platforms": ["darwin", "linux", "win32"], @@ -1013,6 +1091,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[mouse.spec] Mouse should work with mobile viewports and cross process navigations", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[navigation.spec] navigation \"after each\" hook for \"should work with both domcontentloaded and load\"", "platforms": ["darwin", "linux", "win32"], @@ -1085,24 +1169,12 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, - { - "testIdPattern": "[network.spec] network Page.Events.Request should fire for iframes", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[network.spec] network raw network headers Cross-origin set-cookie", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL", "PASS"] }, - { - "testIdPattern": "[network.spec] network Request.frame should work for subframe navigation request", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[network.spec] network Request.headers should define Chrome as user agent header", "platforms": ["darwin", "linux", "win32"], @@ -1181,6 +1253,12 @@ "parameters": ["firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[page.spec] Page Page.client should return the client instance", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[page.spec] Page Page.close should *not* run beforeunload by default", "platforms": ["darwin", "linux", "win32"], @@ -1361,6 +1439,24 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[proxy.spec] request proxy in incognito browser context should respect proxy bypass list when configured at browser level", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[proxy.spec] request proxy in incognito browser context should respect proxy bypass list when configured at context level", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[proxy.spec] request proxy should respect proxy bypass list", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[queryhandler.spec] Query handler tests P selectors should work ARIA selectors", "platforms": ["darwin", "linux", "win32"], @@ -1427,18 +1523,42 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[target.spec] Target Browser.targets should return all of the targets", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[target.spec] Target Browser.waitForTarget should timeout waiting for a non-existent target", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[target.spec] Target should be able to use async waitForTarget", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[target.spec] Target should be able to use the default page in the browser", "platforms": ["darwin", "linux", "win32"], "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[target.spec] Target should contain browser target", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[target.spec] Target should not crash while redirecting if original request was missed", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[TargetManager.spec] *", "platforms": ["darwin", "linux", "win32"], @@ -1601,18 +1721,6 @@ "parameters": ["webDriverBiDi"], "expectations": ["PASS"] }, - { - "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should run in specified frame", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, - { - "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should throw when frame is detached", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[waittask.spec] waittask specs Page.waitForTimeout waits for the given timeout before resolving", "platforms": ["darwin", "linux", "win32"], @@ -1625,6 +1733,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[accessibility.spec] Accessibility filtering children of leaf nodes rich text editable fields should have children", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[accessibility.spec] Accessibility get snapshots while the tree is re-calculated", "platforms": ["darwin", "linux", "win32"], @@ -1781,6 +1895,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[click.spec] Page.click should click the button after navigation", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[click.spec] Page.click should click the button if window.Node is removed", "platforms": ["darwin", "linux", "win32"], @@ -1943,6 +2063,12 @@ "parameters": ["chrome", "webDriverBiDi"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should handle nested frames", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should handle nested frames", "platforms": ["darwin", "linux", "win32"], @@ -1950,9 +2076,9 @@ "expectations": ["FAIL"] }, { - "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should return null for invisible elements", + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should work", "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], + "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"] }, { @@ -1962,17 +2088,11 @@ "expectations": ["FAIL", "PASS"] }, { - "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boundingBox should work", + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boxModel should work", "platforms": ["darwin", "linux", "win32"], "parameters": ["firefox", "webDriverBiDi"], "expectations": ["FAIL"] }, - { - "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.boxModel should return null for invisible elements", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.click should return Point data", "platforms": ["darwin", "linux", "win32"], @@ -1991,6 +2111,12 @@ "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[elementhandle.spec] ElementHandle specs ElementHandle.contentFrame should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[emulation.spec] Emulation Page.emulate should support clicking", "platforms": ["darwin", "linux", "win32"], @@ -2105,6 +2231,18 @@ "parameters": ["cdp", "firefox"], "expectations": ["PASS", "TIMEOUT"] }, + { + "testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[evaluation.spec] Evaluation specs Frame.evaluate should have different execution contexts", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[evaluation.spec] Evaluation specs Page.evaluate should await promise", "platforms": ["darwin", "linux", "win32"], @@ -2195,6 +2333,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame.parent()", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[frame.spec] Frame specs Frame Management should report frame.parent()", "platforms": ["darwin", "linux", "win32"], @@ -2291,6 +2435,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[ignorehttpserrors.spec] ignoreHTTPSErrors should work with mixed content", "platforms": ["darwin", "linux", "win32"], @@ -2417,6 +2567,12 @@ "parameters": ["cdp", "firefox"], "expectations": ["FAIL"] }, + { + "testIdPattern": "[keyboard.spec] Keyboard should type emoji into an iframe", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[keyboard.spec] Keyboard should type emoji into an iframe", "platforms": ["darwin", "linux", "win32"], @@ -3029,6 +3185,18 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[network.spec] network Page.Events.Request should fire for iframes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[network.spec] network Page.Events.Request should fire for iframes", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[network.spec] network Page.setExtraHTTPHeaders should work", "platforms": ["darwin", "linux", "win32"], @@ -3071,6 +3239,18 @@ "parameters": ["cdp", "chrome"], "expectations": ["FAIL", "PASS"] }, + { + "testIdPattern": "[network.spec] network Request.frame should work for subframe navigation request", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, + { + "testIdPattern": "[network.spec] network Request.frame should work for subframe navigation request", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[network.spec] network Request.headers should define Firefox as user agent header", "platforms": ["darwin", "linux", "win32"], @@ -3701,12 +3881,6 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, - { - "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should fail to screenshot a detached element", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["cdp", "firefox"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[screenshot.spec] Screenshots ElementHandle.screenshot should work for an element with an offset", "platforms": ["darwin", "linux", "win32"], @@ -3851,6 +4025,12 @@ "parameters": ["chrome", "webDriverBiDi"], "expectations": ["PASS"] }, + { + "testIdPattern": "[target.spec] Target should not report uninitialized pages", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[target.spec] Target should report when a new page is created and closed", "platforms": ["darwin", "linux", "win32"], @@ -3953,12 +4133,36 @@ "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should run in specified frame", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should run in specified frame", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS"] + }, { "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should run in specified frame", "platforms": ["darwin", "linux", "win32"], "parameters": ["cdp", "firefox"], "expectations": ["SKIP"] }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should throw when frame is detached", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["chrome", "webDriverBiDi"], + "expectations": ["PASS", "TIMEOUT"] + }, + { + "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should throw when frame is detached", + "platforms": ["darwin", "linux", "win32"], + "parameters": ["firefox", "webDriverBiDi"], + "expectations": ["FAIL"] + }, { "testIdPattern": "[waittask.spec] waittask specs Frame.waitForXPath should throw when frame is detached", "platforms": ["darwin", "linux", "win32"], @@ -3971,12 +4175,6 @@ "parameters": ["firefox", "webDriverBiDi"], "expectations": ["PASS"] }, - { - "testIdPattern": "[accessibility.spec] Accessibility filtering children of leaf nodes rich text editable fields should have children", - "platforms": ["darwin", "linux", "win32"], - "parameters": ["chrome", "webDriverBiDi"], - "expectations": ["FAIL"] - }, { "testIdPattern": "[CDPSession.spec] Target.createCDPSession should send events", "platforms": ["win32"], diff --git a/test/src/elementhandle.spec.ts b/test/src/elementhandle.spec.ts index a0c88edd1ae..60e95a790dc 100644 --- a/test/src/elementhandle.spec.ts +++ b/test/src/elementhandle.spec.ts @@ -145,7 +145,7 @@ describe('ElementHandle specs', function () { y: 2 + 5, }); expect(box.content[0]).toEqual({ - x: 1 + 4 + 3 + 1 + 2, // frame.left + div.left + div.marginLeft + div.borderLeft + dif.paddingLeft + x: 1 + 4 + 3 + 1 + 2, // frame.left + div.left + div.marginLeft + div.borderLeft + div.paddingLeft y: 2 + 5, }); });