Repository navigation
[Feature]table selector #4646
Description
Activity
table selector engineer.
+1, really need this to make useful test scripts.
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.
Reacted by Matt Duffield, rough, mekxtari, Alexander Eneroth, Kevin Howard, riawiththesam, Jessica Christin Piontke and Maximilian TeegenI 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 columnAssigneewhere the text of columnTask="Bootstrap 3". It is similar what SQL does.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.).
ruifigueira commented
on Nov 13, 2024 ContributorMore actionsThis 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-cellthat 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 bedroom 11 20 25 23 2 bedroom - 43 52 32 3 bedroom - 13 15 40 Some references regarding data cells / header cells relationship:
- 4.9.12.2 Forming relationships between data cells and header cells
- chromevox apparently implements this algorithm (check kiwibrowser fork for instance)
- ACT Rule (proposal): Table header cell has assigned cells
Reacted by Stefan Kerti and terradek- 4.9.12.2 Forming relationships between data cells and header cells
Rui Figueira (@ruifigueira) very interesting, pls can you share your code with custom selector ?
ruifigueira commented
on Nov 20, 2024 ContributorMore actionsSure, 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 likecolspan,rowspan, multiple column/row headers, etc.Reacted by Maximilian TeegenI have made the following package which really helps me testing HTML-tables. Have a look please:
Here's my suggestion, originally posted in #40248.
New
getByRole()optionsOption Type Description rowIndexnumber Targets elements based on row index, either inferred or assigned with aria-rowindex. Applies totableandgridroles.columnIndexnumber Targets elements based on column index, either inferred or assigned with aria-colindex. Applies totableandgridroles.rowLabelstring | RegExp | string[] | RegExp[] Targets elements based on the accessible name(s) of one or more row headers present in the same row. Applies to tableandgridroles.columnLabelstring | RegExp | string[] | RegExp[] Targets elements based on the accessible name(s) of one or more column headers present in the same row. Applies to tableandgridroles.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); } });
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