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,195 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Keyboard Actions Playground</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f1115;
10
+ --panel: #151925;
11
+ --accent: #4f86ff;
12
+ --text: #e6edf3;
13
+ --muted: #9aa5b1;
14
+ --ok: #18a558;
15
+ }
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+ html,
20
+ body {
21
+ height: 100%;
22
+ }
23
+ body {
24
+ margin: 0;
25
+ padding: 16px;
26
+ color: var(--text);
27
+ background: var(--bg);
28
+ font-family:
29
+ ui-sans-serif,
30
+ system-ui,
31
+ -apple-system,
32
+ Segoe UI,
33
+ Roboto,
34
+ Ubuntu,
35
+ Cantarell,
36
+ Noto Sans,
37
+ Helvetica Neue,
38
+ Arial;
39
+ display: grid;
40
+ grid-template-columns: 2fr 1fr;
41
+ gap: 12px;
42
+ }
43
+ h1 {
44
+ font-size: 18px;
45
+ margin: 0 0 8px;
46
+ }
47
+ .card {
48
+ background: var(--panel);
49
+ border: 1px solid #222839;
50
+ border-radius: 10px;
51
+ padding: 12px;
52
+ }
53
+ .row {
54
+ display: grid;
55
+ grid-template-columns: repeat(2, minmax(0, 1fr));
56
+ gap: 12px;
57
+ }
58
+ textarea,
59
+ input {
60
+ width: 100%;
61
+ background: #0c1426;
62
+ color: var(--text);
63
+ border: 1px solid #28314a;
64
+ border-radius: 8px;
65
+ padding: 10px;
66
+ }
67
+ .mono {
68
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;
69
+ }
70
+ .pill {
71
+ display: inline-flex;
72
+ gap: 6px;
73
+ background: #101628;
74
+ border: 1px solid #1f2740;
75
+ color: var(--muted);
76
+ padding: 4px 8px;
77
+ border-radius: 999px;
78
+ font-size: 12px;
79
+ }
80
+ .value {
81
+ font-weight: 600;
82
+ }
83
+ .ok {
84
+ color: var(--ok);
85
+ }
86
+ .logline {
87
+ color: #d1d5db;
88
+ font-size: 12px;
89
+ }
90
+ </style>
91
+ </head>
92
+ <body>
93
+ <main class="card">
94
+ <h1>Keyboard Actions Playground</h1>
95
+
96
+ <section class="card" style="margin-top: 8px">
97
+ <h2 style="font-size: 14px; margin: 0 0 8px">Editor (type / backspace / enter)</h2>
98
+ <textarea id="editor" rows="6" placeholder="Click here, then type..."></textarea>
99
+ <div style="margin-top: 8px; display: flex; gap: 12px; align-items: center">
100
+ <span class="pill">Lines: <span id="lineCount" class="value">0</span></span>
101
+ <span class="pill">Last key: <span id="lastKey" class="value">n/a</span></span>
102
+ <span class="pill">Modifiers: <span id="modifiers" class="value">none</span></span>
103
+ </div>
104
+ <div style="margin-top: 8px">
105
+ <div class="pill">Mirror</div>
106
+ <pre id="mirror" class="mono" style="margin: 6px 0 0; white-space: pre-wrap"></pre>
107
+ </div>
108
+ </section>
109
+
110
+ <div class="row" style="margin-top: 12px">
111
+ <section class="card">
112
+ <h2 style="font-size: 14px; margin: 0 0 8px">Enter key action</h2>
113
+ <input id="enterTarget" placeholder="Focus here and press Enter" />
114
+ <div style="margin-top: 8px" class="pill">
115
+ Result: <span id="enterResult" class="value">idle</span>
116
+ </div>
117
+ </section>
118
+
119
+ <section class="card">
120
+ <h2 style="font-size: 14px; margin: 0 0 8px">Help</h2>
121
+ <div class="logline">• Click the editor before using keyboard.type/press.</div>
122
+ <div class="logline">• Backspace removes characters; Enter creates a new line.</div>
123
+ <div class="logline">• Use Control+A then Backspace to clear.</div>
124
+ <div class="logline">• Enter inside the input sets the result to "submitted".</div>
125
+ </section>
126
+ </div>
127
+ </main>
128
+
129
+ <aside class="card">
130
+ <h2 style="font-size: 14px; margin: 0 0 8px">Status</h2>
131
+ <div>Active element: <span id="activeEl" class="value mono">n/a</span></div>
132
+ </aside>
133
+
134
+ <script>
135
+ (function () {
136
+ const $ = (s) => document.querySelector(s);
137
+ const editor = $('#editor');
138
+ const mirror = $('#mirror');
139
+ const lineCount = $('#lineCount');
140
+ const lastKey = $('#lastKey');
141
+ const modsEl = $('#modifiers');
142
+ const enterTarget = $('#enterTarget');
143
+ const enterResult = $('#enterResult');
144
+ const activeEl = $('#activeEl');
145
+
146
+ function updateActive() {
147
+ const el = document.activeElement;
148
+ if (!el) {
149
+ activeEl.textContent = 'none';
150
+ return;
151
+ }
152
+ const id = el.id ? '#' + el.id : el.tagName.toLowerCase();
153
+ activeEl.textContent = id;
154
+ }
155
+ updateActive();
156
+ document.addEventListener('focusin', updateActive);
157
+ document.addEventListener('focusout', updateActive);
158
+
159
+ function updateMirror() {
160
+ const v = editor.value;
161
+ mirror.textContent = v;
162
+ const lines = v.length ? v.split('\n').length : 0;
163
+ lineCount.textContent = String(lines);
164
+ }
165
+ editor.addEventListener('input', updateMirror);
166
+
167
+ // Track last key and modifiers globally (document level)
168
+ const mods = new Set();
169
+ function renderMods() {
170
+ modsEl.textContent = mods.size ? Array.from(mods).join('+') : 'none';
171
+ }
172
+ document.addEventListener('keydown', (e) => {
173
+ lastKey.textContent = e.key;
174
+ if (e.key === 'Shift' || e.key === 'Control' || e.key === 'Alt' || e.key === 'Meta') {
175
+ mods.add(e.key);
176
+ renderMods();
177
+ }
178
+ });
179
+ document.addEventListener('keyup', (e) => {
180
+ if (e.key === 'Shift' || e.key === 'Control' || e.key === 'Alt' || e.key === 'Meta') {
181
+ mods.delete(e.key);
182
+ renderMods();
183
+ }
184
+ });
185
+
186
+ // Enter action in input
187
+ enterTarget.addEventListener('keydown', (e) => {
188
+ if (e.key === 'Enter') {
189
+ enterResult.textContent = 'submitted';
190
+ }
191
+ });
192
+ })();
193
+ </script>
194
+ </body>
195
+ </html>
@@ -0,0 +1,131 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Locator Playground</title>
7
+ <style>
8
+ body {
9
+ font-family: system-ui, sans-serif;
10
+ margin: 20px;
11
+ }
12
+
13
+ .product-list {
14
+ list-style: none;
15
+ padding: 0;
16
+ }
17
+
18
+ .product {
19
+ border: 1px solid #ddd;
20
+ border-radius: 6px;
21
+ padding: 12px;
22
+ margin-bottom: 10px;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ }
27
+
28
+ .product-name {
29
+ font-weight: 600;
30
+ }
31
+
32
+ .product-price {
33
+ color: #6b7280;
34
+ margin-left: 8px;
35
+ }
36
+
37
+ .buy-btn {
38
+ padding: 6px 14px;
39
+ border-radius: 4px;
40
+ border: 1px solid #6366f1;
41
+ background: #eef2ff;
42
+ cursor: pointer;
43
+ }
44
+
45
+ #status {
46
+ margin-top: 16px;
47
+ font-size: 14px;
48
+ color: #374151;
49
+ }
50
+ </style>
51
+ </head>
52
+
53
+ <body>
54
+ <h1>Locator Playground</h1>
55
+
56
+ <!-- product list — five cards with stable ids and predictable text -->
57
+ <ul class="product-list" id="product-list">
58
+ <li class="product" id="product-1">
59
+ <span>
60
+ <span class="product-name">Widget Lite</span>
61
+ <span class="product-price">$9</span>
62
+ </span>
63
+ <button class="buy-btn" id="buy-1">Buy</button>
64
+ </li>
65
+ <li class="product" id="product-2">
66
+ <span>
67
+ <span class="product-name">Widget Pro</span>
68
+ <span class="product-price">$29</span>
69
+ </span>
70
+ <button class="buy-btn" id="buy-2">Buy</button>
71
+ </li>
72
+ <li class="product" id="product-3">
73
+ <span>
74
+ <span class="product-name">Widget Max</span>
75
+ <span class="product-price">$49</span>
76
+ </span>
77
+ <button class="buy-btn" id="buy-3">Buy</button>
78
+ </li>
79
+ <li class="product" id="product-4">
80
+ <span>
81
+ <span class="product-name">Gadget Basic</span>
82
+ <span class="product-price">$15</span>
83
+ </span>
84
+ <button class="buy-btn" id="buy-4">Buy</button>
85
+ </li>
86
+ <li class="product" id="product-5">
87
+ <span>
88
+ <span class="product-name">Gadget Plus</span>
89
+ <span class="product-price">$39</span>
90
+ </span>
91
+ <button class="buy-btn" id="buy-5">Buy</button>
92
+ </li>
93
+ </ul>
94
+
95
+ <!-- dynamic item — toggled in/out to test re-resolution -->
96
+ <button id="toggle-btn">Toggle dynamic item</button>
97
+ <div id="dynamic-container"></div>
98
+
99
+ <!-- click result area -->
100
+ <div id="status">idle</div>
101
+
102
+ <script>
103
+ // Mark which buy button was last clicked
104
+ document.querySelectorAll('.buy-btn').forEach(function (btn) {
105
+ btn.addEventListener('click', function () {
106
+ document.getElementById('status').textContent = 'clicked:' + btn.id;
107
+ });
108
+ });
109
+
110
+ // Toggle a dynamic product in and out of the DOM
111
+ var dynamicVisible = false;
112
+ document.getElementById('toggle-btn').addEventListener('click', function () {
113
+ var container = document.getElementById('dynamic-container');
114
+ if (dynamicVisible) {
115
+ container.innerHTML = '';
116
+ } else {
117
+ container.innerHTML =
118
+ '<li class="product" id="product-dynamic">' +
119
+ '<span><span class="product-name">Dynamic Item</span></span>' +
120
+ '<button class="buy-btn" id="buy-dynamic">Buy</button>' +
121
+ '</li>';
122
+ document.getElementById('buy-dynamic').addEventListener('click', function () {
123
+ document.getElementById('status').textContent = 'clicked:buy-dynamic';
124
+ });
125
+ }
126
+ dynamicVisible = !dynamicVisible;
127
+ });
128
+ </script>
129
+ </body>
130
+
131
+ </html>
@@ -0,0 +1,116 @@
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" />
7
+ <title>Craftdriver Login Example</title>
8
+ <style>
9
+ body {
10
+ font-family: system-ui, Arial, sans-serif;
11
+ margin: 2rem;
12
+ }
13
+
14
+ .card {
15
+ max-width: 420px;
16
+ border: 1px solid #ddd;
17
+ border-radius: 8px;
18
+ padding: 16px;
19
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
20
+ }
21
+
22
+ label {
23
+ display: block;
24
+ margin-top: 12px;
25
+ font-weight: 600;
26
+ }
27
+
28
+ input {
29
+ width: 100%;
30
+ padding: 8px;
31
+ border: 1px solid #ccc;
32
+ border-radius: 6px;
33
+ }
34
+
35
+ button {
36
+ margin-top: 16px;
37
+ padding: 10px 14px;
38
+ border: none;
39
+ border-radius: 6px;
40
+ background: #2563eb;
41
+ color: #fff;
42
+ font-weight: 600;
43
+ cursor: pointer;
44
+ }
45
+
46
+ button:hover {
47
+ background: #1d4ed8;
48
+ }
49
+
50
+ #result {
51
+ margin-top: 16px;
52
+ font-weight: 600;
53
+ }
54
+ </style>
55
+ </head>
56
+
57
+ <body>
58
+ <div class="card">
59
+ <h1>Login</h1>
60
+ <form id="login-form">
61
+ <label for="username">Username</label>
62
+ <input id="username" name="username" type="text" autocomplete="off" />
63
+ <label for="password">Password</label>
64
+ <input id="password" name="password" type="password" autocomplete="off" />
65
+ <button id="submit" type="submit">Sign in</button>
66
+ </form>
67
+ <div id="result" aria-live="polite"></div>
68
+ </div>
69
+ <script>
70
+ const form = document.getElementById('login-form');
71
+ const result = document.getElementById('result');
72
+
73
+ // Check for existing session on page load
74
+ function checkSession() {
75
+ const sessionCookie = document.cookie.split(';').find(c => c.trim().startsWith('session='));
76
+ if (sessionCookie) {
77
+ const username = sessionCookie.split('=')[1];
78
+ showLoggedIn(decodeURIComponent(username));
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+
84
+ function showLoggedIn(username) {
85
+ form.style.display = 'none';
86
+ result.innerHTML = `<span id="welcome">Welcome back, ${username}!</span><br><button id="logout" onclick="logout()">Logout</button>`;
87
+ }
88
+
89
+ function logout() {
90
+ document.cookie = 'session=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
91
+ location.reload();
92
+ }
93
+
94
+ // Check session on load
95
+ if (!checkSession()) {
96
+ form.addEventListener('submit', (e) => {
97
+ e.preventDefault();
98
+ const u = document.getElementById('username').value;
99
+ const p = document.getElementById('password').value;
100
+ if (u && p) {
101
+ // Set session cookie (expires in 1 hour)
102
+ const expires = new Date(Date.now() + 3600000).toUTCString();
103
+ document.cookie = `session=${encodeURIComponent(u)}; path=/; expires=${expires}`;
104
+ // Persist UX prefs in localStorage so storageState round-trips capture them.
105
+ localStorage.setItem('lastUser', u);
106
+ localStorage.setItem('theme', 'dark');
107
+ showLoggedIn(u);
108
+ } else {
109
+ result.textContent = 'Missing credentials';
110
+ }
111
+ });
112
+ }
113
+ </script>
114
+ </body>
115
+
116
+ </html>