Custom Cypress commands and utilities that encourage good testing practices by providing DOM Testing Library query methods for end-to-end tests
npx @tessl/cli install tessl/npm-testing-library--cypress@10.1.0Cypress Testing Library extends Cypress with DOM Testing Library query methods, providing simple and complete custom commands that encourage good testing practices. It enables developers to write tests that mirror how users interact with applications, focusing on accessibility and semantic HTML elements rather than implementation details.
npm install --save-dev @testing-library/cypressTo register all commands with Cypress:
import '@testing-library/cypress/add-commands';For configuration:
import { configure } from '@testing-library/cypress';CommonJS:
require('@testing-library/cypress/add-commands');// Add to cypress/support/commands.js or cypress/support/e2e.js
import '@testing-library/cypress/add-commands';
// Use in tests
cy.findByText('Submit').click();
cy.findByLabelText('Email').type('user@example.com');
cy.findByRole('button', { name: 'Save' }).should('be.visible');
cy.findAllByTestId('product-card').should('have.length', 3);
// Configure DOM Testing Library options
cy.configureCypressTestingLibrary({
testIdAttribute: 'data-test-id'
});Cypress Testing Library is built around several key concepts:
find* queries from @testing-library/dom are dynamically converted to Cypress commandsAll query commands support the same patterns: findBy* (single element) and findAllBy* (multiple elements). These commands automatically retry until elements are found or timeout occurs.
// Text-based queries
findByText(id: Matcher, options?: SelectorMatcherOptions): Chainable<JQuery>;
findAllByText(id: Matcher, options?: SelectorMatcherOptions): Chainable<JQuery>;
// Label-based queries
findByLabelText(id: Matcher, options?: SelectorMatcherOptions): Chainable<JQuery>;
findAllByLabelText(id: Matcher, options?: SelectorMatcherOptions): Chainable<JQuery>;
// Placeholder queries
findByPlaceholderText(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
findAllByPlaceholderText(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
// Display value queries
findByDisplayValue(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
findAllByDisplayValue(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
// Alt text queries
findByAltText(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
findAllByAltText(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
// Title attribute queries
findByTitle(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
findAllByTitle(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
// Role-based queries
findByRole(id: ByRoleMatcher, options?: ByRoleOptions): Chainable<JQuery>;
findAllByRole(id: ByRoleMatcher, options?: ByRoleOptions): Chainable<JQuery>;
// Test ID queries
findByTestId(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;
findAllByTestId(id: Matcher, options?: MatcherOptions): Chainable<JQuery>;Usage Examples:
// Text queries - find by visible text content
cy.findByText('Submit Form').click();
cy.findByText(/^Click here/i).should('be.visible');
cy.findAllByText('Delete').should('have.length', 3);
// Label queries - find form elements by their labels
cy.findByLabelText('Email Address').type('user@example.com');
cy.findByLabelText(/password/i).type('secret123');
cy.findAllByLabelText(/required/i).should('not.be.empty');
// Role queries - find by ARIA roles
cy.findByRole('button').click();
cy.findByRole('textbox', { name: /search/i }).type('query');
cy.findAllByRole('listitem').should('have.length.greaterThan', 0);
// Test ID queries - find by data-testid attributes
cy.findByTestId('login-form').should('be.visible');
cy.findAllByTestId(/^product-/i).should('have.length', 5);
// With options
cy.findByText('Loading...', { timeout: 10000 }).should('not.exist');
cy.findByText('Button', { container: cy.get('.modal') }).click();Configure DOM Testing Library options through Cypress:
/**
* Configure DOM Testing Library through Cypress
* @param config - Configuration options for DOM Testing Library
*/
configureCypressTestingLibrary(config: DTLConfiguration): Chainable<void>;Usage Examples:
// Change default test ID attribute
cy.configureCypressTestingLibrary({
testIdAttribute: 'data-test-id'
});
// Configure text normalization
cy.configureCypressTestingLibrary({
normalizer: getDefaultNormalizer({
trim: true,
collapseWhitespace: true
})
});
// Configure default hidden behavior
cy.configureCypressTestingLibrary({
defaultHidden: true
});Configure DOM Testing Library outside of tests:
/**
* Configure DOM Testing Library options
* @param config - Configuration options
* @returns Configuration object
*/
function configure(config: DTLConfiguration): DTLConfiguration;Usage Examples:
import { configure } from '@testing-library/cypress';
// In cypress/support/e2e.js
configure({
testIdAttribute: 'data-test-id',
asyncUtilTimeout: 5000
});Chaining with Previous Subjects:
// Find within a specific container
cy.get('.sidebar').findByText('Settings').click();
// Use within() for scoping
cy.get('.modal').within(() => {
cy.findByLabelText('Name').type('John Doe');
cy.findByRole('button', { name: 'Save' }).click();
});
// Container option
cy.get('.product-list').then($container => {
cy.findByText('Add to Cart', { container: $container }).click();
});Error Handling:
// Check for non-existence
cy.findByText('Error Message').should('not.exist');
// Handle multiple matches (findBy* commands will fail)
cy.findAllByText('Delete').should('have.length', 1);
// Timeout configuration
cy.findByText('Loading Complete', { timeout: 30000 }).should('exist');// Matcher types (from @testing-library/dom)
type Matcher = string | RegExp | ((content: string, element?: Element) => boolean);
type ByRoleMatcher = string;
// Cypress Testing Library specific options
interface CTLMatcherOptions extends Partial<Cypress.Timeoutable>, Partial<Cypress.Loggable> {
/** Container element to search within */
container?: Element | JQuery<Element>;
}
// Combined option types
type MatcherOptions = DTLMatcherOptions | CTLMatcherOptions;
type SelectorMatcherOptions = DTLSelectorMatcherOptions | CTLMatcherOptions;
type ByRoleOptions = DTLByRoleOptions | CTLMatcherOptions;
// DOM Testing Library configuration
interface DTLConfiguration {
/** Attribute used for test IDs (default: 'data-testid') */
testIdAttribute?: string;
/** Default timeout for async utilities */
asyncUtilTimeout?: number;
/** Text normalizer function */
normalizer?: (text: string) => string;
/** Default hidden element behavior */
defaultHidden?: boolean;
/** Custom get error message function */
getElementError?: (message: string | null, container: HTMLElement) => Error;
}
// Cypress command return type
type Chainable<T> = Cypress.Chainable<T>;query* or get* commands: Only find* and findAllBy* commands are availablefindAllBy* commands can return multiple elements (unlike DOM Testing Library's getBy*)container option for scoping queriesfindBy* when expecting exactly one element (will fail if multiple found)findAllBy* when expecting one or more elementsfindByRole, findByLabelText) over test IDs when possibleshould('not.exist') instead of query* commands to check for element absencetestIdAttribute consistently across your applicationcontainer option or within() to scope queries to specific sections