Skip to content

[Feature]: nthMatch locator聽#42842

Description

馃殌 Feature Request

We follow the playwright guidance https://playwright.dev/docs/other-locators#css-locator about preferring user-visible locators (further enforced by the no-raw-locators lint rule), however we've hit some performance issues with getBy()...innerText() calls in loops that folks have written because playwright's api lacks an idiomatic nthMatch/nthChild locator.

For us, this often happens on tables (100+ rows), where we're trying to collect all of the values of a certain column for testing.

const colIdx = 5;
const cellValuesList: string[] = [];
const rows = page.getByRole("table").getByRole("rowgroup").getByRole("row");
for (const row of await rows.all()) {
  const cellValue = await row.getByRole("cell").nth(colIdx).innerText();
  cellValuesList.push(cellValue);
}
expect(expectedColsText).toEqual(cellValuesList)

The inner innerText() call takes about 50ms, and for 100 rows, we burn about 5s just collecting the cell values. Worse, since we're being forced to collect each cell one by one, we have to wrap this whole loop in an expect().toPass() when we're doing assertions (like asserting the column is sorted correctly after a button click). That adds further slowdowns since expect().toPass() retries the entire loop.

I have seen failed proposals for table helpers (#4646, #33111), but I think the problem is more generalizable. My solution this far has been to swap to a css locator for this usecase:

page.getByRole("table").locator(`[role='row'] [role='cell']:nth-child(${cssColIdx})`).allInnerTexts()

However it's still kind of clunky and might be an argument for an official nthChild (or nthMatch) locator in the playwright api.

Example

I imagine it could work sort of like the existing filter api. (I am hand waving a little here as this might be more complicated than it sounds given how chaining locators behaves)

// get the 5th cell, from each row
page.getByRole("table").getByRole("row").nthMatch(5, page.getByRole("cell"))

Motivation

This would be more performant the calling getBy()...innerText() in a loop, and also be more ergonomic, as it means we could "oneshot" the table column assertion by relying on playwright's standard auto-retry behavior rather than needing to fall back to an expect().toPass().

Contrast

// new
await expect(
   page.getByRole("table").getByRole("row").nthMatch(5, page.getByRole("cell"))
).toHaveText(expectedColsText)

// old
await expect(
  const cellValuesList: string[] = [];
  const rows = page.getByRole("table").getByRole("rowgroup").getByRole("row");
  for (const row of await rows.all()) {
    const cellValue = await row.getByRole("cell").nth(5).innerText();
    cellValuesList.push(cellValue);
  }
  expect(expectedColsText).toEqual(cellValuesList)
).toPass()

Activity

  1. changed the title [-][Feature]: nthMatch locator[/-] [+][Feature]: `nthMatch` locator[/+] on Sep 21, 2026
  2. yury-s commented on Sep 21, 2026

    @yury-s
    Member

    If you just want to assert content of several cells you could use expect(...).toHaveText(['cell1', 'cell2',...]) with an array argument, would that work?

  3. seanlafferty-ibm commented on Sep 22, 2026

    @seanlafferty-ibm
    Author

    The focus of this proposal is on the locator side of things, not the assertion side of things. We lack an idiomatic way to get the nthMatch without falling back to css locators. If such an api existed, we could use .toHaveText() without any looping, which would be both a speed and ergonomic improvement

    await expect(
       // get the 5th cell, of every row
       page
         .getByRole("table")
         .getByRole("row")
         .nthMatch(5, page.getByRole("cell")) // <- proposed locator
    ).toHaveText(['cell1', 'cell2', ...]) // assertion against the entire column
  4. yury-s commented on Sep 23, 2026

    @yury-s
    Member

    You can rewrite this using .locator():

    await expect(
       page.getByRole('row').locator(
           page.getByRole('cell').nth(5))
    ).toHaveText(['cell1', 'cell2', ...]);

    would that work?

  5. seanlafferty-ibm commented on Sep 23, 2026

    @seanlafferty-ibm
    Author

    Yes! locator will do the trick- I think the documentation for that function would benefit from an example. Thanks for pointing it out. I noticed the trace viewer doesn't know how to highlight that which I can file a separate issue for

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions