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);
|
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
|
* Represents an in-page JavaScript object. JSHandles can be created with the
|
||||||
* {@link Page.evaluateHandle | page.evaluateHandle} method.
|
* {@link Page.evaluateHandle | page.evaluateHandle} method.
|
||||||
@ -469,27 +475,11 @@ export class ElementHandle<
|
|||||||
if (error) throw new Error(error);
|
if (error) throw new Error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private async _getOOPIFOffsets(
|
||||||
* Returns the middle point within an element unless a specific offset is provided.
|
frame: Frame
|
||||||
*/
|
): Promise<{ offsetX: number; offsetY: number }> {
|
||||||
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;
|
|
||||||
let offsetX = 0;
|
let offsetX = 0;
|
||||||
let offsetY = 0;
|
let offsetY = 0;
|
||||||
let frame = this._frame;
|
|
||||||
while (frame.parentFrame()) {
|
while (frame.parentFrame()) {
|
||||||
const parent = frame.parentFrame();
|
const parent = frame.parentFrame();
|
||||||
if (!frame.isOOPFrame()) {
|
if (!frame.isOOPFrame()) {
|
||||||
@ -511,11 +501,31 @@ export class ElementHandle<
|
|||||||
offsetY += topLeftCorner.y;
|
offsetY += topLeftCorner.y;
|
||||||
frame = parent;
|
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
|
const quads = result.quads
|
||||||
.map((quad) => this._fromProtocolQuad(quad))
|
.map((quad) => this._fromProtocolQuad(quad))
|
||||||
.map((quad) =>
|
.map((quad) => applyOffsetsToQuad(quad, offsetX, offsetY))
|
||||||
quad.map((part) => ({ x: part.x + offsetX, y: part.y + offsetY }))
|
|
||||||
)
|
|
||||||
.map((quad) =>
|
.map((quad) =>
|
||||||
this._intersectQuadWithViewport(quad, clientWidth, clientHeight)
|
this._intersectQuadWithViewport(quad, clientWidth, clientHeight)
|
||||||
)
|
)
|
||||||
@ -860,13 +870,14 @@ export class ElementHandle<
|
|||||||
|
|
||||||
if (!result) return null;
|
if (!result) return null;
|
||||||
|
|
||||||
|
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
||||||
const quad = result.model.border;
|
const quad = result.model.border;
|
||||||
const x = Math.min(quad[0], quad[2], quad[4], quad[6]);
|
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 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 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;
|
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;
|
if (!result) return null;
|
||||||
|
|
||||||
|
const { offsetX, offsetY } = await this._getOOPIFOffsets(this._frame);
|
||||||
|
|
||||||
const { content, padding, border, margin, width, height } = result.model;
|
const { content, padding, border, margin, width, height } = result.model;
|
||||||
return {
|
return {
|
||||||
content: this._fromProtocolQuad(content),
|
content: applyOffsetsToQuad(
|
||||||
padding: this._fromProtocolQuad(padding),
|
this._fromProtocolQuad(content),
|
||||||
border: this._fromProtocolQuad(border),
|
offsetX,
|
||||||
margin: this._fromProtocolQuad(margin),
|
offsetY
|
||||||
|
),
|
||||||
|
padding: applyOffsetsToQuad(
|
||||||
|
this._fromProtocolQuad(padding),
|
||||||
|
offsetX,
|
||||||
|
offsetY
|
||||||
|
),
|
||||||
|
border: applyOffsetsToQuad(
|
||||||
|
this._fromProtocolQuad(border),
|
||||||
|
offsetX,
|
||||||
|
offsetY
|
||||||
|
),
|
||||||
|
margin: applyOffsetsToQuad(
|
||||||
|
this._fromProtocolQuad(margin),
|
||||||
|
offsetX,
|
||||||
|
offsetY
|
||||||
|
),
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
};
|
};
|
||||||
|
@ -282,7 +282,7 @@ describeChromeOnly('OOPIF', function () {
|
|||||||
expect(oopIframe.childFrames()).toHaveLength(0);
|
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();
|
const { server } = getTestState();
|
||||||
await page.goto(server.EMPTY_PAGE);
|
await page.goto(server.EMPTY_PAGE);
|
||||||
const framePromise = page.waitForFrame((frame) => {
|
const framePromise = page.waitForFrame((frame) => {
|
||||||
@ -311,6 +311,21 @@ describeChromeOnly('OOPIF', function () {
|
|||||||
const result = await button.clickablePoint();
|
const result = await button.clickablePoint();
|
||||||
expect(result.x).toBeGreaterThan(150); // padding + margin + border left
|
expect(result.x).toBeGreaterThan(150); // padding + margin + border left
|
||||||
expect(result.y).toBeGreaterThan(150); // padding + margin + border top
|
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 () => {
|
it('should detect existing OOPIFs when Puppeteer connects to an existing page', async () => {
|
||||||
|
Loading…
Reference in New Issue
Block a user