playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[Feature]table selector

Open roughsoft opened this issue 5 years ago • 8 comments

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

roughsoft avatar Dec 09 '20 03:12 roughsoft

table selector engineer.

roughsoft avatar Dec 09 '20 03:12 roughsoft

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

buhichan avatar Dec 22 '20 05:12 buhichan

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.

mxschmitt avatar Dec 22 '20 13:12 mxschmitt

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.

ygsf avatar Jun 07 '21 00:06 ygsf

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.).

rwoll avatar Jun 10 '22 00:06 rwoll

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:

ruifigueira avatar Nov 13 '24 10:11 ruifigueira

@ruifigueira very interesting, pls can you share your code with custom selector ?

qwertysk avatar Nov 20 '24 20:11 qwertysk

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.

ruifigueira avatar Nov 20 '24 21:11 ruifigueira

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

vethman avatar Jun 02 '25 21:06 vethman