@supernal/interface 1.0.9 → 1.0.11

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 (147) hide show
  1. package/dist/cjs/src/browser.js +42 -1
  2. package/dist/cjs/src/browser.js.map +1 -1
  3. package/dist/cjs/src/decorators/ContainerHelpers.js +16 -1
  4. package/dist/cjs/src/decorators/ContainerHelpers.js.map +1 -1
  5. package/dist/cjs/src/decorators/Tool.js +2 -3
  6. package/dist/cjs/src/decorators/Tool.js.map +1 -1
  7. package/dist/cjs/src/testing/graph-tester/core/GraphTester.js +339 -0
  8. package/dist/cjs/src/testing/graph-tester/core/GraphTester.js.map +1 -0
  9. package/dist/cjs/src/testing/graph-tester/core/TestFunction.js +189 -0
  10. package/dist/cjs/src/testing/graph-tester/core/TestFunction.js.map +1 -0
  11. package/dist/cjs/src/testing/graph-tester/core/types.js +24 -0
  12. package/dist/cjs/src/testing/graph-tester/core/types.js.map +1 -0
  13. package/dist/cjs/src/testing/graph-tester/fixtures/index.js +13 -0
  14. package/dist/cjs/src/testing/graph-tester/fixtures/index.js.map +1 -0
  15. package/dist/cjs/src/testing/graph-tester/fixtures/portPool.js +184 -0
  16. package/dist/cjs/src/testing/graph-tester/fixtures/portPool.js.map +1 -0
  17. package/dist/cjs/src/testing/graph-tester/index.js +98 -0
  18. package/dist/cjs/src/testing/graph-tester/index.js.map +1 -0
  19. package/dist/cjs/src/testing/graph-tester/modes/AccessibilityMode.js +230 -0
  20. package/dist/cjs/src/testing/graph-tester/modes/AccessibilityMode.js.map +1 -0
  21. package/dist/cjs/src/testing/graph-tester/modes/PerformanceMode.js +168 -0
  22. package/dist/cjs/src/testing/graph-tester/modes/PerformanceMode.js.map +1 -0
  23. package/dist/cjs/src/testing/graph-tester/modes/SEOMode.js +264 -0
  24. package/dist/cjs/src/testing/graph-tester/modes/SEOMode.js.map +1 -0
  25. package/dist/cjs/src/testing/graph-tester/modes/VisualRegressionMode.js +199 -0
  26. package/dist/cjs/src/testing/graph-tester/modes/VisualRegressionMode.js.map +1 -0
  27. package/dist/cjs/src/testing/graph-tester/modes/index.js +17 -0
  28. package/dist/cjs/src/testing/graph-tester/modes/index.js.map +1 -0
  29. package/dist/cjs/src/testing/graph-tester/reporters/HTMLReporter.js +411 -0
  30. package/dist/cjs/src/testing/graph-tester/reporters/HTMLReporter.js.map +1 -0
  31. package/dist/cjs/src/testing/graph-tester/reporters/JSONReporter.js +127 -0
  32. package/dist/cjs/src/testing/graph-tester/reporters/JSONReporter.js.map +1 -0
  33. package/dist/cjs/src/testing/graph-tester/reporters/MarkdownReporter.js +169 -0
  34. package/dist/cjs/src/testing/graph-tester/reporters/MarkdownReporter.js.map +1 -0
  35. package/dist/cjs/src/testing/graph-tester/reporters/UnifiedReporter.js +118 -0
  36. package/dist/cjs/src/testing/graph-tester/reporters/UnifiedReporter.js.map +1 -0
  37. package/dist/cjs/src/testing/graph-tester/reporters/index.js +17 -0
  38. package/dist/cjs/src/testing/graph-tester/reporters/index.js.map +1 -0
  39. package/dist/cjs/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.js +47 -0
  40. package/dist/cjs/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.js.map +1 -0
  41. package/dist/cjs/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.js +584 -0
  42. package/dist/cjs/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.js.map +1 -0
  43. package/dist/cjs/src/testing/graph-tester/screenshot/ScreenshotAnnotator.js +376 -0
  44. package/dist/cjs/src/testing/graph-tester/screenshot/ScreenshotAnnotator.js.map +1 -0
  45. package/dist/cjs/src/testing/graph-tester/screenshot/index.js +15 -0
  46. package/dist/cjs/src/testing/graph-tester/screenshot/index.js.map +1 -0
  47. package/dist/cjs/src/testing/graph-tester/screenshot/types.js +11 -0
  48. package/dist/cjs/src/testing/graph-tester/screenshot/types.js.map +1 -0
  49. package/dist/cjs/src/testing/selectors.js +1 -1
  50. package/dist/esm/src/browser.d.ts +9 -0
  51. package/dist/esm/src/browser.d.ts.map +1 -1
  52. package/dist/esm/src/browser.js +14 -0
  53. package/dist/esm/src/browser.js.map +1 -1
  54. package/dist/esm/src/decorators/ContainerHelpers.d.ts +2 -1
  55. package/dist/esm/src/decorators/ContainerHelpers.d.ts.map +1 -1
  56. package/dist/esm/src/decorators/ContainerHelpers.js +16 -1
  57. package/dist/esm/src/decorators/ContainerHelpers.js.map +1 -1
  58. package/dist/esm/src/decorators/Tool.d.ts.map +1 -1
  59. package/dist/esm/src/decorators/Tool.js +2 -3
  60. package/dist/esm/src/decorators/Tool.js.map +1 -1
  61. package/dist/esm/src/testing/graph-tester/core/GraphTester.d.ts +110 -0
  62. package/dist/esm/src/testing/graph-tester/core/GraphTester.d.ts.map +1 -0
  63. package/dist/esm/src/testing/graph-tester/core/GraphTester.js +335 -0
  64. package/dist/esm/src/testing/graph-tester/core/GraphTester.js.map +1 -0
  65. package/dist/esm/src/testing/graph-tester/core/TestFunction.d.ts +120 -0
  66. package/dist/esm/src/testing/graph-tester/core/TestFunction.d.ts.map +1 -0
  67. package/dist/esm/src/testing/graph-tester/core/TestFunction.js +184 -0
  68. package/dist/esm/src/testing/graph-tester/core/TestFunction.js.map +1 -0
  69. package/dist/esm/src/testing/graph-tester/core/types.d.ts +331 -0
  70. package/dist/esm/src/testing/graph-tester/core/types.d.ts.map +1 -0
  71. package/dist/esm/src/testing/graph-tester/core/types.js +21 -0
  72. package/dist/esm/src/testing/graph-tester/core/types.js.map +1 -0
  73. package/dist/esm/src/testing/graph-tester/fixtures/index.d.ts +8 -0
  74. package/dist/esm/src/testing/graph-tester/fixtures/index.d.ts.map +1 -0
  75. package/dist/esm/src/testing/graph-tester/fixtures/index.js +7 -0
  76. package/dist/esm/src/testing/graph-tester/fixtures/index.js.map +1 -0
  77. package/dist/esm/src/testing/graph-tester/fixtures/portPool.d.ts +40 -0
  78. package/dist/esm/src/testing/graph-tester/fixtures/portPool.d.ts.map +1 -0
  79. package/dist/esm/src/testing/graph-tester/fixtures/portPool.js +147 -0
  80. package/dist/esm/src/testing/graph-tester/fixtures/portPool.js.map +1 -0
  81. package/dist/esm/src/testing/graph-tester/index.d.ts +84 -0
  82. package/dist/esm/src/testing/graph-tester/index.d.ts.map +1 -0
  83. package/dist/esm/src/testing/graph-tester/index.js +77 -0
  84. package/dist/esm/src/testing/graph-tester/index.js.map +1 -0
  85. package/dist/esm/src/testing/graph-tester/modes/AccessibilityMode.d.ts +40 -0
  86. package/dist/esm/src/testing/graph-tester/modes/AccessibilityMode.d.ts.map +1 -0
  87. package/dist/esm/src/testing/graph-tester/modes/AccessibilityMode.js +193 -0
  88. package/dist/esm/src/testing/graph-tester/modes/AccessibilityMode.js.map +1 -0
  89. package/dist/esm/src/testing/graph-tester/modes/PerformanceMode.d.ts +42 -0
  90. package/dist/esm/src/testing/graph-tester/modes/PerformanceMode.d.ts.map +1 -0
  91. package/dist/esm/src/testing/graph-tester/modes/PerformanceMode.js +131 -0
  92. package/dist/esm/src/testing/graph-tester/modes/PerformanceMode.js.map +1 -0
  93. package/dist/esm/src/testing/graph-tester/modes/SEOMode.d.ts +39 -0
  94. package/dist/esm/src/testing/graph-tester/modes/SEOMode.d.ts.map +1 -0
  95. package/dist/esm/src/testing/graph-tester/modes/SEOMode.js +227 -0
  96. package/dist/esm/src/testing/graph-tester/modes/SEOMode.js.map +1 -0
  97. package/dist/esm/src/testing/graph-tester/modes/VisualRegressionMode.d.ts +83 -0
  98. package/dist/esm/src/testing/graph-tester/modes/VisualRegressionMode.d.ts.map +1 -0
  99. package/dist/esm/src/testing/graph-tester/modes/VisualRegressionMode.js +162 -0
  100. package/dist/esm/src/testing/graph-tester/modes/VisualRegressionMode.js.map +1 -0
  101. package/dist/esm/src/testing/graph-tester/modes/index.d.ts +14 -0
  102. package/dist/esm/src/testing/graph-tester/modes/index.d.ts.map +1 -0
  103. package/dist/esm/src/testing/graph-tester/modes/index.js +10 -0
  104. package/dist/esm/src/testing/graph-tester/modes/index.js.map +1 -0
  105. package/dist/esm/src/testing/graph-tester/reporters/HTMLReporter.d.ts +38 -0
  106. package/dist/esm/src/testing/graph-tester/reporters/HTMLReporter.d.ts.map +1 -0
  107. package/dist/esm/src/testing/graph-tester/reporters/HTMLReporter.js +374 -0
  108. package/dist/esm/src/testing/graph-tester/reporters/HTMLReporter.js.map +1 -0
  109. package/dist/esm/src/testing/graph-tester/reporters/JSONReporter.d.ts +50 -0
  110. package/dist/esm/src/testing/graph-tester/reporters/JSONReporter.d.ts.map +1 -0
  111. package/dist/esm/src/testing/graph-tester/reporters/JSONReporter.js +90 -0
  112. package/dist/esm/src/testing/graph-tester/reporters/JSONReporter.js.map +1 -0
  113. package/dist/esm/src/testing/graph-tester/reporters/MarkdownReporter.d.ts +33 -0
  114. package/dist/esm/src/testing/graph-tester/reporters/MarkdownReporter.d.ts.map +1 -0
  115. package/dist/esm/src/testing/graph-tester/reporters/MarkdownReporter.js +132 -0
  116. package/dist/esm/src/testing/graph-tester/reporters/MarkdownReporter.js.map +1 -0
  117. package/dist/esm/src/testing/graph-tester/reporters/UnifiedReporter.d.ts +30 -0
  118. package/dist/esm/src/testing/graph-tester/reporters/UnifiedReporter.d.ts.map +1 -0
  119. package/dist/esm/src/testing/graph-tester/reporters/UnifiedReporter.js +81 -0
  120. package/dist/esm/src/testing/graph-tester/reporters/UnifiedReporter.js.map +1 -0
  121. package/dist/esm/src/testing/graph-tester/reporters/index.d.ts +14 -0
  122. package/dist/esm/src/testing/graph-tester/reporters/index.d.ts.map +1 -0
  123. package/dist/esm/src/testing/graph-tester/reporters/index.js +10 -0
  124. package/dist/esm/src/testing/graph-tester/reporters/index.js.map +1 -0
  125. package/dist/esm/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.d.ts +33 -0
  126. package/dist/esm/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.d.ts.map +1 -0
  127. package/dist/esm/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.js +43 -0
  128. package/dist/esm/src/testing/graph-tester/screenshot/CanvasAnnotationRenderer.js.map +1 -0
  129. package/dist/esm/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.d.ts +70 -0
  130. package/dist/esm/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.d.ts.map +1 -0
  131. package/dist/esm/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.js +547 -0
  132. package/dist/esm/src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.js.map +1 -0
  133. package/dist/esm/src/testing/graph-tester/screenshot/ScreenshotAnnotator.d.ts +83 -0
  134. package/dist/esm/src/testing/graph-tester/screenshot/ScreenshotAnnotator.d.ts.map +1 -0
  135. package/dist/esm/src/testing/graph-tester/screenshot/ScreenshotAnnotator.js +339 -0
  136. package/dist/esm/src/testing/graph-tester/screenshot/ScreenshotAnnotator.js.map +1 -0
  137. package/dist/esm/src/testing/graph-tester/screenshot/index.d.ts +11 -0
  138. package/dist/esm/src/testing/graph-tester/screenshot/index.d.ts.map +1 -0
  139. package/dist/esm/src/testing/graph-tester/screenshot/index.js +9 -0
  140. package/dist/esm/src/testing/graph-tester/screenshot/index.js.map +1 -0
  141. package/dist/esm/src/testing/graph-tester/screenshot/types.d.ts +331 -0
  142. package/dist/esm/src/testing/graph-tester/screenshot/types.d.ts.map +1 -0
  143. package/dist/esm/src/testing/graph-tester/screenshot/types.js +10 -0
  144. package/dist/esm/src/testing/graph-tester/screenshot/types.js.map +1 -0
  145. package/dist/esm/src/testing/selectors.d.ts +1 -1
  146. package/dist/esm/src/testing/selectors.js +1 -1
  147. package/package.json +19 -6
@@ -0,0 +1,547 @@
1
+ /**
2
+ * HTML annotation renderer.
3
+ *
4
+ * Generates interactive HTML overlay with:
5
+ * - Hoverable bounding boxes
6
+ * - Sidebar with annotation details
7
+ * - Click-to-highlight functionality
8
+ * - Search/filter capabilities
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ import * as fs from 'fs/promises';
13
+ /**
14
+ * HTML annotation renderer.
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const renderer = new HTMLAnnotationRenderer({
19
+ * includeSidebar: true,
20
+ * enableHover: true,
21
+ * outputPath: './screenshots/dashboard.html'
22
+ * });
23
+ *
24
+ * const result = await renderer.render(annotatedScreenshot);
25
+ * console.log(`HTML rendered: ${result.outputPath}`);
26
+ * ```
27
+ */
28
+ export class HTMLAnnotationRenderer {
29
+ constructor(config) {
30
+ this.config = {
31
+ includeSidebar: true,
32
+ enableHover: true,
33
+ enableClickToHighlight: true,
34
+ boundingBoxColor: '#00ff00',
35
+ boundingBoxWidth: 2,
36
+ ...config,
37
+ };
38
+ }
39
+ /**
40
+ * Render annotated screenshot as interactive HTML.
41
+ *
42
+ * @param screenshot - Annotated screenshot
43
+ * @returns Render result
44
+ */
45
+ async render(screenshot) {
46
+ const startTime = Date.now();
47
+ // Generate HTML
48
+ const html = this.generateHTML(screenshot);
49
+ // Write to file
50
+ await fs.writeFile(this.config.outputPath, html, 'utf-8');
51
+ const stats = await fs.stat(this.config.outputPath);
52
+ return {
53
+ outputPath: this.config.outputPath,
54
+ rendererType: 'html',
55
+ fileSize: stats.size,
56
+ duration: Date.now() - startTime,
57
+ };
58
+ }
59
+ /**
60
+ * Generate complete HTML document.
61
+ *
62
+ * @param screenshot - Annotated screenshot
63
+ * @returns HTML string
64
+ */
65
+ generateHTML(screenshot) {
66
+ const { annotations, pageMetadata } = screenshot;
67
+ // Convert screenshot buffer to base64 data URL
68
+ const screenshotDataUrl = `data:image/png;base64,${screenshot.screenshot.toString('base64')}`;
69
+ return `<!DOCTYPE html>
70
+ <html lang="en">
71
+ <head>
72
+ <meta charset="UTF-8">
73
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
74
+ <title>Annotated Screenshot: ${screenshot.route}</title>
75
+ <style>
76
+ * {
77
+ margin: 0;
78
+ padding: 0;
79
+ box-sizing: border-box;
80
+ }
81
+
82
+ body {
83
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
84
+ background: #1e1e1e;
85
+ color: #d4d4d4;
86
+ overflow: hidden;
87
+ }
88
+
89
+ .container {
90
+ display: flex;
91
+ height: 100vh;
92
+ }
93
+
94
+ .screenshot-container {
95
+ flex: 1;
96
+ position: relative;
97
+ overflow: auto;
98
+ background: #2d2d2d;
99
+ }
100
+
101
+ .screenshot {
102
+ position: relative;
103
+ display: inline-block;
104
+ }
105
+
106
+ .screenshot img {
107
+ display: block;
108
+ max-width: 100%;
109
+ height: auto;
110
+ }
111
+
112
+ .annotation-box {
113
+ position: absolute;
114
+ border: ${this.config.boundingBoxWidth}px solid ${this.config.boundingBoxColor};
115
+ pointer-events: ${this.config.enableHover ? 'auto' : 'none'};
116
+ cursor: ${this.config.enableClickToHighlight ? 'pointer' : 'default'};
117
+ transition: border-color 0.2s, box-shadow 0.2s;
118
+ z-index: 1;
119
+ }
120
+
121
+ .annotation-box:hover {
122
+ border-color: #ff00ff;
123
+ box-shadow: 0 0 0 2px rgba(255, 0, 255, 0.3);
124
+ z-index: 100;
125
+ }
126
+
127
+ .annotation-box.highlighted {
128
+ border-color: #ffff00;
129
+ box-shadow: 0 0 0 4px rgba(255, 255, 0, 0.5);
130
+ z-index: 200;
131
+ }
132
+
133
+ .annotation-label {
134
+ position: absolute;
135
+ top: -24px;
136
+ left: 0;
137
+ background: rgba(0, 255, 0, 0.9);
138
+ color: #000;
139
+ padding: 2px 6px;
140
+ border-radius: 3px;
141
+ font-size: 11px;
142
+ font-weight: 600;
143
+ white-space: nowrap;
144
+ display: none;
145
+ z-index: 2;
146
+ }
147
+
148
+ .annotation-box:hover .annotation-label {
149
+ display: block;
150
+ }
151
+
152
+ ${this.config.includeSidebar ? this.getSidebarStyles() : ''}
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <div class="container">
157
+ <div class="screenshot-container">
158
+ <div class="screenshot" style="width: ${pageMetadata.pageSize.width}px; height: ${pageMetadata.pageSize.height}px;">
159
+ <img src="${screenshotDataUrl}" alt="Screenshot" />
160
+ ${this.renderAnnotationBoxes(annotations, pageMetadata)}
161
+ </div>
162
+ </div>
163
+ ${this.config.includeSidebar ? this.renderSidebar(screenshot) : ''}
164
+ </div>
165
+
166
+ <script>
167
+ ${this.generateJavaScript(annotations)}
168
+ </script>
169
+ </body>
170
+ </html>`;
171
+ }
172
+ /**
173
+ * Get sidebar CSS styles.
174
+ */
175
+ getSidebarStyles() {
176
+ return `
177
+ .sidebar {
178
+ width: 400px;
179
+ background: #252526;
180
+ border-left: 1px solid #3e3e42;
181
+ display: flex;
182
+ flex-direction: column;
183
+ overflow: hidden;
184
+ }
185
+
186
+ .sidebar-header {
187
+ padding: 16px;
188
+ border-bottom: 1px solid #3e3e42;
189
+ }
190
+
191
+ .sidebar-header h2 {
192
+ font-size: 16px;
193
+ margin-bottom: 8px;
194
+ }
195
+
196
+ .sidebar-stats {
197
+ display: flex;
198
+ gap: 16px;
199
+ font-size: 13px;
200
+ color: #858585;
201
+ }
202
+
203
+ .sidebar-stat {
204
+ display: flex;
205
+ flex-direction: column;
206
+ gap: 4px;
207
+ }
208
+
209
+ .sidebar-stat-value {
210
+ font-size: 20px;
211
+ font-weight: 600;
212
+ color: #d4d4d4;
213
+ }
214
+
215
+ .sidebar-search {
216
+ padding: 12px 16px;
217
+ border-bottom: 1px solid #3e3e42;
218
+ }
219
+
220
+ .search-input {
221
+ width: 100%;
222
+ padding: 8px 12px;
223
+ background: #3c3c3c;
224
+ border: 1px solid #3e3e42;
225
+ border-radius: 4px;
226
+ color: #d4d4d4;
227
+ font-size: 13px;
228
+ }
229
+
230
+ .search-input:focus {
231
+ outline: none;
232
+ border-color: #007acc;
233
+ }
234
+
235
+ .sidebar-content {
236
+ flex: 1;
237
+ overflow-y: auto;
238
+ padding: 16px;
239
+ }
240
+
241
+ .annotation-item {
242
+ background: #2d2d2d;
243
+ border: 1px solid #3e3e42;
244
+ border-radius: 4px;
245
+ padding: 12px;
246
+ margin-bottom: 8px;
247
+ cursor: pointer;
248
+ transition: background 0.2s, border-color 0.2s;
249
+ }
250
+
251
+ .annotation-item:hover {
252
+ background: #37373d;
253
+ border-color: #007acc;
254
+ }
255
+
256
+ .annotation-item.active {
257
+ background: #37373d;
258
+ border-color: #ffff00;
259
+ }
260
+
261
+ .annotation-item-header {
262
+ display: flex;
263
+ justify-content: space-between;
264
+ align-items: center;
265
+ margin-bottom: 8px;
266
+ }
267
+
268
+ .annotation-item-tag {
269
+ font-size: 13px;
270
+ font-weight: 600;
271
+ color: #4ec9b0;
272
+ }
273
+
274
+ .annotation-item-badge {
275
+ background: #007acc;
276
+ color: #fff;
277
+ padding: 2px 6px;
278
+ border-radius: 3px;
279
+ font-size: 10px;
280
+ font-weight: 600;
281
+ }
282
+
283
+ .annotation-item-testid {
284
+ color: #ce9178;
285
+ font-size: 12px;
286
+ margin-bottom: 4px;
287
+ }
288
+
289
+ .annotation-item-interactions {
290
+ display: flex;
291
+ flex-wrap: wrap;
292
+ gap: 4px;
293
+ margin-top: 8px;
294
+ }
295
+
296
+ .interaction-badge {
297
+ background: #3e3e42;
298
+ color: #d4d4d4;
299
+ padding: 2px 6px;
300
+ border-radius: 3px;
301
+ font-size: 10px;
302
+ }
303
+
304
+ .annotation-details {
305
+ display: none;
306
+ margin-top: 12px;
307
+ padding-top: 12px;
308
+ border-top: 1px solid #3e3e42;
309
+ font-size: 12px;
310
+ color: #858585;
311
+ }
312
+
313
+ .annotation-item.active .annotation-details {
314
+ display: block;
315
+ }
316
+
317
+ .detail-row {
318
+ display: flex;
319
+ gap: 8px;
320
+ margin-bottom: 4px;
321
+ }
322
+
323
+ .detail-label {
324
+ font-weight: 600;
325
+ color: #d4d4d4;
326
+ }
327
+
328
+ .detail-value {
329
+ flex: 1;
330
+ word-break: break-all;
331
+ }`;
332
+ }
333
+ /**
334
+ * Render annotation bounding boxes.
335
+ */
336
+ renderAnnotationBoxes(annotations, pageMetadata) {
337
+ return annotations
338
+ .filter((a) => a.isVisible)
339
+ .map((annotation) => {
340
+ const box = annotation.pageBoundingBox || annotation.boundingBox;
341
+ const label = annotation.testId || annotation.tagName;
342
+ return `
343
+ <div
344
+ class="annotation-box"
345
+ data-annotation-id="${annotation.id}"
346
+ style="
347
+ left: ${box.x}px;
348
+ top: ${box.y}px;
349
+ width: ${box.width}px;
350
+ height: ${box.height}px;
351
+ "
352
+ title="${annotation.selector}"
353
+ >
354
+ <div class="annotation-label">${label}</div>
355
+ </div>`;
356
+ })
357
+ .join('\n');
358
+ }
359
+ /**
360
+ * Render sidebar with annotation list.
361
+ */
362
+ renderSidebar(screenshot) {
363
+ const { annotations, pageMetadata } = screenshot;
364
+ const visibleAnnotations = annotations.filter((a) => a.isVisible);
365
+ const interactiveCount = visibleAnnotations.filter((a) => a.isInteractive).length;
366
+ return `
367
+ <div class="sidebar">
368
+ <div class="sidebar-header">
369
+ <h2>${screenshot.route}</h2>
370
+ <div class="sidebar-stats">
371
+ <div class="sidebar-stat">
372
+ <div class="sidebar-stat-value">${visibleAnnotations.length}</div>
373
+ <div>Elements</div>
374
+ </div>
375
+ <div class="sidebar-stat">
376
+ <div class="sidebar-stat-value">${interactiveCount}</div>
377
+ <div>Interactive</div>
378
+ </div>
379
+ </div>
380
+ </div>
381
+
382
+ <div class="sidebar-search">
383
+ <input
384
+ type="text"
385
+ class="search-input"
386
+ placeholder="Search by tag, testid, or selector..."
387
+ id="search-input"
388
+ />
389
+ </div>
390
+
391
+ <div class="sidebar-content" id="annotation-list">
392
+ ${visibleAnnotations.map((a) => this.renderAnnotationItem(a)).join('\n')}
393
+ </div>
394
+ </div>`;
395
+ }
396
+ /**
397
+ * Render annotation item for sidebar.
398
+ */
399
+ renderAnnotationItem(annotation) {
400
+ const interactions = annotation.interactions.map((i) => i.type).join(', ');
401
+ return `
402
+ <div class="annotation-item" data-annotation-id="${annotation.id}">
403
+ <div class="annotation-item-header">
404
+ <div class="annotation-item-tag">&lt;${annotation.tagName}&gt;</div>
405
+ ${annotation.isInteractive ? '<div class="annotation-item-badge">Interactive</div>' : ''}
406
+ </div>
407
+ ${annotation.testId ? `<div class="annotation-item-testid">data-testid="${annotation.testId}"</div>` : ''}
408
+ ${annotation.interactions.length > 0
409
+ ? `
410
+ <div class="annotation-item-interactions">
411
+ ${annotation.interactions.map((i) => `<div class="interaction-badge">${i.type}</div>`).join('')}
412
+ </div>
413
+ `
414
+ : ''}
415
+ <div class="annotation-details">
416
+ <div class="detail-row">
417
+ <div class="detail-label">Selector:</div>
418
+ <div class="detail-value">${annotation.selector}</div>
419
+ </div>
420
+ <div class="detail-row">
421
+ <div class="detail-label">Size:</div>
422
+ <div class="detail-value">${Math.round(annotation.boundingBox.width)}×${Math.round(annotation.boundingBox.height)}px</div>
423
+ </div>
424
+ ${annotation.textContent
425
+ ? `
426
+ <div class="detail-row">
427
+ <div class="detail-label">Text:</div>
428
+ <div class="detail-value">${this.escapeHtml(annotation.textContent.substring(0, 100))}</div>
429
+ </div>
430
+ `
431
+ : ''}
432
+ </div>
433
+ </div>`;
434
+ }
435
+ /**
436
+ * Generate JavaScript for interactivity.
437
+ */
438
+ generateJavaScript(annotations) {
439
+ return `
440
+ const annotations = ${JSON.stringify(annotations, null, 2)};
441
+
442
+ // Click to highlight
443
+ ${this.config.enableClickToHighlight
444
+ ? `
445
+ document.querySelectorAll('.annotation-box').forEach(box => {
446
+ box.addEventListener('click', (e) => {
447
+ e.stopPropagation();
448
+ const id = box.dataset.annotationId;
449
+
450
+ // Remove previous highlights
451
+ document.querySelectorAll('.annotation-box.highlighted').forEach(b => {
452
+ b.classList.remove('highlighted');
453
+ });
454
+ document.querySelectorAll('.annotation-item.active').forEach(item => {
455
+ item.classList.remove('active');
456
+ });
457
+
458
+ // Highlight this annotation
459
+ box.classList.add('highlighted');
460
+ const sidebarItem = document.querySelector(\`.annotation-item[data-annotation-id="\${id}"]\`);
461
+ if (sidebarItem) {
462
+ sidebarItem.classList.add('active');
463
+ sidebarItem.scrollIntoView({ behavior: 'smooth', block: 'center' });
464
+ }
465
+ });
466
+ });
467
+
468
+ // Sidebar item click
469
+ document.querySelectorAll('.annotation-item').forEach(item => {
470
+ item.addEventListener('click', () => {
471
+ const id = item.dataset.annotationId;
472
+
473
+ // Remove previous highlights
474
+ document.querySelectorAll('.annotation-box.highlighted').forEach(b => {
475
+ b.classList.remove('highlighted');
476
+ });
477
+ document.querySelectorAll('.annotation-item.active').forEach(i => {
478
+ i.classList.remove('active');
479
+ });
480
+
481
+ // Highlight this annotation
482
+ item.classList.add('active');
483
+ const box = document.querySelector(\`.annotation-box[data-annotation-id="\${id}"]\`);
484
+ if (box) {
485
+ box.classList.add('highlighted');
486
+ box.scrollIntoView({ behavior: 'smooth', block: 'center' });
487
+ }
488
+ });
489
+ });
490
+ `
491
+ : ''}
492
+
493
+ // Search functionality
494
+ const searchInput = document.getElementById('search-input');
495
+ if (searchInput) {
496
+ searchInput.addEventListener('input', (e) => {
497
+ const query = e.target.value.toLowerCase();
498
+ const items = document.querySelectorAll('.annotation-item');
499
+
500
+ items.forEach(item => {
501
+ const annotation = annotations.find(a => a.id === item.dataset.annotationId);
502
+ if (!annotation) return;
503
+
504
+ const searchText = [
505
+ annotation.tagName,
506
+ annotation.testId,
507
+ annotation.selector,
508
+ annotation.textContent
509
+ ].filter(Boolean).join(' ').toLowerCase();
510
+
511
+ if (searchText.includes(query)) {
512
+ item.style.display = '';
513
+ } else {
514
+ item.style.display = 'none';
515
+ }
516
+ });
517
+ });
518
+ }
519
+
520
+ // Clear highlights on background click
521
+ document.querySelector('.screenshot-container').addEventListener('click', (e) => {
522
+ if (e.target.classList.contains('screenshot-container') || e.target.tagName === 'IMG') {
523
+ document.querySelectorAll('.annotation-box.highlighted').forEach(b => {
524
+ b.classList.remove('highlighted');
525
+ });
526
+ document.querySelectorAll('.annotation-item.active').forEach(item => {
527
+ item.classList.remove('active');
528
+ });
529
+ }
530
+ });
531
+ `;
532
+ }
533
+ /**
534
+ * Escape HTML entities.
535
+ */
536
+ escapeHtml(text) {
537
+ const map = {
538
+ '&': '&amp;',
539
+ '<': '&lt;',
540
+ '>': '&gt;',
541
+ '"': '&quot;',
542
+ "'": '&#039;',
543
+ };
544
+ return text.replace(/[&<>"']/g, (m) => map[m]);
545
+ }
546
+ }
547
+ //# sourceMappingURL=HTMLAnnotationRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HTMLAnnotationRenderer.js","sourceRoot":"","sources":["../../../../../../src/testing/graph-tester/screenshot/HTMLAnnotationRenderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Core screenshot annotation capture logic.
3
+ *
4
+ * Captures full-page screenshots with element annotations including:
5
+ * - Bounding boxes (viewport and page coordinates)
6
+ * - Element metadata (testid, tag, attributes, styles)
7
+ * - Interaction capabilities
8
+ * - Element hierarchy (parent/child relationships)
9
+ * - ARIA attributes for accessibility analysis
10
+ *
11
+ * Uses single-pass DOM traversal for efficiency.
12
+ *
13
+ * @packageDocumentation
14
+ */
15
+ import type { Page } from '@playwright/test';
16
+ import type { AnnotatedScreenshot } from './types';
17
+ /**
18
+ * Configuration for screenshot annotator.
19
+ */
20
+ export interface ScreenshotAnnotatorConfig {
21
+ /** Output directory for screenshots */
22
+ outputDir: string;
23
+ /** Whether to capture full page (default: true) */
24
+ fullPage?: boolean;
25
+ /** Whether to include invisible elements (default: false) */
26
+ includeInvisible?: boolean;
27
+ /** Minimum element size to annotate (default: 5px) */
28
+ minElementSize?: number;
29
+ /** Whether to detect navigation targets (default: true) */
30
+ detectNavigation?: boolean;
31
+ /** Whether to capture page load metrics (default: true) */
32
+ captureLoadMetrics?: boolean;
33
+ }
34
+ /**
35
+ * Screenshot annotator.
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const annotator = new ScreenshotAnnotator({
40
+ * outputDir: './screenshots',
41
+ * fullPage: true
42
+ * });
43
+ *
44
+ * const result = await annotator.capture(page, '/dashboard');
45
+ * console.log(`Captured ${result.annotations.length} annotations`);
46
+ * ```
47
+ */
48
+ export declare class ScreenshotAnnotator {
49
+ private config;
50
+ constructor(config: ScreenshotAnnotatorConfig);
51
+ /**
52
+ * Capture annotated screenshot for a page.
53
+ *
54
+ * @param page - Playwright page instance
55
+ * @param route - Route being captured
56
+ * @param viewportName - Optional viewport name for responsive testing
57
+ * @returns Annotated screenshot
58
+ */
59
+ capture(page: Page, route: string, viewportName?: string): Promise<AnnotatedScreenshot>;
60
+ /**
61
+ * Extract element annotations via single-pass DOM traversal.
62
+ *
63
+ * @param page - Playwright page instance
64
+ * @returns Array of element annotations
65
+ */
66
+ private extractAnnotations;
67
+ /**
68
+ * Extract page metadata.
69
+ *
70
+ * @param page - Playwright page instance
71
+ * @returns Page metadata
72
+ */
73
+ private extractPageMetadata;
74
+ /**
75
+ * Generate screenshot filename from route and viewport.
76
+ *
77
+ * @param route - Route path
78
+ * @param viewportName - Optional viewport name
79
+ * @returns Sanitized filename
80
+ */
81
+ private generateScreenshotFilename;
82
+ }
83
+ //# sourceMappingURL=ScreenshotAnnotator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenshotAnnotator.d.ts","sourceRoot":"","sources":["../../../../../../src/testing/graph-tester/screenshot/ScreenshotAnnotator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAG7C,OAAO,KAAK,EACV,mBAAmB,EAOpB,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAElB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,sDAAsD;IACtD,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,2DAA2D;IAC3D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAA4B;gBAE9B,MAAM,EAAE,yBAAyB;IAW7C;;;;;;;OAOG;IACG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IA2C7F;;;;;OAKG;YACW,kBAAkB;IAoOhC;;;;;OAKG;YACW,mBAAmB;IAyDjC;;;;;;OAMG;IACH,OAAO,CAAC,0BAA0B;CAiBnC"}