CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testing-library--react

Simple and complete React DOM testing utilities that encourage good testing practices

Pending
Overview
Eval results
Files

queries.mddocs/

Querying Elements

Find elements using accessible patterns. Inherited from @testing-library/dom.

Query Types

Three variants for each query method:

  • getBy*: Throws if not found → Use when element should exist
  • queryBy*: Returns null if not found → Use for negative assertions
  • findBy*: Returns Promise → Use for async elements

Plural forms (getAll*, queryAll*, findAll*) return arrays.

Query Priority (Best to Worst)

  1. getByRole - Most accessible, encourages proper ARIA
  2. getByLabelText - For form inputs
  3. getByPlaceholderText - If no label exists
  4. getByText - For non-interactive content
  5. getByDisplayValue - For filled inputs
  6. getByAltText - For images
  7. getByTitle - When other options unavailable
  8. getByTestId - Last resort, not user-visible

Common Queries

getByRole (Primary)

Primary query method. Finds elements by their ARIA role. Encourages accessible markup and tests behavior users can actually see.

/**
 * Find element by ARIA role
 * @param role - ARIA role (button, heading, textbox, etc.)
 * @param options - Additional constraints
 * @returns Matching HTMLElement
 * @throws When no element or multiple elements found
 */
getByRole(role: string, options?: ByRoleOptions): HTMLElement;

/**
 * Find all elements by ARIA role
 * @returns Array of matching HTMLElements
 * @throws When no elements found
 */
getAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

interface ByRoleOptions {
  /**
   * Filter by accessible name (label, aria-label, text content)
   */
  name?: string | RegExp;

  /**
   * Filter by accessible description (aria-describedby, title)
   */
  description?: string | RegExp;

  /**
   * Include hidden elements (default: false)
   */
  hidden?: boolean;

  /**
   * Filter by selected state (for options, tabs, etc.)
   */
  selected?: boolean;

  /**
   * Filter by checked state (for checkboxes, radio buttons)
   */
  checked?: boolean;

  /**
   * Filter by pressed state (for toggle buttons)
   */
  pressed?: boolean;

  /**
   * Filter by current state (for navigation items)
   */
  current?: boolean | string;

  /**
   * Filter by expanded state (for accordions, dropdowns)
   */
  expanded?: boolean;

  /**
   * Filter by heading level (for heading role)
   */
  level?: number;

  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;

  /**
   * Enable query fallbacks
   */
  queryFallbacks?: boolean;
}

Common Roles:

screen.getByRole('button');              // <button>, role="button"
screen.getByRole('link');                // <a href>
screen.getByRole('heading', { level: 1 });  // <h1>
screen.getByRole('textbox');             // <input type="text">, <textarea>
screen.getByRole('checkbox');            // <input type="checkbox">
screen.getByRole('radio');               // <input type="radio">
screen.getByRole('combobox');            // <select>
screen.getByRole('img');                 // <img>
screen.getByRole('dialog');              // Modal/dialog
screen.getByRole('alert');               // Alert message
screen.getByRole('navigation');          // <nav>
screen.getByRole('list');                // <ul>, <ol>
screen.getByRole('listitem');            // <li>

Examples:

// By role and name
screen.getByRole('button', { name: /submit/i });

// By state
screen.getByRole('checkbox', { checked: true });
screen.getByRole('button', { pressed: true });

// Heading level
screen.getByRole('heading', { level: 2 });

getByLabelText

Finds form elements by their associated label text. Best for form inputs.

/**
 * Find form element by label text
 * @param text - Label text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
getAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

interface SelectorMatcherOptions {
  /**
   * Custom CSS selector to filter results
   */
  selector?: string;

  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;
}

Examples:

// <label htmlFor="email">Email</label><input id="email" />
screen.getByLabelText('Email');

// <input aria-label="Search" />
screen.getByLabelText('Search');

// <label><input /> Remember me</label>
screen.getByLabelText('Remember me');

getByText

Finds elements by their text content. Good for non-interactive text elements.

/**
 * Find element by text content
 * @param text - Text content (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
getAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

Examples:

screen.getByText('Hello World');
screen.getByText(/hello/i);  // Case insensitive
screen.getByText('Hello', { exact: false });  // Partial match
screen.getByText('Submit', { selector: 'button' });  // Filter by tag

getByPlaceholderText

Finds elements by placeholder text. Use as a last resort as placeholders are not accessible replacements for labels.

/**
 * Find element by placeholder text
 * @param text - Placeholder text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

interface MatcherOptions {
  /**
   * Enable exact matching (default: true)
   */
  exact?: boolean;

  /**
   * Custom text normalizer function
   */
  normalizer?: (text: string) => string;
}

getByDisplayValue

Finds form elements by their current value. Useful for testing filled forms.

/**
 * Find form element by current value
 * @param value - Current display value (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];

getByAltText

Finds images and other elements by their alt text.

/**
 * Find element by alt text
 * @param text - Alt text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

getByTitle

Finds elements by their title attribute.

/**
 * Find element by title attribute
 * @param title - Title text (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];

getByTestId

Finds elements by data-testid attribute. Use as a last resort when other queries are not applicable.

/**
 * Find element by test ID
 * @param testId - Test ID value (string or regex)
 * @param options - Matching options
 * @returns Matching HTMLElement
 */
getByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement;
getAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];

Query Variants

queryBy* Queries

Non-throwing variants that return null when element is not found. Use for asserting elements don't exist.

queryByRole(role: string, options?: ByRoleOptions): HTMLElement | null;
queryAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];

queryByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
queryAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

queryByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
queryAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

queryByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];

queryByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement | null;
queryAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];

findBy* Queries

Async queries that return a promise resolving when element is found. Use for elements that appear asynchronously.

findByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement>;
findAllByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement[]>;

findByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
findAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;

findByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
findAllByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;

findByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

findByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
findAllByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;

Utility Functions

screen Object

Pre-bound queries to document.body for convenient access.

import { screen } from '@testing-library/react';

const screen: {
  // All query functions
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other queries

  // Debug utility
  debug: (element?: HTMLElement, maxLength?: number) => void;

  // Log roles utility
  logTestingPlaygroundURL: (element?: HTMLElement) => void;
};

within Function

Get queries bound to a specific element for scoped searches.

/**
 * Get queries scoped to a specific element
 * @param element - Element to scope queries to
 * @returns Object with all query functions bound to element
 */
function within(element: HTMLElement): {
  getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
  // ... all other query functions
};

Query Patterns

Synchronous Queries

// Element exists
const button = screen.getByRole('button');

// Element may not exist
const dialog = screen.queryByRole('dialog');
if (dialog) {
  // Handle dialog
}

// Negative assertion
expect(screen.queryByText('Error')).not.toBeInTheDocument();

// Multiple elements
const items = screen.getAllByRole('listitem');
expect(items).toHaveLength(5);

Async Queries

// Wait for element to appear
const message = await screen.findByText('Loaded');

// Wait for multiple
const buttons = await screen.findAllByRole('button');

// With custom timeout (default: 1000ms)
const slow = await screen.findByText('Slow', {}, { timeout: 3000 });

Scoped Queries

// Query within specific element
const modal = screen.getByRole('dialog');
const closeButton = within(modal).getByRole('button', { name: /close/i });

// Multiple scopes
const nav = screen.getByRole('navigation');
const homeLink = within(nav).getByRole('link', { name: /home/i });

Query Access

// 1. screen object (recommended)
import { screen } from '@testing-library/react';
screen.getByRole('button');

// 2. render result
const { getByRole } = render(<Component />);
getByRole('button');

// 3. within for scoped queries
import { within } from '@testing-library/react';
within(container).getByRole('button');

Testing Patterns

Form Testing

test('form submission', () => {
  render(<LoginForm />);

  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/password/i);
  const submitButton = screen.getByRole('button', { name: /log in/i });

  fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
  fireEvent.change(passwordInput, { target: { value: 'password123' } });
  fireEvent.click(submitButton);
});

List Testing

test('displays items', () => {
  render(<TodoList items={['Task 1', 'Task 2']} />);

  const items = screen.getAllByRole('listitem');
  expect(items).toHaveLength(2);
  expect(items[0]).toHaveTextContent('Task 1');
});

Modal Testing

test('modal interactions', async () => {
  render(<App />);

  // Open modal
  fireEvent.click(screen.getByRole('button', { name: /open/i }));

  // Wait for modal
  const modal = await screen.findByRole('dialog');
  expect(modal).toBeInTheDocument();

  // Query within modal
  const title = within(modal).getByRole('heading');
  expect(title).toHaveTextContent('Modal Title');
});

Conditional Rendering

test('shows error on failure', async () => {
  render(<Component />);

  // Initially no error
  expect(screen.queryByRole('alert')).not.toBeInTheDocument();

  // Trigger error
  fireEvent.click(screen.getByRole('button'));

  // Error appears
  const alert = await screen.findByRole('alert');
  expect(alert).toHaveTextContent('Error occurred');
});

Debug Helpers

// Print DOM
screen.debug();
screen.debug(screen.getByRole('button'));

// Print available roles
import { logRoles } from '@testing-library/react';
logRoles(container);

// Get suggested queries
// Error messages suggest better queries when getBy* fails

Best Practices

  1. Prefer getByRole - Most accessible and resilient to changes
  2. Use regex for flexibility - /submit/i instead of "Submit Form"
  3. Avoid testid - Use semantic queries unless absolutely necessary
  4. Use within() - Scope queries to avoid ambiguity
  5. Async with findBy - Don't use getBy in setTimeout, use findBy
  6. queryBy for absence - Only use queryBy for negative assertions

Install with Tessl CLI

npx tessl i tessl/npm-testing-library--react@16.3.2

docs

async.md

configuration.md

events.md

hooks.md

index.md

queries.md

rendering.md

tile.json