Skip to main content

$$

$$ fetches every element that matches a selector. It returns a WebdriverIO.ElementArray immediately: a real array (Array.isArray is true) that stays thenable until the query finishes. custom$$, react$$ and shadow$$ return the same kind of list, including when you call them on an element ($('parent').$$('child')).

Await the list when you want it resolved. length is then a number, each index is an element, and a normal for...of loop works:

const buttons = await $$('button')
console.log(buttons.length) // 3

for (const button of buttons) {
console.log(await button.getText())
}

The query does not run until something reads the list (await, for await, .length, an index, or an iterator method such as map). Until then:

  • length is a Promise<number>. Use await $$('button').length, not $$('button').length > 0.
  • $$('button')[0] is a chainable element, so await $$('button')[0].getText() works without awaiting the list first.
  • for await (const button of $$('button')) yields each element.
  • for (const button of $$('button')) throws Cannot synchronously iterate over an element list that has not resolved yet. Await the list first, or use for await.

An index past the end of the list waits and refetches until waitforTimeout, the same way a missing single element does. That wait still happens after the list has resolved (const items = await $$('li'); await items[items.length]). A slice or a filter does not: $$('li').slice(0, 2)[3] stays outside that window, and an index past a filtered list does not resolve to an element the filter excluded. .at() uses the same index conversion as a plain array, so .at(1.5) is element 1 and .at(NaN) is element 0.

Using the wdio testrunner this command is a global variable, see Globals for more information. Using WebdriverIO within a standalone script it will be located on the browser object instead (e.g. browser.$$).

You can chain $ or $$ together without wrapping individual commands into await in order to walk down the DOM tree, e.g.:

const imageSrc = await $$('div')[1].nextElement().$$('img')[2].getAttribute('src')

Iterating over elements​

The list also provides asynchronous versions of the Array iteration methods forEach, map, find, findIndex, some, every, filter and reduce. They accept async callbacks and can be called on $$ directly. filter and filterSeries return a new ElementArray, so the selector and the command that found the elements are kept:

const texts = await $$('h3').map((h3) => h3.getText())

for await (const img of $$('img')) {
console.log(await img.getAttribute('src'))
}

Every method except reduce also has a *Series variant (forEachSeries, mapSeries, and so on). The base method runs all callbacks concurrently, while the *Series variant runs one callback at a time, in list order. reduce always runs one callback at a time. Use a *Series variant when the order of your interactions with the page matters.

The concurrent find and findIndex resolve with the first match to finish, which is not necessarily the earliest match in the list, and the concurrent find, findIndex, some and every keep running the remaining callbacks after the result is known. Use the *Series variant to get the earliest match and stop there.

info

For more information on how to select specific elements, check out the Selectors guide.

Usage​

await browser.$$(selector)

Parameters​

NameTypeDetails
selectorString, Functionselector or JS Function to fetch multiple elements

Examples​

example.html
loading...
multipleElements.js
loading...
multipleElements.js
loading...
multipleElements.js
loading...

Returns​

  • <WebdriverIO.ElementArray>

Welcome! How can I help?

WebdriverIO AI Copilot