VALIDATION LAB•DYNAMIC TABLE
Tables
Dynamic Table
Search generated users, sort by name, filter active users, paginate, validate row counts, and click edit/delete for a specific row.
Scenario
Real admin tables combine query state, sorting, filters, pagination, and row actions. Automation must scope locators carefully or it clicks the wrong repeated button.
| Role | Status | Actions | ||
|---|---|---|---|---|
| Hasan Akdoğan | hasan.qa@hakdogan.com | QA Lead | Active | |
| Mira Stone | mira.admin@hakdogan.com | Admin | Active | |
| Alex Reed | alex.engineer@hakdogan.com | Engineer | Invited | |
| Noor Khan | noor.qa@hakdogan.com | QA | Active |
No row selected.
Live widget · interact freely
Manual test checklist
- 1Search for Hasan and confirm only matching users remain
- 2Sort by Name and confirm alphabetical order
- 3Filter Status to Active and confirm no suspended rows remain
- 4Use pagination and confirm page count updates
- 5Click Edit and Delete for a specific row and confirm the action result
Expected result
Search, status filter, sorting, pagination, and row actions compose without losing the selected row or count accuracy.
Automation challenge
Search `Hasan`, sort by name, filter `Active`, assert visible row count, then find the row by text and click its edit/delete button without relying on row index.
Stable selectors
- Search users
[data-testid="dynamic-table-search"] - Status filter
[data-testid="dynamic-status-filter"] - Sort name
[data-testid="dynamic-sort-name"] - Dynamic users table
[data-testid="dynamic-users-table"] - Row count
[data-testid="dynamic-row-count"] - Hasan row
[data-testid="dynamic-row-usr-104"] - Hasan edit
[data-testid="dynamic-edit-usr-104"] - Hasan delete
[data-testid="dynamic-delete-usr-104"] - Action result
[data-testid="dynamic-action-result"]
Locator strategy
Three levels from simple IDs to scoped Playwright locators. IDs and names are easy to learn but are not always the best long-term choice when labels change or components repeat.
Use simple IDs and names to understand how locating elements works.
123
await page.goto('https://lab.hakdogan.com/practice/dynamic-table');
await page.locator('#dynamic-table-search').fill('Hasan');Avoid as primary strategies
XPath (unless there is no alternative), long CSS chains, Tailwind-style utility class selectors, generated or unstable IDs, and volatile framework internals break when layout, styling, or DOM structure shifts.
12
await page.locator('.w-full.rounded-xl.border.bg-blue-500').fill('demo@example.com');
await page.locator('//div[2]/form/div[1]/input').fill('demo@example.com');Reference Playwright spec
123456789101112131415161718192021
import { test, expect } from '@playwright/test';
test('dynamic table search sort filter and row actions', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/dynamic-table');
await page.getByTestId('dynamic-table-search').fill('Hasan');
await page.getByTestId('dynamic-sort-name').click();
await page.getByTestId('dynamic-status-filter').selectOption('Active');
await expect(page.getByTestId('dynamic-row-count')).toContainText('1 match');
const row = page
.getByTestId('dynamic-users-table')
.getByRole('row')
.filter({ hasText: 'Hasan Akdoğan' });
await row.getByRole('button', { name: /edit/i }).click();
await expect(page.getByTestId('dynamic-action-result')).toContainText(
'Editing Hasan Akdoğan',
);
});Headed Test Playback
Simulated headed-browser flow — no real browser is launched.
Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.
Mixed: fill() for stable fields, type() / press() where keyboard semantics matter.
dynamic-table.spec.ts- Open searchable "Users" grid
- Search slice — "Hasan"
- Sort ascending by name column
- Filter to active memberships
- Assert hydrated row quantity
- Pass