npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rickcedwhat/playwright-smart-table

v6.7.5

Published

Smart, column-aware table interactions for Playwright

Readme

Playwright Smart Table

Production-ready table testing for Playwright with smart column-aware locators.

npm version License: MIT


📚 Full Documentation →

Visit the complete documentation at: https://rickcedwhat.github.io/playwright-smart-table/


Why Playwright Smart Table?

Testing HTML tables in Playwright is painful. Traditional approaches are fragile and hard to maintain.

The Problem

Traditional approach:

// ❌ Fragile - breaks if columns reorder
const email = await page.locator('tbody tr').nth(2).locator('td').nth(3).textContent();

// ❌ Brittle XPath
const row = page.locator('//tr[td[contains(text(), "John")]]');

// ❌ Manual column mapping
const headers = await page.locator('thead th').allTextContents();
const emailIndex = headers.indexOf('Email');
const email = await row.locator('td').nth(emailIndex).textContent();

The Solution

Playwright Smart Table:

// ✅ Column-aware - survives column reordering
const row = await table.findRow({ Name: 'John Doe' });
const email = await row.getCell('Email').textContent();

// ✅ Auto-pagination
const allEngineers = await table.findRows({ Department: 'Engineering' });

// ✅ Type-safe
type Employee = { Name: string; Email: string; Department: string };
const table = useTable<Employee>(page.locator('#table'));

Quick Start

Installation

npm install @rickcedwhat/playwright-smart-table

Basic Usage

import { useTable } from '@rickcedwhat/playwright-smart-table';

const table = await useTable(page.locator('#my-table')).init();

// Find row by column values
const row = await table.findRow({ Name: 'John Doe' });

// Access cells by column name
const email = await row.getCell('Email').textContent();

// Search across paginated tables
const allActive = await table.findRows({ Status: 'Active' });

Iterating Across Pages

// forEach — sequential, safe for interactions (parallel: false default)
await table.forEach(async ({ row, rowIndex, stop }) => {
  if (await row.getCell('Status').innerText() === 'Done') stop();
  await row.getCell('Checkbox').click();
});

// map — parallel within page, safe for reads (parallel: true default)
const emails = await table.map(({ row }) => row.getCell('Email').innerText());

// filter — async predicate across all pages, returns SmartRowArray
const active = await table.filter(async ({ row }) =>
  await row.getCell('Status').innerText() === 'Active'
);

// for await...of — low-level page-by-page iteration
for await (const { row, rowIndex } of table) {
  console.log(rowIndex, await row.getCell('Name').innerText());
}

When your pagination strategy supports bulk jumps (goNextBulk), pass { useBulkPagination: true } to map/forEach/filter to advance by multiple pages at once.

map + UI interactions: map defaults to parallel: true. If your callback opens popovers, fills inputs, or otherwise mutates UI state, pass { parallel: false } to avoid overlapping interactions.

filter vs findRows

| Use case | Best tool | |---|---| | Match by column value / regex / locator | findRows | | Computed value (math, range, derived) | filter | | Cross-column OR logic | filter | | Multi-step interaction in predicate (click, read, close) | filter | | Early exit after N matches | filter + stop() |

findRows is faster for column-value matches — Playwright evaluates the locator natively with no DOM reads. filter is more flexible for logic that a CSS selector can't express.

// findRows — structural match, no DOM reads, fast
const notStarted = await table.findRows({
  Status: (cell) => cell.locator('[class*="gray"]')
});

// filter — arbitrary async logic
const expensive = await table.filter(async ({ row }) => {
  const price = parseFloat(await row.getCell('Price').innerText());
  const qty = parseFloat(await row.getCell('Qty').innerText());
  return price * qty > 1000;
});

Advanced: columnOverrides

For complex DOM structures, custom data extraction, or specialized input widgets, use columnOverrides to intercept how Smart Table interacts with specific columns:

const table = useTable(page.locator('#table'), {
  columnOverrides: {
    // Override how data is read from the 'Status' column (e.g., for .toJSON())
    Status: {
      read: async (cell) => {
        const isChecked = await cell.locator('input[type="checkbox"]').isChecked();
        return isChecked ? 'Active' : 'Inactive';
      }
    },
    // Override how data is written to the 'Tags' column (for .smartFill())
    Tags: {
      write: async (cell, value) => {
        await cell.click();
        await page.keyboard.type(value);
        await page.keyboard.press('Enter');
      }
    }
  }
});

Key Features

  • 🎯 Smart Locators - Find rows by content, not position
  • 🧠 Fuzzy Matching - Smart suggestions for typos in column names
  • Smart Initialization - Handles loading states and dynamic headers automatically
  • 📄 Auto-Pagination - Search across all pages automatically
  • 🔍 Column-Aware Access - Access cells by column name
  • 🔁 Iteration Methods - forEach, map, filter, and for await...of across all pages
  • 🛠️ Debug Mode - Visual debugging with slow motion and logging
  • 🔌 Extensible Strategies - Support any table implementation
  • 💪 Type-Safe - Full TypeScript support
  • 🚀 Production-Ready - Battle-tested in real-world applications

When to Use This Library

Use this library when you need to:

  • ✅ Find rows by column values
  • ✅ Access cells by column name
  • ✅ Search across paginated tables
  • ✅ Handle column reordering
  • ✅ Extract structured data
  • ✅ Fill/edit table cells
  • ✅ Work with dynamic tables (MUI DataGrid, AG Grid, etc.)

You might not need this library if:

  • ❌ You don't interact with tables at all
  • ❌ You don't need to find a row based on a value in a cell
  • ❌ You don't need to find a cell based on a value in another cell in the same row

⚠️ Important Note on Pagination & Interactions

When findRows or filter paginates across pages, returned SmartRow locators point to rows that may be off the current DOM page.

  • Data extraction: Safe — toJSON() and cell reads work while the row is visible during iteration.
  • Interactions after pagination: Use await row.bringIntoView() first — it navigates back to the page the row was originally found on, then you can safely click/fill.
const active = await table.filter(async ({ row }) =>
  await row.getCell('Status').innerText() === 'Active'
);

for (const row of active) {
  await row.bringIntoView(); // navigate back to the row's page
  await row.getCell('Checkbox').click(); // safe to interact
}

Documentation

📚 Full documentation available at: https://rickcedwhat.github.io/playwright-smart-table/

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Cedrick Catalan

Links