Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

puppeteer element.click() not working and not throwing an error

Tags:

puppeteer

I have a situation where a button, on a form, that is animated into view, if the element.click() happens while the animation is in progress, it doesn't work.

element.click() doesn't throw an error, doesn't return a failed status (it returns undefined) it just silently doesn't work.

I have tried ensuring the element being clicked is not disabled, and is displayed (visible) but even though both those tests succeed, the click fails.

If I wait 0.4s before clicking, it works because the animation has finished.

I don't want to have to add delays (which are unreliable, and a bodge to be frank), if I can detect when a click worked, and if not automatically retry.

Is there a generic way to detect if a click() has actually been actioned so I can use a retry loop until it does?

like image 485
Austin France Avatar asked Apr 23 '18 10:04

Austin France


People also ask

How do you click a link on a puppeteer?

Puppeteer is capable of handling a link/button on a page. Before clicking an element we must be able to uniquely identify it with the help of any of the locators. In Puppeteer, we can click an element only if its dimensions are greater than zero pixel.

How do you double click on a puppeteer?

You can use mouse. click(x, y[, options]) . First get x and y .


1 Answers

I have determined what is happening, and why I don't get an error, and how to work around the issue.

The main issue is with the way element.click() works. Using DEBUG="puppeteer:*" I was able to see what is going on internally. What element.click() actually does is:-

const box = element.boundingBox();
const x = box.x + (box.width/2);
const y = box.y + (box.height/2);
page.mouse.move(x,y);
page.mouse.down();
sleep(delay);
page.mouse.up();

The problem is that because the view (div) is animating the element's boundingBox() is changing, and between the time of asking for the box position, and completing the click() the element has moved or is not clickable.

An error isn't thrown (promise rejected) because its just a mouse click on a point in the viewport, and not linked to any element. The mouse event is sent, just that nothing responds to it.

One workaround is to add a sufficient delay to allow the animation to finish. Another is to disable animations during tests.

The solution for me was to wait for the position of the element to settle at its destination position, that is I spin on querying the boundingBox() and wait for the x,y to report the elements previously determined position.

In my case, this is as simple as adding at 10,10 to my test script just before the click, or specifically

test-id "form1.button3" at 10,10 click

And in action it works as follows, in this case, the view is being animated back in from the left.

00.571 [selector.test,61] at 8,410
test-id "main.add" info tag button displayed at -84,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at -11,410 size 116,33 enabled not selected check "Add"
test-id "main.add" info tag button displayed at 8,410 size 116,33 enabled not selected check "Add"
00.947 [selector.test,61] click

It wouldn't work for an element that was continually moving or for an element that is covered by something else. For those cases, try page.evaluate(el => el.click(), element).

like image 183
Austin France Avatar answered Sep 28 '22 22:09

Austin France