@zibby/core 0.1.20 → 0.1.22

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 (167) hide show
  1. package/dist/agents/base.js +17 -0
  2. package/dist/backend-client.js +1 -0
  3. package/dist/constants/tool-names.js +1 -0
  4. package/dist/constants/zibby-scratch.js +1 -0
  5. package/dist/constants.js +1 -0
  6. package/dist/enrichment/base.js +1 -0
  7. package/dist/enrichment/enrichers/accessibility-enricher.js +1 -0
  8. package/dist/enrichment/enrichers/dom-enricher.js +1 -0
  9. package/dist/enrichment/enrichers/page-state-enricher.js +1 -0
  10. package/dist/enrichment/enrichers/position-enricher.js +1 -0
  11. package/dist/enrichment/index.js +1 -0
  12. package/dist/enrichment/mcp-integration.js +1 -0
  13. package/dist/enrichment/mcp-ref-enricher.js +1 -0
  14. package/dist/enrichment/pipeline.js +3 -0
  15. package/dist/enrichment/trace-text-enricher.js +1 -0
  16. package/dist/framework/agents/assistant-strategy.js +5 -0
  17. package/dist/framework/agents/base.js +1 -0
  18. package/dist/framework/agents/claude-strategy.js +4 -0
  19. package/dist/framework/agents/codex-strategy.js +4 -0
  20. package/dist/framework/agents/cursor-strategy.js +32 -0
  21. package/dist/framework/agents/gemini-strategy.js +11 -0
  22. package/dist/framework/agents/index.js +13 -0
  23. package/dist/framework/agents/middleware/assistant-round-pipeline.js +3 -0
  24. package/dist/framework/agents/providers/base.js +1 -0
  25. package/dist/framework/agents/providers/index.js +1 -0
  26. package/dist/framework/agents/providers/openai-transport.js +2 -0
  27. package/dist/framework/agents/providers/openai.js +1 -0
  28. package/dist/framework/agents/providers/transport-base.js +1 -0
  29. package/dist/framework/agents/utils/auth-resolver.js +1 -0
  30. package/dist/framework/agents/utils/cursor-output-formatter.js +1 -0
  31. package/dist/framework/agents/utils/openai-proxy-formatter.js +9 -0
  32. package/dist/framework/agents/utils/payload-budget.js +3 -0
  33. package/dist/framework/agents/utils/structured-output-formatter.js +21 -0
  34. package/dist/framework/code-generator.js +10 -0
  35. package/dist/framework/constants.js +1 -0
  36. package/dist/framework/context-loader.js +5 -0
  37. package/dist/framework/function-bridge.js +2 -0
  38. package/dist/framework/function-skill-registry.js +1 -0
  39. package/dist/framework/graph-compiler.js +1 -0
  40. package/dist/framework/graph.js +5 -0
  41. package/dist/framework/index.js +1 -0
  42. package/dist/framework/mcp-client.js +2 -0
  43. package/dist/framework/node-registry.js +9 -0
  44. package/dist/framework/node.js +5 -0
  45. package/dist/framework/output-parser.js +3 -0
  46. package/dist/framework/skill-registry.js +1 -0
  47. package/dist/framework/state-utils.js +1 -0
  48. package/dist/framework/state.js +1 -0
  49. package/dist/framework/tool-resolver.js +1 -0
  50. package/dist/index.js +8 -0
  51. package/dist/runtime/generation/base.js +1 -0
  52. package/dist/runtime/generation/index.js +3 -0
  53. package/dist/runtime/generation/mcp-ref-strategy.js +41 -0
  54. package/dist/runtime/generation/stable-id-strategy.js +16 -0
  55. package/dist/runtime/stable-id-runtime.js +1 -0
  56. package/dist/runtime/verification/base.js +1 -0
  57. package/dist/runtime/verification/index.js +3 -0
  58. package/dist/runtime/verification/playwright-json-strategy.js +1 -0
  59. package/dist/runtime/zibby-runtime.js +1 -0
  60. package/dist/sync/index.js +1 -0
  61. package/dist/sync/uploader.js +1 -0
  62. package/dist/tools/run-playwright-test.js +5 -0
  63. package/dist/utils/adf-converter.js +7 -0
  64. package/dist/utils/ast-utils.js +1 -0
  65. package/dist/utils/ci-setup.js +5 -0
  66. package/dist/utils/cursor-mcp-isolated-home.js +1 -0
  67. package/dist/utils/cursor-utils.js +18 -0
  68. package/dist/utils/live-frame-discovery.js +1 -0
  69. package/dist/utils/logger.js +1 -0
  70. package/dist/utils/mcp-config-writer.js +10 -0
  71. package/dist/utils/mission-control-from-run-states.js +1 -0
  72. package/dist/utils/node-schema-parser.js +1 -0
  73. package/dist/utils/parallel-config.js +1 -0
  74. package/dist/utils/post-process-events.js +1 -0
  75. package/dist/utils/result-handler.js +1 -0
  76. package/{src → dist}/utils/ripple-effect.js +3 -12
  77. package/dist/utils/run-capacity-coordinator.js +1 -0
  78. package/dist/utils/run-capacity-queue.js +2 -0
  79. package/dist/utils/run-index-merge.js +1 -0
  80. package/dist/utils/run-index-post-cli.js +1 -0
  81. package/dist/utils/run-registry.js +3 -0
  82. package/dist/utils/run-state-session.js +2 -0
  83. package/dist/utils/selector-generator.js +4 -0
  84. package/dist/utils/session-state-constants.js +1 -0
  85. package/dist/utils/session-state-live-runs.js +1 -0
  86. package/dist/utils/streaming-parser.js +4 -0
  87. package/dist/utils/test-post-processor.js +18 -0
  88. package/dist/utils/timeline.js +14 -0
  89. package/dist/utils/trace-parser.js +2 -0
  90. package/dist/utils/video-organizer.js +3 -0
  91. package/package.json +49 -35
  92. package/templates/browser-test-automation/README.md +29 -7
  93. package/templates/browser-test-automation/chat.mjs +36 -0
  94. package/templates/browser-test-automation/graph.mjs +5 -9
  95. package/templates/browser-test-automation/nodes/execute-live.mjs +30 -58
  96. package/templates/browser-test-automation/nodes/generate-script.mjs +32 -12
  97. package/templates/browser-test-automation/nodes/utils.mjs +153 -10
  98. package/templates/browser-test-automation/pipeline-ids.js +12 -0
  99. package/templates/browser-test-automation/result-handler.mjs +78 -2
  100. package/templates/browser-test-automation/run-index.mjs +418 -0
  101. package/scripts/export-default-workflows.js +0 -51
  102. package/scripts/patch-cursor-mcp.js +0 -174
  103. package/scripts/setup-ci.sh +0 -115
  104. package/scripts/setup-official-playwright-mcp.sh +0 -226
  105. package/scripts/test-with-video.sh +0 -49
  106. package/src/agents/base.js +0 -361
  107. package/src/constants.js +0 -47
  108. package/src/enrichment/base.js +0 -49
  109. package/src/enrichment/enrichers/accessibility-enricher.js +0 -197
  110. package/src/enrichment/enrichers/dom-enricher.js +0 -171
  111. package/src/enrichment/enrichers/page-state-enricher.js +0 -129
  112. package/src/enrichment/enrichers/position-enricher.js +0 -67
  113. package/src/enrichment/index.js +0 -96
  114. package/src/enrichment/mcp-integration.js +0 -149
  115. package/src/enrichment/mcp-ref-enricher.js +0 -78
  116. package/src/enrichment/pipeline.js +0 -192
  117. package/src/enrichment/trace-text-enricher.js +0 -115
  118. package/src/framework/AGENTS.md +0 -98
  119. package/src/framework/agents/base.js +0 -72
  120. package/src/framework/agents/claude-strategy.js +0 -278
  121. package/src/framework/agents/cursor-strategy.js +0 -540
  122. package/src/framework/agents/index.js +0 -105
  123. package/src/framework/agents/utils/cursor-output-formatter.js +0 -67
  124. package/src/framework/agents/utils/openai-proxy-formatter.js +0 -249
  125. package/src/framework/code-generator.js +0 -301
  126. package/src/framework/constants.js +0 -33
  127. package/src/framework/context-loader.js +0 -101
  128. package/src/framework/function-bridge.js +0 -78
  129. package/src/framework/function-skill-registry.js +0 -20
  130. package/src/framework/graph-compiler.js +0 -342
  131. package/src/framework/graph.js +0 -610
  132. package/src/framework/index.js +0 -28
  133. package/src/framework/node-registry.js +0 -163
  134. package/src/framework/node.js +0 -259
  135. package/src/framework/output-parser.js +0 -71
  136. package/src/framework/skill-registry.js +0 -55
  137. package/src/framework/state-utils.js +0 -52
  138. package/src/framework/state.js +0 -67
  139. package/src/framework/tool-resolver.js +0 -65
  140. package/src/index.js +0 -345
  141. package/src/runtime/generation/base.js +0 -46
  142. package/src/runtime/generation/index.js +0 -70
  143. package/src/runtime/generation/mcp-ref-strategy.js +0 -197
  144. package/src/runtime/generation/stable-id-strategy.js +0 -170
  145. package/src/runtime/stable-id-runtime.js +0 -248
  146. package/src/runtime/verification/base.js +0 -44
  147. package/src/runtime/verification/index.js +0 -67
  148. package/src/runtime/verification/playwright-json-strategy.js +0 -119
  149. package/src/runtime/zibby-runtime.js +0 -299
  150. package/src/sync/index.js +0 -2
  151. package/src/sync/uploader.js +0 -29
  152. package/src/tools/run-playwright-test.js +0 -158
  153. package/src/utils/adf-converter.js +0 -68
  154. package/src/utils/ast-utils.js +0 -37
  155. package/src/utils/ci-setup.js +0 -124
  156. package/src/utils/cursor-utils.js +0 -71
  157. package/src/utils/logger.js +0 -144
  158. package/src/utils/mcp-config-writer.js +0 -115
  159. package/src/utils/node-schema-parser.js +0 -522
  160. package/src/utils/post-process-events.js +0 -55
  161. package/src/utils/result-handler.js +0 -102
  162. package/src/utils/selector-generator.js +0 -239
  163. package/src/utils/streaming-parser.js +0 -387
  164. package/src/utils/test-post-processor.js +0 -211
  165. package/src/utils/timeline.js +0 -217
  166. package/src/utils/trace-parser.js +0 -325
  167. package/src/utils/video-organizer.js +0 -91
@@ -1,325 +0,0 @@
1
- /**
2
- * TraceParser - Extracts exact selectors from Playwright trace.zip files
3
- */
4
-
5
- import { readFileSync, existsSync, readdirSync } from 'fs';
6
- import { join } from 'path';
7
- import { execSync } from 'child_process';
8
- import { tmpdir } from 'os';
9
-
10
- export class TraceParser {
11
- static async parseTraceZip(traceDirPath) {
12
- let traceFilePath;
13
-
14
- if (traceDirPath.endsWith('.zip') && existsSync(traceDirPath)) {
15
- const tempDir = join(tmpdir(), `trace-${Date.now()}`);
16
- execSync(`unzip -q "${traceDirPath}" -d "${tempDir}"`, { stdio: 'pipe' });
17
-
18
- // Find the actual .trace file (trace-{timestamp}.trace, not trace.trace)
19
- const files = readdirSync(tempDir);
20
- const traceFile = files.find(f => f.endsWith('.trace'));
21
- if (!traceFile) throw new Error('No .trace file found in zip');
22
- traceFilePath = join(tempDir, traceFile);
23
- } else if (existsSync(traceDirPath)) {
24
- const files = readdirSync(traceDirPath);
25
- const traceFile = files.find(f => f.endsWith('.trace'));
26
- if (!traceFile) throw new Error('No .trace file found');
27
- traceFilePath = join(traceDirPath, traceFile);
28
- } else {
29
- throw new Error(`Trace not found at ${traceDirPath}`);
30
- }
31
-
32
- try {
33
- const traceContent = readFileSync(traceFilePath, 'utf-8');
34
- const lines = traceContent.trim().split('\n');
35
- const actions = [];
36
-
37
- // NEW: Extract accessibility tree snapshots (contains ACTUAL text)
38
- const accessibilitySnapshots = new Map(); // snapshotName -> Map<ref, node>
39
-
40
- // Extract DOM snapshots from trace events
41
- const domSnapshots = new Map();
42
- for (const line of lines) {
43
- try {
44
- const event = JSON.parse(line);
45
-
46
- // NEW: Parse accessibility snapshots
47
- if (event.type === 'snapshot' && event.snapshot && event.snapshot.accessibility) {
48
- const axTree = new Map();
49
- for (const node of event.snapshot.accessibility) {
50
- if (node.ref) {
51
- axTree.set(node.ref, node);
52
- }
53
- }
54
- accessibilitySnapshots.set(event.snapshotName, axTree);
55
- }
56
-
57
- // Look for frame-snapshot events (contains DOM HTML)
58
- if (event.type === 'frame-snapshot' && event.snapshot) {
59
- const html = Buffer.from(event.snapshot.html || '', 'base64').toString('utf-8');
60
- if (html && html.length > 100) { // Only store meaningful snapshots
61
- domSnapshots.set(event.pageId || 'default', html);
62
- }
63
- }
64
- } catch {
65
- // Ignore snapshot storage errors
66
- }
67
- }
68
-
69
- for (const line of lines) {
70
- try {
71
- const event = JSON.parse(line);
72
- if (event.type === 'before' && event.params && event.params.selector) {
73
- const method = event.method;
74
- if (['click', 'fill', 'type', 'selectOption'].includes(method)) {
75
- const selector = event.params.selector;
76
- const text = event.params.text || event.params.value || '';
77
-
78
- const strategies = [];
79
-
80
- // NEW: Extract ACTUAL text from accessibility tree snapshot
81
- let actualText = null;
82
- let actualRole = null;
83
- let actualAriaLabel = null;
84
-
85
- const ariaRefMatch = selector.match(/aria-ref=([a-z0-9]+)/i);
86
- if (ariaRefMatch && event.snapshotName) {
87
- const ref = ariaRefMatch[1];
88
- const snapshot = accessibilitySnapshots.get(event.snapshotName);
89
- if (snapshot && snapshot.has(ref)) {
90
- const node = snapshot.get(ref);
91
- actualText = node.name || null; // ACTUAL text from DOM
92
- actualRole = node.role || null; // ACTUAL role
93
- actualAriaLabel = node.label || null; // ACTUAL aria-label
94
-
95
- console.log(`[TraceParser] āœ… Found ACTUAL element data: text="${actualText}", role="${actualRole}"`);
96
- }
97
- }
98
-
99
- // 1. Extract every possible robust anchor from the internal selector
100
- const textMatch = selector.match(/internal:text="([^"]+)"/i);
101
- const labelMatch = selector.match(/internal:label="([^"]+)"/i);
102
- const placeholderMatch = selector.match(/internal:placeholder="([^"]+)"/i);
103
- const roleMatch = selector.match(/internal:role=([^ ]+)/i);
104
- const describeMatch = selector.match(/internal:describe="([^"]+)"/i);
105
- const nameMatch = selector.match(/name="([^"]+)"/i);
106
-
107
- if (textMatch) {
108
- strategies.push({ type: 'text', text: textMatch[1] });
109
- // Add fuzzy text variants
110
- const words = textMatch[1].split(' ');
111
- if (words.length > 1) {
112
- strategies.push({ type: 'text', text: words[0], fuzzy: true });
113
- strategies.push({ type: 'text', text: words[words.length - 1], fuzzy: true });
114
- }
115
- }
116
- if (labelMatch) strategies.push({ type: 'label', label: labelMatch[1] });
117
- if (placeholderMatch) strategies.push({ type: 'placeholder', placeholder: placeholderMatch[1] });
118
-
119
- // NEW: PRIORITIZE ACTUAL TEXT from accessibility tree
120
- if (actualText) {
121
- strategies.unshift({ type: 'text', text: actualText, source: 'accessibility-tree' });
122
- // Add fuzzy variants
123
- const words = actualText.split(' ');
124
- if (words.length > 1) {
125
- strategies.push({ type: 'text', text: words[0], fuzzy: true, source: 'accessibility-tree' });
126
- }
127
- }
128
-
129
- if (actualRole || roleMatch) {
130
- const role = actualRole || roleMatch[1];
131
- const name = actualText || (nameMatch ? nameMatch[1] : (textMatch ? textMatch[1] : null));
132
- strategies.unshift({ type: 'role', role, name, source: actualText ? 'accessibility-tree' : 'selector' });
133
- }
134
-
135
- if (actualAriaLabel) {
136
- strategies.unshift({ type: 'label', label: actualAriaLabel, source: 'accessibility-tree' });
137
- }
138
-
139
- // 2. Parse internal:describe (used by playwright-official)
140
- if (describeMatch) {
141
- const description = describeMatch[1];
142
- const roleKeywords = ['link', 'button', 'textbox', 'menuitem', 'submenu', 'combobox', 'checkbox', 'radio', 'tab', 'treeitem', 'menu item'];
143
-
144
- // Check if it ends with a role keyword
145
- let matchedRole = null;
146
- let cleanName = description;
147
-
148
- for (const r of roleKeywords) {
149
- if (description.toLowerCase().endsWith(` ${r}`)) {
150
- matchedRole = r.replace(' ', ''); // Handle "menu item" -> "menuitem"
151
- cleanName = description.substring(0, description.length - r.length - 1);
152
- break;
153
- }
154
- }
155
-
156
- if (matchedRole) {
157
- strategies.push({ type: 'role', role: matchedRole, name: cleanName });
158
- // Strip English descriptions in parentheses for cleaner text matching
159
- const textName = cleanName.replace(/\s*\([^)]+\)\s*$/, '');
160
- strategies.push({ type: 'text', text: textName });
161
- strategies.push({ type: 'text', text: cleanName });
162
-
163
- // Add fuzzy text variants
164
- const words = cleanName.split(' ');
165
- if (words.length > 1) {
166
- strategies.push({ type: 'text', text: words[0], fuzzy: true });
167
- strategies.push({ type: 'text', text: words.slice(0, 2).join(' '), fuzzy: true });
168
- }
169
- } else {
170
- const textName = description.replace(/\s*\([^)]+\)\s*$/, '');
171
- strategies.push({ type: 'text', text: textName });
172
- strategies.push({ type: 'text', text: description });
173
- }
174
- }
175
-
176
- // 3. Extract data-testid, classes, IDs from DOM snapshots
177
- const domStrategies = this.extractDOMStrategies(selector, domSnapshots, textMatch?.[1] || describeMatch?.[1], event.pageId);
178
- strategies.push(...domStrategies);
179
-
180
- // Extract structural context (parent/sibling) from CSS selector chain
181
- const structuralContext = this.extractStructuralContext(selector);
182
-
183
- // Add structural context to high-priority strategies
184
- if (structuralContext.parent || structuralContext.sibling) {
185
- strategies.forEach(strat => {
186
- if (['role', 'text', 'label', 'testid'].includes(strat.type)) {
187
- if (structuralContext.parent) strat.parent = structuralContext.parent;
188
- if (structuralContext.sibling) strat.sibling = structuralContext.sibling;
189
- }
190
- });
191
- }
192
-
193
- // Always add the raw CSS as a final fallback
194
- strategies.push({ type: 'css', value: selector });
195
-
196
- // NEW: Use ACTUAL text if available, fallback to AI description
197
- const finalName = actualText ||
198
- textMatch ? textMatch[1] :
199
- (describeMatch ? describeMatch[1].replace(/\s*\([^)]+\)\s*$/, '') :
200
- `Action ${actions.length}`);
201
-
202
- actions.push({
203
- method,
204
- name: finalName,
205
- action: method === 'type' ? 'fill' : method,
206
- value: text,
207
- strategies,
208
- timestamp: event.startTime,
209
- // NEW: Include metadata for debugging
210
- actualText,
211
- actualRole,
212
- actualAriaLabel
213
- });
214
- }
215
- }
216
- } catch {
217
- // Ignore trace entry parsing errors
218
- }
219
- }
220
- return actions;
221
- } catch (error) {
222
- throw new Error(`Failed to parse trace: ${error.message}`, { cause: error });
223
- }
224
- }
225
-
226
- /**
227
- * Extract data-testid, classes, IDs, and DOM paths from snapshots
228
- */
229
- static extractDOMStrategies(selector, domSnapshots, textHint, pageId) {
230
- const strategies = [];
231
-
232
- if (!domSnapshots || domSnapshots.size === 0 || !textHint) {
233
- return strategies;
234
- }
235
-
236
- try {
237
- // Get the snapshot for this page
238
- const html = domSnapshots.get(pageId);
239
- if (!html) {
240
- return strategies;
241
- }
242
-
243
- // Escape special regex characters in textHint
244
- const escapedHint = textHint.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
245
-
246
- // Extract data-testid patterns
247
- const testidRegex = new RegExp(`data-testid=["']([^"']+)["'][^>]*>[^<]*${escapedHint}`, 'i');
248
- const testidMatch = html.match(testidRegex);
249
- if (testidMatch) {
250
- strategies.push({ type: 'testid', value: testidMatch[1], priority: 'high' });
251
- }
252
-
253
- // Extract stable class names (not dynamic like css-123abc)
254
- const classRegex = new RegExp(`class=["']([^"']+)["'][^>]*>[^<]*${escapedHint}`, 'gi');
255
- const classMatches = html.matchAll(classRegex);
256
- for (const match of classMatches) {
257
- const classes = match[1].split(/\s+/).filter(c =>
258
- c && !c.match(/^(css|jss|makeStyles|MuiBox|MuiStack)-\w+/) && c.length > 2
259
- );
260
- if (classes.length > 0) {
261
- strategies.push({ type: 'class', value: classes.join('.'), priority: 'medium' });
262
- if (classes.length === 1) {
263
- strategies.push({ type: 'class', value: classes[0], priority: 'medium' });
264
- }
265
- }
266
- }
267
-
268
- // Extract stable IDs (not dynamic)
269
- const idRegex = new RegExp(`id=["']([^"']+)["'][^>]*>[^<]*${escapedHint}`, 'i');
270
- const idMatch = html.match(idRegex);
271
- if (idMatch && !idMatch[1].match(/^(root|app|\d+|[a-f0-9-]{20,})$/i)) {
272
- strategies.push({ type: 'id', value: idMatch[1], priority: 'high' });
273
- }
274
-
275
- } catch (e) {
276
- // Silent fail - snapshots are optional enhancement
277
- console.warn(`[TraceParser] DOM extraction failed: ${e.message}`);
278
- }
279
-
280
- return strategies;
281
- }
282
-
283
- /**
284
- * Extract parent/sibling context from Playwright internal selector chains
285
- * Example: "form[name='login'] >> button" => parent: "form[name='login']"
286
- */
287
- static extractStructuralContext(selector) {
288
- const context = { parent: null, sibling: null };
289
-
290
- // Split by >> to get hierarchy (Playwright's chaining operator)
291
- const parts = selector.split('>>').map(p => p.trim());
292
-
293
- if (parts.length > 1) {
294
- // The second-to-last part is likely the parent container
295
- const parentPart = parts[parts.length - 2];
296
-
297
- // Extract clean CSS from internal: prefixes
298
- const cleanParent = parentPart
299
- .replace(/internal:text="[^"]+"\s*/gi, '')
300
- .replace(/internal:role=\S+\s*/gi, '')
301
- .replace(/internal:label="[^"]+"\s*/gi, '')
302
- .trim();
303
-
304
- // Only use if it's a meaningful selector (form, div with id/class, etc)
305
- if (cleanParent && (
306
- cleanParent.match(/^(form|section|nav|header|aside|main|article)\b/) ||
307
- cleanParent.includes('[') ||
308
- cleanParent.match(/[#.]\w/)
309
- )) {
310
- context.parent = cleanParent;
311
- }
312
- }
313
-
314
- // Extract sibling hints from CSS selectors like "input[type='email'] + button"
315
- const lastPart = parts[parts.length - 1];
316
- const siblingMatch = lastPart.match(/([^+~]+)\s*[+~]\s*(.+)/);
317
- if (siblingMatch) {
318
- context.sibling = siblingMatch[1].trim();
319
- }
320
-
321
- return context;
322
- }
323
- }
324
-
325
- export default TraceParser;
@@ -1,91 +0,0 @@
1
- import { readdir, access, copyFile, constants } from 'fs/promises';
2
- import { join, relative } from 'path';
3
-
4
- export async function organizeVideos(options = {}) {
5
- const {
6
- testResultsDir = 'test-results',
7
- testsDir = 'tests',
8
- projectRoot = process.cwd(),
9
- verbose = true,
10
- } = options;
11
-
12
- if (verbose) {
13
- console.log('šŸŽ„ Organizing test videos...\n');
14
- }
15
-
16
- const resultsPath = join(projectRoot, testResultsDir);
17
- const testsPath = join(projectRoot, testsDir);
18
-
19
- try {
20
- const results = await readdir(resultsPath);
21
- let movedCount = 0;
22
-
23
- for (const resultDir of results) {
24
- if (resultDir.startsWith('.')) continue;
25
-
26
- const videoPath = join(resultsPath, resultDir, 'video.webm');
27
-
28
- try {
29
- await access(videoPath, constants.F_OK);
30
- } catch {
31
- continue;
32
- }
33
-
34
- const testName = resultDir
35
- .replace(/-chromium$/, '')
36
- .replace(/-firefox$/, '')
37
- .replace(/-webkit$/, '');
38
-
39
- const testFile = await findTestFile(testsPath, testName);
40
-
41
- if (testFile) {
42
- const videoName = testFile.replace(/\.spec\.(js|ts)$/, '.spec.webm');
43
-
44
- await copyFile(videoPath, videoName);
45
-
46
- if (verbose) {
47
- console.log(`āœ… ${relative(projectRoot, videoName)}`);
48
- }
49
-
50
- movedCount++;
51
- } else if (verbose) {
52
- console.log(`āš ļø Could not find test file for: ${resultDir}`);
53
- }
54
- }
55
-
56
- if (verbose) {
57
- console.log(`\nšŸŽ¬ Organized ${movedCount} video(s)`);
58
- console.log(`šŸ“‚ Videos are now next to their test files in ${testsDir}/`);
59
- }
60
-
61
- return { movedCount, success: true };
62
-
63
- } catch (error) {
64
- if (verbose) {
65
- console.error('āŒ Error organizing videos:', error.message);
66
- }
67
- return { movedCount: 0, success: false, error: error.message };
68
- }
69
- }
70
-
71
- async function findTestFile(testsDir, testName) {
72
- const parts = testName.split('-');
73
-
74
- for (let i = parts.length; i > 0; i--) {
75
- const pathParts = parts.slice(0, i);
76
- const searchPath = pathParts.join('/');
77
-
78
- for (const ext of ['js', 'ts']) {
79
- const testPath = join(testsDir, `${searchPath}.spec.${ext}`);
80
- try {
81
- await access(testPath, constants.F_OK);
82
- return testPath;
83
- } catch {
84
- // File doesn't exist, continue search
85
- }
86
- }
87
- }
88
-
89
- return null;
90
- }
91
-