Skip to content

Commit 545c43d

Browse files
authored
fix: better hittarget testing for clicking (#2217)
While checking for hittarget, we first bubble from a target element up to find the first element without `pointer-events: none` style. This bubbling does not make much sense: we risk desperately clicking "body" element, when we were actually asked to click some deeply-nested "span". Additionally, in many cases the original intent is to click a button. In this case, we should use the enclosing "button" as a hit target directly. Fixes #2175
1 parent b8410bd commit 545c43d

File tree

2 files changed

+20
-4
lines changed

2 files changed

+20
-4
lines changed

src/injected/injectedScript.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,10 +375,9 @@ export default class InjectedScript {
375375

376376
checkHitTargetAt(node: Node, point: types.Point): types.InjectedScriptResult<boolean> {
377377
let element = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;
378-
while (element && window.getComputedStyle(element).pointerEvents === 'none')
379-
element = element.parentElement;
380378
if (!element || !element.isConnected)
381379
return { status: 'notconnected' };
380+
element = element.closest('button, [role=button]') || element;
382381
let hitElement = this._deepElementFromPoint(document, point.x, point.y);
383382
while (hitElement && hitElement !== element)
384383
hitElement = this._parentElementOrShadowHost(hitElement);

test/click.spec.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -503,9 +503,26 @@ describe('Page.click', function() {
503503
expect(await page.evaluate(() => window.result)).toBe('Was not clicked');
504504
});
505505

506-
it('should climb dom for pointer-events:none targets', async({page, server}) => {
507-
await page.setContent('<button><label style="pointer-events:none">Click target</label></button>')
506+
it('should climb dom for inner label with pointer-events:none', async({page, server}) => {
507+
await page.setContent('<button onclick="javascript:window.__CLICKED=true;"><label style="pointer-events:none">Click target</label></button>');
508508
await page.click('text=Click target');
509+
expect(await page.evaluate(() => window.__CLICKED)).toBe(true);
510+
});
511+
it('should climb up to [role=button]', async({page, server}) => {
512+
await page.setContent('<div role=button onclick="javascript:window.__CLICKED=true;"><div style="pointer-events:none"><span><div>Click target</div></span></div>');
513+
await page.click('text=Click target');
514+
expect(await page.evaluate(() => window.__CLICKED)).toBe(true);
515+
});
516+
it('should wait for BUTTON to be clickable when it has pointer-events:none', async({page, server}) => {
517+
await page.setContent('<button onclick="javascript:window.__CLICKED=true;" style="pointer-events:none"><span>Click target</span></button>');
518+
const clickPromise = page.click('text=Click target');
519+
// Do a few roundtrips to the page.
520+
for (let i = 0; i < 5; ++i)
521+
expect(await page.evaluate(() => window.__CLICKED)).toBe(undefined);
522+
// remove `pointer-events: none` css from button.
523+
await page.evaluate(() => document.querySelector('button').style.removeProperty('pointer-events'));
524+
await clickPromise;
525+
expect(await page.evaluate(() => window.__CLICKED)).toBe(true);
509526
});
510527
it('should update modifiers correctly', async({page, server}) => {
511528
await page.goto(server.PREFIX + '/input/button.html');

0 commit comments

Comments
 (0)