craftdriver 1.1.0 → 1.1.1
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.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -1
- package/dist/cli/snapshot.js +31 -4
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/lib/browser.d.ts +6 -0
- package/dist/lib/browser.d.ts.map +1 -1
- package/dist/lib/browser.js +6 -0
- package/dist/lib/browser.js.map +1 -1
- package/dist/lib/by.d.ts +18 -0
- package/dist/lib/by.d.ts.map +1 -1
- package/dist/lib/by.js +141 -17
- package/dist/lib/by.js.map +1 -1
- package/dist/lib/locator.d.ts +28 -0
- package/dist/lib/locator.d.ts.map +1 -1
- package/dist/lib/locator.js +28 -0
- package/dist/lib/locator.js.map +1 -1
- package/docs/browser-api.md +4 -1
- package/docs/public/examples/a11y.html +44 -0
- package/docs/public/examples/clock.html +83 -0
- package/docs/public/examples/console-errors.html +344 -0
- package/docs/public/examples/dialogs.html +42 -0
- package/docs/public/examples/download.html +31 -0
- package/docs/public/examples/dynamic.html +131 -0
- package/docs/public/examples/emulate.html +127 -0
- package/docs/public/examples/evaluate.html +28 -0
- package/docs/public/examples/hover-select.html +295 -0
- package/docs/public/examples/iframe-child.html +51 -0
- package/docs/public/examples/iframes.html +35 -0
- package/docs/public/examples/keyboard.html +195 -0
- package/docs/public/examples/locator.html +131 -0
- package/docs/public/examples/login.html +116 -0
- package/docs/public/examples/mobile.html +270 -0
- package/docs/public/examples/mouse.html +505 -0
- package/docs/public/examples/network.html +293 -0
- package/docs/public/examples/popup-target.html +26 -0
- package/docs/public/examples/popup.html +50 -0
- package/docs/public/examples/screenshot.html +99 -0
- package/docs/public/examples/selectors.html +150 -0
- package/docs/public/examples/session.html +514 -0
- package/docs/public/examples/upload.html +36 -0
- package/docs/recipes/accessibility-gate.md +24 -40
- package/docs/recipes/console-error-gate.md +29 -38
- package/docs/recipes/file-upload-download.md +26 -43
- package/docs/recipes/find-elements.md +142 -0
- package/docs/recipes/login-once-reuse-session.md +24 -44
- package/docs/recipes/mobile-flow-with-network-and-logs.md +26 -41
- package/docs/recipes/mock-api-and-assert-network.md +22 -35
- package/docs/recipes/multi-user-contexts.md +32 -43
- package/docs/recipes/page-objects.md +52 -0
- package/docs/recipes/trace-failing-test.md +31 -44
- package/docs/recipes/virtual-clock-time-sensitive-ui.md +24 -40
- package/docs/recipes/vitest-browser-lifecycle.md +14 -11
- package/docs/recipes.md +20 -1
- package/docs/selectors.md +291 -229
- package/package.json +5 -1
|
@@ -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
|
-
|
|
4
|
-
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
-
|
|
48
|
-
-
|
|
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
|
|