2

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?

antun
  • 2,038
  • 2
  • 22
  • 34

1 Answers1

1

No, it's not essential to always use waitForSelector. If the element is already on the page, then just select it with page.$, page.$eval, etc, otherwise wait for it. If you're not sure whether it'll 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.

On the other hand, I like the expressivity of using page.$ when it's appropriate. It communicates intent that you're sure the element exists, not as clear if you use waitForSelector indiscriminately. page.$ won't throw, so the reporting is weaker, probably pushing the throw to the next line (or further) when you try to access a property on null.

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, don't sleep. 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");
ggorlen
  • 44,755
  • 7
  • 76
  • 106