Skip to content

Commit 793586e

Browse files
authored
fix(click): throw instead of timing out when the element has moved (#1942)
1 parent f11113f commit 793586e

File tree

2 files changed

+12
-52
lines changed

2 files changed

+12
-52
lines changed

src/injected/injected.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -321,20 +321,29 @@ export class Injected {
321321
}
322322

323323
async waitForHitTargetAt(node: Node, timeout: number, point: types.Point): Promise<InjectedResult> {
324-
let element = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
324+
const targetElement = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
325+
let element = targetElement;
325326
while (element && window.getComputedStyle(element).pointerEvents === 'none')
326327
element = element.parentElement;
327328
if (!element)
328329
return { status: 'notconnected' };
329-
const result = await this.poll('raf', timeout, (): 'notconnected' | boolean => {
330+
const result = await this.poll('raf', timeout, (): 'notconnected' | 'moved' | boolean => {
330331
if (!element!.isConnected)
331332
return 'notconnected';
333+
const clientRect = targetElement!.getBoundingClientRect();
334+
if (clientRect.left > point.x || clientRect.left + clientRect.width < point.x ||
335+
clientRect.top > point.y || clientRect.top + clientRect.height < point.y)
336+
return 'moved';
332337
let hitElement = this._deepElementFromPoint(document, point.x, point.y);
333338
while (hitElement && hitElement !== element)
334339
hitElement = this._parentElementOrShadowHost(hitElement);
335340
return hitElement === element;
336341
});
337-
return { status: result === 'notconnected' ? 'notconnected' : (result ? 'success' : 'timeout') };
342+
if (result === 'notconnected')
343+
return { status: 'notconnected' };
344+
if (result === 'moved')
345+
return { status: 'error', error: 'Element has moved during the action' };
346+
return { status: result ? 'success' : 'timeout' };
338347
}
339348

340349
private _parentElementOrShadowHost(element: Element): Element | undefined {

test/click.spec.js

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -506,55 +506,6 @@ describe('Page.click', function() {
506506
await page.click('button');
507507
expect(await page.evaluate('window.clicked')).toBe(true);
508508
});
509-
it('should fail to click a button animated via CSS animations and setInterval', async({page}) => {
510-
// This test has a setInterval that consistently animates a button.
511-
const buttonSize = 10;
512-
const containerWidth = 500;
513-
const transition = 100;
514-
await page.setContent(`
515-
<html>
516-
<body>
517-
<div style="border: 1px solid black; height: 50px; overflow: auto; width: ${containerWidth}px;">
518-
<button style="border: none; height: ${buttonSize}px; width: ${buttonSize}px; transition: left ${transition}ms linear 0s; left: 0; position: relative" onClick="window.clicked++"></button>
519-
</div>
520-
</body>
521-
<script>
522-
window.atLeft = true;
523-
const animateLeft = () => {
524-
const button = document.querySelector('button');
525-
button.style.left = window.atLeft ? '${containerWidth - buttonSize}px' : '0px';
526-
console.log('set ' + button.style.left);
527-
window.atLeft = !window.atLeft;
528-
};
529-
window.clicked = 0;
530-
const dump = () => {
531-
const button = document.querySelector('button');
532-
const clientRect = button.getBoundingClientRect();
533-
const rect = { x: clientRect.top, y: clientRect.left, width: clientRect.width, height: clientRect.height };
534-
requestAnimationFrame(dump);
535-
};
536-
dump();
537-
</script>
538-
</html>
539-
`);
540-
await page.evaluate(transition => {
541-
window.setInterval(animateLeft, transition);
542-
animateLeft();
543-
}, transition);
544-
545-
// Ideally, we we detect the button to be continuously animating, and timeout waiting for it to stop.
546-
// That does not happen though:
547-
// - Chromium headless does not issue rafs between first and second animateLeft() calls.
548-
// - Chromium and WebKit keep element bounds the same when for 2 frames when changing left to a new value.
549-
// This test currently documents our flaky behavior, because it's unclear whether we could
550-
// guarantee timeout.
551-
const error1 = await page.click('button', { timeout: 250 }).catch(e => e);
552-
if (error1)
553-
expect(error1.message).toContain('timeout exceeded');
554-
const error2 = await page.click('button', { timeout: 250 }).catch(e => e);
555-
if (error2)
556-
expect(error2.message).toContain('timeout exceeded');
557-
});
558509
it('should report nice error when element is detached and force-clicked', async({page, server}) => {
559510
await page.goto(server.PREFIX + '/input/animating-button.html');
560511
await page.evaluate(() => addButton());

0 commit comments

Comments
 (0)