[Feature]table selector
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
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.
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.
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.).
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 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
@ruifigueira very interesting, pls can you share your code with custom selector ?
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.
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