Preset Development Guide 🧩
playwright-smart-table uses a lean core: essential strategies for standard tables, with custom presets for complex grids (AG-Grid, Glide, React Data Grid, MUI Data Grid, or custom virtual lists).
This guide shows you how to build robust, reusable strategies.
Adding a library preset to the core repo? Put it in src/presets/ (either as a single file <name>.ts or a directory <name>/ with index.ts as the entry point). Add helper modules in that same directory if needed (e.g. glide/columns.ts). Register the preset in src/presets/index.ts. Follow PRESET_TEMPLATE.md for the standard structure.
Preset file layout (in-repo):
src/presets/<name>.ts(orsrc/presets/<name>/index.ts) — preset and strategies; exports aTableConfigobject.src/presets/index.ts— imports each preset and exposes them for the public API.
Core Concepts
All strategies receive a TableContext object:
export interface TableContext {
root: Locator; // The root element of the table
config: FinalTableConfig;// The full table configuration
page: Page; // The Playwright Page object
resolve: (selector: Selector, parent: Locator | Page) => Locator; // Helper to resolve selectors
}1. Pagination Strategies
A pagination strategy is responsible for moving to the next page of data.
Signature:
type PaginationStrategy = (context: TableContext) => Promise<boolean>;Return Value:
true: Pagination succeeded (moved to next page).false: End of data reached (no next page).
Example: Clicking a specific "Load More" button
import type { PaginationStrategy } from '@rickcedwhat/playwright-smart-table';
export const clickLoadMore = (buttonSelector: string): PaginationStrategy => {
return async ({ root, page }) => {
const btn = root.locator(buttonSelector);
// Check if button exists and is enabled
if (await btn.count() === 0 || !(await btn.isEnabled())) {
return false; // No more pages
}
// Capture state before click (e.g., number of rows)
const rowCountBefore = await root.locator('tbody tr').count();
await btn.click();
// Wait for effect (e.g., row count increase)
await page.waitForFunction(
(count) => document.querySelectorAll('tbody tr').length > count,
rowCountBefore
);
return true; // Successfully loaded more
};
};2. Fill Strategies
A fill strategy defines how smartFill interacts with input fields.
Signature:
type FillStrategy = (args: {
row: SmartRow;
data: Record<string, any>;
options?: FillOptions
}) => Promise<void>;Example: Custom Date Picker Interaction
If your table uses a complex date picker that smartFill doesn't handle natively:
import type { FillStrategy } from '@rickcedwhat/playwright-smart-table';
export const customFill: FillStrategy = async ({ row, data, options }) => {
for (const [column, value] of Object.entries(data)) {
const cell = row.getCell(column);
// Custom handling for 'Date' column
if (column === 'Date') {
await cell.click(); // Open picker
await row.page().locator('.date-picker-day', { hasText: String(value) }).click();
continue; // Skip default handling
}
// Fallback to default logic for other columns?
const input = cell.locator('input');
await input.fill(String(value));
}
};3. Header Strategies
Strategies to find and parse column headers.
Signature:
type HeaderStrategy = (context: TableContext) => Promise<string[]>;Example: Parsing complex usage of aria-label
export const ariaHeaderStrategy: HeaderStrategy = async ({ root, config, resolve }) => {
const headers = await resolve(config.headerSelector, root).all();
return Promise.all(headers.map(h => h.getAttribute('aria-label') || h.innerText()));
};Best Practices
- Use
resolve: Always use theresolvehelper from context instead ofpage.locator()when finding elements inside the table. It handles theSelectortype (string vs function) correctly. - Wait for Stability: Pagination strategies should wait for the table to stabilize (Spinner to disappear, row count to change) before returning
true. - Return False: Ensure your pagination strategy clearly returns
falsewhen it's logically impossible to go further (button disabled, end of list). - Type Safety: Import types from
@rickcedwhat/playwright-smart-tableto ensure your presets comply with the interface.
Sharing Presets
If you build a strategy for a popular library (like Material UI v6, TanStack Table v9), consider contributing it back to the core library or publishing it as a separate package!