Config Options
All options passed to useTable(locator, config). Every option is optional unless noted.
import { useTable } from '@rickcedwhat/playwright-smart-table';
const table = await useTable(page.locator('#my-table'), {
rowSelector: 'tbody tr',
maxPages: 20,
strategies: { ... },
}).init();→ API: Table Methods · API: Strategies
Selectors
All three selectors share one type:
type TableSelector = string | ((parent: Locator) => Locator)A string is resolved relative to the parent; a function receives the parent Locator — the table root for headers and rows, the row for cells.
headerSelector
Signature
headerSelector?: TableSelectorSelector for the column header elements, relative to the table root. Accepts a CSS string or a function that receives the root Locator. Default: 'thead th'.
// CSS string
headerSelector: 'thead th'
// Locator function
headerSelector: (root) => root.locator('[role="columnheader"]')
// Headers rendered as a separate table outside the root
headerSelector: (root) => root.page().locator('#header-table th')String-only features
The dataAttribute viewport strategy reads headers in the browser and needs a CSS string for scrollToColumn. Given a function, it logs a one-time warning and skips the horizontal scroll. The MUI DataGrid preset falls back to .MuiDataGrid-columnHeader.
rowSelector
Signature
rowSelector?: TableSelectorSelector for the table rows, relative to the table root. Accepts a CSS string or a function that receives the root Locator. Default: 'tbody tr'.
rowSelector: 'tbody tr'
rowSelector: '[role="row"].data-row'
// Locator function — e.g. skip group-header rows
rowSelector: (root) => root.locator('tbody tr').filter({ hasNot: root.page().locator('td[colspan]') })String-only features
The dataAttribute viewport strategy measures rows in the browser and needs a CSS string. Given a function, it logs a one-time warning, treats every mounted row as possibly visible (no overscan filtering, no row-range shortcut), and skips scrollToRow. The MUI DataGrid preset falls back to [data-rowindex] for scrolling and ranges, and only loses overscan filtering.
cellSelector
Signature
cellSelector?: TableSelectorSelector for cells within a row. Accepts a CSS string or a function that receives the row Locator. Default: 'td'.
// CSS string
cellSelector: 'td'
// Locator function
cellSelector: (row) => row.locator('[role="gridcell"]')Behavior
maxPages
Signature
maxPages?: numberMaximum number of pages to scan during iteration and search operations. Prevents runaway pagination on unexpectedly large tables.
Defaults to 1. If you configure a pagination strategy, set this explicitly or iteration and search will never leave page 1 — init() logs a warning when it sees a pagination strategy with maxPages: 1.
maxPages: 50concurrency
Signature
concurrency?: RowIterationModeDefault concurrency mode for forEach, map, filter, and toArray. Can be overridden per-call.
'sequential'— one row at a time, in order. Default for all iteration methods (since v6.22.0).'parallel'— all rows on the current page run concurrently. Faster for read-only callbacks.'synchronized'— rows run in parallel but page navigation waits for all callbacks to finish.
concurrency: 'parallel'autoScroll
Signature
autoScroll?: booleanWhen true, PST scrolls the table locator into view during init(). Useful when tables render below the fold.
autoScroll: trueHooks
headerTransformer
Signature
headerTransformer?: (args: { text: string, index: number, locator: Locator, seenHeaders: Set<string> }) => string | Promise<string>Rename or normalize column headers at init time. Runs once per header cell during init().
headerTransformer: ({ text, index }) => text.trim() || `Column ${index}`// Rename a known system column
headerTransformer: ({ text }) =>
text.includes('__checkbox__') ? 'Select' : text.trim()onReset
Signature
onReset?: (context: TableContext) => Promise<void>Called after table.reset(). Use to navigate back to the first page, clear filters, or perform any teardown the table requires.
onReset: async ({ root, page }) => {
await page.locator('.pagination-first').click();
}Advanced
columnOverrides
Signature
columnOverrides?: Partial<Record<keyof T, ColumnOverride<T[keyof T]>>>Per-column read and write overrides. Use read to customize how cell text is extracted; use write to customize how values are filled.
columnOverrides: {
Status: {
read: async (cell) => cell.locator('.badge').innerText(),
write: async ({ cell, targetValue }) => {
await cell.locator('select').selectOption(targetValue);
},
},
}→ Guide: Column Overrides · Guide: Fill Cells
syntheticColumns
Signature
syntheticColumns?: Record<string, SyntheticColumnDef<T>>Computed columns with no DOM presence. Each key becomes a virtual column whose value comes from compute(row).
Synthetic columns are available in toJSON(), row.getValue(), getHeaders(), and findRow / findRows / countRows filters. They are not available in:
getRow()filters — usefindRow()(synthetics need async evaluation)row.getCell()orsmartFill()— there's no cell to return or fill
compute may read real columns and columnOverrides, but not other synthetic columns.
syntheticColumns: {
Total: {
compute: async (row) => {
const price = Number(await row.getValue('Price'));
const qty = Number(await row.getValue('Qty'));
return price * qty;
},
},
}
// Computed values are stringified before matching
const row = await table.findRow({ Total: '40' }, { exact: true });emptyState
Signature
emptyState?: LocatorLocator for the element that replaces the table when there are no results. If header resolution fails during init(), a visible configured emptyState locator lets init() succeed and isEmpty() returns true; otherwise, init() throws the header resolution error. A table with resolvable headers and zero rows can initialize successfully without emptyState.
Row lookup operations still throw when isEmpty() is true — check it first.
const table = await useTable(page.locator('#orders'), {
emptyState: page.getByText('No orders found'),
}).init();
if (table.isEmpty()) return;debug
Signature
debug?: DebugConfigDevelopment aids: internal logging and slow-motion delays. Remove before committing — debug.slow warns when it detects CI.
logLevel—'none'(default) ·'error'·'info'·'verbose'slow— delay in ms for every operation, or per type:{ pagination, getCell, findRow, default }
debug: { logLevel: 'verbose' }
debug: { logLevel: 'info', slow: { pagination: 500 } }strategies
Signature
strategies?: TableStrategiesAll pluggable strategy overrides — pagination, loading detection, header discovery, sorting, viewport navigation, and more.
strategies: {
pagination: PaginationStrategies.infiniteScroll({ ... }),
loading: { isCellLoading: async (cell) => ... },
}