11

I'm trying to iterate over a list of dynamic elements with Playwright, I've tried a couple of things already, but none have been working:

await this.page.locator('li').click();
const elements = await this.page.locator('ul > li');
await elements.click()
await this.page.$$('ul > li').click();
await this.page.click('ul > li');
const divCounts = await elements.evaluateAll(async (divs) => await divs.click());
this.page.click('ul > li > i.red', { strict: false, clickCount: 1 },)
const elements = await this.page.$$('ul > li > i.red')

elements.forEach(async value => {
  console.log(value)
  await this.page.click('ul > li > i.red', { strict: false, clickCount: 1 },)
  await value.click();
})
Gabriel Costa
  • 374
  • 1
  • 3
  • 16

5 Answers5

13

Since https://playwright.dev/docs/api/class-locator#locator-element-handles doesn't have a good example on how to use .elementHandles().

Another way to solve this issue is as follows

const checkboxLocator = page.locator('tbody tr input[type="checkbox"]');
for (const el of await checkboxLocator.elementHandles()) {
  await el.check();
}
Machtyn
  • 2,982
  • 6
  • 38
  • 64
5

I managed to do it with the following code:

test('user can click multiple li', async ({ page }) => {
  const items = page.locator('ul > li');
  for (let i = 0; i < await items.count(); i++) {
    await items.nth(i).click();
  }
})
DauleDK
  • 3,313
  • 11
  • 55
  • 98
IslamTaha
  • 1,056
  • 1
  • 10
  • 17
2

A similar question was asked recently on the Playwright Slack community. This is copy-pasted and minimally adjusted from the answer by one of the maintainers there.

let listItems = this.page.locator('ul > li');

// In case the li elements don't appear all together, you have to wait before the loop below. What element to wait for depends on your situation.
await listItems.nth(9).waitFor(); 

for (let i = 0; i < listItems.count(); i++) {
  await listItems.nth(i).click();
}
refactoreric
  • 276
  • 1
  • 2
  • This will fire them all off at once, it seems playwright does not expose a collection that we could foreach over – user1529413 Dec 10 '21 at 17:52
  • @user1529413 What else do you want than firing them all off at once? Should it wait for something else after each click()? About the collection to foreach over, it is possible, with [locator.elementHandles()](https://playwright.dev/docs/api/class-locator#locator-element-handles), but in general elementHandles are less trouble-free than locators. You should use them with an as short scope as possible because they correspond to objects in the browser DOM which may disappear at will (depending on how the frontend code works). Locators are safer because they are re-evaluated and do auto-waiting. – refactoreric Dec 10 '21 at 20:03
  • thanks `locator.elementHandles` & `for await (` seems right for my situation. – user1529413 Dec 11 '21 at 05:22
  • @refactorreric To answer your question each item in the list might update the over all page. The list of LI are like a queue of actions to be preformed, after preforming a single action the entire page state is updated. In web page testing I don't think we should ever assume that there is a one-size-fits-all – user1529413 Dec 14 '21 at 16:45
1

You can achieve that using $$eval and pure client side javascript.

const results = await page.$$eval(`ul > li`, (allListItems) => {
    allListItems.forEach(async singleListItem => await singleListItem.click()) 
});

Please note that what you write inside the callback, will be executed on the browser. So if you want to output anything, you need to return it. That way it will end up inside the results variable.

Parsa Safavi
  • 218
  • 2
  • 10
0

This works for me (my example):

// reset state and remove all existing bookmarks
const bookmarkedItems = await page.locator('.bookmark img[src="/static/img/like_orange.png"]');
const bookmarkedItemsCounter = await bookmarkedItems.count();
if (bookmarkedItemsCounter) {
  for (let i = 0; i < bookmarkedItemsCounter; i++) {
    await bookmarkedItems.nth(i).click();
  }
}
await page.waitForTimeout(1000);

If try to solve your task should be:

test('click by each li element in the list', async ({ page }) => {
  await page.goto(some_url);
  const liItems = await page.locator('ul > li');
  const liItemCounter = await liItems.count();
  if (liItemCounter) {
    for (let i = 0; i < liItemCounter; i++) {
      await liItems.nth(i).click();
    }
  }
  await page.waitForTimeout(1000);
});
Igor Fedun
  • 91
  • 2
  • 4
  • `if (bookmarkedItemsCounter)` is superfluous. A `for` loop already checks for there being 0 elements. – ggorlen Jul 23 '22 at 22:55