snapdrive-ios 0.1.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.ja.md +95 -0
  3. package/README.md +95 -0
  4. package/dist/cli.d.ts +7 -0
  5. package/dist/cli.d.ts.map +1 -0
  6. package/dist/cli.js +265 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/core/command-executor.d.ts +15 -0
  9. package/dist/core/command-executor.d.ts.map +1 -0
  10. package/dist/core/command-executor.js +64 -0
  11. package/dist/core/command-executor.js.map +1 -0
  12. package/dist/core/element-finder.d.ts +81 -0
  13. package/dist/core/element-finder.d.ts.map +1 -0
  14. package/dist/core/element-finder.js +246 -0
  15. package/dist/core/element-finder.js.map +1 -0
  16. package/dist/core/idb-client.d.ts +68 -0
  17. package/dist/core/idb-client.d.ts.map +1 -0
  18. package/dist/core/idb-client.js +327 -0
  19. package/dist/core/idb-client.js.map +1 -0
  20. package/dist/core/image-differ.d.ts +55 -0
  21. package/dist/core/image-differ.d.ts.map +1 -0
  22. package/dist/core/image-differ.js +211 -0
  23. package/dist/core/image-differ.js.map +1 -0
  24. package/dist/core/index.d.ts +9 -0
  25. package/dist/core/index.d.ts.map +1 -0
  26. package/dist/core/index.js +9 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/core/report-generator.d.ts +31 -0
  29. package/dist/core/report-generator.d.ts.map +1 -0
  30. package/dist/core/report-generator.js +675 -0
  31. package/dist/core/report-generator.js.map +1 -0
  32. package/dist/core/scenario-runner.d.ts +54 -0
  33. package/dist/core/scenario-runner.d.ts.map +1 -0
  34. package/dist/core/scenario-runner.js +701 -0
  35. package/dist/core/scenario-runner.js.map +1 -0
  36. package/dist/core/simctl-client.d.ts +64 -0
  37. package/dist/core/simctl-client.d.ts.map +1 -0
  38. package/dist/core/simctl-client.js +214 -0
  39. package/dist/core/simctl-client.js.map +1 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +11 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/interfaces/config.interface.d.ts +37 -0
  45. package/dist/interfaces/config.interface.d.ts.map +1 -0
  46. package/dist/interfaces/config.interface.js +14 -0
  47. package/dist/interfaces/config.interface.js.map +1 -0
  48. package/dist/interfaces/element.interface.d.ts +49 -0
  49. package/dist/interfaces/element.interface.d.ts.map +1 -0
  50. package/dist/interfaces/element.interface.js +5 -0
  51. package/dist/interfaces/element.interface.js.map +1 -0
  52. package/dist/interfaces/index.d.ts +7 -0
  53. package/dist/interfaces/index.d.ts.map +1 -0
  54. package/dist/interfaces/index.js +7 -0
  55. package/dist/interfaces/index.js.map +1 -0
  56. package/dist/interfaces/scenario.interface.d.ts +101 -0
  57. package/dist/interfaces/scenario.interface.d.ts.map +1 -0
  58. package/dist/interfaces/scenario.interface.js +5 -0
  59. package/dist/interfaces/scenario.interface.js.map +1 -0
  60. package/dist/server.d.ts +28 -0
  61. package/dist/server.d.ts.map +1 -0
  62. package/dist/server.js +943 -0
  63. package/dist/server.js.map +1 -0
  64. package/dist/utils/index.d.ts +5 -0
  65. package/dist/utils/index.d.ts.map +1 -0
  66. package/dist/utils/index.js +5 -0
  67. package/dist/utils/index.js.map +1 -0
  68. package/dist/utils/logger.d.ts +24 -0
  69. package/dist/utils/logger.d.ts.map +1 -0
  70. package/dist/utils/logger.js +50 -0
  71. package/dist/utils/logger.js.map +1 -0
  72. package/package.json +67 -0
@@ -0,0 +1,675 @@
1
+ /**
2
+ * HTML Report Generator for test results
3
+ * Generates self-contained HTML with embedded base64 images
4
+ */
5
+ import { writeFile, readFile, mkdir } from 'node:fs/promises';
6
+ import { existsSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { Logger } from '../utils/logger.js';
10
+ export class ReportGenerator {
11
+ logger;
12
+ logoDataUri = null;
13
+ constructor(logger) {
14
+ this.logger = logger ?? new Logger('report-generator');
15
+ }
16
+ /**
17
+ * Load logo image as base64 data URI
18
+ */
19
+ async getLogoDataUri() {
20
+ if (this.logoDataUri !== null) {
21
+ return this.logoDataUri;
22
+ }
23
+ try {
24
+ // Get the path to the logo relative to this source file
25
+ const __filename = fileURLToPath(import.meta.url);
26
+ const __dirname = dirname(__filename);
27
+ const logoPath = join(__dirname, '../../docs/images/logo.png');
28
+ if (!existsSync(logoPath)) {
29
+ this.logger.debug(`Logo not found: ${logoPath}`);
30
+ this.logoDataUri = '';
31
+ return null;
32
+ }
33
+ const buffer = await readFile(logoPath);
34
+ this.logoDataUri = `data:image/png;base64,${buffer.toString('base64')}`;
35
+ return this.logoDataUri;
36
+ }
37
+ catch (error) {
38
+ this.logger.debug(`Failed to load logo: ${error}`);
39
+ this.logoDataUri = '';
40
+ return null;
41
+ }
42
+ }
43
+ /**
44
+ * Generate HTML report for test results
45
+ */
46
+ async generateReport(result) {
47
+ const reportPath = `${result.resultsDir}/report.html`;
48
+ const html = await this.buildHtml(result);
49
+ await mkdir(dirname(reportPath), { recursive: true });
50
+ await writeFile(reportPath, html, 'utf-8');
51
+ this.logger.info(`Report generated: ${reportPath}`);
52
+ return reportPath;
53
+ }
54
+ /**
55
+ * Read image file and convert to base64 data URI
56
+ */
57
+ async imageToDataUri(imagePath) {
58
+ try {
59
+ if (!existsSync(imagePath)) {
60
+ this.logger.debug(`Image not found: ${imagePath}`);
61
+ return null;
62
+ }
63
+ const buffer = await readFile(imagePath);
64
+ const base64 = buffer.toString('base64');
65
+ return `data:image/png;base64,${base64}`;
66
+ }
67
+ catch (error) {
68
+ this.logger.debug(`Failed to read image: ${imagePath}`, { error: String(error) });
69
+ return null;
70
+ }
71
+ }
72
+ async buildHtml(result) {
73
+ const passRate = result.totalTests > 0 ? ((result.passed / result.totalTests) * 100).toFixed(1) : '0';
74
+ // Load logo
75
+ const logoDataUri = await this.getLogoDataUri();
76
+ // Build test case HTML with embedded images
77
+ const testCasesHtml = [];
78
+ for (const tc of result.results) {
79
+ const tcHtml = await this.buildTestCaseHtml(tc);
80
+ testCasesHtml.push(tcHtml);
81
+ }
82
+ return `<!DOCTYPE html>
83
+ <html lang="ja">
84
+ <head>
85
+ <meta charset="UTF-8">
86
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
87
+ <title>SnapDrive Test Report - ${result.runId}</title>
88
+ <style>
89
+ :root {
90
+ --color-pass: #22c55e;
91
+ --color-fail: #ef4444;
92
+ --color-bg: #f8fafc;
93
+ --color-card: #ffffff;
94
+ --color-border: #e2e8f0;
95
+ --color-text: #1e293b;
96
+ --color-text-muted: #64748b;
97
+ }
98
+ * { box-sizing: border-box; margin: 0; padding: 0; }
99
+ body {
100
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
101
+ background: var(--color-bg);
102
+ color: var(--color-text);
103
+ line-height: 1.5;
104
+ padding: 2rem;
105
+ }
106
+ .container { max-width: 1400px; margin: 0 auto; }
107
+ .report-header {
108
+ display: flex;
109
+ flex-direction: column;
110
+ align-items: flex-start;
111
+ gap: 0.5rem;
112
+ margin-bottom: 1.5rem;
113
+ }
114
+ .report-logo {
115
+ height: 56px;
116
+ width: auto;
117
+ }
118
+ h1 { font-size: 1.25rem; margin: 0; color: var(--color-text-muted); }
119
+ h2 { font-size: 1.25rem; margin-bottom: 0.75rem; }
120
+ h3 { font-size: 1rem; margin-bottom: 0.5rem; }
121
+ h4 { font-size: 0.875rem; margin-bottom: 0.5rem; color: var(--color-text-muted); }
122
+ .summary {
123
+ background: var(--color-card);
124
+ border-radius: 8px;
125
+ padding: 1.5rem;
126
+ margin-bottom: 2rem;
127
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
128
+ }
129
+ .summary-grid {
130
+ display: grid;
131
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
132
+ gap: 1rem;
133
+ margin-top: 1rem;
134
+ }
135
+ .summary-item {
136
+ text-align: center;
137
+ padding: 1rem;
138
+ background: var(--color-bg);
139
+ border-radius: 6px;
140
+ }
141
+ .summary-item .value { font-size: 2rem; font-weight: 700; }
142
+ .summary-item .label { color: var(--color-text-muted); font-size: 0.875rem; }
143
+ .summary-item.pass .value { color: var(--color-pass); }
144
+ .summary-item.fail .value { color: var(--color-fail); }
145
+ .test-case {
146
+ background: var(--color-card);
147
+ border-radius: 8px;
148
+ margin-bottom: 1.5rem;
149
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
150
+ overflow: hidden;
151
+ }
152
+ .test-case-header {
153
+ padding: 1rem 1.5rem;
154
+ border-bottom: 1px solid var(--color-border);
155
+ display: flex;
156
+ justify-content: space-between;
157
+ align-items: center;
158
+ }
159
+ .test-case-header.pass { border-left: 4px solid var(--color-pass); }
160
+ .test-case-header.fail { border-left: 4px solid var(--color-fail); }
161
+ .badge {
162
+ padding: 0.25rem 0.75rem;
163
+ border-radius: 9999px;
164
+ font-size: 0.75rem;
165
+ font-weight: 600;
166
+ text-transform: uppercase;
167
+ }
168
+ .badge.pass { background: #dcfce7; color: #166534; }
169
+ .badge.fail { background: #fee2e2; color: #991b1b; }
170
+ .test-case-body { padding: 1.5rem; }
171
+ .steps { margin-bottom: 1.5rem; }
172
+ .step {
173
+ display: flex;
174
+ align-items: center;
175
+ gap: 0.75rem;
176
+ padding: 0.5rem 0;
177
+ border-bottom: 1px solid var(--color-border);
178
+ }
179
+ .step:last-child { border-bottom: none; }
180
+ .step-icon {
181
+ width: 24px;
182
+ height: 24px;
183
+ border-radius: 50%;
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: center;
187
+ font-size: 0.75rem;
188
+ flex-shrink: 0;
189
+ }
190
+ .step-icon.pass { background: #dcfce7; color: #166534; }
191
+ .step-icon.fail { background: #fee2e2; color: #991b1b; }
192
+ .step-action {
193
+ font-family: monospace;
194
+ background: var(--color-bg);
195
+ padding: 0.25rem 0.5rem;
196
+ border-radius: 4px;
197
+ font-size: 0.875rem;
198
+ }
199
+ .step-error {
200
+ color: var(--color-fail);
201
+ font-size: 0.75rem;
202
+ flex: 1;
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
205
+ }
206
+ .step-duration {
207
+ color: var(--color-text-muted);
208
+ font-size: 0.75rem;
209
+ margin-left: auto;
210
+ flex-shrink: 0;
211
+ }
212
+ .checkpoints { margin-top: 1rem; }
213
+ .checkpoint {
214
+ margin-bottom: 1.5rem;
215
+ padding: 1rem;
216
+ background: var(--color-bg);
217
+ border-radius: 6px;
218
+ }
219
+ .checkpoint-header {
220
+ display: flex;
221
+ justify-content: space-between;
222
+ align-items: center;
223
+ margin-bottom: 1rem;
224
+ }
225
+ .diff-percent { font-weight: 600; }
226
+ .diff-percent.pass { color: var(--color-pass); }
227
+ .diff-percent.fail { color: var(--color-fail); }
228
+ .screenshot-compare {
229
+ display: flex;
230
+ flex-direction: row;
231
+ gap: 1rem;
232
+ overflow-x: auto;
233
+ padding-bottom: 0.5rem;
234
+ }
235
+ .screenshot-item {
236
+ flex: 1 1 0;
237
+ min-width: 200px;
238
+ max-width: 400px;
239
+ text-align: center;
240
+ }
241
+ .screenshot-item img {
242
+ width: 100%;
243
+ height: auto;
244
+ border: 3px solid var(--color-border);
245
+ border-radius: 4px;
246
+ cursor: pointer;
247
+ transition: transform 0.2s, border-color 0.2s;
248
+ }
249
+ .screenshot-item img:hover {
250
+ transform: scale(1.02);
251
+ border-color: #3b82f6;
252
+ }
253
+ .screenshot-item.diff img { border-color: var(--color-fail); }
254
+ .screenshot-item.actual img { border-color: #3b82f6; }
255
+ .screenshot-item.baseline img { border-color: #22c55e; }
256
+ .screenshot-label {
257
+ margin-top: 0.5rem;
258
+ font-size: 0.75rem;
259
+ color: var(--color-text-muted);
260
+ font-weight: 600;
261
+ }
262
+ .no-image {
263
+ padding: 3rem 1rem;
264
+ color: var(--color-text-muted);
265
+ background: #f1f5f9;
266
+ border-radius: 4px;
267
+ font-size: 0.875rem;
268
+ }
269
+ .meta {
270
+ color: var(--color-text-muted);
271
+ font-size: 0.875rem;
272
+ margin-top: 1rem;
273
+ }
274
+ .modal {
275
+ display: none;
276
+ position: fixed;
277
+ top: 0;
278
+ left: 0;
279
+ width: 100%;
280
+ height: 100%;
281
+ background: rgba(0,0,0,0.95);
282
+ z-index: 1000;
283
+ justify-content: center;
284
+ align-items: center;
285
+ flex-direction: column;
286
+ }
287
+ .modal.active { display: flex; }
288
+ .modal img {
289
+ max-width: 95%;
290
+ max-height: 90%;
291
+ object-fit: contain;
292
+ }
293
+ .modal-close {
294
+ position: absolute;
295
+ top: 1rem;
296
+ right: 1rem;
297
+ color: white;
298
+ font-size: 2rem;
299
+ cursor: pointer;
300
+ background: none;
301
+ border: none;
302
+ width: 48px;
303
+ height: 48px;
304
+ }
305
+ .modal-label {
306
+ color: white;
307
+ margin-top: 1rem;
308
+ font-size: 0.875rem;
309
+ }
310
+ /* Full page checkpoint styles */
311
+ .full-page-badge {
312
+ display: inline-block;
313
+ padding: 0.125rem 0.5rem;
314
+ background: #dbeafe;
315
+ color: #1e40af;
316
+ border-radius: 4px;
317
+ font-size: 0.625rem;
318
+ font-weight: 600;
319
+ text-transform: uppercase;
320
+ margin-left: 0.5rem;
321
+ vertical-align: middle;
322
+ }
323
+ .scroll-segments {
324
+ margin-top: 1.5rem;
325
+ padding-top: 1rem;
326
+ border-top: 1px solid var(--color-border);
327
+ }
328
+ .scroll-segments h5 {
329
+ font-size: 0.75rem;
330
+ color: var(--color-text-muted);
331
+ margin-bottom: 0.75rem;
332
+ }
333
+ .segments-grid {
334
+ display: grid;
335
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
336
+ gap: 0.75rem;
337
+ }
338
+ .segment-item {
339
+ text-align: center;
340
+ }
341
+ .segment-item img {
342
+ width: 100%;
343
+ height: auto;
344
+ border: 2px solid var(--color-border);
345
+ border-radius: 4px;
346
+ cursor: pointer;
347
+ transition: transform 0.2s, border-color 0.2s;
348
+ }
349
+ .segment-item img:hover {
350
+ transform: scale(1.05);
351
+ border-color: #3b82f6;
352
+ }
353
+ .segment-label {
354
+ margin-top: 0.25rem;
355
+ font-size: 0.625rem;
356
+ color: var(--color-text-muted);
357
+ }
358
+ /* Route simulation checkpoint styles */
359
+ .route-badge {
360
+ display: inline-block;
361
+ padding: 0.125rem 0.5rem;
362
+ background: #fef3c7;
363
+ color: #92400e;
364
+ border-radius: 4px;
365
+ font-size: 0.625rem;
366
+ font-weight: 600;
367
+ text-transform: uppercase;
368
+ margin-left: 0.5rem;
369
+ vertical-align: middle;
370
+ }
371
+ .waypoints-section {
372
+ margin-top: 1.5rem;
373
+ padding-top: 1rem;
374
+ border-top: 1px solid var(--color-border);
375
+ }
376
+ .waypoints-section h5 {
377
+ font-size: 0.75rem;
378
+ color: var(--color-text-muted);
379
+ margin-bottom: 0.75rem;
380
+ }
381
+ .waypoints-list {
382
+ display: flex;
383
+ flex-direction: column;
384
+ gap: 1rem;
385
+ }
386
+ .waypoint-comparison {
387
+ background: #fffbeb;
388
+ border: 1px solid #fbbf24;
389
+ border-radius: 6px;
390
+ padding: 0.75rem;
391
+ }
392
+ .waypoint-header {
393
+ display: flex;
394
+ justify-content: space-between;
395
+ align-items: center;
396
+ margin-bottom: 0.5rem;
397
+ }
398
+ .waypoint-title {
399
+ font-weight: 600;
400
+ font-size: 0.875rem;
401
+ color: #92400e;
402
+ }
403
+ .waypoint-status {
404
+ font-size: 0.75rem;
405
+ font-weight: 600;
406
+ }
407
+ .waypoint-status.pass { color: var(--color-pass); }
408
+ .waypoint-status.fail { color: var(--color-fail); }
409
+ .waypoint-images {
410
+ display: flex;
411
+ gap: 0.5rem;
412
+ overflow-x: auto;
413
+ }
414
+ .waypoint-img-item {
415
+ flex: 1 1 0;
416
+ min-width: 100px;
417
+ max-width: 200px;
418
+ text-align: center;
419
+ }
420
+ .waypoint-img-item img {
421
+ width: 100%;
422
+ height: auto;
423
+ border: 2px solid var(--color-border);
424
+ border-radius: 4px;
425
+ cursor: pointer;
426
+ transition: transform 0.2s, border-color 0.2s;
427
+ }
428
+ .waypoint-img-item img:hover {
429
+ transform: scale(1.02);
430
+ border-color: #f59e0b;
431
+ }
432
+ .waypoint-img-label {
433
+ margin-top: 0.25rem;
434
+ font-size: 0.625rem;
435
+ color: var(--color-text-muted);
436
+ }
437
+ .waypoint-img-placeholder {
438
+ padding: 2rem 0.5rem;
439
+ color: var(--color-text-muted);
440
+ background: #f1f5f9;
441
+ border-radius: 4px;
442
+ font-size: 0.75rem;
443
+ }
444
+ </style>
445
+ </head>
446
+ <body>
447
+ <div class="container">
448
+ <div class="report-header">
449
+ ${logoDataUri ? `<img src="${logoDataUri}" alt="SnapDrive" class="report-logo">` : ''}
450
+ <h1>Test Report</h1>
451
+ </div>
452
+
453
+ <div class="summary">
454
+ <h2>Summary</h2>
455
+ <div class="summary-grid">
456
+ <div class="summary-item">
457
+ <div class="value">${result.totalTests}</div>
458
+ <div class="label">Total Tests</div>
459
+ </div>
460
+ <div class="summary-item pass">
461
+ <div class="value">${result.passed}</div>
462
+ <div class="label">Passed</div>
463
+ </div>
464
+ <div class="summary-item fail">
465
+ <div class="value">${result.failed}</div>
466
+ <div class="label">Failed</div>
467
+ </div>
468
+ <div class="summary-item">
469
+ <div class="value">${passRate}%</div>
470
+ <div class="label">Pass Rate</div>
471
+ </div>
472
+ <div class="summary-item">
473
+ <div class="value">${(result.durationMs / 1000).toFixed(1)}s</div>
474
+ <div class="label">Duration</div>
475
+ </div>
476
+ </div>
477
+ <div class="meta">
478
+ Run ID: ${this.escapeHtml(result.runId)}<br>
479
+ Started: ${result.startTime}<br>
480
+ Ended: ${result.endTime}
481
+ </div>
482
+ </div>
483
+
484
+ <h2>Test Cases</h2>
485
+ ${testCasesHtml.join('\n')}
486
+ </div>
487
+
488
+ <div class="modal" id="imageModal" onclick="closeModal()">
489
+ <button class="modal-close" onclick="closeModal()">&times;</button>
490
+ <img id="modalImage" src="" alt="Full size screenshot">
491
+ <div class="modal-label" id="modalLabel"></div>
492
+ </div>
493
+
494
+ <script>
495
+ function openModal(src, label) {
496
+ document.getElementById('modalImage').src = src;
497
+ document.getElementById('modalLabel').textContent = label || '';
498
+ document.getElementById('imageModal').classList.add('active');
499
+ }
500
+ function closeModal() {
501
+ document.getElementById('imageModal').classList.remove('active');
502
+ }
503
+ document.addEventListener('keydown', (e) => {
504
+ if (e.key === 'Escape') closeModal();
505
+ });
506
+ </script>
507
+ </body>
508
+ </html>`;
509
+ }
510
+ async buildTestCaseHtml(tc) {
511
+ const statusClass = tc.success ? 'pass' : 'fail';
512
+ // Build checkpoints HTML with embedded images
513
+ let checkpointsHtml = '';
514
+ if (tc.checkpoints.length > 0) {
515
+ const cpHtmlParts = [];
516
+ for (const cp of tc.checkpoints) {
517
+ const cpHtml = await this.buildCheckpointHtml(cp);
518
+ cpHtmlParts.push(cpHtml);
519
+ }
520
+ checkpointsHtml = `
521
+ <div class="checkpoints">
522
+ <h4>Screenshot Checkpoints</h4>
523
+ ${cpHtmlParts.join('\n')}
524
+ </div>`;
525
+ }
526
+ return `
527
+ <div class="test-case">
528
+ <div class="test-case-header ${statusClass}">
529
+ <div>
530
+ <h3>${this.escapeHtml(tc.testCaseName)}</h3>
531
+ <span class="meta">${this.escapeHtml(tc.testCaseId)} - ${(tc.durationMs / 1000).toFixed(2)}s</span>
532
+ </div>
533
+ <span class="badge ${statusClass}">${tc.success ? 'PASS' : 'FAIL'}</span>
534
+ </div>
535
+ <div class="test-case-body">
536
+ <div class="steps">
537
+ <h4>Steps (${tc.steps.length})</h4>
538
+ ${tc.steps
539
+ .map((step, idx) => `
540
+ <div class="step">
541
+ <span class="step-icon ${step.success ? 'pass' : 'fail'}">${step.success ? '✓' : '✗'}</span>
542
+ <span class="step-action">${idx + 1}. ${step.action}</span>
543
+ ${step.error ? `<span class="step-error">${this.escapeHtml(step.error.slice(0, 100))}</span>` : ''}
544
+ <span class="step-duration">${step.duration}ms</span>
545
+ </div>
546
+ `)
547
+ .join('')}
548
+ </div>
549
+ ${checkpointsHtml}
550
+ </div>
551
+ </div>`;
552
+ }
553
+ async buildCheckpointHtml(cp) {
554
+ // Load images as base64 data URIs
555
+ const [actualDataUri, baselineDataUri, diffDataUri] = await Promise.all([
556
+ this.imageToDataUri(cp.actualPath),
557
+ this.imageToDataUri(cp.baselinePath),
558
+ cp.diffPath ? this.imageToDataUri(cp.diffPath) : Promise.resolve(null),
559
+ ]);
560
+ const buildImageHtml = (dataUri, label, cssClass) => {
561
+ const noImageMsg = label === 'Baseline' ? 'Baseline not found' : (label === 'Diff' ? 'No differences' : 'No image');
562
+ if (!dataUri) {
563
+ return `
564
+ <div class="screenshot-item ${cssClass}">
565
+ <div class="no-image">${noImageMsg}</div>
566
+ <div class="screenshot-label">${label}</div>
567
+ </div>`;
568
+ }
569
+ return `
570
+ <div class="screenshot-item ${cssClass}">
571
+ <img src="${dataUri}" alt="${label}" onclick="openModal(this.src, '${this.escapeHtml(cp.name)} - ${label}')">
572
+ <div class="screenshot-label">${label}</div>
573
+ </div>`;
574
+ };
575
+ // Build scroll segments section if this is a full-page checkpoint
576
+ let segmentsHtml = '';
577
+ if (cp.isFullPage && cp.segmentPaths && cp.segmentPaths.length > 1) {
578
+ const segmentDataUris = await Promise.all(cp.segmentPaths.map(p => this.imageToDataUri(p)));
579
+ const segmentItems = segmentDataUris
580
+ .map((dataUri, idx) => {
581
+ if (!dataUri)
582
+ return '';
583
+ return `
584
+ <div class="segment-item">
585
+ <img src="${dataUri}" alt="Segment ${idx + 1}" onclick="openModal(this.src, '${this.escapeHtml(cp.name)} - Segment ${idx + 1}')">
586
+ <div class="segment-label">Segment ${idx + 1}</div>
587
+ </div>`;
588
+ })
589
+ .join('');
590
+ segmentsHtml = `
591
+ <div class="scroll-segments">
592
+ <h5>Scroll Segments (${cp.segmentPaths.length})</h5>
593
+ <div class="segments-grid">
594
+ ${segmentItems}
595
+ </div>
596
+ </div>`;
597
+ }
598
+ // Build waypoints section if this is a route simulation checkpoint
599
+ let waypointsHtml = '';
600
+ if (cp.isRouteSimulation && cp.waypointResults && cp.waypointResults.length > 0) {
601
+ const waypointItemsHtml = [];
602
+ for (const wp of cp.waypointResults) {
603
+ const [wpActualUri, wpBaselineUri, wpDiffUri] = await Promise.all([
604
+ this.imageToDataUri(wp.actualPath),
605
+ this.imageToDataUri(wp.baselinePath),
606
+ wp.diffPath ? this.imageToDataUri(wp.diffPath) : Promise.resolve(null),
607
+ ]);
608
+ const statusClass = wp.match ? 'pass' : 'fail';
609
+ const statusIcon = wp.match ? '✓' : '✗';
610
+ const diffText = `${wp.differencePercent.toFixed(1)}%`;
611
+ const buildWpImageHtml = (dataUri, label) => {
612
+ if (!dataUri) {
613
+ return `<div class="waypoint-img-placeholder">${label === 'Baseline' ? 'No baseline' : 'No image'}</div>`;
614
+ }
615
+ return `<img src="${dataUri}" alt="${label}" onclick="openModal(this.src, '${this.escapeHtml(cp.name)} - Waypoint ${wp.index + 1} ${label}')">`;
616
+ };
617
+ waypointItemsHtml.push(`
618
+ <div class="waypoint-comparison">
619
+ <div class="waypoint-header">
620
+ <span class="waypoint-title">Waypoint ${wp.index + 1}</span>
621
+ <span class="waypoint-status ${statusClass}">${statusIcon} ${diffText}</span>
622
+ </div>
623
+ <div class="waypoint-images">
624
+ <div class="waypoint-img-item">
625
+ ${buildWpImageHtml(wpActualUri, 'Actual')}
626
+ <div class="waypoint-img-label">Actual</div>
627
+ </div>
628
+ <div class="waypoint-img-item">
629
+ ${buildWpImageHtml(wpBaselineUri, 'Baseline')}
630
+ <div class="waypoint-img-label">Baseline</div>
631
+ </div>
632
+ <div class="waypoint-img-item">
633
+ ${buildWpImageHtml(wpDiffUri, 'Diff')}
634
+ <div class="waypoint-img-label">Diff</div>
635
+ </div>
636
+ </div>
637
+ </div>`);
638
+ }
639
+ waypointsHtml = `
640
+ <div class="waypoints-section">
641
+ <h5>Route Waypoints (${cp.waypointResults.length})</h5>
642
+ <div class="waypoints-list">
643
+ ${waypointItemsHtml.join('')}
644
+ </div>
645
+ </div>`;
646
+ }
647
+ const fullPageBadge = cp.isFullPage ? '<span class="full-page-badge">Full Page</span>' : '';
648
+ const routeBadge = cp.isRouteSimulation ? '<span class="route-badge">Route</span>' : '';
649
+ return `
650
+ <div class="checkpoint">
651
+ <div class="checkpoint-header">
652
+ <strong>${this.escapeHtml(cp.name)}</strong> ${fullPageBadge}${routeBadge}
653
+ <span class="diff-percent ${cp.match ? 'pass' : 'fail'}">
654
+ ${cp.match ? `✓ Match (${cp.differencePercent.toFixed(2)}%)` : `✗ ${cp.differencePercent.toFixed(2)}% different`}
655
+ </span>
656
+ </div>
657
+ <div class="screenshot-compare">
658
+ ${buildImageHtml(actualDataUri, 'Actual', 'actual')}
659
+ ${buildImageHtml(baselineDataUri, 'Baseline', 'baseline')}
660
+ ${buildImageHtml(diffDataUri, 'Diff', 'diff')}
661
+ </div>
662
+ ${segmentsHtml}
663
+ ${waypointsHtml}
664
+ </div>`;
665
+ }
666
+ escapeHtml(str) {
667
+ return str
668
+ .replace(/&/g, '&amp;')
669
+ .replace(/</g, '&lt;')
670
+ .replace(/>/g, '&gt;')
671
+ .replace(/"/g, '&quot;')
672
+ .replace(/'/g, '&#039;');
673
+ }
674
+ }
675
+ //# sourceMappingURL=report-generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-generator.js","sourceRoot":"","sources":["../../src/core/report-generator.ts"],"names":[],"mappings":"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"}