Find a row by its text, read a cell, add up a column, sort, page through results and click buttons inside a specific row.
Every solution on this page starts with this hook. With baseURL set to https://hiraanachwani.com in your playwright.config.ts, it opens this page before each test.
A plain table of employees. Nothing moves; it's all about locating the right cell.
Employees
Name
Department
City
Salary
Priya Sharma
QA
Pune
$85,000
James Carter
Development
London
$98,000
Ravi Patel
QA
Bengaluru
$72,000
Emma Schmidt
DevOps
Berlin
$91,000
Kenji Sato
Development
Tokyo
$105,000
Olivia Brown
QA
Toronto
$78,000
Try this
Check the table has 6 employees.
Find Ravi Patel's city without hard-coding his row number.
List everyone in the QA department.
Add up the Salary column and check the total is $529,000.
Locator tip:getByRole('row', { name: /Ravi Patel/ }) finds a row by any text in it, so the test still works when rows are added or reordered. Then getByRole('cell').nth(2) picks the third cell in that row.
Show Playwright solution
test('static table', async ({ page }) => {
const table = page.getByRole('table', { name: 'Employees' });
const rows = table.locator('tbody').getByRole('row');
await expect(rows).toHaveCount(6);
// Find a row by its text, then a cell by position within that row
const ravi = table.getByRole('row', { name: /Ravi Patel/ });
await expect(ravi.getByRole('cell').nth(2)).toHaveText('Bengaluru');
// Keep only the rows that have a cell reading exactly "QA"
const qaRows = rows.filter({ has: page.getByRole('cell', { name: 'QA', exact: true }) });
await expect(qaRows.locator('td:first-child')).toHaveText(['Priya Sharma', 'Ravi Patel', 'Olivia Brown']);
// Read a whole column and add it up
const salaries = await rows.locator('td:nth-child(4)').allTextContents();
const total = salaries
.map((salary) => Number(salary.replace(/[^0-9]/g, '')))
.reduce((sum, salary) => sum + salary, 0);
expect(total).toBe(529000);
});
Sortable table
Click a column heading to sort by it. Click again to reverse the order.
Leaderboard
Zara Ali
Pakistan
88
Ben Okafor
Nigeria
95
Chloe Martin
France
72
Arjun Nair
India
91
Lucas Silva
Brazil
64
Try this
Sort by Score and check the scores go from lowest to highest.
Click Score again and check the top player is Ben Okafor.
Check the Score heading reports its sort direction.
Locator tip: read a whole column with allTextContents(), then compare it with a sorted copy in plain TypeScript. Sortable headings set aria-sort to ascending or descending, which you can assert directly.
23 orders, shown 5 at a time. The order you want may be on any page.
Orders
Order
Customer
Status
Amount
ORD-1001
Aarav Mehta
Delivered
$25.00
ORD-1002
Fatima Khan
Cancelled
$62.13
ORD-1003
Noah Williams
Processing
$99.26
ORD-1004
Sophie Turner
Shipped
$136.39
ORD-1005
Diego Alvarez
Delivered
$173.52
ORD-1006
Ananya Iyer
Delivered
$30.65
ORD-1007
Liam Chen
Processing
$67.78
ORD-1008
Mei Tanaka
Shipped
$104.91
ORD-1009
Aarav Mehta
Delivered
$141.04
ORD-1010
Fatima Khan
Cancelled
$178.17
ORD-1011
Noah Williams
Delivered
$35.30
ORD-1012
Sophie Turner
Shipped
$72.43
ORD-1013
Diego Alvarez
Delivered
$109.56
ORD-1014
Ananya Iyer
Cancelled
$146.69
ORD-1015
Liam Chen
Processing
$183.82
ORD-1016
Mei Tanaka
Delivered
$40.95
ORD-1017
Aarav Mehta
Delivered
$77.08
ORD-1018
Fatima Khan
Cancelled
$114.21
ORD-1019
Noah Williams
Processing
$151.34
ORD-1020
Sophie Turner
Shipped
$188.47
ORD-1021
Diego Alvarez
Delivered
$45.60
ORD-1022
Ananya Iyer
Cancelled
$82.73
ORD-1023
Liam Chen
Processing
$119.86
Try this
Check Previous is disabled on page 1.
Page through until order ORD-1017 is visible, then read its customer.
Go to the last page and check it has 3 orders and Next is disabled.
Locator tip: rows on other pages are hidden, and role locators skip hidden elements. So "click Next until the row is visible" is a simple loop. Give it a limit so a missing row can't loop forever.
Show Playwright solution
test('paginated table', async ({ page }) => {
const table = page.getByRole('table', { name: 'Orders' });
const previous = page.getByRole('button', { name: 'Previous' });
const next = page.getByRole('button', { name: 'Next' });
await expect(previous).toBeDisabled();
// Click Next until the order shows up, at most 10 times
const order = table.getByRole('row', { name: /ORD-1017/ });
for (let i = 0; i < 10 && !(await order.isVisible()); i++) {
await next.click();
}
await expect(order.getByRole('cell').nth(1)).toHaveText('Aarav Mehta');
await expect(page.getByTestId('page-status')).toHaveText('Page 4 of 5');
await next.click();
await expect(next).toBeDisabled();
await expect(table.locator('tbody').getByRole('row')).toHaveCount(3);
});
Row actions
Every row has the same two buttons. The challenge is clicking the one in the right row.
Tasks
Task
Status
Actions
Write test plan
To do
Fix login bug
In progress
Review pull request
To do
Update release notes
Done
Try this
Mark Write test plan as done and check its status changes to Done.
Delete Fix login bug and check the table has 3 tasks left.
Check the summary says 2 of 3 tasks done.
Locator tip: there are four Delete buttons, so getByRole('button', { name: 'Delete' }) alone would fail. Find the row first, then the button inside it: row.getByRole('button', { name: 'Delete' }).