0
# Enhanced Assertions
1
2
DOM-specific matchers extending Vitest's expect API with browser-aware assertions and element polling for reliable test assertions in browser environments.
3
4
## Capabilities
5
6
### Element Polling
7
8
Enhanced expectation API that polls for elements to handle asynchronous DOM changes.
9
10
```typescript { .api }
11
/**
12
* Element polling expectation - shorthand for expect.poll(() => locator.element())
13
* Waits for element to exist and be available for assertions
14
* @param element - Element, locator, or null to poll for
15
* @param options - Polling options including timeout and interval
16
* @returns Promise-based assertion interface for DOM elements
17
*/
18
expect.element<T extends Element | Locator | null>(
19
element: T,
20
options?: ExpectPollOptions
21
): PromisifyDomAssertion<Awaited<Element | null>>;
22
23
interface ExpectPollOptions {
24
/** Maximum time to wait for condition in milliseconds */
25
timeout?: number;
26
/** Polling interval in milliseconds */
27
interval?: number;
28
/** Custom error message */
29
message?: string;
30
}
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { expect } from "vitest";
37
import { page } from "@vitest/browser/context";
38
39
// Wait for element to appear and assert visibility
40
await expect.element(page.getByText("Loading complete")).toBeVisible();
41
42
// Poll with custom timeout
43
await expect.element(
44
page.getByRole("alert"),
45
{ timeout: 10000 }
46
).toBeInTheDocument();
47
48
// Poll for element that might not exist
49
await expect.element(
50
page.getByText("Optional message").query(),
51
{ timeout: 2000 }
52
).toBeNull();
53
54
// Poll with custom interval and message
55
await expect.element(
56
page.getByTestId("data-loaded"),
57
{
58
timeout: 5000,
59
interval: 100,
60
message: "Data should load within 5 seconds"
61
}
62
).toBeVisible();
63
64
// Complex polling scenarios
65
test("async form submission", async () => {
66
await userEvent.click(page.getByRole("button", { name: "Submit" }));
67
68
// Wait for loading spinner
69
await expect.element(page.getByTestId("spinner")).toBeVisible();
70
71
// Wait for spinner to disappear
72
await expect.element(page.getByTestId("spinner")).not.toBeVisible();
73
74
// Wait for success message
75
await expect.element(page.getByText("Form submitted successfully")).toBeVisible();
76
});
77
```
78
79
### DOM-Specific Matchers
80
81
Extended assertion methods specifically designed for DOM elements and browser testing.
82
83
#### Visibility Matchers
84
85
```typescript { .api }
86
/**
87
* Assert element visibility states
88
*/
89
interface DOMMatchers {
90
/** Element is visible to the user */
91
toBeVisible(): Promise<void>;
92
/** Element exists in DOM but may not be visible */
93
toBeInTheDocument(): Promise<void>;
94
/** Element is hidden from the user */
95
toBeHidden(): Promise<void>;
96
}
97
```
98
99
**Usage Examples:**
100
101
```typescript
102
// Basic visibility assertions
103
await expect.element(page.getByRole("button")).toBeVisible();
104
await expect.element(page.getByTestId("hidden-panel")).toBeHidden();
105
await expect.element(page.getByText("Success")).toBeInTheDocument();
106
107
// Negative assertions
108
await expect.element(page.getByRole("alert")).not.toBeVisible();
109
await expect.element(page.getByText("Error")).not.toBeInTheDocument();
110
111
// Conditional visibility
112
test("menu visibility", async () => {
113
const menu = page.getByRole("menu");
114
const menuButton = page.getByRole("button", { name: "Menu" });
115
116
// Menu initially hidden
117
await expect.element(menu).toBeHidden();
118
119
// Click to show menu
120
await userEvent.click(menuButton);
121
await expect.element(menu).toBeVisible();
122
123
// Click to hide menu
124
await userEvent.click(menuButton);
125
await expect.element(menu).toBeHidden();
126
});
127
```
128
129
#### Content Matchers
130
131
```typescript { .api }
132
/**
133
* Assert element content and text
134
*/
135
interface ContentMatchers {
136
/** Element contains specific text content */
137
toHaveTextContent(text: string | RegExp): Promise<void>;
138
/** Input element has specific value */
139
toHaveValue(value: string | number): Promise<void>;
140
/** Element has specific attribute with value */
141
toHaveAttribute(name: string, value?: string | RegExp): Promise<void>;
142
/** Element has specific CSS class */
143
toHaveClass(className: string | RegExp | (string | RegExp)[]): Promise<void>;
144
/** Element has specific CSS styles */
145
toHaveStyle(style: string | object): Promise<void>;
146
/** Element is empty (has no content) */
147
toBeEmptyDOMElement(): Promise<void>;
148
/** Element contains another element */
149
toContainElement(element: Element): Promise<void>;
150
/** Element contains specific HTML content */
151
toContainHTML(html: string): Promise<void>;
152
}
153
```
154
155
**Usage Examples:**
156
157
```typescript
158
// Text content assertions
159
await expect.element(page.getByRole("heading")).toHaveTextContent("Welcome");
160
await expect.element(page.getByTestId("counter")).toHaveTextContent(/\d+/);
161
162
// Form value assertions
163
await expect.element(page.getByLabelText("Username")).toHaveValue("john");
164
await expect.element(page.getByRole("slider")).toHaveValue(50);
165
166
// Attribute assertions
167
await expect.element(page.getByRole("link")).toHaveAttribute("href", "/home");
168
await expect.element(page.getByRole("button")).toHaveAttribute("disabled");
169
await expect.element(page.getByTestId("image")).toHaveAttribute("alt", /profile/i);
170
171
// CSS class assertions
172
await expect.element(page.getByRole("alert")).toHaveClass("error");
173
await expect.element(page.getByTestId("card")).toHaveClass(["card", "shadow"]);
174
await expect.element(page.getByRole("button")).toHaveClass(/btn-primary/);
175
176
// Style assertions
177
await expect.element(page.getByTestId("modal")).toHaveStyle("display: block");
178
await expect.element(page.getByRole("progressbar")).toHaveStyle({
179
width: "75%",
180
backgroundColor: "green"
181
});
182
```
183
184
#### State Matchers
185
186
```typescript { .api }
187
/**
188
* Assert element states and properties
189
*/
190
interface StateMatchers {
191
/** Form control is disabled */
192
toBeDisabled(): Promise<void>;
193
/** Form control is enabled */
194
toBeEnabled(): Promise<void>;
195
/** Checkbox or radio is checked */
196
toBeChecked(): Promise<void>;
197
/** Checkbox is partially checked (indeterminate state) */
198
toBePartiallyChecked(): Promise<void>;
199
/** Element has focus */
200
toHaveFocus(): Promise<void>;
201
/** Select element has specific option selected */
202
toHaveDisplayValue(value: string | RegExp | (string | RegExp)[]): Promise<void>;
203
/** Form element is required */
204
toBeRequired(): Promise<void>;
205
/** Form element is invalid */
206
toBeInvalid(): Promise<void>;
207
/** Form element is valid */
208
toBeValid(): Promise<void>;
209
/** Form has specific values for all fields */
210
toHaveFormValues(values: Record<string, any>): Promise<void>;
211
/** Input has specific text selection */
212
toHaveSelection(selection: { start: number; end: number }): Promise<void>;
213
}
214
```
215
216
**Usage Examples:**
217
218
```typescript
219
// Form state assertions
220
await expect.element(page.getByLabelText("Submit")).toBeDisabled();
221
await expect.element(page.getByLabelText("Email")).toBeEnabled();
222
await expect.element(page.getByLabelText("Terms")).toBeChecked();
223
await expect.element(page.getByRole("textbox")).toHaveFocus();
224
225
// Validation state assertions
226
await expect.element(page.getByLabelText("Email")).toBeRequired();
227
await expect.element(page.getByLabelText("Invalid Email")).toBeInvalid();
228
await expect.element(page.getByLabelText("Valid Email")).toBeValid();
229
230
// Select assertions
231
await expect.element(page.getByLabelText("Country")).toHaveDisplayValue("United States");
232
await expect.element(page.getByLabelText("Multiple Select")).toHaveDisplayValue(["Option 1", "Option 2"]);
233
234
// Interactive state testing
235
test("form interaction states", async () => {
236
const input = page.getByLabelText("Username");
237
const button = page.getByRole("button", { name: "Submit" });
238
239
// Initially disabled
240
await expect.element(button).toBeDisabled();
241
242
// Enable after input
243
await userEvent.fill(input, "testuser");
244
await expect.element(button).toBeEnabled();
245
246
// Focus management
247
await userEvent.click(input);
248
await expect.element(input).toHaveFocus();
249
250
await userEvent.tab();
251
await expect.element(button).toHaveFocus();
252
});
253
```
254
255
### Accessibility Matchers
256
257
Specialized matchers for testing accessibility properties and ARIA attributes.
258
259
```typescript { .api }
260
/**
261
* Accessibility-focused assertions
262
*/
263
interface AccessibilityMatchers {
264
/** Element has specific accessible name */
265
toHaveAccessibleName(name: string | RegExp): Promise<void>;
266
/** Element has specific accessible description */
267
toHaveAccessibleDescription(description: string | RegExp): Promise<void>;
268
/** Element has specific accessible error message */
269
toHaveAccessibleErrorMessage(message: string | RegExp): Promise<void>;
270
/** Element has specific ARIA role */
271
toHaveRole(role: string): Promise<void>;
272
/** Element has specific ARIA attribute */
273
toHaveAriaAttribute(name: string, value?: string | RegExp): Promise<void>;
274
}
275
```
276
277
**Usage Examples:**
278
279
```typescript
280
// Accessible name assertions
281
await expect.element(page.getByRole("button")).toHaveAccessibleName("Submit Form");
282
await expect.element(page.getByRole("img")).toHaveAccessibleName(/profile picture/i);
283
284
// Accessible description
285
await expect.element(page.getByRole("textbox")).toHaveAccessibleDescription("Enter your email address");
286
287
// ARIA role assertions
288
await expect.element(page.getByTestId("alert")).toHaveRole("alert");
289
await expect.element(page.getByTestId("navigation")).toHaveRole("navigation");
290
291
// ARIA attribute assertions
292
await expect.element(page.getByRole("tab")).toHaveAriaAttribute("selected", "true");
293
await expect.element(page.getByRole("menu")).toHaveAriaAttribute("expanded", "false");
294
await expect.element(page.getByRole("progressbar")).toHaveAriaAttribute("valuenow", "75");
295
296
// Comprehensive accessibility testing
297
test("accessible form", async () => {
298
const form = page.getByRole("form");
299
const emailInput = form.getByLabelText("Email");
300
const submitButton = form.getByRole("button", { name: "Submit" });
301
302
// Form structure
303
await expect.element(form).toHaveRole("form");
304
await expect.element(form).toHaveAccessibleName("Contact Form");
305
306
// Input accessibility
307
await expect.element(emailInput).toHaveRole("textbox");
308
await expect.element(emailInput).toBeRequired();
309
await expect.element(emailInput).toHaveAccessibleDescription("We'll never share your email");
310
311
// Button accessibility
312
await expect.element(submitButton).toHaveRole("button");
313
await expect.element(submitButton).toHaveAccessibleName("Submit Contact Form");
314
});
315
```
316
317
### Advanced Assertion Patterns
318
319
Complex assertion scenarios for comprehensive browser testing.
320
321
**Waiting for Multiple Elements:**
322
323
```typescript
324
test("page load completion", async () => {
325
// Wait for multiple elements to appear
326
await Promise.all([
327
expect.element(page.getByRole("navigation")).toBeVisible(),
328
expect.element(page.getByRole("main")).toBeVisible(),
329
expect.element(page.getByRole("contentinfo")).toBeVisible()
330
]);
331
332
// Ensure loading indicators are gone
333
await Promise.all([
334
expect.element(page.getByTestId("loading-spinner")).not.toBeVisible(),
335
expect.element(page.getByText("Loading...")).not.toBeInTheDocument()
336
]);
337
});
338
```
339
340
**Conditional Assertions:**
341
342
```typescript
343
test("conditional error display", async () => {
344
await userEvent.click(page.getByRole("button", { name: "Submit" }));
345
346
// Wait for either success or error
347
const successMessage = page.getByText("Success").query();
348
const errorMessage = page.getByRole("alert").query();
349
350
if (successMessage) {
351
await expect.element(successMessage).toBeVisible();
352
await expect.element(errorMessage).not.toBeInTheDocument();
353
} else {
354
await expect.element(errorMessage).toBeVisible();
355
await expect.element(errorMessage).toHaveTextContent(/error/i);
356
}
357
});
358
```
359
360
**List and Collection Assertions:**
361
362
```typescript
363
test("dynamic list updates", async () => {
364
const list = page.getByRole("list");
365
366
// Initial state
367
await expect.element(list).toBeVisible();
368
369
// Add item
370
await userEvent.click(page.getByRole("button", { name: "Add Item" }));
371
372
// Wait for new item to appear
373
await expect.element(page.getByText("New Item")).toBeVisible();
374
375
// Count items
376
const items = page.getByRole("listitem").elements();
377
expect(items).toHaveLength(4);
378
379
// Check each item
380
for (let i = 0; i < items.length; i++) {
381
await expect.element(page.getByRole("listitem").nth(i)).toBeVisible();
382
}
383
});
384
```
385
386
### Configuration
387
388
Configure polling behavior globally or per-test:
389
390
```typescript
391
// In test setup or vitest.config.ts
392
expect.poll.timeout = 10000; // Default timeout for expect.element
393
expect.poll.interval = 200; // Default polling interval
394
395
// Per-test configuration
396
test("slow loading test", async () => {
397
// Override for this test
398
await expect.element(
399
page.getByText("Slow content"),
400
{ timeout: 30000, interval: 500 }
401
).toBeVisible();
402
});
403
```
404
405
## Types
406
407
Enhanced assertion type definitions:
408
409
```typescript { .api }
410
/**
411
* Options for element polling expectations
412
*/
413
interface ExpectPollOptions {
414
timeout?: number;
415
interval?: number;
416
message?: string;
417
}
418
419
/**
420
* Promise-based DOM assertion interface
421
*/
422
type PromisifyDomAssertion<T> = {
423
[K in keyof Assertion<T>]: Assertion<T>[K] extends (...args: infer A) => infer R
424
? (...args: A) => Promise<R>
425
: Assertion<T>[K];
426
};
427
428
/**
429
* Extended expect interface with element method
430
*/
431
interface ExpectStatic {
432
element<T extends Element | Locator | null>(
433
element: T,
434
options?: ExpectPollOptions
435
): PromisifyDomAssertion<Awaited<Element | null>>;
436
}
437
```