Search code examples
node.jsplaywright

Playwright how to wait for locator that matches multiple elements to not be visible


I am trying to wait for an element that indicates a page is still loading, and exists on the page multiple time, to not be visible (think table with loading data placeholders).

Playwright documentation suggests using Locators is best practice, and therefore I initially tried to achieve this by doing:

locator.waitFor({state: "hidden")

However that errors due to Locators being strict and only being allow to match one element.

I'm now doing it with the following code:

page.waitForSelector(".foo .bar", {state: "hidden"})

This is non-ideal for a couple of reasons:

  • I'm storing page elements as locators in the Page Object Model, and you seemingly cannot access the selector of a locator, meaning the selector is duplicated in the code
  • I believe page.waitForSelector will use an ElementHandle which is discouraged

Is there any way to turn off the strictness constraint on a Locator? Or a way to achieve this using the Locator. I'm aware you can do .count on a Locator which matches multiple elements, but I've not found a nice way to combine that with waiting for the count to be 0.


Solution

  • I got this working in the end using the evaluateAll method. Example code:

    async waitForAllHidden(locator: Locator, timeout: number = 10000) {
        const start = Date.now()
        const elementsVisible = async () => (
            await locator.evaluateAll(elements =>
                elements.map(element => element.hidden))
        ).includes(false)
    
        while (await elementsVisible()) {
            if (start + timeout < Date.now()) {
                throw (`Timeout waiting for all elements to be hidden.
                        Locator: ${locator}. Timeout: ${timeout}ms`);
            }
        }
        console.log(`All elements hidden: ${locator}`)
    }
    

    Update for 2023:

    There are new methods available which can now be used for this purpose:

    toHaveCount() e.g.

    await expect(yourLocator).toHaveCount(0);
    

    poll() e.g.

    await expect.poll(async () => {
      for (const e of await yourLocator.all()) {
        if (await e.isVisible()) return false
      }
      return true
    }, {
      message: 'youLocator is still visible', // optional custom error message
      intervals: [1_000, 2_000, 10_000], // optional polling overrides
      timeout: 10000, // optional timeout override
    }).toBe(true);