angrykoala/wendigo

A more easy way to click a button?

lborgman opened this issue · 14 comments

I just tested Wendigo. It seems very nice and easy to get started with. I did something like this:

    const div = await browser.query("div#id");
    const det = await div.query("details");
    const btn = await det.query("form button");

To my suprise it just worked.

However now I want to click btn. I can't find an easy way to do that? Is there one? I tried this (which seems natural), but it did not work:

    await browser.click(btn);

Hi @lborgman the way you are doing it seems fine (check that btn is set to a DomElement variable and not null). Remember that if you have more than 1 button in your form, query only returns the first one

Alternatively, you could do the following:

await btn.click()

Or, in a one-liner:

await browser.click("#id details form button");

If you think you may have encountered a bug in wendigo, please provide the exact error you are having and an example of your html and code that reproduces the problem to debug (you can answer this same issue if this is the case).

Otherwise, I hope this answer solves your problem.

Thanks. Then I think it might be a problem with that the DOM is not ready for the click. Here is some more info.

The button is initially hidden (in a details> - summary>...).

I let Wendigo click summary and then click the button. The error I get might indicate that I should wait for something. Normally I use MutationObserver for this, but I have no idea how to do it here.

(node:11332) UnhandledPromiseRejectionWarning: Error: Node is either not visible or not an HTMLElement
    at ElementHandle._clickablePoint (C:\Users\lejon\bitbucket\easycaped\node_modules\puppeteer\lib\cjs\puppeteer\common\JSHandle.js:329:19)
    at processTicksAndRejections (internal/process/task_queues.js:97:5)

Hi @lborgman yep, that makes sense. This is a common issue when creating automated tests and wendigo is full of helper methods to solve these problems.

If you have an animation or something set in a way that your button is not instantly visible, the following may solve your problem:

After clicking summary (and, in theory, your button should eventually be visible) try await browser.waitAndClick("#id details form button"). This will wait for the button to be visible before clicking it (by default it will timeout at 500ms but you can set the time you need as second parameter)

Also, if you are having trouble debugging, remember that in createBrowser you can set the options headless: false to make the browser visible so it may give you some clues on why is not working.

Remember to check the docs to find other methods or options that may help solve your problems

Thanks. I tried

    await browser.waitAndClick(btn);

But that did not work:

waitFor is deprecated and will be removed in a future release. See https://github.com/puppeteer/puppeteer/issues/6214 for details and how to migrate your code.
(node:10168) UnhandledPromiseRejectionWarning: Error: Unsupported target type: object

Could you please try await browser.waitAndClick("#id details form button") (or the full css path to your button)?

As stated in the docs DomElements selectors not supported., because you are trying to wait until an element is available, waitAndClick do not support DomElement objects (which, already exists).

By using the css path you can make sure wendigo will wait until an element exists

The waitFor is deprecated is just a warning that should not affect this problem (#468 )

I tried that way instead (and checked with browser.query so I did not misspell anything). This time I get a timeout:

waitFor is deprecated ...
(node:12864) UnhandledPromiseRejectionWarning: TimeoutError: [waitAndClick] Timeout of 500ms exceeded.

Perhaps you could look up the Xpath to btn in your code and use that in an internal call to browser.click? (Or something similar.)

Here is my quick-and-dirty function for situations like this in my code:

function wait4mutations(elt, ms, observeWhat) {
    observeWhat = observeWhat || { attributes: true, characterData: true, childList: true, subtree: true, };
    return new Promise(resolve => {
        let tmr;
        function fin() { resolve(); mu.disconnect(); }
        function restartTimer() {
            clearTimeout(tmr);
            setTimeout(fin, ms);
        }
        const mu = new MutationObserver(mutations => {
            restartTimer();
        });
        restartTimer();
        mu.observe(elt, observeWhat);
        // { attributes: true, characterData: true, childList: true, subtree: true, }
    });
}

Hi @lborgman

  1. Ensure your button is actually visible when you are calling Click, as mentioned, waitAndClick will wait a maximum of 500ms before triggering a timeour, if your button takes more than that to be visible you can increase this timeout.

  2. "Perhaps you could look up the Xpath to btn in your code and use that in an internal call to browser.click" In your code, btn should be an instance of a DomElement (in fact, this object is a thin wrapper over a bare native Html Element), click will emulate a real click on that element (which requires it to be visible). Using an xpath to that element won't solve anything, as the element is already stored in that object. If your element is not visible (which may be the problem that is happening here) using button.triggerEvent(btn, "click") may work, as this will trigger the event directly instead of emulating a real user click.

Please, read the docs carefully and make sure that the steps that you try to execute with wendigo are similar to what a real user would do.

If you want to propose changes to how Wendigo work I'm happy to try and improve the library based on feedback, but I would need a way to replicate the problem we are trying to solve (with minimal html and a working example of the js code) as well as a discussion on why this need to change. In this case, click is a heavily used & tested functionality that works as described in the documentation (with the exception of bugs that may exists) so I would need either a proof that a bug exists or an exact description on what improvements may a change on this core feature bring.

Thanks, I am thinking about it. I have been using MutationObserver for so long now to get a more responsive and reliable app. But the tests does not need to be that quick so maybe a simple timeout is indeed the best.

While thinking about it I stumbled upon the link below. It is possible to implement this in Puppeteer (and so in Wendigo, of course).

puppeteer/puppeteer#2945

Hi @lborgman If you are concerned about performance, waitForClick won't actually wait for 500ms (or whatever timeout you set) that is the maximum time to wait. Under the hood, waitFor will poll every few milliseconds until the element is available. Obviously there is a slight performance overhead, but for most use cases is good enough, considering how little the overhead is compared to opening a whole chromium instance, it is probably not worth the effort this kind of optimization here.

If you have tight performance constraints, consider ditching the idea of using a browser automation tool (or, at least, a chromium based one) entirely, Both wendigo and Puppeteer are intended for testing or webscraping emulating a real user as close as possible, and usually not suitable for live/realtime apps.

Finally, as you say, wendigo is simply a wrapper over puppeteer, so you are free to implement you own observer using the solution mentioned in that issue.

Thanks, I think you are very right. I got the logic a bit backward, not so used to this perspective. (I am just going to use this for simple unit tests.)

So I tried your suggestion (a longer timeout). And my own version of this (with just a wait in my code).

There seem to be a bug either in Wendigo or Puppeteer. When I run this with {headless: false} it works, with {headless: true} it fails.

Hi @lborgman headless browser is way faster than a non-headless one, you may need a longer timeout when running as headless:true. How your web is being rendered may also be affecting.

However, I really need access to a reproducible example of the problem to check if it is indeed a bug and find a solution, it is impossible for me to infer why headless may not be working on a webpage for which I know nothing. If you are able to add a minimal and working example of the problem I'll try to help.

I have just mailed you my test case.