craftdriver 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +1 -1
  3. package/dist/cli/snapshot.js +31 -4
  4. package/dist/cli/snapshot.js.map +1 -1
  5. package/dist/lib/bidi/index.d.ts +4 -5
  6. package/dist/lib/bidi/index.d.ts.map +1 -1
  7. package/dist/lib/bidi/index.js +18 -8
  8. package/dist/lib/bidi/index.js.map +1 -1
  9. package/dist/lib/bidi/logs.d.ts +12 -1
  10. package/dist/lib/bidi/logs.d.ts.map +1 -1
  11. package/dist/lib/bidi/logs.js +20 -8
  12. package/dist/lib/bidi/logs.js.map +1 -1
  13. package/dist/lib/browser.d.ts +6 -9
  14. package/dist/lib/browser.d.ts.map +1 -1
  15. package/dist/lib/browser.js +6 -4
  16. package/dist/lib/browser.js.map +1 -1
  17. package/dist/lib/by.d.ts +18 -0
  18. package/dist/lib/by.d.ts.map +1 -1
  19. package/dist/lib/by.js +141 -17
  20. package/dist/lib/by.js.map +1 -1
  21. package/dist/lib/locator.d.ts +28 -0
  22. package/dist/lib/locator.d.ts.map +1 -1
  23. package/dist/lib/locator.js +28 -0
  24. package/dist/lib/locator.js.map +1 -1
  25. package/docs/browser-api.md +4 -2
  26. package/docs/browser-logs.md +8 -23
  27. package/docs/public/examples/a11y.html +44 -0
  28. package/docs/public/examples/clock.html +83 -0
  29. package/docs/public/examples/console-errors.html +344 -0
  30. package/docs/public/examples/dialogs.html +42 -0
  31. package/docs/public/examples/download.html +31 -0
  32. package/docs/public/examples/dynamic.html +131 -0
  33. package/docs/public/examples/emulate.html +127 -0
  34. package/docs/public/examples/evaluate.html +28 -0
  35. package/docs/public/examples/hover-select.html +295 -0
  36. package/docs/public/examples/iframe-child.html +51 -0
  37. package/docs/public/examples/iframes.html +35 -0
  38. package/docs/public/examples/keyboard.html +195 -0
  39. package/docs/public/examples/locator.html +131 -0
  40. package/docs/public/examples/login.html +116 -0
  41. package/docs/public/examples/mobile.html +270 -0
  42. package/docs/public/examples/mouse.html +505 -0
  43. package/docs/public/examples/network.html +293 -0
  44. package/docs/public/examples/popup-target.html +26 -0
  45. package/docs/public/examples/popup.html +50 -0
  46. package/docs/public/examples/screenshot.html +99 -0
  47. package/docs/public/examples/selectors.html +150 -0
  48. package/docs/public/examples/session.html +514 -0
  49. package/docs/public/examples/upload.html +36 -0
  50. package/docs/recipes/accessibility-gate.md +24 -40
  51. package/docs/recipes/console-error-gate.md +27 -39
  52. package/docs/recipes/file-upload-download.md +24 -44
  53. package/docs/recipes/find-elements.md +142 -0
  54. package/docs/recipes/login-once-reuse-session.md +22 -45
  55. package/docs/recipes/mobile-flow-with-network-and-logs.md +22 -41
  56. package/docs/recipes/mock-api-and-assert-network.md +22 -35
  57. package/docs/recipes/multi-user-contexts.md +32 -43
  58. package/docs/recipes/page-objects.md +52 -0
  59. package/docs/recipes/trace-failing-test.md +31 -44
  60. package/docs/recipes/virtual-clock-time-sensitive-ui.md +24 -40
  61. package/docs/recipes/vitest-browser-lifecycle.md +15 -15
  62. package/docs/recipes.md +20 -1
  63. package/docs/selectors.md +291 -229
  64. package/docs/tracing.md +2 -2
  65. package/package.json +5 -1
  66. package/skills/craftdriver/SKILL.md +3 -2
  67. package/skills/craftdriver/cheatsheet.md +2 -1
  68. package/skills/craftdriver/patterns.md +2 -5
@@ -0,0 +1,514 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Session Management Example</title>
8
+ <style>
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ max-width: 900px;
12
+ margin: 40px auto;
13
+ padding: 20px;
14
+ background: #f5f5f5;
15
+ }
16
+
17
+ .card {
18
+ background: white;
19
+ border-radius: 8px;
20
+ padding: 20px;
21
+ margin-bottom: 20px;
22
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
23
+ }
24
+
25
+ h1 {
26
+ color: #333;
27
+ }
28
+
29
+ h2 {
30
+ color: #666;
31
+ margin-top: 0;
32
+ }
33
+
34
+ button {
35
+ background: #007bff;
36
+ color: white;
37
+ border: none;
38
+ padding: 10px 20px;
39
+ border-radius: 4px;
40
+ cursor: pointer;
41
+ margin-right: 10px;
42
+ margin-bottom: 10px;
43
+ }
44
+
45
+ button:hover {
46
+ background: #0056b3;
47
+ }
48
+
49
+ button.danger {
50
+ background: #dc3545;
51
+ }
52
+
53
+ button.danger:hover {
54
+ background: #c82333;
55
+ }
56
+
57
+ button.success {
58
+ background: #28a745;
59
+ }
60
+
61
+ button.success:hover {
62
+ background: #218838;
63
+ }
64
+
65
+ .grid {
66
+ display: grid;
67
+ grid-template-columns: 1fr 1fr;
68
+ gap: 20px;
69
+ }
70
+
71
+ @media (max-width: 768px) {
72
+ .grid {
73
+ grid-template-columns: 1fr;
74
+ }
75
+ }
76
+
77
+ .display {
78
+ background: #f8f9fa;
79
+ border: 1px solid #dee2e6;
80
+ border-radius: 4px;
81
+ padding: 15px;
82
+ margin-top: 15px;
83
+ font-family: monospace;
84
+ white-space: pre-wrap;
85
+ word-break: break-all;
86
+ min-height: 80px;
87
+ }
88
+
89
+ input[type="text"] {
90
+ padding: 8px 12px;
91
+ border: 1px solid #ced4da;
92
+ border-radius: 4px;
93
+ margin-right: 10px;
94
+ margin-bottom: 10px;
95
+ width: 150px;
96
+ }
97
+
98
+ label {
99
+ display: inline-block;
100
+ margin-bottom: 5px;
101
+ font-weight: 500;
102
+ }
103
+
104
+ .form-group {
105
+ margin-bottom: 15px;
106
+ }
107
+
108
+ .status {
109
+ padding: 10px;
110
+ border-radius: 4px;
111
+ margin-bottom: 15px;
112
+ }
113
+
114
+ .status.logged-in {
115
+ background: #d4edda;
116
+ border: 1px solid #c3e6cb;
117
+ color: #155724;
118
+ }
119
+
120
+ .status.logged-out {
121
+ background: #f8d7da;
122
+ border: 1px solid #f5c6cb;
123
+ color: #721c24;
124
+ }
125
+
126
+ code {
127
+ background: #e9ecef;
128
+ padding: 2px 6px;
129
+ border-radius: 3px;
130
+ }
131
+
132
+ table {
133
+ width: 100%;
134
+ border-collapse: collapse;
135
+ margin-top: 10px;
136
+ }
137
+
138
+ th,
139
+ td {
140
+ text-align: left;
141
+ padding: 8px;
142
+ border-bottom: 1px solid #dee2e6;
143
+ }
144
+
145
+ th {
146
+ background: #f8f9fa;
147
+ }
148
+ </style>
149
+ </head>
150
+
151
+ <body>
152
+ <h1>🔐 Session Management Example</h1>
153
+ <p>This page demonstrates cookies and localStorage for testing BiDi session state persistence.</p>
154
+
155
+ <!-- Current Session Status -->
156
+ <div class="card">
157
+ <h2>Current Session Status</h2>
158
+ <div id="session-status" class="status logged-out">
159
+ Checking session...
160
+ </div>
161
+ <div id="user-info" style="display: none;">
162
+ <table>
163
+ <tr>
164
+ <th>Username</th>
165
+ <td id="display-username">-</td>
166
+ </tr>
167
+ <tr>
168
+ <th>Role</th>
169
+ <td id="display-role">-</td>
170
+ </tr>
171
+ <tr>
172
+ <th>Login Time</th>
173
+ <td id="display-login-time">-</td>
174
+ </tr>
175
+ <tr>
176
+ <th>Session ID</th>
177
+ <td id="display-session-id">-</td>
178
+ </tr>
179
+ </table>
180
+ </div>
181
+ </div>
182
+
183
+ <!-- Login Form -->
184
+ <div id="login-section" class="card">
185
+ <h2>Login</h2>
186
+ <p>Login to create session cookies and localStorage data.</p>
187
+
188
+ <div class="form-group">
189
+ <label for="username">Username:</label><br>
190
+ <input type="text" id="username" placeholder="Enter username" value="testuser">
191
+ </div>
192
+
193
+ <div class="form-group">
194
+ <label for="password">Password:</label><br>
195
+ <input type="text" id="password" placeholder="Enter password" value="secret123">
196
+ </div>
197
+
198
+ <button id="login-btn" class="success" onclick="login()">Login</button>
199
+ <p style="font-size: 13px; color: #666; margin-top: 10px;">
200
+ 💡 Any username/password works. This simulates authentication.
201
+ </p>
202
+ </div>
203
+
204
+ <!-- Logout -->
205
+ <div id="logout-section" class="card" style="display: none;">
206
+ <h2>Session Active</h2>
207
+ <p>Your session is active. Click logout to clear all session data.</p>
208
+ <button id="logout-btn" class="danger" onclick="logout()">Logout</button>
209
+ </div>
210
+
211
+ <div class="grid">
212
+ <!-- Cookies Section -->
213
+ <div class="card">
214
+ <h2>🍪 Cookies</h2>
215
+ <p>Session data stored in cookies:</p>
216
+
217
+ <div class="form-group">
218
+ <input type="text" id="cookie-name" placeholder="Cookie name">
219
+ <input type="text" id="cookie-value" placeholder="Cookie value">
220
+ <button id="set-cookie-btn" onclick="setCookie()">Set Cookie</button>
221
+ </div>
222
+
223
+ <button id="refresh-cookies-btn" onclick="refreshCookies()">Refresh</button>
224
+ <button id="clear-cookies-btn" class="danger" onclick="clearAllCookies()">Clear All</button>
225
+
226
+ <div id="cookies-display" class="display">Loading cookies...</div>
227
+ </div>
228
+
229
+ <!-- LocalStorage Section -->
230
+ <div class="card">
231
+ <h2>💾 LocalStorage</h2>
232
+ <p>Session data stored in localStorage:</p>
233
+
234
+ <div class="form-group">
235
+ <input type="text" id="storage-key" placeholder="Key">
236
+ <input type="text" id="storage-value" placeholder="Value">
237
+ <button id="set-storage-btn" onclick="setStorageItem()">Set Item</button>
238
+ </div>
239
+
240
+ <button id="refresh-storage-btn" onclick="refreshStorage()">Refresh</button>
241
+ <button id="clear-storage-btn" class="danger" onclick="clearAllStorage()">Clear All</button>
242
+
243
+ <div id="storage-display" class="display">Loading localStorage...</div>
244
+ </div>
245
+ </div>
246
+
247
+ <!-- SessionStorage Section -->
248
+ <div class="card">
249
+ <h2>📦 SessionStorage</h2>
250
+ <p>Temporary session data (cleared when tab closes):</p>
251
+
252
+ <div class="form-group">
253
+ <input type="text" id="session-key" placeholder="Key">
254
+ <input type="text" id="session-value" placeholder="Value">
255
+ <button id="set-session-btn" onclick="setSessionItem()">Set Item</button>
256
+ <button id="refresh-session-btn" onclick="refreshSession()">Refresh</button>
257
+ <button id="clear-session-btn" class="danger" onclick="clearAllSession()">Clear All</button>
258
+ </div>
259
+
260
+ <div id="session-display" class="display">Loading sessionStorage...</div>
261
+ </div>
262
+
263
+ <!-- Preferences Card -->
264
+ <div class="card">
265
+ <h2>⚙️ User Preferences (saved to localStorage)</h2>
266
+ <p>These preferences persist across sessions:</p>
267
+
268
+ <div class="form-group">
269
+ <label><input type="checkbox" id="pref-darkmode" onchange="savePreferences()"> Dark Mode</label><br>
270
+ <label><input type="checkbox" id="pref-notifications" onchange="savePreferences()"> Enable
271
+ Notifications</label><br>
272
+ <label><input type="checkbox" id="pref-analytics" onchange="savePreferences()"> Allow Analytics</label>
273
+ </div>
274
+
275
+ <div class="form-group">
276
+ <label for="pref-language">Language:</label>
277
+ <select id="pref-language" onchange="savePreferences()">
278
+ <option value="en">English</option>
279
+ <option value="es">Spanish</option>
280
+ <option value="fr">French</option>
281
+ <option value="de">German</option>
282
+ </select>
283
+ </div>
284
+ </div>
285
+
286
+ <script>
287
+ // Generate session ID
288
+ function generateSessionId() {
289
+ return 'sess_' + Math.random().toString(36).substr(2, 16);
290
+ }
291
+
292
+ // Cookie helpers
293
+ function getCookies() {
294
+ const cookies = {};
295
+ document.cookie.split(';').forEach(cookie => {
296
+ const [name, value] = cookie.trim().split('=');
297
+ if (name) cookies[name] = decodeURIComponent(value || '');
298
+ });
299
+ return cookies;
300
+ }
301
+
302
+ function setCookieValue(name, value, days = 7) {
303
+ const expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString();
304
+ document.cookie = `${name}=${encodeURIComponent(value)}; expires=${expires}; path=/`;
305
+ }
306
+
307
+ function deleteCookie(name) {
308
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
309
+ }
310
+
311
+ // Login function
312
+ function login() {
313
+ const username = document.getElementById('username').value || 'testuser';
314
+ const password = document.getElementById('password').value || 'secret123';
315
+ const sessionId = generateSessionId();
316
+ const loginTime = new Date().toISOString();
317
+
318
+ // Set session cookies
319
+ setCookieValue('session_id', sessionId);
320
+ setCookieValue('auth_token', 'jwt_' + btoa(username + ':' + Date.now()));
321
+ setCookieValue('user_id', 'user_' + Math.floor(Math.random() * 10000));
322
+
323
+ // Set localStorage
324
+ localStorage.setItem('currentUser', JSON.stringify({
325
+ username: username,
326
+ role: 'admin',
327
+ loginTime: loginTime,
328
+ sessionId: sessionId
329
+ }));
330
+ localStorage.setItem('isLoggedIn', 'true');
331
+ localStorage.setItem('lastActivity', loginTime);
332
+
333
+ // Set sessionStorage
334
+ sessionStorage.setItem('tabSession', sessionId);
335
+ sessionStorage.setItem('pageViews', '1');
336
+
337
+ updateUI();
338
+ refreshAll();
339
+ }
340
+
341
+ // Logout function
342
+ function logout() {
343
+ // Clear session cookies
344
+ deleteCookie('session_id');
345
+ deleteCookie('auth_token');
346
+ deleteCookie('user_id');
347
+
348
+ // Clear session-related localStorage (keep preferences)
349
+ localStorage.removeItem('currentUser');
350
+ localStorage.removeItem('isLoggedIn');
351
+ localStorage.removeItem('lastActivity');
352
+
353
+ // Clear sessionStorage
354
+ sessionStorage.clear();
355
+
356
+ updateUI();
357
+ refreshAll();
358
+ }
359
+
360
+ // Update UI based on session state
361
+ function updateUI() {
362
+ const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
363
+ const statusDiv = document.getElementById('session-status');
364
+ const userInfo = document.getElementById('user-info');
365
+ const loginSection = document.getElementById('login-section');
366
+ const logoutSection = document.getElementById('logout-section');
367
+
368
+ if (isLoggedIn) {
369
+ const user = JSON.parse(localStorage.getItem('currentUser') || '{}');
370
+ statusDiv.className = 'status logged-in';
371
+ statusDiv.textContent = `✅ Logged in as ${user.username}`;
372
+ userInfo.style.display = 'block';
373
+ loginSection.style.display = 'none';
374
+ logoutSection.style.display = 'block';
375
+
376
+ document.getElementById('display-username').textContent = user.username;
377
+ document.getElementById('display-role').textContent = user.role;
378
+ document.getElementById('display-login-time').textContent = user.loginTime;
379
+ document.getElementById('display-session-id').textContent = user.sessionId;
380
+ } else {
381
+ statusDiv.className = 'status logged-out';
382
+ statusDiv.textContent = '❌ Not logged in';
383
+ userInfo.style.display = 'none';
384
+ loginSection.style.display = 'block';
385
+ logoutSection.style.display = 'none';
386
+ }
387
+ }
388
+
389
+ // Refresh displays
390
+ function refreshCookies() {
391
+ const cookies = getCookies();
392
+ const display = document.getElementById('cookies-display');
393
+ if (Object.keys(cookies).length === 0) {
394
+ display.textContent = '(no cookies set)';
395
+ } else {
396
+ display.textContent = JSON.stringify(cookies, null, 2);
397
+ }
398
+ }
399
+
400
+ function refreshStorage() {
401
+ const display = document.getElementById('storage-display');
402
+ const items = {};
403
+ for (let i = 0; i < localStorage.length; i++) {
404
+ const key = localStorage.key(i);
405
+ try {
406
+ items[key] = JSON.parse(localStorage.getItem(key));
407
+ } catch {
408
+ items[key] = localStorage.getItem(key);
409
+ }
410
+ }
411
+ if (Object.keys(items).length === 0) {
412
+ display.textContent = '(localStorage is empty)';
413
+ } else {
414
+ display.textContent = JSON.stringify(items, null, 2);
415
+ }
416
+ }
417
+
418
+ function refreshSession() {
419
+ const display = document.getElementById('session-display');
420
+ const items = {};
421
+ for (let i = 0; i < sessionStorage.length; i++) {
422
+ const key = sessionStorage.key(i);
423
+ items[key] = sessionStorage.getItem(key);
424
+ }
425
+ if (Object.keys(items).length === 0) {
426
+ display.textContent = '(sessionStorage is empty)';
427
+ } else {
428
+ display.textContent = JSON.stringify(items, null, 2);
429
+ }
430
+ }
431
+
432
+ function refreshAll() {
433
+ refreshCookies();
434
+ refreshStorage();
435
+ refreshSession();
436
+ }
437
+
438
+ // Manual cookie/storage setters
439
+ function setCookie() {
440
+ const name = document.getElementById('cookie-name').value;
441
+ const value = document.getElementById('cookie-value').value;
442
+ if (name) {
443
+ setCookieValue(name, value);
444
+ refreshCookies();
445
+ }
446
+ }
447
+
448
+ function setStorageItem() {
449
+ const key = document.getElementById('storage-key').value;
450
+ const value = document.getElementById('storage-value').value;
451
+ if (key) {
452
+ localStorage.setItem(key, value);
453
+ refreshStorage();
454
+ }
455
+ }
456
+
457
+ function setSessionItem() {
458
+ const key = document.getElementById('session-key').value;
459
+ const value = document.getElementById('session-value').value;
460
+ if (key) {
461
+ sessionStorage.setItem(key, value);
462
+ refreshSession();
463
+ }
464
+ }
465
+
466
+ // Clear functions
467
+ function clearAllCookies() {
468
+ const cookies = getCookies();
469
+ Object.keys(cookies).forEach(name => deleteCookie(name));
470
+ refreshCookies();
471
+ updateUI();
472
+ }
473
+
474
+ function clearAllStorage() {
475
+ localStorage.clear();
476
+ refreshStorage();
477
+ updateUI();
478
+ }
479
+
480
+ function clearAllSession() {
481
+ sessionStorage.clear();
482
+ refreshSession();
483
+ }
484
+
485
+ // Preferences
486
+ function savePreferences() {
487
+ const prefs = {
488
+ darkMode: document.getElementById('pref-darkmode').checked,
489
+ notifications: document.getElementById('pref-notifications').checked,
490
+ analytics: document.getElementById('pref-analytics').checked,
491
+ language: document.getElementById('pref-language').value
492
+ };
493
+ localStorage.setItem('userPreferences', JSON.stringify(prefs));
494
+ refreshStorage();
495
+ }
496
+
497
+ function loadPreferences() {
498
+ const prefs = JSON.parse(localStorage.getItem('userPreferences') || '{}');
499
+ document.getElementById('pref-darkmode').checked = prefs.darkMode || false;
500
+ document.getElementById('pref-notifications').checked = prefs.notifications || false;
501
+ document.getElementById('pref-analytics').checked = prefs.analytics || false;
502
+ document.getElementById('pref-language').value = prefs.language || 'en';
503
+ }
504
+
505
+ // Initialize
506
+ document.addEventListener('DOMContentLoaded', () => {
507
+ loadPreferences();
508
+ updateUI();
509
+ refreshAll();
510
+ });
511
+ </script>
512
+ </body>
513
+
514
+ </html>
@@ -0,0 +1,36 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>File Upload Example</title>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>File Upload</h1>
11
+
12
+ <!-- single file input -->
13
+ <input id="file-input" type="file">
14
+ <div id="result"></div>
15
+
16
+ <!-- multiple file input -->
17
+ <input id="multi-input" type="file" multiple>
18
+ <div id="multi-result"></div>
19
+
20
+ <!-- non-file input for error-path test -->
21
+ <input id="text-input" type="text">
22
+
23
+ <script>
24
+ document.getElementById('file-input').addEventListener('change', function () {
25
+ const f = this.files[0];
26
+ document.getElementById('result').textContent = f ? f.name : '';
27
+ });
28
+
29
+ document.getElementById('multi-input').addEventListener('change', function () {
30
+ const names = Array.from(this.files).map(f => f.name).join(', ');
31
+ document.getElementById('multi-result').textContent = names;
32
+ });
33
+ </script>
34
+ </body>
35
+
36
+ </html>
@@ -1,51 +1,35 @@
1
1
  # Run Accessibility Gates
2
2
 
3
- Use this pattern when CI should reject serious accessibility regressions, while
4
- still allowing deliberate rule exceptions in one place.
3
+ A page can look right and still be unusable with a screen reader. Wire an
4
+ accessibility check into CI and serious regressions fail the build instead of
5
+ shipping. `check()` throws an `A11yError` listing every violation and its help
6
+ URL; scope it to a component to gate exactly the region you care about. This
7
+ recipe checks a clean region of the live
8
+ [accessibility example](https://dtopuzov.github.io/craftdriver/examples/a11y.html).
5
9
 
6
10
  ```ts
7
- import { afterAll, beforeAll, beforeEach, describe, it } from 'vitest';
8
- import { Browser } from 'craftdriver';
9
-
10
- const A11Y_OPTIONS = {
11
- minImpact: 'serious' as const,
12
- disableRules: [
13
- // Example: keep temporary exceptions explicit and reviewed.
14
- 'color-contrast',
15
- ],
16
- };
17
-
18
- describe('accessibility gate', () => {
19
- let browser: Browser;
20
-
21
- beforeAll(async () => {
22
- browser = await Browser.launch({ browserName: 'chrome' });
23
- });
24
-
25
- beforeEach(async () => {
26
- await browser.navigateTo('http://localhost:3000/settings');
27
- });
28
-
29
- afterAll(async () => {
30
- await browser.quit();
31
- });
32
-
33
- it('passes page-level accessibility checks', async () => {
34
- await browser.a11y.check(A11Y_OPTIONS);
35
- });
36
-
37
- it('passes modal accessibility checks', async () => {
38
- await browser.getByRole('button', { name: 'Edit profile' }).click();
39
- await browser.locator('#profile-modal').a11y.check(A11Y_OPTIONS);
40
- });
41
- });
11
+ await browser.navigateTo('https://dtopuzov.github.io/craftdriver/examples/a11y.html');
12
+
13
+ // Passes silently when the region is clean; throws A11yError (with violations
14
+ // and help URLs) when it is not — so a regression fails the test.
15
+ await browser.locator('#good').a11y.check({ minImpact: 'serious' });
16
+ ```
17
+
18
+ ## Audit Without Failing
19
+
20
+ When you want to inspect or report violations instead of failing, use `audit()`
21
+ — it returns the report rather than throwing:
22
+
23
+ ```ts
24
+ const report = await browser.a11y.audit({ minImpact: 'serious' });
25
+ console.log(`${report.violations.length} violation(s)`);
42
26
  ```
43
27
 
44
28
  ## Notes
45
29
 
46
- - Use `check()` when violations should fail the test.
47
- - Use `audit()` when you want to write a report or inspect violations manually.
48
- - Prefer scoped checks for dynamic UI such as dialogs, menus, and checkout panels.
30
+ - Use `check()` when violations should fail the test; use `audit()` to inspect or write a report.
31
+ - Scope with `locator(...)` / `find(...)` for dynamic UI such as dialogs, menus, and checkout panels.
32
+ - `minImpact` filters by severity so you can gate on `serious`/`critical` first and tighten later.
49
33
 
50
34
  ## Learn More
51
35