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:
Dmitry Gozman 2020-08-20 16:49:19 -07:00 committed by GitHub
parent 012f9425bf
commit e2bb6a07cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 1 deletions

View file

@ -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> {

View file

@ -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');