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

@smartbit4all/playwright-qa

v0.1.11

Published

Playwright-based testing and documentation framework for smartbit4all projects

Readme

@smartbit4all/playwright-qa

Playwright-based testing and documentation framework. Provides screenshot utilities, typed datapools with locale support, and TestStep/TestSuite conventions.

Installation

npm install @smartbit4all/playwright-qa

Requires Node.js 20+ and Playwright as a peer dependency:

npm install @playwright/test
npx playwright install --with-deps chromium

Quick Start

import { test } from '@playwright/test';
import { initSuite, screenshot } from '@smartbit4all/playwright-qa';

test.beforeAll(() => {
  initSuite({ screenshotDir: './screenshots' });
});

test('take a screenshot', async ({ page }) => {
  await page.goto('https://demo.playwright.dev/todomvc');
  await screenshot(page, 'todomvc-home');
});

Suite Setup

Every TestSuite should call initSuite() in beforeAll. This single call resets all internal state (config, datapools, screenshot directory, seed handlers) and optionally sets up the screenshot directory.

import { initSuite } from '@smartbit4all/playwright-qa';

// With screenshots — each run creates a new timestamped subdirectory
initSuite({ screenshotDir: './screenshots' });

// Clean previous run — deletes the last run's folder before creating a new one
initSuite({ screenshotDir: './screenshots', cleanScreenshots: true });

// Without screenshots — tests run normally, screenshot calls return null
initSuite();

By default, each run creates a new timestamped subdirectory and the latest symlink always points to the most recent run. Set cleanScreenshots: true to delete all previous runs before starting — useful when disk space is limited or only the latest run matters.

When no screenshotDir is provided, all screenshot functions (screenshot, screenshotHighlight, screenshotRegion, screenshotRegionHighlight) become no-ops and return null instead of a file path. This means TestSteps that include screenshot calls work without modification — no conditional logic needed.

When multiple suites run in the same test execution with the same screenshotDir, the directory is created once and reused — all screenshots land in a single timestamped folder. This works even if a suite fails mid-run: the next suite picks up the same directory and continues the autoPrefix counter from where it left off (e.g., if the failed suite ended at 0005-, the next suite starts at 0006-). The framework tracks this via a .current-run marker file in the screenshot base directory.

Viewport

Use getViewport() in your playwright.config.ts to automatically switch between full-size and headed viewport:

import { defineConfig } from '@playwright/test';
import { getViewport } from '@smartbit4all/playwright-qa';

export default defineConfig({
  use: {
    viewport: getViewport(),
  },
});

When running with --headed, the viewport automatically shrinks to fit on screen (default: 1280x720). In headless/CI mode, the full viewport is used (default: 1920x1080). Both sizes are configurable in playwright-qa.config.json:

{
  "viewport": { "width": 1920, "height": 1080 },
  "headedViewport": { "width": 1280, "height": 720 }
}

API Reference

Screenshot

import { screenshot, screenshotHighlight, screenshotRegion, screenshotRegionHighlight } from '@smartbit4all/playwright-qa';

// Full page screenshot — returns file path, or null if screenshots are disabled
await screenshot(page, 'page-name');
await screenshot(page, 'category/page-name');         // name path = subdirectory
await screenshot(page, 'page-name', { fullPage: true });

// Full page with highlighted element — accepts CSS selector or Locator
await screenshotHighlight(page, 'name', '#selector');
await screenshotHighlight(page, 'name', '#selector', { style: 'subtle' });
await screenshotHighlight(page, 'name', findMenuItem(page, 'Új mappa'));  // Locator

// Region screenshot (single element) — accepts CSS selector or Locator
await screenshotRegion(page, 'name', '#region-selector');

// Region with highlighted element inside — both accept CSS selector or Locator
await screenshotRegionHighlight(page, 'name', '#region', '#highlight');
await screenshotRegionHighlight(page, 'name', findPopupMenu(page), findMenuItem(page, 'Új mappa'));

All screenshot functions return Promise<string | null>. They return null when no screenshot directory has been configured (see Suite Setup). Highlight and region parameters accept both CSS selectors (string) and Playwright Locators.

Datapool

import { datapool, configureDatapool } from '@smartbit4all/playwright-qa';

// Global configuration (typically in beforeAll)
configureDatapool({ locale: 'hu', basePath: './datapools' });

// Load a datapool
const companies = datapool<Company>('companies');

// Access items
const item = companies.byKey('it4all');      // by key (meta.key field, default: "code")
const derived = companies.derive('it4all', { name: 'Modified' }); // template + overrides
const first = companies.findFirst('city', 'Budapest');            // first match by field
const matches = companies.findAll('role', 'admin');               // all matches by field
const random = companies.random();           // random item
const all = companies.all();                 // all items
const count = companies.count();             // count

Built-in Steps

The package ships reusable TestSteps for common smartbit4all UI patterns (grid, tree navigation). These are available via a separate subpath import to keep them distinct from the core API:

import { selectGridRow, navigateInTree } from '@smartbit4all/playwright-qa/steps';

Forms

import {
  fillField, fillDate, fillDateTime,
  selectOption, selectMultiple, selectRadio,
  setCheckbox, setCheckboxGroup, setToggle,
  addChip, removeChip, setChips,
  uploadFile,
} from '@smartbit4all/playwright-qa/steps';

// Fill a text input or textarea — by data-testid (preferred) or label text (fallback)
await fillField(dialog, 'data.name', 'Teszt mappa');               // data-testid
await fillField(page, 'dossierContentData.name', 'Teszt dosszié'); // data-testid
await fillField(page, 'Megjegyzés', 'Ez egy megjegyzés');           // label fallback

// Date picker (ISO format YYYY-MM-DD)
await fillDate(dialog, 'dataSheet.startDate', '2026-04-15');

// Date-time picker (date + time)
await fillDateTime(dialog, 'data.deadline', '2026-04-15', '14:30');

// Select from a dropdown (mat-select / p-dropdown) — by data-testid or label
await selectOption(page, 'selectedDocumentTypeCategory', 'Ügyintézés');  // data-testid
await selectOption(dialog, 'Kategória', 'Ügyintézés');                   // label fallback

// Multi-select dropdown
await selectMultiple(dialog, 'data.skills', ['Angular', 'React', 'Vue']);

// Select a radio button — by data-testid on the radio group or label
await selectRadio(dialog, 'data.canIncludeFiles', 'Nem');       // data-testid
await selectRadio(dialog, 'Tartalmazhat almappákat', 'Igen');   // label fallback

// Set a checkbox — by data-testid or label
await setCheckbox(dialog, 'Aktív', true);

// Checkbox group (CHECK_BOX_2) — set multiple checkboxes at once
await setCheckboxGroup(dialog, 'permissions', { 'Olvasás': true, 'Írás': true, 'Törlés': false });

// Toggle switch
await setToggle(dialog, 'data.isActive', true);

// Chips — add, remove, or set declaratively
await addChip(dialog, 'data.tags', 'urgent');
await removeChip(dialog, 'data.tags', 'draft');
await setChips(dialog, 'data.tags', ['urgent', 'review']);

// File upload
await uploadFile(dialog, 'data.attachment', '/path/to/file.pdf');

All form utilities accept a data-testid value or a visible label text as identifier. When a data-testid match is found on the element, it takes priority; otherwise the function falls back to label text matching (.smart-form-widget-label, h3, h4, or label). They work with both Angular Material and PrimeNG components, and accept any Locator or Page as container — use topDialog(page) to scope to a dialog.

Generic field setter

For data-driven scenarios, setField and setFields route to the right utility based on the value type and the widget's DOM signature — no need to know which form widget you are targeting.

import { setField, setFields } from '@smartbit4all/playwright-qa/steps';

// One field at a time
await setField(dialog, 'data.name', 'Teszt mappa');           // → fillField
await setField(dialog, 'data.canIncludeFiles', 'Igen');       // → selectRadio (widget has mat-radio-group)
await setField(dialog, 'data.category', 'Ügyintézés');         // → selectOption (widget has mat-select / p-dropdown)
await setField(dialog, 'data.isActive', true);                 // → setToggle / setCheckbox
await setField(dialog, 'data.skills', ['Angular', 'React']);   // → selectMultiple or setChips
await setField(dialog, 'data.deadline', '2026-04-15 14:30');   // → fillDateTime (matches YYYY-MM-DD HH:mm)
await setField(dialog, 'data.attachment', '/path/file.pdf');   // → uploadFile (widget has smart-file-editor)
await setField(dialog, 'permissions', { 'Olvasás': true, 'Írás': false }); // → setCheckboxGroup

// Many fields at once
await setFields(dialog, {
  'data.name': 'Teszt mappa',
  'data.canIncludeFiles': 'Nem',
  'data.isActive': true,
  'data.tags': ['urgent', 'review'],
  'permissions': { 'Olvasás': true, 'Írás': true },
});

Routing is decided by value's TypeScript type plus the DOM tags inside the widget:

  • Record<string, boolean>setCheckboxGroup
  • boolean + mat-slide-toggle / p-inputSwitchsetToggle
  • boolean + mat-checkbox / p-checkboxsetCheckbox
  • string[] + mat-chip-grid / p-chipssetChips
  • string[] + p-multiSelect / mat-select[multiple]selectMultiple
  • string + smart-file-editoruploadFile
  • string + mat-select / p-dropdownselectOption
  • string + mat-radio-group / p-radiobuttonselectRadio
  • string matching YYYY-MM-DD HH:mmfillDateTime
  • string + any input / textareafillField

If no branch matches, setField throws with the identifier in the message. Datetime detection is value-format based (a single YYYY-MM-DD HH:mm string), since the Material datetime widget has no distinctive tag — pass an ISO date+time string and let setField split it.

Grid

import {
  selectGridRow,
  checkGridRow,
  checkGridRowsOnPage,
  checkAllGridRows,
  filterAndSelectGridRow,
  applyGridFilters,
  clearGridFilters,
} from '@smartbit4all/playwright-qa/steps';

// Find a row by table content (paginates automatically) and double-click it
await selectGridRow(page, { 'Azonosító': 'DOC-001' });

// Find by simple text match
await selectGridRow(page, 'DOC-001');

// Find by row data-testid (rendered from row.id on the <tr>)
await selectGridRow(page, { rowId: 'doc-12345' });

// Open the row's context menu and click an action
await selectGridRow(page, { 'Azonosító': 'DOC-001' }, 'Szerkesztés');

// Open context menu only (for screenshots) — pass true, then close with Escape
await selectGridRow(page, { 'Azonosító': 'DOC-001' }, true);
await screenshot(page, 'grid-context-menu');
await page.keyboard.press('Escape');

// Multi-select grid: check/uncheck a single row (paginates if needed)
await checkGridRow(page, { 'Cím': 'Dokumentátor' }, true);
await checkGridRow(page, { 'Cím': 'Dokumentátor' }, false);

// Check/uncheck all matching rows on the current page (no pagination)
await checkGridRowsOnPage(page, 'Admin', true);

// Select all / deselect all via header checkbox
await checkAllGridRows(page, true);

// Use the filter form above the grid, then select the row
await filterAndSelectGridRow(page, { 'Azonosító': 'DOC-001' });

// Apply/clear filters independently
await applyGridFilters(page, { 'Név': 'Teszt' });
await clearGridFilters(page);

Column keys in a row filter accept either the header label or the header data-testid (rendered from col.propertyName on the <th>). Both resolve to the same column, so { 'Adószám': '12345' } and { 'taxNumber': '12345' } are equivalent — prefer data-testid for stability against label/locale changes.

For row-level addressing, { rowId: 'doc-12345' } matches the <tr> data-testid attribute (rendered from row.id). rowId must be the only key in the filter object and is supported by selectGridRow, checkGridRow, and checkGridRowsOnPage. filterAndSelectGridRow throws if given a rowId filter — use selectGridRow instead, since row identifiers do not need a filter form pass.

All grid functions accept Page or Locator as container — use a Locator to scope to a dialog or section:

const section = dialog.locator('smart-component-layout[data-testid="participants"]');
await checkGridRow(section, { 'Cím': 'Dokumentátor' }, true);

Navigation

import { navigateToMain, navigateInTree } from '@smartbit4all/playwright-qa/steps';

// Click the logo to return to the main screen
await navigateToMain(page);

// Navigate a tree (PrimeNG or Angular Material) — expands intermediate nodes, clicks the last one
await navigateInTree(page, ['Ügyek']);
await navigateInTree(page, ['Dokumentumok', 'Tender']);

// Open the last node's context menu (hamburger button) and click an action
await navigateInTree(page, ['Dokumentumok', 'Bejövő e-mail'], 'Új mappa');

// Open context menu only (for screenshots) — pass true, then close with Escape
await navigateInTree(page, ['Dokumentumok', 'Bejövő e-mail'], true);
await screenshot(page, 'context-menu-open');
await page.keyboard.press('Escape');

Dialog

import { topDialog, clickInDialog } from '@smartbit4all/playwright-qa/steps';

// Get the topmost open dialog (Angular Material or PrimeNG)
const dialog = topDialog(page);

// Click a button inside the topmost dialog — useful when multiple dialogs are stacked
await clickInDialog(page, 'Mentés');

// Example: dialog-in-dialog workflow
await clickInDialog(page, 'Akció beállítás');   // opens second dialog on top
await screenshot(page, 'second-dialog');
await clickInDialog(page, 'Mentés');             // clicks Mentés in the TOP dialog
// first dialog is still open
await clickInDialog(page, 'Bezárás');            // closes first dialog

When multiple dialogs are open, topDialog always targets the last (topmost) one. This prevents accidentally clicking buttons in a background dialog.

Utilities

import { waitForAngularIdle } from '@smartbit4all/playwright-qa/steps';

// Wait for Angular SPA to settle (double networkidle with pause)
await waitForAngularIdle(page);

waitForAngularIdle is also available for project-specific steps that need the same wait pattern.

Locators

import { findButton, findPopupMenu, findMenuItem } from '@smartbit4all/playwright-qa/steps';

// Find a button by data-testid (preferred) or visible text (fallback)
await findButton(page, 'REFRESH').click();           // matches data-testid="REFRESH"
await findButton(page, 'Mentés').click();             // matches button text "Mentés"

// Works inside a container (e.g., dialog)
await findButton(topDialog(page), 'Mentés').click();

// Find the currently visible popup menu (Angular Material or PrimeNG)
const menu = findPopupMenu(page);

// Find a menu item by data-testid or text
await findMenuItem(page, 'Szerkesztés').click();
await findMenuItem(page, 'DELETE_ACTION').click();    // matches data-testid="DELETE_ACTION"

// Combine with screenshot functions for highlighting
await screenshotHighlight(page, 'menu-highlight', findMenuItem(page, 'Új mappa'));
await screenshotRegionHighlight(page, 'menu-region',
  findPopupMenu(page), findMenuItem(page, 'Új mappa'));

All built-in steps (clickInDialog, selectGridRow, navigateInTree) use these locators internally, so they automatically support data-testid values alongside text matching.

Debug

import { dumpDom } from '@smartbit4all/playwright-qa/steps';

// Dump visible elements on the page — useful when writing a new locator
console.log(await dumpDom(page));

// Scope to a container (dialog, section, grid row)
console.log(await dumpDom(topDialog(page)));

// Narrow with a selector
console.log(await dumpDom(page, { selector: 'button, [data-testid]' }));

// Include hidden elements and longer text
console.log(await dumpDom(page, { visibleOnly: false, maxText: 200 }));

Each entry contains tag, id, classes, testId and own text (text nodes only, not the recursive textContent). Non-rendering tags (script, style, meta, link, head, html, noscript) are filtered out, and by default only visible elements are returned. Use this during test authoring to discover available data-testid values without manually inspecting the DOM in DevTools.

The testId field falls back through data-testiddata-automationid. PrimeNG MenuItem (e.g. grid row popup menus) does not expose data-testid; only the automationId MenuItem property is supported, and it renders as the data-automationid DOM attribute. findMenuItem honors both attributes.

Developer Guide — Writing TestSteps

TestSteps are simple async functions that implement one atomic UI operation.

Rules:

  • Receive data as parameters — never import or reference datapools
  • Include locator logic, waits, technical assertions, and screenshots
  • Group related steps in one file per entity/feature
// steps/company.steps.ts
import { Page } from '@playwright/test';
import { screenshot } from '@smartbit4all/playwright-qa';

export async function fillCompanyForm(page: Page, company: Company) {
  await page.fill('[data-testid="company-name"]', company.name);
  await page.fill('[data-testid="tax-number"]', company.taxNumber);
  await screenshot(page, 'company/form-filled');
  await page.click('[data-testid="save-button"]');
  await page.waitForSelector('.success-toast');
  await screenshot(page, 'company/saved');
}

Test Designer Guide — Writing TestSuites

TestSuites are Playwright test.describe blocks that read like a scenario script.

Rules:

  • Call initSuite() in beforeAll to reset state and configure screenshots
  • Initialize datapools in beforeAll
  • Select data, then call TestSteps in order
  • Add business-level assertions
// suites/company-management.suite.ts
import { test } from '@playwright/test';
import { initSuite, datapool, unique, type DataPool } from '@smartbit4all/playwright-qa';
import { navigateToMain, selectGridRow } from '@smartbit4all/playwright-qa/steps';
import { fillCompanyForm } from '../steps/company.steps';
import type { Company } from '../types';

test.describe('Company Management', () => {
  let companies: DataPool<Company>;

  test.beforeAll(() => {
    initSuite({ screenshotDir: './screenshots' });
    companies = datapool<Company>('companies');
  });

  test('Create new company', async ({ page }) => {
    const company = companies.derive('it4all', { taxNumber: unique('TAX') });
    await fillCompanyForm(page, company);
    await navigateToMain(page);
    await selectGridRow(page, { 'Adószám': company.taxNumber });
  });
});

Seed Framework

Seed populates application data from datapools — either via API or through the UI.

API Seeding

import { registerSeedHandler, seedViaApi } from '@smartbit4all/playwright-qa';

// Register a handler (project-specific)
registerSeedHandler<Company>('companies', {
  seed: async (company, options) => {
    await fetch(`${options.baseUrl}/api/companies`, {
      method: 'POST',
      headers: { Authorization: `Bearer ${options.authToken}` },
      body: JSON.stringify(company),
    });
  },
});

// Seed all companies
const result = await seedViaApi('companies', { baseUrl, authToken });
console.log(`Seeded ${result.success}/${result.total}`);

UI Seeding

import { registerUiSeedStep, seedViaUi } from '@smartbit4all/playwright-qa';

// Register a UI seed step (reuses your TestSteps)
registerUiSeedStep<Company>('companies', async (page, company) => {
  await fillCompanyForm(page, company);
});

// Seed through the UI
const result = await seedViaUi(page, 'companies', { basePath: './datapools', locale: 'hu' });

Seed Profiles

For complex data with dependencies, use seed profiles:

{
  "name": "development-full",
  "order": ["users", "organizations", "companies", "documents"],
  "dependencies": {
    "documents": ["companies", "users"],
    "companies": ["organizations"]
  }
}
import { seed } from '@smartbit4all/playwright-qa';

const profile = JSON.parse(fs.readFileSync('seed-profiles/dev.json', 'utf-8'));
const results = await seed(profile, 'api', { baseUrl, authToken, basePath: './datapools' });

CI Setup

Azure DevOps

  1. Copy templates/ci/playwright-qa-pipeline.yml to your project repo
  2. Set pipeline variables:
    • BASE_URL: Your application URL (e.g., https://staging.example.com)
    • LOCALE: Datapool locale (e.g., hu)
  3. The pipeline will:
    • Run all Playwright tests
    • Publish JUnit results to the Test tab
    • Upload screenshots and HTML report as artifacts

Project Setup

Copy the templates/playwright-qa/ directory to your project repo:

cp -r node_modules/@smartbit4all/playwright-qa/templates/playwright-qa ./playwright-qa
cd playwright-qa
npm install
npx playwright install --with-deps chromium

Development

git clone <repo-url>
cd platform-playwright
npm install
npx playwright install --with-deps chromium
npm test

Testing locally in a project

Use npm link to try the package in your own project without publishing:

# In platform-playwright:
npm run build
npm link

# In your project:
npm link @smartbit4all/playwright-qa

After linking, your project uses the local build directly. When you make changes to the package, rebuild with npm run build — no need to re-link.

To remove the link later:

# In your project:
npm unlink @smartbit4all/playwright-qa
npm install

License

LGPL-3.0-or-later