Originally published at anton.qa.
Playwright 1.64 adds within(), a shortcut for selecting inside each chosen row.
A locator (finds page elements) describes which cells or rows to use.
Here, the task is checking one item name from every stock row.
Use nth(2) to choose the third matching cell. Counting starts at zero.
With within(rows), Playwright makes that choice separately inside each row.
That helps when your check should include every displayed row.
A table-wide nth(2) checks just one cell.
Run the complete example
The public example contains the table, tests, and setup instructions.
All data is synthetic, meaning made-up sample data.
The tests load local HTML, the page's structure, without application network requests.
Use Node.js 24, which runs JavaScript locally, and npm, which installs project packages.
The project pins Playwright to 1.64.1.
git clone https://github.com/antongulin/anton-qa-resources.git
cd anton-qa-resources/posts/playwright-within-table-cells
npm ci
npx playwright install chromium
npm run verify
On Linux, missing browser system packages may require this installation command:
npx playwright install --with-deps chromium
Installation creates a local package folder and downloads the Chromium browser.
The runner may also create local test-result files.
Start with three rows
The third cell contains the item name:
| SKU | Bin | Item |
|---|---|---|
| T-101 | North | Apricot |
| T-202 | West | Blueberry |
| T-303 | East | Coconut |
SKU means product code. Bin names the stock location.
This is the complete table.html fixture, the example's page:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Synthetic stock table</title>
<table>
<caption>Synthetic stock</caption>
<thead>
<tr><th>SKU</th><th>Bin</th><th>Item</th></tr>
</thead>
<tbody>
<tr><td>T-101</td><td>North</td><td>Apricot</td></tr>
<tr><td>T-202</td><td>West</td><td>Blueberry</td></tr>
<tr><td>T-303</td><td>East</td><td>Coconut</td></tr>
</tbody>
</table>
</html>
Make the choice inside each row
The table's body is tbody. Selecting its rows leaves out the header row.
getByRole('cell') finds ordinary table cells.
This is the first complete test from tests/table.spec.js:
import { readFileSync } from 'node:fs';
import { test, expect } from '@playwright/test';
test('P1: select the third cell within each row, compared with global nth', async ({ page }) => {
await page.setContent(readFileSync(new URL('../table.html', import.meta.url), 'utf8'));
const rows = page.locator('tbody').getByRole('row');
const cells = page.getByRole('cell');
const thirdInEachRow = cells.nth(2).within(rows);
const thirdInTable = cells.nth(2);
await expect(rows).toHaveCount(3);
await expect(thirdInEachRow).toHaveCount(3);
await expect(thirdInEachRow).toHaveText(['Apricot', 'Blueberry', 'Coconut']);
await expect(thirdInTable).toHaveCount(1);
await expect(thirdInTable).toHaveText('Apricot');
console.log(`within each row: ${JSON.stringify(await thirdInEachRow.allTextContents())}`);
console.log(`global nth(2): ${JSON.stringify(await thirdInTable.allTextContents())}`);
});
cells.nth(2).within(rows) selects Apricot, Blueberry, and Coconut.
The test checks both the number of selected cells and their text.
It compares the same table with cells.nth(2), which selects only Apricot.
Both choices work. They answer different questions about the page.
Use the per-row choice when every row belongs in your check.
Read the actual result
The verified run used Node.js 24.21.0, Playwright 1.64.1, and Chromium.
Both tests passed, with these printed values:
within each row: ["Apricot","Blueberry","Coconut"]
global nth(2): ["Apricot"]
varied rows: ["Coconut","Apricot","Coconut"]
The second test changes row order and repeats an item name.
Its rows contain five, three, and four ordinary cells.
The result still follows each row's third cell, in document order.
The full test file includes that second case.
Keep the limits in view
These checks cover Chromium and rows with at least three ordinary cells.
Merged cells need a separate check. This example does not measure speed.
The number 2 still depends on the cell's position.
If the table changes, review whether the third cell still means the same thing.
Earlier Playwright versions could select columns through other patterns.
within() makes this per-row choice explicit. It does not always mean less code.
Try the complete example with your table's structure.
The official documentation explains the selection rule.
The 1.64 release notes introduce the shortcut.
I'm Anton. I build AI tools and measure how fast they really run. Former Apple engineer. I make ollamatps.com and write about building and measuring AI at anton.qa.
Top comments (0)