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,31 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>File Download Example</title>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>File Download</h1>
11
+
12
+ <!-- clicking this triggers a download of a small text file -->
13
+ <button id="download-btn" onclick="downloadFile()">Download report.txt</button>
14
+
15
+ <script>
16
+ function downloadFile() {
17
+ const content = 'craftdriver download test\nline 2\nline 3\n';
18
+ const blob = new Blob([content], { type: 'text/plain' });
19
+ const url = URL.createObjectURL(blob);
20
+ const a = document.createElement('a');
21
+ a.href = url;
22
+ a.download = 'report.txt';
23
+ document.body.appendChild(a);
24
+ a.click();
25
+ document.body.removeChild(a);
26
+ URL.revokeObjectURL(url);
27
+ }
28
+ </script>
29
+ </body>
30
+
31
+ </html>
@@ -0,0 +1,131 @@
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 Dynamic Elements</title>
8
+ <style>
9
+ body {
10
+ font-family: system-ui, Arial, sans-serif;
11
+ margin: 1rem;
12
+ }
13
+
14
+ .container {
15
+ max-width: 1024px;
16
+ margin: 0 auto;
17
+ }
18
+
19
+ .grid {
20
+ display: grid;
21
+ grid-template-columns: repeat(2, minmax(0, 1fr));
22
+ gap: 16px;
23
+ align-items: start;
24
+ }
25
+
26
+ .card {
27
+ border: 1px solid #ddd;
28
+ border-radius: 8px;
29
+ padding: 16px;
30
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
31
+ }
32
+
33
+ h2 {
34
+ font-size: 1.1rem;
35
+ margin: 0 0 8px;
36
+ }
37
+
38
+ button {
39
+ margin-top: 8px;
40
+ padding: 8px 12px;
41
+ border: none;
42
+ border-radius: 6px;
43
+ background: #2563eb;
44
+ color: #fff;
45
+ font-weight: 600;
46
+ cursor: pointer;
47
+ }
48
+
49
+ button:hover {
50
+ background: #1d4ed8;
51
+ }
52
+
53
+ .muted {
54
+ color: #666;
55
+ font-size: 0.9rem;
56
+ margin: 0;
57
+ }
58
+ </style>
59
+ </head>
60
+
61
+ <body>
62
+ <div class="container">
63
+ <div class="grid">
64
+ <div class="card">
65
+ <h2>1) Appear after 1s (not in DOM)</h2>
66
+ <p class="muted">Click the button; element will be created after ~1s.</p>
67
+ <button id="btn-appear" type="button">Create element</button>
68
+ <div id="appear-container" style="margin-top: 12px"></div>
69
+ </div>
70
+
71
+ <div class="card">
72
+ <h2>2) Hidden → Visible after 1s</h2>
73
+ <p class="muted">Element exists in DOM but is hidden and becomes visible.</p>
74
+ <button id="btn-unhide" type="button">Unhide after 1s</button>
75
+ <div style="margin-top: 12px">
76
+ <div id="hidden-el" style="display: none">I was hidden</div>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="card">
81
+ <h2>3) Visible → Removed (immediately)</h2>
82
+ <p class="muted">Click the button to remove the element from DOM right away.</p>
83
+ <button id="btn-remove" type="button">Remove now</button>
84
+ <div style="margin-top: 12px">
85
+ <div id="to-remove">I will be removed</div>
86
+ </div>
87
+ </div>
88
+
89
+ <div class="card">
90
+ <h2>4) Visible → Hidden (immediately)</h2>
91
+ <p class="muted">Click the button to hide the element (display:none).</p>
92
+ <button id="btn-hide" type="button">Hide now</button>
93
+ <div style="margin-top: 12px">
94
+ <div id="to-hide">I will be hidden</div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <script>
101
+ document.getElementById('btn-appear').addEventListener('click', () => {
102
+ const container = document.getElementById('appear-container');
103
+ container.textContent = ''; // clear previous runs
104
+ setTimeout(() => {
105
+ const el = document.createElement('div');
106
+ el.id = 'delayed-greeting';
107
+ el.textContent = 'Hello after 1s';
108
+ container.appendChild(el);
109
+ }, 1000);
110
+ });
111
+
112
+ document.getElementById('btn-unhide').addEventListener('click', () => {
113
+ const el = document.getElementById('hidden-el');
114
+ setTimeout(() => {
115
+ el.style.display = 'block';
116
+ }, 1000);
117
+ });
118
+
119
+ document.getElementById('btn-remove').addEventListener('click', () => {
120
+ const el = document.getElementById('to-remove');
121
+ if (el) el.remove();
122
+ });
123
+
124
+ document.getElementById('btn-hide').addEventListener('click', () => {
125
+ const el = document.getElementById('to-hide');
126
+ if (el) el.style.display = 'none';
127
+ });
128
+ </script>
129
+ </body>
130
+
131
+ </html>
@@ -0,0 +1,127 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Emulation fixture</title>
6
+ <style>
7
+ body { font-family: system-ui, sans-serif; margin: 20px; background: #fff; color: #111; }
8
+ #scheme-readout { padding: 8px; border: 1px solid #ccc; }
9
+ /* prefers-color-scheme observable via CSS */
10
+ @media (prefers-color-scheme: dark) {
11
+ body { background: #111; color: #eee; }
12
+ #scheme-readout { border-color: #555; }
13
+ }
14
+ /* prefers-reduced-motion observable via CSS */
15
+ .spinner { animation: spin 1s linear infinite; display: inline-block; }
16
+ @keyframes spin { to { transform: rotate(360deg); } }
17
+ @media (prefers-reduced-motion: reduce) {
18
+ .spinner { animation: none; }
19
+ }
20
+ /* forced-colors observable via CSS */
21
+ #fc-readout { background: canvas; color: canvastext; padding: 8px; }
22
+ </style>
23
+ </head>
24
+ <body>
25
+ <h1>Emulation fixture</h1>
26
+
27
+ <!-- prefers-color-scheme -->
28
+ <section>
29
+ <h2>Color scheme</h2>
30
+ <div id="scheme-readout">readout</div>
31
+ <script>
32
+ (function () {
33
+ var el = document.getElementById('scheme-readout');
34
+ function update() {
35
+ var dark = matchMedia('(prefers-color-scheme: dark)').matches;
36
+ var light = matchMedia('(prefers-color-scheme: light)').matches;
37
+ el.textContent = dark ? 'dark' : light ? 'light' : 'no-preference';
38
+ }
39
+ update();
40
+ matchMedia('(prefers-color-scheme: dark)').addEventListener('change', update);
41
+ })();
42
+ </script>
43
+ </section>
44
+
45
+ <!-- prefers-reduced-motion -->
46
+ <section>
47
+ <h2>Reduced motion</h2>
48
+ <span id="spinner" class="spinner">*</span>
49
+ <div id="rm-readout"></div>
50
+ <script>
51
+ (function () {
52
+ var el = document.getElementById('rm-readout');
53
+ function update() {
54
+ el.textContent = matchMedia('(prefers-reduced-motion: reduce)').matches
55
+ ? 'reduce' : 'no-preference';
56
+ }
57
+ update();
58
+ matchMedia('(prefers-reduced-motion: reduce)').addEventListener('change', update);
59
+ })();
60
+ </script>
61
+ </section>
62
+
63
+ <!-- forced-colors -->
64
+ <section>
65
+ <h2>Forced colors</h2>
66
+ <div id="fc-readout"></div>
67
+ <script>
68
+ (function () {
69
+ var el = document.getElementById('fc-readout');
70
+ function update() {
71
+ el.textContent = matchMedia('(forced-colors: active)').matches
72
+ ? 'active' : 'none';
73
+ }
74
+ update();
75
+ })();
76
+ </script>
77
+ </section>
78
+
79
+ <!-- locale -->
80
+ <section>
81
+ <h2>Locale</h2>
82
+ <div id="lang-readout"></div>
83
+ <div id="number-readout"></div>
84
+ <script>
85
+ document.getElementById('lang-readout').textContent = navigator.language;
86
+ // 1234.5 in en-US -> "1,234.5", in de-DE -> "1.234,5"
87
+ document.getElementById('number-readout').textContent =
88
+ new Intl.NumberFormat().format(1234.5);
89
+ </script>
90
+ </section>
91
+
92
+ <!-- timezone -->
93
+ <section>
94
+ <h2>Timezone</h2>
95
+ <div id="tz-readout"></div>
96
+ <script>
97
+ // Resolved IANA zone, e.g. "Europe/Berlin"
98
+ document.getElementById('tz-readout').textContent =
99
+ Intl.DateTimeFormat().resolvedOptions().timeZone;
100
+ </script>
101
+ </section>
102
+
103
+ <!-- offline -->
104
+ <section>
105
+ <h2>Network</h2>
106
+ <div id="online-readout"></div>
107
+ <button id="fetch-btn" type="button">fetch /</button>
108
+ <div id="fetch-readout"></div>
109
+ <script>
110
+ var online = document.getElementById('online-readout');
111
+ function syncOnline() { online.textContent = navigator.onLine ? 'online' : 'offline'; }
112
+ syncOnline();
113
+ window.addEventListener('online', syncOnline);
114
+ window.addEventListener('offline', syncOnline);
115
+
116
+ document.getElementById('fetch-btn').addEventListener('click', function () {
117
+ var out = document.getElementById('fetch-readout');
118
+ out.textContent = '';
119
+ fetch('/').then(
120
+ function (r) { out.textContent = 'ok:' + r.status; },
121
+ function (e) { out.textContent = 'error:' + (e && e.message || e); }
122
+ );
123
+ });
124
+ </script>
125
+ </section>
126
+ </body>
127
+ </html>
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <title>Evaluate Playground</title>
7
+ </head>
8
+
9
+ <body>
10
+ <h1>Evaluate Playground</h1>
11
+
12
+ <!-- target element for element.evaluate() tests -->
13
+ <button id="action-btn" class="primary active">Click me</button>
14
+
15
+ <!-- state exposed on window.__state for evaluate() tests -->
16
+ <script>
17
+ window.__state = {
18
+ count: 0,
19
+ name: 'craftdriver',
20
+ };
21
+
22
+ document.getElementById('action-btn').addEventListener('click', function () {
23
+ window.__state.count++;
24
+ });
25
+ </script>
26
+ </body>
27
+
28
+ </html>
@@ -0,0 +1,295 @@
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>Hover & Select Examples</title>
8
+ <style>
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ max-width: 800px;
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
+ /* Tooltip */
35
+ .tooltip-container {
36
+ position: relative;
37
+ display: inline-block;
38
+ padding: 10px 20px;
39
+ background: #007bff;
40
+ color: white;
41
+ border-radius: 4px;
42
+ cursor: pointer;
43
+ }
44
+
45
+ .tooltip {
46
+ visibility: hidden;
47
+ opacity: 0;
48
+ position: absolute;
49
+ bottom: 125%;
50
+ left: 50%;
51
+ transform: translateX(-50%);
52
+ background: #333;
53
+ color: white;
54
+ padding: 8px 12px;
55
+ border-radius: 4px;
56
+ white-space: nowrap;
57
+ font-size: 14px;
58
+ transition: opacity 0.2s;
59
+ z-index: 1000;
60
+ }
61
+
62
+ .tooltip::after {
63
+ content: '';
64
+ position: absolute;
65
+ top: 100%;
66
+ left: 50%;
67
+ margin-left: -5px;
68
+ border-width: 5px;
69
+ border-style: solid;
70
+ border-color: #333 transparent transparent transparent;
71
+ }
72
+
73
+ .tooltip-container:hover .tooltip {
74
+ visibility: visible;
75
+ opacity: 1;
76
+ }
77
+
78
+ /* Dropdown menu */
79
+ .dropdown {
80
+ position: relative;
81
+ display: inline-block;
82
+ }
83
+
84
+ .dropdown-button {
85
+ background: #28a745;
86
+ color: white;
87
+ padding: 10px 20px;
88
+ border: none;
89
+ border-radius: 4px;
90
+ cursor: pointer;
91
+ }
92
+
93
+ .dropdown-menu {
94
+ visibility: hidden;
95
+ opacity: 0;
96
+ position: absolute;
97
+ top: 100%;
98
+ left: 0;
99
+ background: white;
100
+ min-width: 200px;
101
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
102
+ border-radius: 4px;
103
+ margin-top: 5px;
104
+ transition: opacity 0.2s;
105
+ z-index: 1000;
106
+ }
107
+
108
+ .dropdown:hover .dropdown-menu {
109
+ visibility: visible;
110
+ opacity: 1;
111
+ }
112
+
113
+ .dropdown-item {
114
+ padding: 10px 15px;
115
+ cursor: pointer;
116
+ border-bottom: 1px solid #eee;
117
+ }
118
+
119
+ .dropdown-item:last-child {
120
+ border-bottom: none;
121
+ }
122
+
123
+ .dropdown-item:hover {
124
+ background: #f5f5f5;
125
+ }
126
+
127
+ /* Selects */
128
+ select {
129
+ padding: 10px;
130
+ border: 1px solid #ced4da;
131
+ border-radius: 4px;
132
+ font-size: 14px;
133
+ min-width: 200px;
134
+ margin-bottom: 10px;
135
+ }
136
+
137
+ .result {
138
+ background: #f8f9fa;
139
+ border: 1px solid #dee2e6;
140
+ border-radius: 4px;
141
+ padding: 15px;
142
+ margin-top: 15px;
143
+ font-family: monospace;
144
+ }
145
+
146
+ label {
147
+ display: block;
148
+ margin-top: 15px;
149
+ margin-bottom: 5px;
150
+ font-weight: 500;
151
+ }
152
+ </style>
153
+ </head>
154
+
155
+ <body>
156
+ <h1>🎯 Hover & Select Examples</h1>
157
+ <p>Test page for hover interactions and native select elements.</p>
158
+
159
+ <!-- Tooltip Example -->
160
+ <div class="card">
161
+ <h2>1. Tooltip on Hover</h2>
162
+ <p>Hover over the button to see a tooltip:</p>
163
+
164
+ <div class="tooltip-container" id="tooltip-trigger">
165
+ Hover Me
166
+ <span class="tooltip" id="tooltip-text">This is a helpful tooltip!</span>
167
+ </div>
168
+
169
+ <div id="tooltip-status" class="result">Tooltip not shown</div>
170
+ </div>
171
+
172
+ <!-- Dropdown Menu Example -->
173
+ <div class="card">
174
+ <h2>2. Dropdown Menu on Hover</h2>
175
+ <p>Hover over the button to reveal menu:</p>
176
+
177
+ <div class="dropdown">
178
+ <button class="dropdown-button" id="dropdown-trigger">Menu ▼</button>
179
+ <div class="dropdown-menu" id="dropdown-menu">
180
+ <div class="dropdown-item" id="menu-item-1" onclick="selectMenuItem('Profile')">👤 Profile</div>
181
+ <div class="dropdown-item" id="menu-item-2" onclick="selectMenuItem('Settings')">⚙️ Settings</div>
182
+ <div class="dropdown-item" id="menu-item-3" onclick="selectMenuItem('Logout')">🚪 Logout</div>
183
+ </div>
184
+ </div>
185
+
186
+ <div id="menu-selection" class="result">No menu item selected</div>
187
+ </div>
188
+
189
+ <!-- Native Select Elements -->
190
+ <div class="card">
191
+ <h2>3. Native Select Dropdowns</h2>
192
+
193
+ <label for="language-select">Language:</label>
194
+ <select id="language-select" onchange="updateSelection()">
195
+ <option value="">-- Choose Language --</option>
196
+ <option value="en">English</option>
197
+ <option value="es">Spanish</option>
198
+ <option value="fr">French</option>
199
+ <option value="de">German</option>
200
+ <option value="ja">Japanese</option>
201
+ </select>
202
+
203
+ <label for="country-select">Country:</label>
204
+ <select id="country-select" onchange="updateSelection()">
205
+ <option value="">-- Choose Country --</option>
206
+ <option value="us">United States</option>
207
+ <option value="uk">United Kingdom</option>
208
+ <option value="ca">Canada</option>
209
+ <option value="au">Australia</option>
210
+ <option value="jp">Japan</option>
211
+ </select>
212
+
213
+ <label for="framework-select">Framework:</label>
214
+ <select id="framework-select" onchange="updateSelection()">
215
+ <option value="">-- Choose Framework --</option>
216
+ <option value="react">React</option>
217
+ <option value="vue">Vue</option>
218
+ <option value="angular">Angular</option>
219
+ <option value="svelte">Svelte</option>
220
+ </select>
221
+
222
+ <div id="select-result" class="result">Make selections above...</div>
223
+ </div>
224
+
225
+ <!-- Hover State Change -->
226
+ <div class="card">
227
+ <h2>4. Hover State Detection</h2>
228
+ <p>These elements change state on hover:</p>
229
+
230
+ <div id="hover-box"
231
+ style="display: inline-block; padding: 20px; background: #007bff; color: white; border-radius: 4px; cursor: pointer; transition: all 0.2s;">
232
+ Hover Box
233
+ </div>
234
+
235
+ <div id="hover-box-status" class="result" style="margin-top: 10px;">Hover state: not hovered</div>
236
+ </div>
237
+
238
+ <script>
239
+ // Tooltip tracking
240
+ const tooltipTrigger = document.getElementById('tooltip-trigger');
241
+ const tooltipStatus = document.getElementById('tooltip-status');
242
+
243
+ tooltipTrigger.addEventListener('mouseenter', () => {
244
+ tooltipStatus.textContent = 'Tooltip is visible';
245
+ tooltipStatus.style.background = '#d4edda';
246
+ });
247
+
248
+ tooltipTrigger.addEventListener('mouseleave', () => {
249
+ tooltipStatus.textContent = 'Tooltip hidden';
250
+ tooltipStatus.style.background = '#f8f9fa';
251
+ });
252
+
253
+ // Dropdown menu item selection
254
+ function selectMenuItem(item) {
255
+ document.getElementById('menu-selection').textContent = `Selected: ${item}`;
256
+ document.getElementById('menu-selection').style.background = '#d4edda';
257
+ }
258
+
259
+ // Select updates
260
+ function updateSelection() {
261
+ const language = document.getElementById('language-select').value;
262
+ const country = document.getElementById('country-select').value;
263
+ const framework = document.getElementById('framework-select').value;
264
+
265
+ const parts = [];
266
+ if (language) parts.push(`Language: ${language}`);
267
+ if (country) parts.push(`Country: ${country}`);
268
+ if (framework) parts.push(`Framework: ${framework}`);
269
+
270
+ const result = document.getElementById('select-result');
271
+ result.textContent = parts.length > 0 ? parts.join(' | ') : 'Make selections above...';
272
+ result.style.background = parts.length > 0 ? '#d4edda' : '#f8f9fa';
273
+ }
274
+
275
+ // Hover box
276
+ const hoverBox = document.getElementById('hover-box');
277
+ const hoverBoxStatus = document.getElementById('hover-box-status');
278
+
279
+ hoverBox.addEventListener('mouseenter', () => {
280
+ hoverBox.style.background = '#0056b3';
281
+ hoverBox.style.transform = 'scale(1.05)';
282
+ hoverBoxStatus.textContent = 'Hover state: hovered';
283
+ hoverBoxStatus.style.background = '#d4edda';
284
+ });
285
+
286
+ hoverBox.addEventListener('mouseleave', () => {
287
+ hoverBox.style.background = '#007bff';
288
+ hoverBox.style.transform = 'scale(1)';
289
+ hoverBoxStatus.textContent = 'Hover state: not hovered';
290
+ hoverBoxStatus.style.background = '#f8f9fa';
291
+ });
292
+ </script>
293
+ </body>
294
+
295
+ </html>
@@ -0,0 +1,51 @@
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>Iframe Child Page</title>
8
+ <style>
9
+ body {
10
+ font-family: system-ui, sans-serif;
11
+ margin: 1rem;
12
+ }
13
+
14
+ button {
15
+ padding: 8px 14px;
16
+ border: none;
17
+ border-radius: 6px;
18
+ background: #2563eb;
19
+ color: #fff;
20
+ font-weight: 600;
21
+ cursor: pointer;
22
+ }
23
+
24
+ button:hover {
25
+ background: #1d4ed8;
26
+ }
27
+
28
+ #child-result {
29
+ margin-top: 0.5rem;
30
+ font-weight: bold;
31
+ }
32
+ </style>
33
+ </head>
34
+
35
+ <body>
36
+ <h2>Child Frame</h2>
37
+ <button id="child-btn">Click Me</button>
38
+ <p id="child-result"></p>
39
+ <input id="child-input" type="text" placeholder="type here" />
40
+ <p id="child-value"></p>
41
+ <script>
42
+ document.getElementById('child-btn').addEventListener('click', function () {
43
+ document.getElementById('child-result').textContent = 'clicked';
44
+ });
45
+ document.getElementById('child-input').addEventListener('input', function () {
46
+ document.getElementById('child-value').textContent = this.value;
47
+ });
48
+ </script>
49
+ </body>
50
+
51
+ </html>
@@ -0,0 +1,35 @@
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 Iframes</title>
8
+ <style>
9
+ body {
10
+ font-family: system-ui, sans-serif;
11
+ margin: 1rem;
12
+ }
13
+
14
+ iframe {
15
+ border: 2px solid #ddd;
16
+ border-radius: 8px;
17
+ width: 480px;
18
+ height: 200px;
19
+ }
20
+
21
+ #parent-status {
22
+ margin-top: 0.5rem;
23
+ font-weight: bold;
24
+ }
25
+ </style>
26
+ </head>
27
+
28
+ <body>
29
+ <h1>Iframe Test Page</h1>
30
+ <p>Parent page content.</p>
31
+ <p id="parent-status"></p>
32
+ <iframe id="my-frame" src="/iframe-child.html"></iframe>
33
+ </body>
34
+
35
+ </html>