fix(click): allow clicking 1x1 sized elements (#3538)
One by one seems like a resonable minimum size for clicking. It is not surprising to see a 1x1 native accessible control that is covered by a custom control that handles input instead.
This commit is contained in:
parent
012f9425bf
commit
e2bb6a07cd
24
src/dom.ts
24
src/dom.ts
|
|
@ -257,7 +257,8 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
if (!quads || !quads.length)
|
||||
return 'error:notvisible';
|
||||
|
||||
const filtered = quads.map(quad => intersectQuadWithViewport(quad)).filter(quad => computeQuadArea(quad) > 1);
|
||||
// Allow 1x1 elements. Compensate for rounding errors by comparing with 0.99 instead.
|
||||
const filtered = quads.map(quad => intersectQuadWithViewport(quad)).filter(quad => computeQuadArea(quad) > 0.99);
|
||||
if (!filtered.length)
|
||||
return 'error:notinviewport';
|
||||
// Return the middle point of the first quad.
|
||||
|
|
@ -266,6 +267,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
|||
result.x += point.x / 4;
|
||||
result.y += point.y / 4;
|
||||
}
|
||||
compensateHalfIntegerRoundingError(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -815,6 +817,26 @@ function roundPoint(point: types.Point): types.Point {
|
|||
};
|
||||
}
|
||||
|
||||
function compensateHalfIntegerRoundingError(point: types.Point) {
|
||||
// Firefox internally uses integer coordinates, so 8.5 is converted to 9 when clicking.
|
||||
//
|
||||
// This does not work nicely for small elements. For example, 1x1 square with corners
|
||||
// (8;8) and (9;9) is targeted when clicking at (8;8) but not when clicking at (9;9).
|
||||
// So, clicking at (8.5;8.5) will effectively click at (9;9) and miss the target.
|
||||
//
|
||||
// Therefore, we skew half-integer values from the interval (8.49, 8.51) towards
|
||||
// (8.47, 8.49) that is rounded towards 8. This means clicking at (8.5;8.5) will
|
||||
// be replaced with (8.48;8.48) and will effectively click at (8;8).
|
||||
//
|
||||
// Other browsers use float coordinates, so this change should not matter.
|
||||
const remainderX = point.x - Math.floor(point.x);
|
||||
if (remainderX > 0.49 && remainderX < 0.51)
|
||||
point.x -= 0.02;
|
||||
const remainderY = point.y - Math.floor(point.y);
|
||||
if (remainderY > 0.49 && remainderY < 0.51)
|
||||
point.y -= 0.02;
|
||||
}
|
||||
|
||||
export type SchedulableTask<T> = (injectedScript: js.JSHandle<InjectedScript>) => Promise<js.JSHandle<types.InjectedScriptPoll<T>>>;
|
||||
|
||||
export function waitForSelectorTask(selector: SelectorInfo, state: 'attached' | 'detached' | 'visible' | 'hidden', root?: ElementHandle): SchedulableTask<Element | undefined> {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,12 @@ it('should not throw UnhandledPromiseRejection when page closes', async({browser
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should click the 1x1 div', async({page, server}) => {
|
||||
await page.setContent(`<div style="width: 1px; height: 1px;" onclick="window.__clicked = true"></div>`);
|
||||
await page.click('div');
|
||||
expect(await page.evaluate('window.__clicked')).toBe(true);
|
||||
});
|
||||
|
||||
it('should click the button after navigation ', async({page, server}) => {
|
||||
await page.goto(server.PREFIX + '/input/button.html');
|
||||
await page.click('button');
|
||||
|
|
|
|||
Loading…
Reference in a new issue