Sitelet https://github.com/thomasmikava/testing-library-queries
Skip to content

Repository files navigation

testing-library-queries

Reusable, typed selector descriptors for Testing Library and Playwright.

Define selectors once in dependency-free files, then execute those same selectors through the adapter used by each test runtime.

Installation

npm install --save-dev testing-library-queries
pnpm add --save-dev testing-library-queries
yarn add --dev testing-library-queries

@testing-library/dom is an optional peer dependency. Install it only when you use the Testing Library adapter.

Playwright is not a peer dependency. The Playwright adapter uses structural types and does not import Playwright at runtime.

Selector Files

Selector files should import from the root package only. The root package is descriptor-only and does not import Testing Library or Playwright.

import { buildQuery, by } from 'testing-library-queries';

export const browserWorkspaceSelector = {
  folderPanel: () => by.selector('[data-type="folder-panel"]'),
  folderPanelTitle: (name: string) => by.text(name),
  createListInput: () => by.selector('input[placeholder="List title..."]'),
  createTaskInput: () => by.selector('input[placeholder="New task..."]'),
  createSubtaskInput: () => by.role('textbox', { name: 'New subtask' }),
  activeTask: (title: string) => buildQuery.selectorWithText(() => '[data-type="task-row"]')(title),
};

Testing Library Usage

Use the Testing Library adapter from testing-library-queries/testing-library.

import { render } from '@testing-library/react';
import { screen, within } from 'testing-library-queries/testing-library';
import { browserWorkspaceSelector } from './BrowserWorkspace.selector';

render(<BrowserWorkspace />);

const folderPanel = await screen.find(browserWorkspaceSelector.folderPanel());

expect(within(folderPanel).get(browserWorkspaceSelector.folderPanelTitle('Work HQ'))).toBeInTheDocument();

The adapter also keeps CSS selector helpers:

screen.getBySelector('[data-type="folder-panel"]');
screen.queryAllBySelector('[data-type="task-row"]');

Playwright Usage

Use the Playwright adapter from testing-library-queries/playwright.

import { expect, test } from '@playwright/test';
import { createPlaywrightQueries } from 'testing-library-queries/playwright';
import { browserWorkspaceSelector } from '../src/BrowserWorkspace.selector';

test('creates a list', async ({ page }) => {
  const q = createPlaywrightQueries(page);

  await page.goto('/workspace');
  await q.get(browserWorkspaceSelector.createListInput()).fill('Today');
  await q.get(browserWorkspaceSelector.createListInput()).press('Enter');

  await expect(q.get(browserWorkspaceSelector.folderPanelTitle('Today'))).toBeVisible();
});

get() returns a Playwright Locator when called with a Playwright Page or Locator, so normal Locator methods are inferred:

const q = createPlaywrightQueries(page);

await q.get(browserWorkspaceSelector.createTaskInput()).fill('Ship v3');
await q.within(q.get(browserWorkspaceSelector.folderPanel())).get(browserWorkspaceSelector.folderPanelTitle('Work HQ'));

Public Entrypoints

import { by, buildQuery } from 'testing-library-queries';
import { screen, within } from 'testing-library-queries/testing-library';
import { createPlaywrightQueries } from 'testing-library-queries/playwright';

The root entrypoint exports descriptor builders only:

  • by
  • buildQuery
  • descriptor and helper types

The root entrypoint intentionally does not export screen, within, or any runtime adapter.

Portable Selectors

These descriptors are portable across Testing Library and Playwright:

  • by.selector(css)
  • by.role(role, options)
  • by.text(text, options)
  • by.labelText(text, options)
  • by.placeholderText(text, options)
  • by.altText(text, options)
  • by.title(text, options)
  • by.testId(id)
  • buildQuery.selectorWithText(...)
  • buildQuery.chain(...), when every step is portable and the chain has no transform

Playwright option behavior:

  • by.role(..., { hidden: true }) is translated to Playwright's includeHidden.
  • selector, ignore, and normalizer options throw in Playwright because they are Testing Library-only semantics.
  • function text matchers throw in Playwright because Playwright supports string and RegExp matchers, not arbitrary predicate functions.

Testing Library-Only Selectors

These descriptors are supported by the Testing Library adapter only:

  • by.displayValue(...)
  • buildQuery.from(...)
  • buildQuery.transform(...)
  • buildQuery.intersect(...)
  • buildQuery.hasText(...)
  • chains with parent()
  • chains with transform(...)
  • custom function text matchers
  • Testing Library-only options such as selector, ignore, and normalizer

The Playwright adapter throws a clear unsupported-query or unsupported-option error for these cases.

API

by

by.role('button', { name: 'Save' });
by.text('Item');
by.labelText('Email');
by.placeholderText('Search');
by.testId('submit-button');
by.altText('Avatar');
by.title('More information');
by.displayValue('Current value');
by.selector('[data-type="task-row"]');

buildQuery.selectorWithText

const taskRow = buildQuery.selectorWithText((status: string) => `[data-status="${status}"]`);

screen.get(taskRow('Active task', 'active'));
await q.get(taskRow('Active task', 'active')).click();

buildQuery.chain

const buttonInPanel = buildQuery
  .chain(by.selector('[data-panel="actions"]'))
  .pipe(by.role('button', { name: 'Save' }))
  .build();

screen.get(buttonInPanel);
await q.get(buttonInPanel).click();

Testing Library Custom Queries

const activeButton = buildQuery.from((container) => container.querySelectorAll('button.active'), {
  name: 'active button',
});

screen.get(activeButton);
const cardFromHeader = buildQuery.transform(
  (container) => container.querySelectorAll('[data-card-title]'),
  (title) => title.closest('[data-card]'),
  { name: 'card' },
);

const card = screen.get(cardFromHeader);

Migration

See MIGRATION.md for the v2 to v3 migration guide.

Verification

npm run lint
npm run typecheck
npm test -- --run
npm run build
npm run check:exports

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages