0
# Querying Elements
1
2
Find elements using accessible patterns. Inherited from @testing-library/dom.
3
4
## Query Types
5
6
Three variants for each query method:
7
8
- **getBy\***: Throws if not found → Use when element should exist
9
- **queryBy\***: Returns null if not found → Use for negative assertions
10
- **findBy\***: Returns Promise → Use for async elements
11
12
Plural forms (**getAll\***, **queryAll\***, **findAll\***) return arrays.
13
14
## Query Priority (Best to Worst)
15
16
1. **getByRole** - Most accessible, encourages proper ARIA
17
2. **getByLabelText** - For form inputs
18
3. **getByPlaceholderText** - If no label exists
19
4. **getByText** - For non-interactive content
20
5. **getByDisplayValue** - For filled inputs
21
6. **getByAltText** - For images
22
7. **getByTitle** - When other options unavailable
23
8. **getByTestId** - Last resort, not user-visible
24
25
## Common Queries
26
27
### getByRole (Primary)
28
29
**Primary query method.** Finds elements by their ARIA role. Encourages accessible markup and tests behavior users can actually see.
30
31
```typescript { .api }
32
/**
33
* Find element by ARIA role
34
* @param role - ARIA role (button, heading, textbox, etc.)
35
* @param options - Additional constraints
36
* @returns Matching HTMLElement
37
* @throws When no element or multiple elements found
38
*/
39
getByRole(role: string, options?: ByRoleOptions): HTMLElement;
40
41
/**
42
* Find all elements by ARIA role
43
* @returns Array of matching HTMLElements
44
* @throws When no elements found
45
*/
46
getAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];
47
48
interface ByRoleOptions {
49
/**
50
* Filter by accessible name (label, aria-label, text content)
51
*/
52
name?: string | RegExp;
53
54
/**
55
* Filter by accessible description (aria-describedby, title)
56
*/
57
description?: string | RegExp;
58
59
/**
60
* Include hidden elements (default: false)
61
*/
62
hidden?: boolean;
63
64
/**
65
* Filter by selected state (for options, tabs, etc.)
66
*/
67
selected?: boolean;
68
69
/**
70
* Filter by checked state (for checkboxes, radio buttons)
71
*/
72
checked?: boolean;
73
74
/**
75
* Filter by pressed state (for toggle buttons)
76
*/
77
pressed?: boolean;
78
79
/**
80
* Filter by current state (for navigation items)
81
*/
82
current?: boolean | string;
83
84
/**
85
* Filter by expanded state (for accordions, dropdowns)
86
*/
87
expanded?: boolean;
88
89
/**
90
* Filter by heading level (for heading role)
91
*/
92
level?: number;
93
94
/**
95
* Enable exact matching (default: true)
96
*/
97
exact?: boolean;
98
99
/**
100
* Custom text normalizer function
101
*/
102
normalizer?: (text: string) => string;
103
104
/**
105
* Enable query fallbacks
106
*/
107
queryFallbacks?: boolean;
108
}
109
```
110
111
**Common Roles:**
112
```typescript
113
screen.getByRole('button'); // <button>, role="button"
114
screen.getByRole('link'); // <a href>
115
screen.getByRole('heading', { level: 1 }); // <h1>
116
screen.getByRole('textbox'); // <input type="text">, <textarea>
117
screen.getByRole('checkbox'); // <input type="checkbox">
118
screen.getByRole('radio'); // <input type="radio">
119
screen.getByRole('combobox'); // <select>
120
screen.getByRole('img'); // <img>
121
screen.getByRole('dialog'); // Modal/dialog
122
screen.getByRole('alert'); // Alert message
123
screen.getByRole('navigation'); // <nav>
124
screen.getByRole('list'); // <ul>, <ol>
125
screen.getByRole('listitem'); // <li>
126
```
127
128
**Examples:**
129
```typescript
130
// By role and name
131
screen.getByRole('button', { name: /submit/i });
132
133
// By state
134
screen.getByRole('checkbox', { checked: true });
135
screen.getByRole('button', { pressed: true });
136
137
// Heading level
138
screen.getByRole('heading', { level: 2 });
139
```
140
141
### getByLabelText
142
143
Finds form elements by their associated label text. Best for form inputs.
144
145
```typescript { .api }
146
/**
147
* Find form element by label text
148
* @param text - Label text (string or regex)
149
* @param options - Matching options
150
* @returns Matching HTMLElement
151
*/
152
getByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
153
getAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
154
155
interface SelectorMatcherOptions {
156
/**
157
* Custom CSS selector to filter results
158
*/
159
selector?: string;
160
161
/**
162
* Enable exact matching (default: true)
163
*/
164
exact?: boolean;
165
166
/**
167
* Custom text normalizer function
168
*/
169
normalizer?: (text: string) => string;
170
}
171
```
172
173
**Examples:**
174
```typescript
175
// <label htmlFor="email">Email</label><input id="email" />
176
screen.getByLabelText('Email');
177
178
// <input aria-label="Search" />
179
screen.getByLabelText('Search');
180
181
// <label><input /> Remember me</label>
182
screen.getByLabelText('Remember me');
183
```
184
185
### getByText
186
187
Finds elements by their text content. Good for non-interactive text elements.
188
189
```typescript { .api }
190
/**
191
* Find element by text content
192
* @param text - Text content (string or regex)
193
* @param options - Matching options
194
* @returns Matching HTMLElement
195
*/
196
getByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement;
197
getAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
198
```
199
200
**Examples:**
201
```typescript
202
screen.getByText('Hello World');
203
screen.getByText(/hello/i); // Case insensitive
204
screen.getByText('Hello', { exact: false }); // Partial match
205
screen.getByText('Submit', { selector: 'button' }); // Filter by tag
206
```
207
208
### getByPlaceholderText
209
210
Finds elements by placeholder text. Use as a last resort as placeholders are not accessible replacements for labels.
211
212
```typescript { .api }
213
/**
214
* Find element by placeholder text
215
* @param text - Placeholder text (string or regex)
216
* @param options - Matching options
217
* @returns Matching HTMLElement
218
*/
219
getByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
220
getAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];
221
222
interface MatcherOptions {
223
/**
224
* Enable exact matching (default: true)
225
*/
226
exact?: boolean;
227
228
/**
229
* Custom text normalizer function
230
*/
231
normalizer?: (text: string) => string;
232
}
233
```
234
235
### getByDisplayValue
236
237
Finds form elements by their current value. Useful for testing filled forms.
238
239
```typescript { .api }
240
/**
241
* Find form element by current value
242
* @param value - Current display value (string or regex)
243
* @param options - Matching options
244
* @returns Matching HTMLElement
245
*/
246
getByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement;
247
getAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];
248
```
249
250
### getByAltText
251
252
Finds images and other elements by their alt text.
253
254
```typescript { .api }
255
/**
256
* Find element by alt text
257
* @param text - Alt text (string or regex)
258
* @param options - Matching options
259
* @returns Matching HTMLElement
260
*/
261
getByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement;
262
getAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];
263
```
264
265
### getByTitle
266
267
Finds elements by their title attribute.
268
269
```typescript { .api }
270
/**
271
* Find element by title attribute
272
* @param title - Title text (string or regex)
273
* @param options - Matching options
274
* @returns Matching HTMLElement
275
*/
276
getByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement;
277
getAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];
278
```
279
280
### getByTestId
281
282
Finds elements by data-testid attribute. Use as a last resort when other queries are not applicable.
283
284
```typescript { .api }
285
/**
286
* Find element by test ID
287
* @param testId - Test ID value (string or regex)
288
* @param options - Matching options
289
* @returns Matching HTMLElement
290
*/
291
getByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement;
292
getAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];
293
```
294
295
## Query Variants
296
297
### queryBy* Queries
298
299
Non-throwing variants that return `null` when element is not found. Use for asserting elements don't exist.
300
301
```typescript { .api }
302
queryByRole(role: string, options?: ByRoleOptions): HTMLElement | null;
303
queryAllByRole(role: string, options?: ByRoleOptions): HTMLElement[];
304
305
queryByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
306
queryAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
307
308
queryByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
309
queryAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];
310
311
queryByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement | null;
312
queryAllByText(text: string | RegExp, options?: SelectorMatcherOptions): HTMLElement[];
313
314
queryByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement | null;
315
queryAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): HTMLElement[];
316
317
queryByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement | null;
318
queryAllByAltText(text: string | RegExp, options?: MatcherOptions): HTMLElement[];
319
320
queryByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement | null;
321
queryAllByTitle(title: string | RegExp, options?: MatcherOptions): HTMLElement[];
322
323
queryByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement | null;
324
queryAllByTestId(testId: string | RegExp, options?: MatcherOptions): HTMLElement[];
325
```
326
327
### findBy* Queries
328
329
Async queries that return a promise resolving when element is found. Use for elements that appear asynchronously.
330
331
```typescript { .api }
332
findByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement>;
333
findAllByRole(role: string, options?: ByRoleOptions): Promise<HTMLElement[]>;
334
335
findByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
336
findAllByLabelText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;
337
338
findByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
339
findAllByPlaceholderText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
340
341
findByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement>;
342
findAllByText(text: string | RegExp, options?: SelectorMatcherOptions): Promise<HTMLElement[]>;
343
344
findByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
345
findAllByDisplayValue(value: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
346
347
findByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
348
findAllByAltText(text: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
349
350
findByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
351
findAllByTitle(title: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
352
353
findByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement>;
354
findAllByTestId(testId: string | RegExp, options?: MatcherOptions): Promise<HTMLElement[]>;
355
```
356
357
## Utility Functions
358
359
### screen Object
360
361
Pre-bound queries to `document.body` for convenient access.
362
363
```typescript { .api }
364
import { screen } from '@testing-library/react';
365
366
const screen: {
367
// All query functions
368
getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
369
// ... all other queries
370
371
// Debug utility
372
debug: (element?: HTMLElement, maxLength?: number) => void;
373
374
// Log roles utility
375
logTestingPlaygroundURL: (element?: HTMLElement) => void;
376
};
377
```
378
379
### within Function
380
381
Get queries bound to a specific element for scoped searches.
382
383
```typescript { .api }
384
/**
385
* Get queries scoped to a specific element
386
* @param element - Element to scope queries to
387
* @returns Object with all query functions bound to element
388
*/
389
function within(element: HTMLElement): {
390
getByRole: (role: string, options?: ByRoleOptions) => HTMLElement;
391
// ... all other query functions
392
};
393
```
394
395
## Query Patterns
396
397
### Synchronous Queries
398
```typescript
399
// Element exists
400
const button = screen.getByRole('button');
401
402
// Element may not exist
403
const dialog = screen.queryByRole('dialog');
404
if (dialog) {
405
// Handle dialog
406
}
407
408
// Negative assertion
409
expect(screen.queryByText('Error')).not.toBeInTheDocument();
410
411
// Multiple elements
412
const items = screen.getAllByRole('listitem');
413
expect(items).toHaveLength(5);
414
```
415
416
### Async Queries
417
```typescript
418
// Wait for element to appear
419
const message = await screen.findByText('Loaded');
420
421
// Wait for multiple
422
const buttons = await screen.findAllByRole('button');
423
424
// With custom timeout (default: 1000ms)
425
const slow = await screen.findByText('Slow', {}, { timeout: 3000 });
426
```
427
428
### Scoped Queries
429
```typescript
430
// Query within specific element
431
const modal = screen.getByRole('dialog');
432
const closeButton = within(modal).getByRole('button', { name: /close/i });
433
434
// Multiple scopes
435
const nav = screen.getByRole('navigation');
436
const homeLink = within(nav).getByRole('link', { name: /home/i });
437
```
438
439
## Query Access
440
441
```typescript
442
// 1. screen object (recommended)
443
import { screen } from '@testing-library/react';
444
screen.getByRole('button');
445
446
// 2. render result
447
const { getByRole } = render(<Component />);
448
getByRole('button');
449
450
// 3. within for scoped queries
451
import { within } from '@testing-library/react';
452
within(container).getByRole('button');
453
```
454
455
## Testing Patterns
456
457
### Form Testing
458
```typescript
459
test('form submission', () => {
460
render(<LoginForm />);
461
462
const emailInput = screen.getByLabelText(/email/i);
463
const passwordInput = screen.getByLabelText(/password/i);
464
const submitButton = screen.getByRole('button', { name: /log in/i });
465
466
fireEvent.change(emailInput, { target: { value: 'user@example.com' } });
467
fireEvent.change(passwordInput, { target: { value: 'password123' } });
468
fireEvent.click(submitButton);
469
});
470
```
471
472
### List Testing
473
```typescript
474
test('displays items', () => {
475
render(<TodoList items={['Task 1', 'Task 2']} />);
476
477
const items = screen.getAllByRole('listitem');
478
expect(items).toHaveLength(2);
479
expect(items[0]).toHaveTextContent('Task 1');
480
});
481
```
482
483
### Modal Testing
484
```typescript
485
test('modal interactions', async () => {
486
render(<App />);
487
488
// Open modal
489
fireEvent.click(screen.getByRole('button', { name: /open/i }));
490
491
// Wait for modal
492
const modal = await screen.findByRole('dialog');
493
expect(modal).toBeInTheDocument();
494
495
// Query within modal
496
const title = within(modal).getByRole('heading');
497
expect(title).toHaveTextContent('Modal Title');
498
});
499
```
500
501
### Conditional Rendering
502
```typescript
503
test('shows error on failure', async () => {
504
render(<Component />);
505
506
// Initially no error
507
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
508
509
// Trigger error
510
fireEvent.click(screen.getByRole('button'));
511
512
// Error appears
513
const alert = await screen.findByRole('alert');
514
expect(alert).toHaveTextContent('Error occurred');
515
});
516
```
517
518
## Debug Helpers
519
520
```typescript
521
// Print DOM
522
screen.debug();
523
screen.debug(screen.getByRole('button'));
524
525
// Print available roles
526
import { logRoles } from '@testing-library/react';
527
logRoles(container);
528
529
// Get suggested queries
530
// Error messages suggest better queries when getBy* fails
531
```
532
533
## Best Practices
534
535
1. **Prefer getByRole** - Most accessible and resilient to changes
536
2. **Use regex for flexibility** - `/submit/i` instead of "Submit Form"
537
3. **Avoid testid** - Use semantic queries unless absolutely necessary
538
4. **Use within()** - Scope queries to avoid ambiguity
539
5. **Async with findBy** - Don't use getBy in setTimeout, use findBy
540
6. **queryBy for absence** - Only use queryBy for negative assertions
541