javascriptpuppeteer

Do you have to use waitForSelector before accessing it with Puppeteer?


I've been working on a project that has a lot of tests that are very flakey. Often, a selector isn't found, so until now, our kludgey solution was to add manual delays here and there.

Previously, we had code like this:

const selector = 'my-selector';
const ele = await page.$(selector);
await ele.click();

I recently tried adding a waitForSelector before the $ call like so:

const selector = 'my-selector';
await page.waitForSelector(selector);
const ele = await page.$(selector);
await ele.click();

It seems to be more reliable now. Is it basically essential to always use page.waitForSelector() before accessing an elementpage.$()? Or is there a better way of doing this?


Solution

  • No, it's not always necessary to use waitForSelector. If the element is already on the page, then you can select it with non-wait selections like page.$, page.$eval, etc. In cases where you want an immediate true/false existence check for a condition, page.$ is better than a waitForSelector. But if you're not sure whether an element will be there or not, best to wait for it.

    waitForSelector is basically a common-case convenience wrapper on the more general waitForFunction which either registers a requestAnimationFrame loop or MutationObserver to wait for the callback predicate to be true. The waitForSelector docs claim "If at the moment of calling the method the selector already exists, the method will return immediately" but I don't see that it runs a preliminary page.$-type query before attaching a requestAnimationFrame from the present code.

    On the other hand, page.$ and page.$eval and company simply run a document.querySelector for you with minimal overhead. Nonetheless, I wouldn't worry too much about performance; if you were to replace all page.$ calls with waitForSelector calls, it'd probably not be super noticable.

    Note that waitForSelector returns the first matching element, so the code can be written as

    const selector = 'my-selector';
    const ele = await page.waitForSelector(selector);
    await ele.click();
    

    Whatever you do, avoid sleeping. waitForTimeout is being removed from the Puppeteer API, which is a good thing.

    If you get tired of typing out page.waitForSelector, you can always write a little wrapper for it:

    const [page] = await browser.pages();
    const $ = (...args) => page.waitForSelector(...args);
    const foo = await $(".foo");
    const bar = await $(".bar");
    const baz = await $(".baz");
    

    Since the original post, Puppeteer now supports locators, which auto wait.

    Disclosure: I'm the author of the linked blog post.