Puppeteer Getting List of Elements with Same Selector

Background:

Using NodeJS/CucumberJS/Puppeteer to build end-to-end regression test for an emberJS solution.

Problem:

Selecting (page.click) and getting textContent of one of the elements when there are several dynamic elements with the same selector? (In my case, I have 4 elements with the same selector = [data-test-foo4=”true”])

I know, that with:

const text = await page.evaluate( () => document.querySelector('[data-test-foo4="true"]').textContent );

I can get the text of the first element, but how do I select the other elements with the same selector? I’ve tried:

var text = await page.evaluate( () => document.querySelectorAll('[data-test-foo4="true"]').textContent )[1];
console.log('text=" + text);

but it gives me “text = undefined’

Also, the following:

await page.click('[data-test-foo4="true"]');

selects the first elements with that selector, but how can I select the next one with that selector?

You can use Array.from() to create an array containing all of the textContent values of each element matching your selector:

const text = await page.evaluate(() => Array.from(document.querySelectorAll('[data-test-foo4="true"]'), element => element.textContent));

console.log(text[0]);
console.log(text[1]);
console.log(text[2]);

If you need to click more than one element containing a given selector, you can create an ElementHandle array using page.$$() and click each one using elementHandle.click():

const example = await page.$$('[data-test-foo4="true"]');

await example[0].click();
await example[1].click();
await example[2].click();

https://github.com/puppeteer/puppeteer/blob/v5.5.0/docs/api.md#frameselector-1

const pageFrame = page.mainFrame();
const elems = await pageFrame.$$(selector);

Not mentioned yet is the awesome page.$$eval which is basically a wrapper for this common pattern:

page.evaluate(() => callback([...document.querySelectorAll(selector)]))

For example,

const puppeteer = require("puppeteer"); // ^16.2.0

const html = `<!DOCTYPE html>
<html>
<body>
<ul>
  <li data-test-foo4="true">red</li>
  <li data-test-foo4="false">blue</li>
  <li data-test-foo4="true">purple</li>
</ul>
</body>
</html>`;

let browser;
(async () => {
  browser = await puppeteer.launch();
  const [page] = await browser.pages();
  await page.setContent(html);

  const sel="[data-test-foo4="true"]";
  const text = await page.$$eval(sel, els => els.map(e => e.textContent));
  console.log(text); // => [ 'red', 'purple' ]
})()
  .catch(err => console.error(err))
  .finally(() => browser?.close())
;

If you want to pass additional data from Node for $$eval to use in the browser context, you can add additional arguments:

const text = await page.$$eval(
  '[data-test-foo4="true"]',
  (els, data) => els.map(e => e.textContent + data),
  "X" // 'data' passed to the callback
);
console.log(text); // => [ 'redX', 'purpleX' ]

You can use page.$$eval to issue a native DOM click on each element:

const text = await page.$$eval(sel, els => els.forEach(el => el.click()));

or use page.$$ to return the elements back to Node to issue trusted Puppeteer clicks:

for (const el of await page.$$('[data-test-foo4="true"]')) {
  await el.click();
}


The answers/resolutions are collected from stackoverflow, are licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0 .
Read More:   "You may need an appropriate loader to handle this file type" with Webpack and Babel

Similar Posts