fix: apply OOPIF offsets to bounding box and box model calls (#7906)
The doc for boundingBox says that it should return the boundingBox relative to the main frame, therefore, this fix would make the actual implementation correspond to the documentation. boxModel documentation does not have this note but I think it'd make sense to have it match the behaviour of the boundingBox API.
This commit is contained in:
parent
d7937b806d
commit
a566263ba2
@ -88,6 +88,12 @@ export function createJSHandle(
|
||||
return new JSHandle(context, context._client, remoteObject);
|
||||
}
|
||||
|
||||
const applyOffsetsToQuad = (
|
||||
quad: Array<{ x: number; y: number }>,
|
||||
offsetX: number,
|
||||
offsetY: number
|
||||
) => quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY }));
|
||||
|
||||
/**
|
||||
* Represents an in-page JavaScript object. JSHandles can be created with the
|
||||
* {@link Page.evaluateHandle | page.evaluateHandle} method.
|
||||
@ -469,27 +475,11 @@ export class ElementHandle<
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the middle point within an element unless a specific offset is provided.
|
||||
*/
|
||||
async clickablePoint(offset?: Offset): Promise<Point> {
|
||||
const [result, layoutMetrics] = await Promise.all([
|
||||
this._client
|
||||
.send('DOM.getContentQuads', {
|
||||
objectId: this._remoteObject.objectId,
|
||||
})
|
||||
.catch(debugError),
|
||||
this._page.client().send('Page.getLayoutMetrics'),
|
||||
]);
|
||||
if (!result || !result.quads.length)
|
||||
throw new Error('Node is either not clickable or not an HTMLElement');
|
||||
// Filter out quads that have too small area to click into.
|
||||
// Fallback to `layoutViewport` in case of using Firefox.
|
||||
const { clientWidth, clientHeight } =
|
||||
layoutMetrics.cssLayoutViewport || layoutMetrics.layoutViewport;
|
||||
private async _getOOPIFOffsets(
|
||||
frame: Frame
|
||||
): Promise<{ offsetX: number; offsetY: number }> {
|
||||
let offsetX = 0;
|
||||
let offsetY = 0;
|
||||
let frame = this._frame;
|
||||
while (frame.parentFrame()) {
|
||||
const parent = frame.parentFrame();
|
||||
if (!frame.isOOPFrame()) {
|
||||
@ -511,11 +501,31 @@ export class ElementHandle<
|
||||
offsetY += topLeftCorner.y;
|
||||
frame = parent;
|
||||
}
|
||||
return { offsetX, offsetY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the middle point within an element unless a specific offset is provided.
|
||||
*/
|
||||
async clickablePoint(offset?: Offset): Promise<Point> {
|
||||
const [result, layoutMetrics] = await Promise.all([
|
||||
this._client
|
||||
.send('DOM.getContentQuads', {
|
||||
objectId: this._remoteObject.objectId,
|
||||
})
|
||||
.catch(debugError),
|
||||
this._page.client().send('Page.getLayoutMetrics'),
|
||||
]);
|
||||
if (!result || !result.quads.length)
|
||||
throw new Error('Node is either not clickable or not an HTMLElement');
|
||||
// Filter out quads that have too small area to click into.
|
||||
// Fallback to `layoutViewport` in case of using Firefox.
|
||||
const { clientWidth, clientHeight } =
|
||||
layoutMetrics.cssLayoutViewport || layoutMetrics.layoutViewport;
|
||||
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
||||
const quads = result.quads
|
||||
.map((quad) => this._fromProtocolQuad(quad))
|
||||
.map((quad) =>
|
||||
quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY }))
|
||||
)
|
||||
.map((quad) => applyOffsetsToQuad(quad, offsetX, offsetY))
|
||||
.map((quad) =>
|
||||
this._intersectQuadWithViewport(quad, clientWidth, clientHeight)
|
||||
)
|
||||
@ -860,13 +870,14 @@ export class ElementHandle<
|
||||
|
||||
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, y, width, height };
|
||||
return { x: x + offsetX, y: y + offsetY, width, height };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -882,12 +893,30 @@ export class ElementHandle<
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
||||
|
||||
const { content, padding, border, margin, width, height } = result.model;
|
||||
return {
|
||||
content: this._fromProtocolQuad(content),
|
||||
padding: this._fromProtocolQuad(padding),
|
||||
border: this._fromProtocolQuad(border),
|
||||
margin: this._fromProtocolQuad(margin),
|
||||
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,
|
||||
};
|
||||
|
@ -282,7 +282,7 @@ describeChromeOnly('OOPIF', function () {
|
||||
expect(oopIframe.childFrames()).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('clickablePoint should work for elements inside OOPIFs', async () => {
|
||||
it('clickablePoint, boundingBox, boxModel should work for elements inside OOPIFs', async () => {
|
||||
const { server } = getTestState();
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
const framePromise = page.waitForFrame((frame) => {
|
||||
@ -311,6 +311,21 @@ describeChromeOnly('OOPIF', function () {
|
||||
const result = await button.clickablePoint();
|
||||
expect(result.x).toBeGreaterThan(150); // padding + margin + border left
|
||||
expect(result.y).toBeGreaterThan(150); // padding + margin + border top
|
||||
const resultBoxModel = await button.boxModel();
|
||||
for (const quad of [
|
||||
resultBoxModel.content,
|
||||
resultBoxModel.border,
|
||||
resultBoxModel.margin,
|
||||
resultBoxModel.padding,
|
||||
]) {
|
||||
for (const part of quad) {
|
||||
expect(part.x).toBeGreaterThan(150); // padding + margin + border left
|
||||
expect(part.y).toBeGreaterThan(150); // padding + margin + border top
|
||||
}
|
||||
}
|
||||
const resultBoundingBox = await button.boundingBox();
|
||||
expect(resultBoundingBox.x).toBeGreaterThan(150); // padding + margin + border left
|
||||
expect(resultBoundingBox.y).toBeGreaterThan(150); // padding + margin + border top
|
||||
});
|
||||
|
||||
it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
|
||||
|
Loading…
Reference in New Issue
Block a user