or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

assertions.mddom-testing.mdindex.mdmocking.md

dom-testing.mddocs/

0

# DOM Testing

1

2

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.

3

4

## Capabilities

5

6

### Element Queries

7

8

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).

9

10

#### Get Queries (Sync, throws if not found)

11

12

```typescript { .api }

13

/**

14

* Get element by ARIA role

15

* @param container - Container element to search within

16

* @param role - ARIA role to search for

17

* @param options - Additional query options

18

* @returns Found element (throws if not found)

19

*/

20

function getByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement;

21

function getAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];

22

23

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

24

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

25

26

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

27

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

28

29

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

30

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

31

32

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

33

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

34

35

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

36

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

37

38

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

39

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

40

41

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

42

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

43

44

interface ByRoleOptions extends MatcherOptions {

45

checked?: boolean;

46

selected?: boolean;

47

expanded?: boolean;

48

pressed?: boolean;

49

level?: number;

50

name?: string | RegExp;

51

description?: string | RegExp;

52

}

53

54

interface SelectorMatcherOptions extends MatcherOptions {

55

selector?: string;

56

}

57

58

interface MatcherOptions {

59

exact?: boolean;

60

normalizer?: (text: string) => string;

61

}

62

```

63

64

#### Find Queries (Async, throws if not found)

65

66

```typescript { .api }

67

/**

68

* Asynchronously find element by ARIA role

69

* @param container - Container element to search within

70

* @param role - ARIA role to search for

71

* @param options - Additional query options

72

* @param waitForOptions - Options for waiting

73

* @returns Promise resolving to found element

74

*/

75

function findByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement>;

76

function findAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions, waitForOptions?: WaitForOptions): Promise<HTMLElement[]>;

77

78

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

79

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

80

81

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

82

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

83

84

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

85

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

86

87

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

88

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

89

90

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

91

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

92

93

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

94

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

95

96

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

97

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

98

```

99

100

#### Query Queries (Sync, returns null if not found)

101

102

```typescript { .api }

103

/**

104

* Query element by ARIA role (returns null if not found)

105

* @param container - Container element to search within

106

* @param role - ARIA role to search for

107

* @param options - Additional query options

108

* @returns Found element or null

109

*/

110

function queryByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement | null;

111

function queryAllByRole(container: HTMLElement, role: string, options?: ByRoleOptions): HTMLElement[];

112

113

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

114

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

115

116

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

117

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

118

119

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

120

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

121

122

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

123

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

124

125

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

126

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

127

128

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

129

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

130

131

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

132

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

133

134

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

135

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

136

```

137

138

**Query Usage Examples:**

139

140

```typescript

141

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

142

143

export const QueryStory = {

144

play: async ({ canvasElement }) => {

145

const canvas = within(canvasElement);

146

147

// Get queries (throw if not found)

148

const button = canvas.getByRole('button', { name: /submit/i });

149

const heading = canvas.getByText('Welcome');

150

const input = canvas.getByLabelText('Email');

151

152

// Find queries (async, wait for element)

153

const asyncContent = await canvas.findByText('Loaded content');

154

const asyncButton = await canvas.findByRole('button', { name: /save/i });

155

156

// Query queries (return null if not found)

157

const optionalElement = canvas.queryByTestId('optional-feature');

158

if (optionalElement) {

159

// Element exists, interact with it

160

expect(optionalElement).toBeInTheDocument();

161

}

162

163

// Multiple elements

164

const allButtons = canvas.getAllByRole('button');

165

const allInputs = await canvas.findAllByRole('textbox');

166

167

expect(allButtons.length).toBeGreaterThan(0);

168

expect(allInputs.length).toBeGreaterThan(0);

169

},

170

};

171

```

172

173

### Element Scoping

174

175

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

176

177

```typescript { .api }

178

/**

179

* Create bound queries scoped to a specific element

180

* @param element - Element to scope queries within

181

* @returns Object with all query functions bound to the element

182

*/

183

function within(element: HTMLElement): BoundFunctions<typeof queries>;

184

185

type BoundFunctions<T> = {

186

[K in keyof T]: T[K] extends (...args: any[]) => any

187

? (...args: Parameters<T[K]>) => ReturnType<T[K]>

188

: T[K];

189

};

190

191

/**

192

* Global screen object for document-wide queries (deprecated in Storybook context)

193

* Use within(canvasElement) instead

194

*/

195

const screen: BoundFunctions<typeof queries>;

196

```

197

198

**Scoping Usage Examples:**

199

200

```typescript

201

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

202

203

export const ScopingStory = {

204

play: async ({ canvasElement }) => {

205

// Scope queries to the story canvas

206

const canvas = within(canvasElement);

207

208

// Find a modal or specific section

209

const modal = canvas.getByRole('dialog');

210

const modalScope = within(modal);

211

212

// Queries within modal only

213

const modalButton = modalScope.getByRole('button', { name: /close/i });

214

const modalHeading = modalScope.getByRole('heading');

215

216

// This would find buttons anywhere in canvas

217

const allCanvasButtons = canvas.getAllByRole('button');

218

219

// This only finds buttons within the modal

220

const modalButtons = modalScope.getAllByRole('button');

221

222

expect(modalButtons.length).toBeLesserThanOrEqual(allCanvasButtons.length);

223

},

224

};

225

```

226

227

### User Interactions

228

229

Simulate user interactions with elements using the instrumented userEvent API.

230

231

```typescript { .api }

232

interface UserEvent {

233

/**

234

* Click an element

235

* @param element - Element to click

236

* @param options - Click options

237

*/

238

click(element: Element, options?: ClickOptions): Promise<void>;

239

240

/**

241

* Double-click an element

242

* @param element - Element to double-click

243

* @param options - Click options

244

*/

245

dblClick(element: Element, options?: ClickOptions): Promise<void>;

246

247

/**

248

* Type text into an element

249

* @param element - Element to type into

250

* @param text - Text to type

251

* @param options - Typing options

252

*/

253

type(element: Element, text: string, options?: TypeOptions): Promise<void>;

254

255

/**

256

* Clear an input element

257

* @param element - Element to clear

258

*/

259

clear(element: Element): Promise<void>;

260

261

/**

262

* Select options in a select element

263

* @param element - Select element

264

* @param values - Values to select

265

*/

266

selectOptions(element: Element, values: string | string[]): Promise<void>;

267

268

/**

269

* Deselect options in a select element

270

* @param element - Select element

271

* @param values - Values to deselect

272

*/

273

deselectOptions(element: Element, values: string | string[]): Promise<void>;

274

275

/**

276

* Upload files to a file input

277

* @param element - File input element

278

* @param file - File or files to upload

279

*/

280

upload(element: Element, file: File | File[]): Promise<void>;

281

282

/**

283

* Tab to the next focusable element

284

* @param options - Tab options

285

*/

286

tab(options?: TabOptions): Promise<void>;

287

288

/**

289

* Hover over an element

290

* @param element - Element to hover

291

*/

292

hover(element: Element): Promise<void>;

293

294

/**

295

* Stop hovering over an element

296

* @param element - Element to stop hovering

297

*/

298

unhover(element: Element): Promise<void>;

299

300

/**

301

* Paste text into an element

302

* @param text - Text to paste

303

*/

304

paste(text: string): Promise<void>;

305

306

/**

307

* Press keyboard key(s)

308

* @param keys - Key or key combination to press

309

*/

310

keyboard(keys: string): Promise<void>;

311

}

312

313

interface ClickOptions {

314

button?: number;

315

detail?: number;

316

ctrlKey?: boolean;

317

shiftKey?: boolean;

318

altKey?: boolean;

319

metaKey?: boolean;

320

}

321

322

interface TypeOptions {

323

delay?: number;

324

skipClick?: boolean;

325

skipAutoClose?: boolean;

326

initialSelectionStart?: number;

327

initialSelectionEnd?: number;

328

}

329

330

interface TabOptions {

331

shift?: boolean;

332

}

333

```

334

335

**User Interaction Examples:**

336

337

```typescript

338

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

339

340

export const InteractionStory = {

341

play: async ({ canvasElement }) => {

342

const canvas = within(canvasElement);

343

344

// Basic interactions

345

const button = canvas.getByRole('button', { name: /submit/i });

346

await userEvent.click(button);

347

348

const input = canvas.getByLabelText('Username');

349

await userEvent.type(input, 'john.doe');

350

expect(input).toHaveValue('john.doe');

351

352

// Clear and retype

353

await userEvent.clear(input);

354

await userEvent.type(input, 'jane.doe');

355

expect(input).toHaveValue('jane.doe');

356

357

// Select dropdown

358

const select = canvas.getByLabelText('Country');

359

await userEvent.selectOptions(select, 'us');

360

expect(select).toHaveValue('us');

361

362

// Multiple selections

363

const multiSelect = canvas.getByLabelText('Skills');

364

await userEvent.selectOptions(multiSelect, ['javascript', 'typescript']);

365

366

// File upload

367

const fileInput = canvas.getByLabelText('Upload file');

368

const file = new File(['content'], 'test.txt', { type: 'text/plain' });

369

await userEvent.upload(fileInput, file);

370

expect(fileInput.files![0]).toBe(file);

371

372

// Keyboard interactions

373

await userEvent.keyboard('{Enter}');

374

await userEvent.keyboard('{Escape}');

375

await userEvent.keyboard('{ctrl}a'); // Select all

376

377

// Hover effects

378

const tooltip = canvas.getByText('Hover me');

379

await userEvent.hover(tooltip);

380

await expect(canvas.findByText('Tooltip content')).resolves.toBeInTheDocument();

381

382

await userEvent.unhover(tooltip);

383

expect(canvas.queryByText('Tooltip content')).not.toBeInTheDocument();

384

},

385

};

386

```

387

388

### Async Utilities

389

390

Wait for conditions and element changes in the DOM.

391

392

```typescript { .api }

393

/**

394

* Wait for a condition to be met

395

* @param callback - Function to test condition

396

* @param options - Wait options

397

* @returns Promise resolving when condition is met

398

*/

399

function waitFor<T>(callback: () => T | Promise<T>, options?: WaitForOptions): Promise<T>;

400

401

/**

402

* Wait for elements to be removed from the DOM

403

* @param callback - Function returning elements to wait for removal

404

* @param options - Wait options

405

* @returns Promise resolving when elements are removed

406

*/

407

function waitForElementToBeRemoved(

408

callback: () => Element | Element[] | Promise<Element | Element[]>,

409

options?: WaitForOptions

410

): Promise<void>;

411

412

interface WaitForOptions {

413

container?: HTMLElement;

414

timeout?: number;

415

interval?: number;

416

onTimeout?: (error: Error) => Error;

417

mutationObserverOptions?: MutationObserverInit;

418

}

419

```

420

421

**Async Utility Examples:**

422

423

```typescript

424

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

425

426

export const AsyncStory = {

427

play: async ({ canvasElement }) => {

428

const canvas = within(canvasElement);

429

430

// Wait for async content to appear

431

await waitFor(() => {

432

expect(canvas.getByText('Loading complete')).toBeInTheDocument();

433

});

434

435

// Wait for API call to complete

436

const loadButton = canvas.getByRole('button', { name: /load data/i });

437

await userEvent.click(loadButton);

438

439

await waitFor(

440

async () => {

441

const dataList = canvas.getByRole('list');

442

const items = canvas.getAllByRole('listitem');

443

expect(items.length).toBeGreaterThan(0);

444

return items;

445

},

446

{ timeout: 5000 }

447

);

448

449

// Wait for element removal

450

const dismissButton = canvas.getByRole('button', { name: /dismiss/i });

451

await userEvent.click(dismissButton);

452

453

await waitForElementToBeRemoved(

454

() => canvas.queryByText('Notification message'),

455

{ timeout: 3000 }

456

);

457

458

// Custom timeout and interval

459

await waitFor(

460

() => {

461

const status = canvas.getByTestId('status');

462

expect(status).toHaveTextContent('Ready');

463

},

464

{

465

timeout: 10000,

466

interval: 500,

467

}

468

);

469

},

470

};

471

```

472

473

### DOM Utilities

474

475

Additional utilities for DOM interaction and debugging.

476

477

```typescript { .api }

478

/**

479

* Create DOM events programmatically

480

* @param eventName - Name of the event

481

* @param node - Target node for the event

482

* @param init - Event initialization options

483

*/

484

function createEvent(eventName: string, node: Element, init?: EventInit): Event;

485

486

/**

487

* Fire events on DOM elements

488

*/

489

const fireEvent: FireFunction & FireObject;

490

491

interface FireFunction {

492

(element: Element, event: Event): boolean;

493

}

494

495

interface FireObject {

496

click(element: Element, options?: EventInit): boolean;

497

change(element: Element, options?: EventInit): boolean;

498

input(element: Element, options?: EventInit): boolean;

499

keyDown(element: Element, options?: KeyboardEventInit): boolean;

500

keyPress(element: Element, options?: KeyboardEventInit): boolean;

501

keyUp(element: Element, options?: KeyboardEventInit): boolean;

502

mouseDown(element: Element, options?: MouseEventInit): boolean;

503

mouseEnter(element: Element, options?: MouseEventInit): boolean;

504

mouseLeave(element: Element, options?: MouseEventInit): boolean;

505

mouseMove(element: Element, options?: MouseEventInit): boolean;

506

mouseOut(element: Element, options?: MouseEventInit): boolean;

507

mouseOver(element: Element, options?: MouseEventInit): boolean;

508

mouseUp(element: Element, options?: MouseEventInit): boolean;

509

focus(element: Element, options?: FocusEventInit): boolean;

510

blur(element: Element, options?: FocusEventInit): boolean;

511

submit(element: Element, options?: EventInit): boolean;

512

}

513

514

/**

515

* Pretty print DOM structure for debugging

516

* @param element - Element to print

517

* @param maxLength - Maximum length of output

518

* @param options - Pretty printing options

519

*/

520

function prettyDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): string;

521

522

/**

523

* Log DOM structure to console

524

* @param element - Element to log

525

* @param maxLength - Maximum length of output

526

* @param options - Pretty printing options

527

*/

528

function logDOM(element?: Element, maxLength?: number, options?: PrettyDOMOptions): void;

529

530

/**

531

* Log ARIA roles for debugging accessibility

532

* @param element - Element to analyze

533

*/

534

function logRoles(element: Element): void;

535

536

/**

537

* Get suggested query for an element

538

* @param element - Element to analyze

539

* @param variant - Query variant to suggest

540

*/

541

function getSuggestedQuery(element: Element, variant?: 'get' | 'find' | 'query'): string;

542

543

/**

544

* Get default text normalizer function

545

* @returns Default normalizer function

546

*/

547

function getDefaultNormalizer(): (text: string) => string;

548

549

/**

550

* Get error message for element not found

551

* @param message - Error message

552

* @param container - Container element

553

* @returns Error instance

554

*/

555

function getElementError(message: string, container: HTMLElement): Error;

556

557

/**

558

* Get text content from a DOM node

559

* @param node - DOM node to extract text from

560

* @returns Text content

561

*/

562

function getNodeText(node: Node): string;

563

564

/**

565

* Get bound query functions for a specific element

566

* @param element - Element to bind queries to

567

* @returns Object with bound query functions

568

*/

569

function getQueriesForElement(element: HTMLElement): BoundFunctions<typeof queries>;

570

571

/**

572

* Get all ARIA roles for an element

573

* @param element - Element to analyze

574

* @returns Object with role information

575

*/

576

function getRoles(element: Element): { [role: string]: HTMLElement[] };

577

578

/**

579

* Check if element is accessible

580

* @param element - Element to check

581

* @returns True if element is accessible

582

*/

583

function isInaccessible(element: Element): boolean;

584

585

/**

586

* Pretty format values for debugging

587

* @param value - Value to format

588

* @param options - Formatting options

589

* @returns Formatted string

590

*/

591

function prettyFormat(value: any, options?: any): string;

592

593

/**

594

* Query helper utilities

595

*/

596

const queryHelpers: {

597

queryByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement | null;

598

queryAllByAttribute: (attribute: string, container: HTMLElement, id: string, options?: MatcherOptions) => HTMLElement[];

599

getElementError: (message: string, container: HTMLElement) => Error;

600

};

601

602

/**

603

* All available query functions

604

*/

605

const queries: {

606

queryByRole: typeof queryByRole;

607

queryAllByRole: typeof queryAllByRole;

608

getByRole: typeof getByRole;

609

getAllByRole: typeof getAllByRole;

610

findByRole: typeof findByRole;

611

findAllByRole: typeof findAllByRole;

612

// ... (all other query functions)

613

};

614

615

interface PrettyDOMOptions {

616

highlight?: boolean;

617

filterNode?: (node: Node) => boolean;

618

}

619

```

620

621

**DOM Utility Examples:**

622

623

```typescript

624

import {

625

fireEvent,

626

createEvent,

627

prettyDOM,

628

logDOM,

629

logRoles,

630

getSuggestedQuery,

631

within

632

} from '@storybook/test';

633

634

export const UtilityStory = {

635

play: async ({ canvasElement }) => {

636

const canvas = within(canvasElement);

637

638

// Manual event firing

639

const button = canvas.getByRole('button');

640

fireEvent.click(button);

641

fireEvent.mouseEnter(button);

642

fireEvent.mouseLeave(button);

643

644

// Custom events

645

const customEvent = createEvent('customEvent', button, {

646

bubbles: true,

647

cancelable: true

648

});

649

fireEvent(button, customEvent);

650

651

// Debugging utilities

652

const form = canvas.getByRole('form');

653

654

// Log DOM structure

655

console.log('Form DOM:', prettyDOM(form));

656

logDOM(form); // Logs to console with nice formatting

657

658

// Analyze accessibility

659

logRoles(form); // Shows all ARIA roles

660

661

// Get suggested queries

662

const input = canvas.getByLabelText('Email');

663

console.log('Suggested query:', getSuggestedQuery(input));

664

// Output: getByLabelText(/email/i)

665

666

// Additional utility functions

667

console.log('Node text:', getNodeText(input));

668

console.log('Is accessible:', !isInaccessible(input));

669

console.log('Element roles:', getRoles(form));

670

671

// Get bound queries for specific element

672

const boundQueries = getQueriesForElement(form);

673

const formButton = boundQueries.getByRole('button');

674

675

// Pretty formatting for debugging

676

console.log('Pretty formatted value:', prettyFormat({ key: 'value' }));

677

},

678

};

679

```

680

681

### Configuration

682

683

Configure testing-library behavior globally.

684

685

```typescript { .api }

686

/**

687

* Configure testing-library options

688

* @param options - Configuration options

689

*/

690

function configure(options: ConfigureOptions): void;

691

692

/**

693

* Get current configuration

694

* @returns Current configuration object

695

*/

696

function getConfig(): Config;

697

698

interface ConfigureOptions {

699

testIdAttribute?: string;

700

asyncUtilTimeout?: number;

701

computedStyleSupportsPseudoElements?: boolean;

702

defaultHidden?: boolean;

703

showOriginalStackTrace?: boolean;

704

throwSuggestions?: boolean;

705

getElementError?: (message: string, container: HTMLElement) => Error;

706

}

707

708

interface Config extends ConfigureOptions {

709

testIdAttribute: string;

710

asyncUtilTimeout: number;

711

computedStyleSupportsPseudoElements: boolean;

712

defaultHidden: boolean;

713

showOriginalStackTrace: boolean;

714

throwSuggestions: boolean;

715

}

716

```

717

718

**Configuration Examples:**

719

720

```typescript

721

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

722

723

// Configure at story level or in preview.js

724

configure({

725

testIdAttribute: 'data-cy', // Use Cypress test IDs

726

asyncUtilTimeout: 5000, // Increase timeout

727

throwSuggestions: false, // Disable query suggestions

728

});

729

730

export const ConfigStory = {

731

play: async () => {

732

const config = getConfig();

733

console.log('Current timeout:', config.asyncUtilTimeout);

734

735

// Now getByTestId uses 'data-cy' instead of 'data-testid'

736

},

737

};

738

```

739

740

### Query by Attribute

741

742

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

743

744

```typescript { .api }

745

/**

746

* Query elements by any attribute value

747

* @param container - Container element to search within

748

* @param attribute - Attribute name to search for

749

* @param value - Attribute value to match

750

* @param options - Additional query options

751

* @returns Found element or null

752

*/

753

function queryByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement | null;

754

function queryAllByAttribute(container: HTMLElement, attribute: string, value: string, options?: MatcherOptions): HTMLElement[];

755

```

756

757

**Usage Examples:**

758

759

```typescript

760

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

761

762

export const AttributeQueryStory = {

763

play: async ({ canvasElement }) => {

764

const canvas = within(canvasElement);

765

766

// Query by custom attribute

767

const element = queryByAttribute(canvasElement, 'data-custom', 'value');

768

if (element) {

769

console.log('Found element with data-custom="value"');

770

}

771

772

// Query all elements with specific attribute

773

const allElements = queryAllByAttribute(canvasElement, 'aria-expanded', 'true');

774

console.log(`Found ${allElements.length} expanded elements`);

775

},

776

};

777

```

778

779

### Fire Events

780

781

Manually trigger DOM events for advanced testing scenarios.

782

783

```typescript { .api }

784

interface FireEvent {

785

(element: Element, event: Event): boolean;

786

787

// Event-specific methods

788

abort(element: Element, eventProperties?: {}): boolean;

789

animationEnd(element: Element, eventProperties?: {}): boolean;

790

animationIteration(element: Element, eventProperties?: {}): boolean;

791

animationStart(element: Element, eventProperties?: {}): boolean;

792

blur(element: Element, eventProperties?: {}): boolean;

793

canPlay(element: Element, eventProperties?: {}): boolean;

794

canPlayThrough(element: Element, eventProperties?: {}): boolean;

795

change(element: Element, eventProperties?: {}): boolean;

796

click(element: Element, eventProperties?: {}): boolean;

797

contextMenu(element: Element, eventProperties?: {}): boolean;

798

copy(element: Element, eventProperties?: {}): boolean;

799

cut(element: Element, eventProperties?: {}): boolean;

800

doubleClick(element: Element, eventProperties?: {}): boolean;

801

drag(element: Element, eventProperties?: {}): boolean;

802

dragEnd(element: Element, eventProperties?: {}): boolean;

803

dragEnter(element: Element, eventProperties?: {}): boolean;

804

dragExit(element: Element, eventProperties?: {}): boolean;

805

dragLeave(element: Element, eventProperties?: {}): boolean;

806

dragOver(element: Element, eventProperties?: {}): boolean;

807

dragStart(element: Element, eventProperties?: {}): boolean;

808

drop(element: Element, eventProperties?: {}): boolean;

809

durationChange(element: Element, eventProperties?: {}): boolean;

810

emptied(element: Element, eventProperties?: {}): boolean;

811

encrypted(element: Element, eventProperties?: {}): boolean;

812

ended(element: Element, eventProperties?: {}): boolean;

813

error(element: Element, eventProperties?: {}): boolean;

814

focus(element: Element, eventProperties?: {}): boolean;

815

focusIn(element: Element, eventProperties?: {}): boolean;

816

focusOut(element: Element, eventProperties?: {}): boolean;

817

input(element: Element, eventProperties?: {}): boolean;

818

invalid(element: Element, eventProperties?: {}): boolean;

819

keyDown(element: Element, eventProperties?: {}): boolean;

820

keyPress(element: Element, eventProperties?: {}): boolean;

821

keyUp(element: Element, eventProperties?: {}): boolean;

822

load(element: Element, eventProperties?: {}): boolean;

823

loadStart(element: Element, eventProperties?: {}): boolean;

824

loadedData(element: Element, eventProperties?: {}): boolean;

825

loadedMetadata(element: Element, eventProperties?: {}): boolean;

826

mouseDown(element: Element, eventProperties?: {}): boolean;

827

mouseEnter(element: Element, eventProperties?: {}): boolean;

828

mouseLeave(element: Element, eventProperties?: {}): boolean;

829

mouseMove(element: Element, eventProperties?: {}): boolean;

830

mouseOut(element: Element, eventProperties?: {}): boolean;

831

mouseOver(element: Element, eventProperties?: {}): boolean;

832

mouseUp(element: Element, eventProperties?: {}): boolean;

833

paste(element: Element, eventProperties?: {}): boolean;

834

pause(element: Element, eventProperties?: {}): boolean;

835

play(element: Element, eventProperties?: {}): boolean;

836

playing(element: Element, eventProperties?: {}): boolean;

837

pointerCancel(element: Element, eventProperties?: {}): boolean;

838

pointerDown(element: Element, eventProperties?: {}): boolean;

839

pointerEnter(element: Element, eventProperties?: {}): boolean;

840

pointerLeave(element: Element, eventProperties?: {}): boolean;

841

pointerMove(element: Element, eventProperties?: {}): boolean;

842

pointerOut(element: Element, eventProperties?: {}): boolean;

843

pointerOver(element: Element, eventProperties?: {}): boolean;

844

pointerUp(element: Element, eventProperties?: {}): boolean;

845

progress(element: Element, eventProperties?: {}): boolean;

846

rateChange(element: Element, eventProperties?: {}): boolean;

847

scroll(element: Element, eventProperties?: {}): boolean;

848

seeked(element: Element, eventProperties?: {}): boolean;

849

seeking(element: Element, eventProperties?: {}): boolean;

850

select(element: Element, eventProperties?: {}): boolean;

851

stalled(element: Element, eventProperties?: {}): boolean;

852

submit(element: Element, eventProperties?: {}): boolean;

853

suspend(element: Element, eventProperties?: {}): boolean;

854

timeUpdate(element: Element, eventProperties?: {}): boolean;

855

touchCancel(element: Element, eventProperties?: {}): boolean;

856

touchEnd(element: Element, eventProperties?: {}): boolean;

857

touchMove(element: Element, eventProperties?: {}): boolean;

858

touchStart(element: Element, eventProperties?: {}): boolean;

859

transitionEnd(element: Element, eventProperties?: {}): boolean;

860

volumeChange(element: Element, eventProperties?: {}): boolean;

861

waiting(element: Element, eventProperties?: {}): boolean;

862

wheel(element: Element, eventProperties?: {}): boolean;

863

}

864

865

/**

866

* Create custom DOM events

867

* @param eventName - Name of the event to create

868

* @param node - Target element for the event

869

* @param init - Event initialization options

870

* @returns Created event object

871

*/

872

function createEvent(eventName: string, node: Element, init?: {}): Event;

873

874

const fireEvent: FireEvent;

875

```

876

877

**Usage Examples:**

878

879

```typescript

880

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

881

882

export const FireEventStory = {

883

play: async ({ canvasElement }) => {

884

const canvas = within(canvasElement);

885

const button = canvas.getByRole('button');

886

887

// Fire common events

888

fireEvent.click(button);

889

fireEvent.mouseOver(button);

890

fireEvent.focus(button);

891

892

// Fire events with custom properties

893

fireEvent.keyDown(button, { key: 'Enter', code: 'Enter' });

894

895

// Create and fire custom events

896

const customEvent = createEvent('custom-event', button, { detail: { custom: 'data' } });

897

fireEvent(button, customEvent);

898

},

899

};

900

```

901

902

### Debug and Utility Functions

903

904

Helper functions for debugging, introspection, and DOM manipulation.

905

906

```typescript { .api }

907

/**

908

* Pretty-print DOM element structure for debugging

909

* @param element - Element to print (defaults to document.body)

910

* @param maxLength - Maximum string length (default: 7000)

911

* @param options - Formatting options

912

* @returns Formatted string representation of DOM

913

*/

914

function prettyDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): string;

915

916

/**

917

* Log DOM structure to console

918

* @param element - Element to log (defaults to document.body)

919

* @param maxLength - Maximum string length

920

* @param options - Formatting options

921

*/

922

function logDOM(element?: Element | HTMLDocument, maxLength?: number, options?: {}): void;

923

924

/**

925

* Log all ARIA roles found in the DOM

926

* @param element - Container element (defaults to document.body)

927

*/

928

function logRoles(element?: Element | HTMLDocument): void;

929

930

/**

931

* Get all ARIA roles present in an element

932

* @param element - Container element

933

* @returns Array of role information objects

934

*/

935

function getRoles(element: Element): Array<{ role: string; elements: Element[] }>;

936

937

/**

938

* Get text content of a node including hidden text

939

* @param node - DOM node to extract text from

940

* @returns Text content string

941

*/

942

function getNodeText(node: Node): string;

943

944

/**

945

* Check if an element is inaccessible to screen readers

946

* @param element - Element to check

947

* @returns True if element is inaccessible

948

*/

949

function isInaccessible(element: Element): boolean;

950

951

/**

952

* Get suggested query for finding an element

953

* @param element - Element to analyze

954

* @param variant - Query variant preference ('get' | 'find' | 'query')

955

* @param method - Query method preference

956

* @returns Suggested query string

957

*/

958

function getSuggestedQuery(element: Element, variant?: string, method?: string): string;

959

960

/**

961

* Create an error with element information

962

* @param message - Error message

963

* @param container - Container element for context

964

* @returns Error with DOM context

965

*/

966

function getElementError(message: string, container: Element): Error;

967

968

/**

969

* Get default text normalizer function

970

* @returns Default normalizer function

971

*/

972

function getDefaultNormalizer(): (text: string) => string;

973

974

/**

975

* Pretty format any value for display

976

* @param value - Value to format

977

* @returns Formatted string representation

978

*/

979

function prettyFormat(value: any): string;

980

```

981

982

**Usage Examples:**

983

984

```typescript

985

import {

986

prettyDOM, logDOM, logRoles, getRoles, getNodeText,

987

isInaccessible, getSuggestedQuery, within

988

} from '@storybook/test';

989

990

export const DebugUtilsStory = {

991

play: async ({ canvasElement }) => {

992

const canvas = within(canvasElement);

993

994

// Debug DOM structure

995

console.log('DOM structure:', prettyDOM(canvasElement));

996

logDOM(canvasElement, 1000); // Log to console with max length

997

998

// Analyze accessibility

999

logRoles(canvasElement); // Log all ARIA roles

1000

const roles = getRoles(canvasElement);

1001

console.log('Available roles:', roles.map(r => r.role));

1002

1003

// Get element text content

1004

const button = canvas.getByRole('button');

1005

console.log('Button text:', getNodeText(button));

1006

1007

// Check accessibility

1008

if (isInaccessible(button)) {

1009

console.warn('Button is not accessible to screen readers');

1010

}

1011

1012

// Get suggested query for element

1013

const suggestion = getSuggestedQuery(button, 'get');

1014

console.log('Suggested query:', suggestion);

1015

},

1016

};

1017

```

1018

1019

### Query Building and Customization

1020

1021

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

1022

1023

```typescript { .api }

1024

/**

1025

* Build custom query functions from base query logic

1026

* @param queryName - Name for the query type

1027

* @param queryAllByAttribute - Function to find all matching elements

1028

* @param getMultipleError - Error function for multiple matches

1029

* @param getMissingError - Error function for no matches

1030

* @returns Object with get, getAll, query, queryAll, find, findAll functions

1031

*/

1032

function buildQueries(

1033

queryName: string,

1034

queryAllByAttribute: (container: Element, ...args: any[]) => Element[],

1035

getMultipleError: (container: Element, ...args: any[]) => string,

1036

getMissingError: (container: Element, ...args: any[]) => string

1037

): {

1038

[key: string]: (...args: any[]) => Element | Element[] | null | Promise<Element | Element[]>;

1039

};

1040

1041

/**

1042

* Get bound query functions for a specific element

1043

* @param element - Element to bind queries to

1044

* @returns Object with all query functions pre-bound to the element

1045

*/

1046

function getQueriesForElement(element: Element): BoundFunctions<typeof queries>;

1047

1048

/**

1049

* Collection of all base query functions

1050

*/

1051

const queries: {

1052

queryByRole: typeof queryByRole;

1053

queryAllByRole: typeof queryAllByRole;

1054

getByRole: typeof getByRole;

1055

getAllByRole: typeof getAllByRole;

1056

findByRole: typeof findByRole;

1057

findAllByRole: typeof findAllByRole;

1058

// ... all other query variants

1059

};

1060

1061

/**

1062

* Helpers for building custom queries

1063

*/

1064

const queryHelpers: {

1065

queryByAttribute: typeof queryByAttribute;

1066

queryAllByAttribute: typeof queryAllByAttribute;

1067

buildQueries: typeof buildQueries;

1068

getMultipleElementsFoundError: (message: string, container: Element) => Error;

1069

getElementError: typeof getElementError;

1070

};

1071

```

1072

1073

**Usage Examples:**

1074

1075

```typescript

1076

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

1077

1078

// Build custom query for data-cy attributes (Cypress style)

1079

const [

1080

queryByCy,

1081

queryAllByCy,

1082

getByCy,

1083

getAllByCy,

1084

findByCy,

1085

findAllByCy,

1086

] = buildQueries(

1087

'Cy',

1088

(container, id) => queryHelpers.queryAllByAttribute(container, 'data-cy', id),

1089

() => 'Found multiple elements with the same data-cy attribute',

1090

() => 'Unable to find an element with the data-cy attribute'

1091

);

1092

1093

export const CustomQueryStory = {

1094

play: async ({ canvasElement }) => {

1095

// Use custom query

1096

const element = getByCy(canvasElement, 'submit-button');

1097

1098

// Get all queries bound to an element

1099

const boundQueries = getQueriesForElement(canvasElement);

1100

const sameElement = boundQueries.getByCy('submit-button');

1101

},

1102

};

1103

```

1104

1105

### Screen Object (Legacy - Use within() instead)

1106

1107

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

1108

1109

```typescript { .api }

1110

/**

1111

* Global query object (deprecated in Storybook context)

1112

* @deprecated Use within(canvasElement) instead for Storybook stories

1113

*/

1114

const screen: BoundFunctions<typeof queries>;

1115

```

1116

1117

**Migration Example:**

1118

1119

```typescript

1120

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

1121

1122

export const MigrationStory = {

1123

play: async ({ canvasElement }) => {

1124

// ❌ Don't use screen in Storybook (will show warning)

1125

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

1126

1127

// ✅ Use within() instead

1128

const canvas = within(canvasElement);

1129

const button = canvas.getByRole('button');

1130

},

1131

};

1132

```