Skip to content

[Feature]table selector #4646

Description

@roughsoft

We know that the verify the result is very import job.
And in the these job ,the table result data is very horrible .
If we can make table selector, to make our get table data easy, for example
araylist=getTable(selector)
element=getTableCell(col,row,selector)
array=getTableRow(selector)
ref product:
https://sahipro.com/docs/sahi-apis/accessor-apis.html#Table%20Elements
https://docs.testproject.io/testproject-addons/available-addons/html-table-actions

Activity

  1. roughsoft commented on Dec 9, 2020

    @roughsoft
    Author

    table selector engineer.

  2. buhichan commented on Dec 22, 2020

    @buhichan

    +1, really need this to make useful test scripts.

  3. mxschmitt commented on Dec 22, 2020

    @mxschmitt
    Contributor

    Something like accessing the rows by index and the columns by index or header value would be nice I think. Had that use case in one of my past projects.

  4. ygsf commented on Jun 7, 2021

    @ygsf

    I think table selector will be very useful. Tables are used a lot in CRM screens. I think it will be especially useful if we can select based on table data and column name rather than row/column number.
    I will try to explain it using example of the table here: https://www.seleniumeasy.com/test/table-search-filter-demo.html
    I can find a cell using row and column number. playwright.$(':nth-match(tr,5) >> :nth-match(td,3)') returns <td>Emily John</td>
    I also can take a row using text: playwright.$('tr:has-text("Bootstrap 3") >> :nth-match(td,3)') It works in this example but may not work if the other row has the same text in the other column.
    It would be great to be able to find a cell by saying: bring me the value of column Assignee where the text of column Task="Bootstrap 3". It is similar what SQL does.

  5. rwoll commented on Jun 10, 2022

    @rwoll
    Member

    https://github.com/microsoft/playwright/pull/14763/files#diff-2d78fbd6a992ff94180cd23823c6d2554508544536c13cdfaa696de2f9f998a6R157 coincidentally implemented a naïve table selector where the API looks like:

      await expect.poll(report.detailsTable).toEqual([
        ['Status', 'Spec', 'Error'],
        ['❌', ' > a.test.js > failing', expect.stringContaining('expect(1).toBe(2)')],
        ['❌', ' > a.test.js > timeout', expect.stringContaining('Timeout of 500ms exceeded.')],
        ['✅', ' > a.test.js > passing', ''],
        ['⏩', ' > a.test.js > skip', ''],
      ]);
    

    where you represent the table as an array. Since this was part of simply writing an assertion for a specific scenario, it does not concern itself with all the intricacies of tables (e.g. multi-spanning cells, partially matching some rows, etc.).

  6. ruifigueira commented on Nov 13, 2024

    @ruifigueira
    Contributor

    This is a very common use case of mine. I ended up creating a custom selector for it but I also think playwright should support this natively.

    For instance, I have a custom selector by-header-cell that retrieves all data cells that correspond to a given header cell. I can then intercept cells by row and column:

    const aptCells = page.locator('by-header-cell=Apt');
    const twoBedroomCells = page.locator('by-header-cell=2 bedroom');
    const availabilityCell = aptCells.and(twoBedroomCells);
    await expect(availabilityCell).toHaveText('43');
    Studio Apt Chalet Villa
    1 bedroom11202523
    2 bedroom-435232
    3 bedroom-131540

    Some references regarding data cells / header cells relationship:

  7. qwertysk commented on Nov 20, 2024

    @qwertysk

    Rui Figueira (@ruifigueira) very interesting, pls can you share your code with custom selector ?

  8. ruifigueira commented on Nov 20, 2024

    @ruifigueira
    Contributor

    Sure, here's a basic selector and example test:

    import { test as base, expect } from '@playwright/test';
    
    const byHeaderCellSelector = () => ({
      queryAll: (root: Document | Element | ShadowRoot, text: string) => {
        return Array.from(root.querySelectorAll('th')).filter(e => e.textContent === text).flatMap(header => {
          if (header.scope === 'row')
            return Array.from(header.parentElement!.querySelectorAll('td'));
          return Array.from(root.querySelectorAll('td')).filter(e => e.cellIndex === header.cellIndex);
        });
      },
    });
    
    const test = base.extend<{}, { selectorRegistration: void }>({
      selectorRegistration: [async ({ playwright }, use) => {
        await playwright.selectors.register('by-header-cell', byHeaderCellSelector);
        await use();
      }, { scope: 'worker', auto: true }],
    });
    
    test('check availability', async ({ page }) => {
      // https://www.w3.org/WAI/tutorials/tables/caption-summary/#using-aria-describedby-to-provide-a-table-summary
      await page.setContent(`
        <table>
          <thead>
            <tr>
              <td></td>
              <th scope="col">Studio</th>
              <th scope="col"><abbr title="Apartment">Apt<abbr></th>
              <th scope="col">Chalet</th>
              <th scope="col">Villa</th>
            </tr>
          </thead>
          <tbody>
            <tr><th scope="row">1 bedroom</th><td>11</td><td>20</td><td>25</td><td>23</td></tr>
            <tr><th scope="row">2 bedroom</th><td>-</td><td>43</td><td>52</td><td>32</td></tr>
            <tr><th scope="row">3 bedroom</th><td>-</td><td>13</td><td>15</td><td>40</td></tr>
          </tbody>
        </table>
      `);
      const aptCells = page.locator('by-header-cell=Apt');
      const twoBedroomCells = page.locator('by-header-cell=2 bedroom');
      const availabilityCell = aptCells.and(twoBedroomCells);
      await expect(availabilityCell).toHaveText('43');
    });

    NOTE:
    This, as I said, is a basic selector, and doesn't handle more advanced cases like colspan, rowspan, multiple column/row headers, etc.

  9. vethman commented on Jun 2, 2025

    @vethman

    I have made the following package which really helps me testing HTML-tables. Have a look please:

    https://www.npmjs.com/package/@cerios/playwright-table

  10. andyearnshaw commented on Apr 17, 2026

    @andyearnshaw

    Here's my suggestion, originally posted in #40248.

    New getByRole() options

    Option Type Description
    rowIndex number Targets elements based on row index, either inferred or assigned with aria-rowindex. Applies to table and grid roles.
    columnIndex number Targets elements based on column index, either inferred or assigned with aria-colindex. Applies to table and grid roles.
    rowLabel string | RegExp | string[] | RegExp[] Targets elements based on the accessible name(s) of one or more row headers present in the same row. Applies to table and grid roles.
    columnLabel string | RegExp | string[] | RegExp[] Targets elements based on the accessible name(s) of one or more column headers present in the same row. Applies to table and grid roles.

    Example

    test("I can change a cell value", ({ page }) => {
      const cell = page
        .getByRole("table", { name: "My Table" })
        .getByRole("cell", { rowLabel: "Gross Profit", columnLabel: "April 2026" });
    
      expect(cell).toContainText("£ 0");
      cell.pressSequentially("123")
      cell.press("Enter");
      expect(cell).toContainText("£ 123");
    });
    
    test("My table is sorted alphabetically", ({ page }) => {
      const table = page
        .getByRole("table", { name: "My Table" });
    
      const order = {
        1: "Apples",
        2: "Bananas",
        3: "Cherries"
      };
    
      for (const [index, text] of Object.entries(order)) {
        expect(
          table.getByRole("rowheader", { rowIndex: index + 1 })
        ).toHaveTextContent(text);
      }
    });
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions