CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-storybook--test

Utilities for testing your stories inside play functions

Pending
Overview
Eval results
Files

dom-testing.mddocs/

DOM Testing

Complete DOM testing utilities including queries, user interactions, and async utilities. All functions are instrumented for Storybook debugging and work seamlessly with the addon-interactions panel. Based on @testing-library/dom and @testing-library/user-event.

Capabilities

Element Queries

Query DOM elements using various strategies. Each query type comes in three variants: get (throws if not found), find (async, throws if not found), and query (returns null if not found).

Get Queries (Sync, throws if not found)

/**
 * Get element by ARIA role
 * @param container - Container element to search within
 * @param role - ARIA role to search for
 * @param options - Additional query options
 * @returns Found element (throws if not found)
 */
function getByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement;
function getAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];

function getByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
function getAllByText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

function getByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
function getAllByLabelText(container: HTMLElement, text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];

function getByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByPlaceholderText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];

function getByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByAltText(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];

function getByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions): HTMLElement[];

function getByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement;
function getAllByDisplayValue(container: HTMLElement, value: string | RegExp, options?: MatcherOptions): HTMLElement[];

function getByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement;
function getAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions): HTMLElement[];

interface ByRoleOptions extends MatcherOptions {
  checked?: boolean;
  selected?: boolean;
  expanded?: boolean;
  pressed?: boolean;
  level?: number;
  name?: string | RegExp;
  description?: string | RegExp;
}

interface SelectorMatcherOptions extends MatcherOptions {
  selector?: string;
}

interface MatcherOptions {
  exact?: boolean;
  normalizer?: (text: string) => string;
}

Find Queries (Async, throws if not found)

/**
 * Asynchronously find element by ARIA role
 * @param container - Container element to search within
 * @param role - ARIA role to search for
 * @param options - Additional query options
 * @param waitForOptions - Options for waiting
 * @returns Promise resolving to found element
 */
function findByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;

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

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

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

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

function findByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByTitle(container: HTMLElement, text: string | RegExp, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;

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

function findByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;
function findAllByTestId(container: HTMLElement, testId: string, options?: MatcherOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;

Query Queries (Sync, returns null if not found)

/**
 * Query element by ARIA role (returns null if not found)
 * @param container - Container element to search within
 * @param role - ARIA role to search for
 * @param options - Additional query options
 * @returns Found element or null
 */
function queryByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement | null;
function queryAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];

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

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

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

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

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

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

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

function queryByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement | null;
function queryAllByAttribute(container: HTMLElement, name: string, value?: string | RegExp, options?: MatcherOptions): HTMLElement[];

Query Usage Examples:

import { within, getByRole, findByText, queryByTestId } from '@storybook/test';

export const QueryStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Get queries (throw if not found)
    const button = canvas.getByRole('button', { name: /submit/i });
    const heading = canvas.getByText('Welcome');
    const input = canvas.getByLabelText('Email');
    
    // Find queries (async, wait for element)
    const asyncContent = await canvas.findByText('Loaded content');
    const asyncButton = await canvas.findByRole('button', { name: /save/i });
    
    // Query queries (return null if not found)
    const optionalElement = canvas.queryByTestId('optional-feature');
    if (optionalElement) {
      // Element exists, interact with it
      expect(optionalElement).toBeInTheDocument();
    }
    
    // Multiple elements
    const allButtons = canvas.getAllByRole('button');
    const allInputs = await canvas.findAllByRole('textbox');
    
    expect(allButtons.length).toBeGreaterThan(0);
    expect(allInputs.length).toBeGreaterThan(0);
  },
};

Element Scoping

Scope queries to specific parts of the DOM using the within function.

/**
 * Create bound queries scoped to a specific element
 * @param element - Element to scope queries within
 * @returns Object with all query functions bound to the element
 */
function within(element: HTMLElement): BoundFunctions<typeof queries>;

type BoundFunctions<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => any
    ? (...args: Parameters<T[K]>) => ReturnType<T[K]>
    : T[K];
};

/**
 * Global screen object for document-wide queries (deprecated in Storybook context)
 * Use within(canvasElement) instead
 */
const screen: BoundFunctions<typeof queries>;

Scoping Usage Examples:

import { within, expect } from '@storybook/test';

export const ScopingStory = {
  play: async ({ canvasElement }) => {
    // Scope queries to the story canvas
    const canvas = within(canvasElement);
    
    // Find a modal or specific section
    const modal = canvas.getByRole('dialog');
    const modalScope = within(modal);
    
    // Queries within modal only
    const modalButton = modalScope.getByRole('button', { name: /close/i });
    const modalHeading = modalScope.getByRole('heading');
    
    // This would find buttons anywhere in canvas
    const allCanvasButtons = canvas.getAllByRole('button');
    
    // This only finds buttons within the modal
    const modalButtons = modalScope.getAllByRole('button');
    
    expect(modalButtons.length).toBeLesserThanOrEqual(allCanvasButtons.length);
  },
};

User Interactions

Simulate user interactions with elements using the instrumented userEvent API.

interface UserEvent {
  /**
   * Click an element
   * @param element - Element to click
   * @param options - Click options
   */
  click(element: Element, options?: ClickOptions): Promise<void>;
  
  /**
   * Double-click an element
   * @param element - Element to double-click
   * @param options - Click options
   */
  dblClick(element: Element, options?: ClickOptions): Promise<void>;
  
  /**
   * Type text into an element
   * @param element - Element to type into
   * @param text - Text to type
   * @param options - Typing options
   */
  type(element: Element, text: string, options?: TypeOptions): Promise<void>;
  
  /**
   * Clear an input element
   * @param element - Element to clear
   */
  clear(element: Element): Promise<void>;
  
  /**
   * Select options in a select element
   * @param element - Select element
   * @param values - Values to select
   */
  selectOptions(element: Element, values: string | string[]): Promise<void>;
  
  /**
   * Deselect options in a select element
   * @param element - Select element
   * @param values - Values to deselect
   */
  deselectOptions(element: Element, values: string | string[]): Promise<void>;
  
  /**
   * Upload files to a file input
   * @param element - File input element
   * @param file - File or files to upload
   */
  upload(element: Element, file: File | File[]): Promise<void>;
  
  /**
   * Tab to the next focusable element
   * @param options - Tab options
   */
  tab(options?: TabOptions): Promise<void>;
  
  /**
   * Hover over an element
   * @param element - Element to hover
   */
  hover(element: Element): Promise<void>;
  
  /**
   * Stop hovering over an element
   * @param element - Element to stop hovering
   */
  unhover(element: Element): Promise<void>;
  
  /**
   * Paste text into an element
   * @param text - Text to paste
   */
  paste(text: string): Promise<void>;
  
  /**
   * Press keyboard key(s)
   * @param keys - Key or key combination to press
   */
  keyboard(keys: string): Promise<void>;
}

interface ClickOptions {
  button?: number;
  detail?: number;
  ctrlKey?: boolean;
  shiftKey?: boolean;
  altKey?: boolean;
  metaKey?: boolean;
}

interface TypeOptions {
  delay?: number;
  skipClick?: boolean;
  skipAutoClose?: boolean;
  initialSelectionStart?: number;
  initialSelectionEnd?: number;
}

interface TabOptions {
  shift?: boolean;
}

User Interaction Examples:

import { userEvent, within, expect } from '@storybook/test';

export const InteractionStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Basic interactions
    const button = canvas.getByRole('button', { name: /submit/i });
    await userEvent.click(button);
    
    const input = canvas.getByLabelText('Username');
    await userEvent.type(input, 'john.doe');
    expect(input).toHaveValue('john.doe');
    
    // Clear and retype
    await userEvent.clear(input);
    await userEvent.type(input, 'jane.doe');
    expect(input).toHaveValue('jane.doe');
    
    // Select dropdown
    const select = canvas.getByLabelText('Country');
    await userEvent.selectOptions(select, 'us');
    expect(select).toHaveValue('us');
    
    // Multiple selections
    const multiSelect = canvas.getByLabelText('Skills');
    await userEvent.selectOptions(multiSelect, ['javascript', 'typescript']);
    
    // File upload
    const fileInput = canvas.getByLabelText('Upload file');
    const file = new File(['content'], 'test.txt', { type: 'text/plain' });
    await userEvent.upload(fileInput, file);
    expect(fileInput.files![0]).toBe(file);
    
    // Keyboard interactions
    await userEvent.keyboard('{Enter}');
    await userEvent.keyboard('{Escape}');
    await userEvent.keyboard('{ctrl}a'); // Select all
    
    // Hover effects
    const tooltip = canvas.getByText('Hover me');
    await userEvent.hover(tooltip);
    await expect(canvas.findByText('Tooltip content')).resolves.toBeInTheDocument();
    
    await userEvent.unhover(tooltip);
    expect(canvas.queryByText('Tooltip content')).not.toBeInTheDocument();
  },
};

Async Utilities

Wait for conditions and element changes in the DOM.

/**
 * Wait for a condition to be met
 * @param callback - Function to test condition
 * @param options - Wait options
 * @returns Promise resolving when condition is met
 */
function waitFor<T>(callback: () => T | Promise<T>, options?: WaitForOptions): Promise<T>;

/**
 * Wait for elements to be removed from the DOM
 * @param callback - Function returning elements to wait for removal
 * @param options - Wait options
 * @returns Promise resolving when elements are removed
 */
function waitForElementToBeRemoved(
  callback: () => Element | Element[] | Promise<Element | Element[]>,
  options?: WaitForOptions
): Promise<void>;

interface WaitForOptions {
  container?: HTMLElement;
  timeout?: number;
  interval?: number;
  onTimeout?: (error: Error) => Error;
  mutationObserverOptions?: MutationObserverInit;
}

Async Utility Examples:

import { waitFor, waitForElementToBeRemoved, within, userEvent, expect } from '@storybook/test';

export const AsyncStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Wait for async content to appear
    await waitFor(() => {
      expect(canvas.getByText('Loading complete')).toBeInTheDocument();
    });
    
    // Wait for API call to complete
    const loadButton = canvas.getByRole('button', { name: /load data/i });
    await userEvent.click(loadButton);
    
    await waitFor(
      async () => {
        const dataList = canvas.getByRole('list');
        const items = canvas.getAllByRole('listitem');
        expect(items.length).toBeGreaterThan(0);
        return items;
      },
      { timeout: 5000 }
    );
    
    // Wait for element removal
    const dismissButton = canvas.getByRole('button', { name: /dismiss/i });
    await userEvent.click(dismissButton);
    
    await waitForElementToBeRemoved(
      () => canvas.queryByText('Notification message'),
      { timeout: 3000 }
    );
    
    // Custom timeout and interval
    await waitFor(
      () => {
        const status = canvas.getByTestId('status');
        expect(status).toHaveTextContent('Ready');
      },
      {
        timeout: 10000,
        interval: 500,
      }
    );
  },
};

DOM Utilities

Additional utilities for DOM interaction and debugging.

/**
 * Create DOM events programmatically
 * @param eventName - Name of the event
 * @param node - Target node for the event
 * @param init - Event initialization options
 */
function createEvent(eventName: string, node: Element, init?: EventInit): Event;

/**
 * Fire events on DOM elements
 */
const fireEvent: FireFunction & FireObject;

interface FireFunction {
  (element: Element, event: Event): boolean;
}

interface FireObject {
  click(element: Element, options?: EventInit): boolean;
  change(element: Element, options?: EventInit): boolean;
  input(element: Element, options?: EventInit): boolean;
  keyDown(element: Element, options?: KeyboardEventInit): boolean;
  keyPress(element: Element, options?: KeyboardEventInit): boolean;
  keyUp(element: Element, options?: KeyboardEventInit): boolean;
  mouseDown(element: Element, options?: MouseEventInit): boolean;
  mouseEnter(element: Element, options?: MouseEventInit): boolean;
  mouseLeave(element: Element, options?: MouseEventInit): boolean;
  mouseMove(element: Element, options?: MouseEventInit): boolean;
  mouseOut(element: Element, options?: MouseEventInit): boolean;
  mouseOver(element: Element, options?: MouseEventInit): boolean;
  mouseUp(element: Element, options?: MouseEventInit): boolean;
  focus(element: Element, options?: FocusEventInit): boolean;
  blur(element: Element, options?: FocusEventInit): boolean;
  submit(element: Element, options?: EventInit): boolean;
}

/**
 * Pretty print DOM structure for debugging
 * @param element - Element to print
 * @param maxLength - Maximum length of output
 * @param options - Pretty printing options
 */
function prettyDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): string;

/**
 * Log DOM structure to console
 * @param element - Element to log
 * @param maxLength - Maximum length of output
 * @param options - Pretty printing options
 */
function logDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): void;

/**
 * Log ARIA roles for debugging accessibility
 * @param element - Element to analyze
 */
function logRoles(element: Element): void;

/**
 * Get suggested query for an element
 * @param element - Element to analyze
 * @param variant - Query variant to suggest
 */
function getSuggestedQuery(element: Element, variant?: 'get' | 'find' | 'query'): string;

/**
 * Get default text normalizer function
 * @returns Default normalizer function
 */
function getDefaultNormalizer(): (text: string) => string;

/**
 * Get error message for element not found
 * @param message - Error message
 * @param container - Container element
 * @returns Error instance
 */
function getElementError(message: string, container: HTMLElement): Error;

/**
 * Get text content from a DOM node
 * @param node - DOM node to extract text from
 * @returns Text content
 */
function getNodeText(node: Node): string;

/**
 * Get bound query functions for a specific element
 * @param element - Element to bind queries to
 * @returns Object with bound query functions
 */
function getQueriesForElement(element: HTMLElement): BoundFunctions<typeof queries>;

/**
 * Get all ARIA roles for an element
 * @param element - Element to analyze
 * @returns Object with role information
 */
function getRoles(element: Element): { [role: string]: HTMLElement[] };

/**
 * Check if element is accessible
 * @param element - Element to check
 * @returns True if element is accessible
 */
function isInaccessible(element: Element): boolean;

/**
 * Pretty format values for debugging
 * @param value - Value to format
 * @param options - Formatting options
 * @returns Formatted string
 */
function prettyFormat(value: any, options?: any): string;

/**
 * Query helper utilities
 */
const queryHelpers: {
  queryByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement | null;
  queryAllByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement[];
  getElementError: (message: string, container: HTMLElement) => Error;
};

/**
 * All available query functions
 */
const queries: {
  queryByRole: typeof queryByRole;
  queryAllByRole: typeof queryAllByRole;
  getByRole: typeof getByRole;
  getAllByRole: typeof getAllByRole;
  findByRole: typeof findByRole;
  findAllByRole: typeof findAllByRole;
  // ... (all other query functions)
};

interface PrettyDOMOptions {
  highlight?: boolean;
  filterNode?: (node: Node) => boolean;
}

DOM Utility Examples:

import { 
  fireEvent, 
  createEvent, 
  prettyDOM, 
  logDOM, 
  logRoles, 
  getSuggestedQuery, 
  within 
} from '@storybook/test';

export const UtilityStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Manual event firing
    const button = canvas.getByRole('button');
    fireEvent.click(button);
    fireEvent.mouseEnter(button);
    fireEvent.mouseLeave(button);
    
    // Custom events
    const customEvent = createEvent('customEvent', button, { 
      bubbles: true,
      cancelable: true 
    });
    fireEvent(button, customEvent);
    
    // Debugging utilities
    const form = canvas.getByRole('form');
    
    // Log DOM structure
    console.log('Form DOM:', prettyDOM(form));
    logDOM(form); // Logs to console with nice formatting
    
    // Analyze accessibility
    logRoles(form); // Shows all ARIA roles
    
    // Get suggested queries
    const input = canvas.getByLabelText('Email');
    console.log('Suggested query:', getSuggestedQuery(input));
    // Output: getByLabelText(/email/i)
    
    // Additional utility functions
    console.log('Node text:', getNodeText(input));
    console.log('Is accessible:', !isInaccessible(input));
    console.log('Element roles:', getRoles(form));
    
    // Get bound queries for specific element
    const boundQueries = getQueriesForElement(form);
    const formButton = boundQueries.getByRole('button');
    
    // Pretty formatting for debugging
    console.log('Pretty formatted value:', prettyFormat({ key: 'value' }));
  },
};

Configuration

Configure testing-library behavior globally.

/**
 * Configure testing-library options
 * @param options - Configuration options
 */
function configure(options: ConfigureOptions): void;

/**
 * Get current configuration
 * @returns Current configuration object
 */
function getConfig(): Config;

interface ConfigureOptions {
  testIdAttribute?: string;
  asyncUtilTimeout?: number;
  computedStyleSupportsPseudoElements?: boolean;
  defaultHidden?: boolean;
  showOriginalStackTrace?: boolean;
  throwSuggestions?: boolean;
  getElementError?: (message: string, container: HTMLElement) => Error;
}

interface Config extends ConfigureOptions {
  testIdAttribute: string;
  asyncUtilTimeout: number;
  computedStyleSupportsPseudoElements: boolean;
  defaultHidden: boolean;
  showOriginalStackTrace: boolean;
  throwSuggestions: boolean;
}

Configuration Examples:

import { configure, getConfig } from '@storybook/test';

// Configure at story level or in preview.js
configure({
  testIdAttribute: 'data-cy', // Use Cypress test IDs
  asyncUtilTimeout: 5000,     // Increase timeout
  throwSuggestions: false,    // Disable query suggestions
});

export const ConfigStory = {
  play: async () => {
    const config = getConfig();
    console.log('Current timeout:', config.asyncUtilTimeout);
    
    // Now getByTestId uses 'data-cy' instead of 'data-testid'
  },
};

Query by Attribute

Generic attribute-based queries for custom attributes not covered by other query types.

/**
 * Query elements by any attribute value
 * @param container - Container element to search within
 * @param attribute - Attribute name to search for
 * @param value - Attribute value to match
 * @param options - Additional query options
 * @returns Found element or null
 */
function queryByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement | null;
function queryAllByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement[];

Usage Examples:

import { queryByAttribute, queryAllByAttribute, within } from '@storybook/test';

export const AttributeQueryStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Query by custom attribute
    const element = queryByAttribute(canvasElement, 'data-custom', 'value');
    if (element) {
      console.log('Found element with data-custom="value"');
    }
    
    // Query all elements with specific attribute
    const allElements = queryAllByAttribute(canvasElement, 'aria-expanded', 'true');
    console.log(`Found ${allElements.length} expanded elements`);
  },
};

Fire Events

Manually trigger DOM events for advanced testing scenarios.

interface FireEvent {
  (element: Element, event: Event): boolean;
  
  // Event-specific methods
  abort(element: Element, eventProperties?: {}): boolean;
  animationEnd(element: Element, eventProperties?: {}): boolean;
  animationIteration(element: Element, eventProperties?: {}): boolean;
  animationStart(element: Element, eventProperties?: {}): boolean;
  blur(element: Element, eventProperties?: {}): boolean;
  canPlay(element: Element, eventProperties?: {}): boolean;
  canPlayThrough(element: Element, eventProperties?: {}): boolean;
  change(element: Element, eventProperties?: {}): boolean;
  click(element: Element, eventProperties?: {}): boolean;
  contextMenu(element: Element, eventProperties?: {}): boolean;
  copy(element: Element, eventProperties?: {}): boolean;
  cut(element: Element, eventProperties?: {}): boolean;
  doubleClick(element: Element, eventProperties?: {}): boolean;
  drag(element: Element, eventProperties?: {}): boolean;
  dragEnd(element: Element, eventProperties?: {}): boolean;
  dragEnter(element: Element, eventProperties?: {}): boolean;
  dragExit(element: Element, eventProperties?: {}): boolean;
  dragLeave(element: Element, eventProperties?: {}): boolean;
  dragOver(element: Element, eventProperties?: {}): boolean;
  dragStart(element: Element, eventProperties?: {}): boolean;
  drop(element: Element, eventProperties?: {}): boolean;
  durationChange(element: Element, eventProperties?: {}): boolean;
  emptied(element: Element, eventProperties?: {}): boolean;
  encrypted(element: Element, eventProperties?: {}): boolean;
  ended(element: Element, eventProperties?: {}): boolean;
  error(element: Element, eventProperties?: {}): boolean;
  focus(element: Element, eventProperties?: {}): boolean;
  focusIn(element: Element, eventProperties?: {}): boolean;
  focusOut(element: Element, eventProperties?: {}): boolean;
  input(element: Element, eventProperties?: {}): boolean;
  invalid(element: Element, eventProperties?: {}): boolean;
  keyDown(element: Element, eventProperties?: {}): boolean;
  keyPress(element: Element, eventProperties?: {}): boolean;
  keyUp(element: Element, eventProperties?: {}): boolean;
  load(element: Element, eventProperties?: {}): boolean;
  loadStart(element: Element, eventProperties?: {}): boolean;
  loadedData(element: Element, eventProperties?: {}): boolean;
  loadedMetadata(element: Element, eventProperties?: {}): boolean;
  mouseDown(element: Element, eventProperties?: {}): boolean;
  mouseEnter(element: Element, eventProperties?: {}): boolean;
  mouseLeave(element: Element, eventProperties?: {}): boolean;
  mouseMove(element: Element, eventProperties?: {}): boolean;
  mouseOut(element: Element, eventProperties?: {}): boolean;
  mouseOver(element: Element, eventProperties?: {}): boolean;
  mouseUp(element: Element, eventProperties?: {}): boolean;
  paste(element: Element, eventProperties?: {}): boolean;
  pause(element: Element, eventProperties?: {}): boolean;
  play(element: Element, eventProperties?: {}): boolean;
  playing(element: Element, eventProperties?: {}): boolean;
  pointerCancel(element: Element, eventProperties?: {}): boolean;
  pointerDown(element: Element, eventProperties?: {}): boolean;
  pointerEnter(element: Element, eventProperties?: {}): boolean;
  pointerLeave(element: Element, eventProperties?: {}): boolean;
  pointerMove(element: Element, eventProperties?: {}): boolean;
  pointerOut(element: Element, eventProperties?: {}): boolean;
  pointerOver(element: Element, eventProperties?: {}): boolean;
  pointerUp(element: Element, eventProperties?: {}): boolean;
  progress(element: Element, eventProperties?: {}): boolean;
  rateChange(element: Element, eventProperties?: {}): boolean;
  scroll(element: Element, eventProperties?: {}): boolean;
  seeked(element: Element, eventProperties?: {}): boolean;
  seeking(element: Element, eventProperties?: {}): boolean;
  select(element: Element, eventProperties?: {}): boolean;
  stalled(element: Element, eventProperties?: {}): boolean;
  submit(element: Element, eventProperties?: {}): boolean;
  suspend(element: Element, eventProperties?: {}): boolean;
  timeUpdate(element: Element, eventProperties?: {}): boolean;
  touchCancel(element: Element, eventProperties?: {}): boolean;
  touchEnd(element: Element, eventProperties?: {}): boolean;
  touchMove(element: Element, eventProperties?: {}): boolean;
  touchStart(element: Element, eventProperties?: {}): boolean;
  transitionEnd(element: Element, eventProperties?: {}): boolean;
  volumeChange(element: Element, eventProperties?: {}): boolean;
  waiting(element: Element, eventProperties?: {}): boolean;
  wheel(element: Element, eventProperties?: {}): boolean;
}

/**
 * Create custom DOM events
 * @param eventName - Name of the event to create
 * @param node - Target element for the event
 * @param init - Event initialization options
 * @returns Created event object
 */
function createEvent(eventName: string, node: Element, init?: {}): Event;

const fireEvent: FireEvent;

Usage Examples:

import { fireEvent, createEvent, within } from '@storybook/test';

export const FireEventStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
    
    // Fire common events
    fireEvent.click(button);
    fireEvent.mouseOver(button);
    fireEvent.focus(button);
    
    // Fire events with custom properties
    fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' });
    
    // Create and fire custom events
    const customEvent = createEvent('custom-event', button, { detail: { custom: 'data' } });
    fireEvent(button, customEvent);
  },
};

Debug and Utility Functions

Helper functions for debugging, introspection, and DOM manipulation.

/**
 * Pretty-print DOM element structure for debugging
 * @param element - Element to print (defaults to document.body)
 * @param maxLength - Maximum string length (default: 7000)
 * @param options - Formatting options
 * @returns Formatted string representation of DOM
 */
function prettyDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): string;

/**
 * Log DOM structure to console
 * @param element - Element to log (defaults to document.body)
 * @param maxLength - Maximum string length
 * @param options - Formatting options
 */
function logDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): void;

/**
 * Log all ARIA roles found in the DOM
 * @param element - Container element (defaults to document.body)
 */
function logRoles(element?: Element | HTMLDocument): void;

/**
 * Get all ARIA roles present in an element
 * @param element - Container element
 * @returns Array of role information objects
 */
function getRoles(element: Element): Array<{ role: string; elements: Element[] }>;

/**
 * Get text content of a node including hidden text
 * @param node - DOM node to extract text from
 * @returns Text content string
 */
function getNodeText(node: Node): string;

/**
 * Check if an element is inaccessible to screen readers
 * @param element - Element to check
 * @returns True if element is inaccessible
 */
function isInaccessible(element: Element): boolean;

/**
 * Get suggested query for finding an element
 * @param element - Element to analyze
 * @param variant - Query variant preference ('get' | 'find' | 'query')
 * @param method - Query method preference
 * @returns Suggested query string
 */
function getSuggestedQuery(element: Element, variant?: string, method?: string): string;

/**
 * Create an error with element information
 * @param message - Error message
 * @param container - Container element for context
 * @returns Error with DOM context
 */
function getElementError(message: string, container: Element): Error;

/**
 * Get default text normalizer function
 * @returns Default normalizer function
 */
function getDefaultNormalizer(): (text: string) => string;

/**
 * Pretty format any value for display
 * @param value - Value to format
 * @returns Formatted string representation
 */
function prettyFormat(value: any): string;

Usage Examples:

import { 
  prettyDOM, logDOM, logRoles, getRoles, getNodeText, 
  isInaccessible, getSuggestedQuery, within 
} from '@storybook/test';

export const DebugUtilsStory = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    
    // Debug DOM structure
    console.log('DOM structure:', prettyDOM(canvasElement));
    logDOM(canvasElement, 1000); // Log to console with max length
    
    // Analyze accessibility
    logRoles(canvasElement); // Log all ARIA roles
    const roles = getRoles(canvasElement);
    console.log('Available roles:', roles.map(r => r.role));
    
    // Get element text content
    const button = canvas.getByRole('button');
    console.log('Button text:', getNodeText(button));
    
    // Check accessibility
    if (isInaccessible(button)) {
      console.warn('Button is not accessible to screen readers');
    }
    
    // Get suggested query for element
    const suggestion = getSuggestedQuery(button, 'get');
    console.log('Suggested query:', suggestion);
  },
};

Query Building and Customization

Advanced utilities for building custom queries and extending testing-library functionality.

/**
 * Build custom query functions from base query logic
 * @param queryName - Name for the query type
 * @param queryAllByAttribute - Function to find all matching elements
 * @param getMultipleError - Error function for multiple matches
 * @param getMissingError - Error function for no matches
 * @returns Object with get, getAll, query, queryAll, find, findAll functions
 */
function buildQueries(
  queryName: string,
  queryAllByAttribute: (container: Element, ...args: any[]) => Element[],
  getMultipleError: (container: Element, ...args: any[]) => string,
  getMissingError: (container: Element, ...args: any[]) => string
): {
  [key: string]: (...args: any[]) => Element | Element[] | null | Promise<Element | Element[]>;
};

/**
 * Get bound query functions for a specific element
 * @param element - Element to bind queries to
 * @returns Object with all query functions pre-bound to the element
 */
function getQueriesForElement(element: Element): BoundFunctions<typeof queries>;

/**
 * Collection of all base query functions
 */
const queries: {
  queryByRole: typeof queryByRole;
  queryAllByRole: typeof queryAllByRole;
  getByRole: typeof getByRole;
  getAllByRole: typeof getAllByRole;
  findByRole: typeof findByRole;
  findAllByRole: typeof findAllByRole;
  // ... all other query variants
};

/**
 * Helpers for building custom queries
 */
const queryHelpers: {
  queryByAttribute: typeof queryByAttribute;
  queryAllByAttribute: typeof queryAllByAttribute;
  buildQueries: typeof buildQueries;
  getMultipleElementsFoundError: (message: string, container: Element) => Error;
  getElementError: typeof getElementError;
};

Usage Examples:

import { buildQueries, getQueriesForElement, queryHelpers } from '@storybook/test';

// Build custom query for data-cy attributes (Cypress style)  
const [
  queryByCy,
  queryAllByCy,
  getByCy,
  getAllByCy,
  findByCy,
  findAllByCy,
] = buildQueries(
  'Cy',
  (container, id) => queryHelpers.queryAllByAttribute(container, 'data-cy', id),
  () => 'Found multiple elements with the same data-cy attribute',
  () => 'Unable to find an element with the data-cy attribute'
);

export const CustomQueryStory = {
  play: async ({ canvasElement }) => {
    // Use custom query
    const element = getByCy(canvasElement, 'submit-button');
    
    // Get all queries bound to an element
    const boundQueries = getQueriesForElement(canvasElement);
    const sameElement = boundQueries.getByCy('submit-button');
  },
};

Screen Object (Legacy - Use within() instead)

The screen object provides global queries but is discouraged in Storybook. Use within(canvasElement) instead.

/**
 * Global query object (deprecated in Storybook context)
 * @deprecated Use within(canvasElement) instead for Storybook stories
 */
const screen: BoundFunctions<typeof queries>;

Migration Example:

import { screen, within } from '@storybook/test';

export const MigrationStory = {
  play: async ({ canvasElement }) => {
    // ❌ Don't use screen in Storybook (will show warning)
    // const button = screen.getByRole('button');
    
    // ✅ Use within() instead
    const canvas = within(canvasElement);
    const button = canvas.getByRole('button');
  },
};

Install with Tessl CLI

npx tessl i tessl/npm-storybook--test

docs

assertions.md

dom-testing.md

index.md

mocking.md

tile.json