afterburn-cli 1.0.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 (188) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/ai/gemini-client.d.ts +21 -0
  4. package/dist/ai/gemini-client.js +105 -0
  5. package/dist/ai/gemini-client.js.map +1 -0
  6. package/dist/ai/index.d.ts +1 -0
  7. package/dist/ai/index.js +3 -0
  8. package/dist/ai/index.js.map +1 -0
  9. package/dist/analysis/diagnosis-schema.d.ts +106 -0
  10. package/dist/analysis/diagnosis-schema.js +54 -0
  11. package/dist/analysis/diagnosis-schema.js.map +1 -0
  12. package/dist/analysis/error-analyzer.d.ts +9 -0
  13. package/dist/analysis/error-analyzer.js +573 -0
  14. package/dist/analysis/error-analyzer.js.map +1 -0
  15. package/dist/analysis/index.d.ts +4 -0
  16. package/dist/analysis/index.js +6 -0
  17. package/dist/analysis/index.js.map +1 -0
  18. package/dist/analysis/source-mapper.d.ts +19 -0
  19. package/dist/analysis/source-mapper.js +329 -0
  20. package/dist/analysis/source-mapper.js.map +1 -0
  21. package/dist/analysis/ui-auditor.d.ts +9 -0
  22. package/dist/analysis/ui-auditor.js +104 -0
  23. package/dist/analysis/ui-auditor.js.map +1 -0
  24. package/dist/artifacts/artifact-storage.d.ts +44 -0
  25. package/dist/artifacts/artifact-storage.js +99 -0
  26. package/dist/artifacts/artifact-storage.js.map +1 -0
  27. package/dist/artifacts/index.d.ts +1 -0
  28. package/dist/artifacts/index.js +3 -0
  29. package/dist/artifacts/index.js.map +1 -0
  30. package/dist/browser/browser-manager.d.ts +45 -0
  31. package/dist/browser/browser-manager.js +88 -0
  32. package/dist/browser/browser-manager.js.map +1 -0
  33. package/dist/browser/challenge-detector.d.ts +10 -0
  34. package/dist/browser/challenge-detector.js +58 -0
  35. package/dist/browser/challenge-detector.js.map +1 -0
  36. package/dist/browser/cookie-dismisser.d.ts +18 -0
  37. package/dist/browser/cookie-dismisser.js +76 -0
  38. package/dist/browser/cookie-dismisser.js.map +1 -0
  39. package/dist/browser/index.d.ts +4 -0
  40. package/dist/browser/index.js +6 -0
  41. package/dist/browser/index.js.map +1 -0
  42. package/dist/browser/stealth-browser.d.ts +13 -0
  43. package/dist/browser/stealth-browser.js +59 -0
  44. package/dist/browser/stealth-browser.js.map +1 -0
  45. package/dist/cli/commander-cli.d.ts +2 -0
  46. package/dist/cli/commander-cli.js +150 -0
  47. package/dist/cli/commander-cli.js.map +1 -0
  48. package/dist/cli/doctor.d.ts +34 -0
  49. package/dist/cli/doctor.js +124 -0
  50. package/dist/cli/doctor.js.map +1 -0
  51. package/dist/cli/first-run.d.ts +6 -0
  52. package/dist/cli/first-run.js +58 -0
  53. package/dist/cli/first-run.js.map +1 -0
  54. package/dist/cli/index.d.ts +3 -0
  55. package/dist/cli/index.js +5 -0
  56. package/dist/cli/index.js.map +1 -0
  57. package/dist/cli/progress.d.ts +11 -0
  58. package/dist/cli/progress.js +30 -0
  59. package/dist/cli/progress.js.map +1 -0
  60. package/dist/core/engine.d.ts +33 -0
  61. package/dist/core/engine.js +269 -0
  62. package/dist/core/engine.js.map +1 -0
  63. package/dist/core/index.d.ts +3 -0
  64. package/dist/core/index.js +4 -0
  65. package/dist/core/index.js.map +1 -0
  66. package/dist/core/validation.d.ts +52 -0
  67. package/dist/core/validation.js +228 -0
  68. package/dist/core/validation.js.map +1 -0
  69. package/dist/discovery/crawler.d.ts +58 -0
  70. package/dist/discovery/crawler.js +240 -0
  71. package/dist/discovery/crawler.js.map +1 -0
  72. package/dist/discovery/discovery-pipeline.d.ts +22 -0
  73. package/dist/discovery/discovery-pipeline.js +256 -0
  74. package/dist/discovery/discovery-pipeline.js.map +1 -0
  75. package/dist/discovery/element-mapper.d.ts +21 -0
  76. package/dist/discovery/element-mapper.js +422 -0
  77. package/dist/discovery/element-mapper.js.map +1 -0
  78. package/dist/discovery/index.d.ts +8 -0
  79. package/dist/discovery/index.js +8 -0
  80. package/dist/discovery/index.js.map +1 -0
  81. package/dist/discovery/link-validator.d.ts +15 -0
  82. package/dist/discovery/link-validator.js +137 -0
  83. package/dist/discovery/link-validator.js.map +1 -0
  84. package/dist/discovery/sitemap-builder.d.ts +19 -0
  85. package/dist/discovery/sitemap-builder.js +166 -0
  86. package/dist/discovery/sitemap-builder.js.map +1 -0
  87. package/dist/discovery/spa-detector.d.ts +12 -0
  88. package/dist/discovery/spa-detector.js +271 -0
  89. package/dist/discovery/spa-detector.js.map +1 -0
  90. package/dist/execution/error-detector.d.ts +10 -0
  91. package/dist/execution/error-detector.js +87 -0
  92. package/dist/execution/error-detector.js.map +1 -0
  93. package/dist/execution/evidence-capture.d.ts +8 -0
  94. package/dist/execution/evidence-capture.js +37 -0
  95. package/dist/execution/evidence-capture.js.map +1 -0
  96. package/dist/execution/index.d.ts +5 -0
  97. package/dist/execution/index.js +7 -0
  98. package/dist/execution/index.js.map +1 -0
  99. package/dist/execution/step-handlers.d.ts +48 -0
  100. package/dist/execution/step-handlers.js +349 -0
  101. package/dist/execution/step-handlers.js.map +1 -0
  102. package/dist/execution/test-data.d.ts +50 -0
  103. package/dist/execution/test-data.js +160 -0
  104. package/dist/execution/test-data.js.map +1 -0
  105. package/dist/execution/workflow-executor.d.ts +56 -0
  106. package/dist/execution/workflow-executor.js +331 -0
  107. package/dist/execution/workflow-executor.js.map +1 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +5 -0
  110. package/dist/index.js.map +1 -0
  111. package/dist/mcp/entry.d.ts +2 -0
  112. package/dist/mcp/entry.js +5 -0
  113. package/dist/mcp/entry.js.map +1 -0
  114. package/dist/mcp/index.d.ts +2 -0
  115. package/dist/mcp/index.js +4 -0
  116. package/dist/mcp/index.js.map +1 -0
  117. package/dist/mcp/server.d.ts +3 -0
  118. package/dist/mcp/server.js +19 -0
  119. package/dist/mcp/server.js.map +1 -0
  120. package/dist/mcp/tools.d.ts +2 -0
  121. package/dist/mcp/tools.js +162 -0
  122. package/dist/mcp/tools.js.map +1 -0
  123. package/dist/planning/heuristic-planner.d.ts +7 -0
  124. package/dist/planning/heuristic-planner.js +238 -0
  125. package/dist/planning/heuristic-planner.js.map +1 -0
  126. package/dist/planning/index.d.ts +3 -0
  127. package/dist/planning/index.js +5 -0
  128. package/dist/planning/index.js.map +1 -0
  129. package/dist/planning/plan-schema.d.ts +74 -0
  130. package/dist/planning/plan-schema.js +39 -0
  131. package/dist/planning/plan-schema.js.map +1 -0
  132. package/dist/planning/workflow-planner.d.ts +39 -0
  133. package/dist/planning/workflow-planner.js +211 -0
  134. package/dist/planning/workflow-planner.js.map +1 -0
  135. package/dist/reports/health-scorer.d.ts +14 -0
  136. package/dist/reports/health-scorer.js +88 -0
  137. package/dist/reports/health-scorer.js.map +1 -0
  138. package/dist/reports/html-generator.d.ts +10 -0
  139. package/dist/reports/html-generator.js +155 -0
  140. package/dist/reports/html-generator.js.map +1 -0
  141. package/dist/reports/index.d.ts +4 -0
  142. package/dist/reports/index.js +6 -0
  143. package/dist/reports/index.js.map +1 -0
  144. package/dist/reports/markdown-generator.d.ts +10 -0
  145. package/dist/reports/markdown-generator.js +334 -0
  146. package/dist/reports/markdown-generator.js.map +1 -0
  147. package/dist/reports/priority-ranker.d.ts +22 -0
  148. package/dist/reports/priority-ranker.js +608 -0
  149. package/dist/reports/priority-ranker.js.map +1 -0
  150. package/dist/screenshots/dual-format.d.ts +14 -0
  151. package/dist/screenshots/dual-format.js +59 -0
  152. package/dist/screenshots/dual-format.js.map +1 -0
  153. package/dist/screenshots/index.d.ts +2 -0
  154. package/dist/screenshots/index.js +4 -0
  155. package/dist/screenshots/index.js.map +1 -0
  156. package/dist/screenshots/screenshot-manager.d.ts +33 -0
  157. package/dist/screenshots/screenshot-manager.js +86 -0
  158. package/dist/screenshots/screenshot-manager.js.map +1 -0
  159. package/dist/testing/accessibility-auditor.d.ts +23 -0
  160. package/dist/testing/accessibility-auditor.js +44 -0
  161. package/dist/testing/accessibility-auditor.js.map +1 -0
  162. package/dist/testing/index.d.ts +4 -0
  163. package/dist/testing/index.js +5 -0
  164. package/dist/testing/index.js.map +1 -0
  165. package/dist/testing/meta-auditor.d.ts +16 -0
  166. package/dist/testing/meta-auditor.js +268 -0
  167. package/dist/testing/meta-auditor.js.map +1 -0
  168. package/dist/testing/performance-monitor.d.ts +15 -0
  169. package/dist/testing/performance-monitor.js +64 -0
  170. package/dist/testing/performance-monitor.js.map +1 -0
  171. package/dist/types/artifacts.d.ts +58 -0
  172. package/dist/types/artifacts.js +3 -0
  173. package/dist/types/artifacts.js.map +1 -0
  174. package/dist/types/discovery.d.ts +124 -0
  175. package/dist/types/discovery.js +3 -0
  176. package/dist/types/discovery.js.map +1 -0
  177. package/dist/types/execution.d.ts +154 -0
  178. package/dist/types/execution.js +3 -0
  179. package/dist/types/execution.js.map +1 -0
  180. package/dist/types/index.d.ts +2 -0
  181. package/dist/types/index.js +4 -0
  182. package/dist/types/index.js.map +1 -0
  183. package/dist/utils/sanitizer.d.ts +25 -0
  184. package/dist/utils/sanitizer.js +98 -0
  185. package/dist/utils/sanitizer.js.map +1 -0
  186. package/package.json +86 -0
  187. package/templates/report.hbs +202 -0
  188. package/templates/styles/report.css +607 -0
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Builds a hierarchical sitemap tree from flat page data based on URL path structure.
3
+ * Creates intermediate placeholder nodes for uncrawled parent paths.
4
+ *
5
+ * @param pages - Flat array of crawled pages
6
+ * @param rootUrl - Root URL of the site (e.g., https://example.com)
7
+ * @returns Root node of the sitemap tree
8
+ */
9
+ export function buildSitemap(pages, rootUrl) {
10
+ // Parse root URL
11
+ const rootUrlParsed = new URL(rootUrl);
12
+ const rootHostname = rootUrlParsed.hostname;
13
+ // Normalize URL helper (remove trailing slash, normalize path)
14
+ const normalizePath = (path) => {
15
+ return path === '/' ? '/' : path.replace(/\/$/, '');
16
+ };
17
+ // Find or create root node
18
+ const rootPage = pages.find(p => {
19
+ try {
20
+ const url = new URL(p.url);
21
+ return url.hostname === rootHostname && normalizePath(url.pathname) === '/';
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ });
27
+ const root = rootPage ? {
28
+ url: rootPage.url,
29
+ title: rootPage.title || 'Home',
30
+ path: '/',
31
+ children: [],
32
+ pageData: rootPage,
33
+ depth: 0,
34
+ } : {
35
+ // Synthetic root if homepage wasn't crawled
36
+ url: rootUrl,
37
+ title: 'Home',
38
+ path: '/',
39
+ children: [],
40
+ pageData: {
41
+ url: rootUrl,
42
+ title: 'Home',
43
+ forms: [],
44
+ buttons: [],
45
+ links: [],
46
+ menus: [],
47
+ otherInteractive: [],
48
+ crawledAt: new Date().toISOString(),
49
+ },
50
+ depth: 0,
51
+ };
52
+ // Track all nodes by normalized path for quick lookup
53
+ const nodesByPath = new Map();
54
+ nodesByPath.set('/', root);
55
+ // Sort pages by URL path depth (fewer segments = closer to root)
56
+ const sortedPages = pages
57
+ .filter(p => {
58
+ try {
59
+ const url = new URL(p.url);
60
+ return url.hostname === rootHostname && normalizePath(url.pathname) !== '/';
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ })
66
+ .sort((a, b) => {
67
+ const aSegments = new URL(a.url).pathname.split('/').filter(Boolean).length;
68
+ const bSegments = new URL(b.url).pathname.split('/').filter(Boolean).length;
69
+ return aSegments - bSegments;
70
+ });
71
+ // Build tree by walking path segments
72
+ for (const page of sortedPages) {
73
+ try {
74
+ const url = new URL(page.url);
75
+ const fullPath = normalizePath(url.pathname);
76
+ // Split path into segments (e.g., /dashboard/settings -> ['dashboard', 'settings'])
77
+ const segments = fullPath.split('/').filter(Boolean);
78
+ // Walk the tree, creating intermediate nodes as needed
79
+ let currentPath = '';
80
+ let parentNode = root;
81
+ for (let i = 0; i < segments.length; i++) {
82
+ const segment = segments[i];
83
+ currentPath += '/' + segment;
84
+ const normalizedCurrentPath = normalizePath(currentPath);
85
+ // Check if node exists for this path
86
+ let node = nodesByPath.get(normalizedCurrentPath);
87
+ if (!node) {
88
+ // Check if this is the final segment (matches the page we're processing)
89
+ const isFinalSegment = i === segments.length - 1 && normalizedCurrentPath === fullPath;
90
+ if (isFinalSegment) {
91
+ // Create node from page data
92
+ node = {
93
+ url: page.url,
94
+ title: page.title || segment,
95
+ path: normalizedCurrentPath,
96
+ children: [],
97
+ pageData: page,
98
+ depth: parentNode.depth + 1,
99
+ };
100
+ }
101
+ else {
102
+ // Create placeholder node for intermediate path
103
+ node = {
104
+ url: `${rootUrlParsed.origin}${normalizedCurrentPath}`,
105
+ title: segment.charAt(0).toUpperCase() + segment.slice(1),
106
+ path: normalizedCurrentPath,
107
+ children: [],
108
+ pageData: {
109
+ url: `${rootUrlParsed.origin}${normalizedCurrentPath}`,
110
+ title: segment.charAt(0).toUpperCase() + segment.slice(1),
111
+ forms: [],
112
+ buttons: [],
113
+ links: [],
114
+ menus: [],
115
+ otherInteractive: [],
116
+ crawledAt: new Date().toISOString(),
117
+ },
118
+ depth: parentNode.depth + 1,
119
+ };
120
+ }
121
+ // Add to parent's children and index
122
+ parentNode.children.push(node);
123
+ nodesByPath.set(normalizedCurrentPath, node);
124
+ }
125
+ // Move to next level
126
+ parentNode = node;
127
+ }
128
+ // Handle query string pages - group under path without query
129
+ if (url.search) {
130
+ const queryNode = {
131
+ url: page.url,
132
+ title: page.title || 'Query: ' + url.search,
133
+ path: fullPath + url.search,
134
+ children: [],
135
+ pageData: page,
136
+ depth: parentNode.depth + 1,
137
+ };
138
+ parentNode.children.push(queryNode);
139
+ }
140
+ }
141
+ catch {
142
+ // Invalid URL, skip
143
+ continue;
144
+ }
145
+ }
146
+ return root;
147
+ }
148
+ /**
149
+ * Renders a sitemap tree as human-readable indented text.
150
+ * Format: Title (path) with 2 spaces per depth level.
151
+ *
152
+ * @param node - Root or any node in the tree
153
+ * @param indent - Current indentation level (internal use)
154
+ * @returns Multi-line string representation of the tree
155
+ */
156
+ export function printSitemapTree(node, indent = 0) {
157
+ const indentation = ' '.repeat(indent);
158
+ let output = `${indentation}${node.title} (${node.path})\n`;
159
+ // Sort children by path for consistent output
160
+ const sortedChildren = [...node.children].sort((a, b) => a.path.localeCompare(b.path));
161
+ for (const child of sortedChildren) {
162
+ output += printSitemapTree(child, indent + 1);
163
+ }
164
+ return output;
165
+ }
166
+ //# sourceMappingURL=sitemap-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap-builder.js","sourceRoot":"","sources":["../../src/discovery/sitemap-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Page } from 'playwright';
2
+ import type { SPAFramework } from '../types/discovery.js';
3
+ /**
4
+ * Detect SPA framework from window properties and DOM attributes
5
+ * Priority: Next.js before React, Nuxt before Vue (meta-frameworks use underlying frameworks)
6
+ */
7
+ export declare function detectSPAFramework(page: Page): Promise<SPAFramework>;
8
+ /**
9
+ * Intercept History API and discover client-side routes by clicking navigation elements
10
+ * Returns array of unique discovered route URLs
11
+ */
12
+ export declare function interceptRouteChanges(page: Page): Promise<string[]>;
@@ -0,0 +1,271 @@
1
+ // SPA framework detection and client-side route discovery via History API interception
2
+ /**
3
+ * Detect SPA framework from window properties and DOM attributes
4
+ * Priority: Next.js before React, Nuxt before Vue (meta-frameworks use underlying frameworks)
5
+ */
6
+ export async function detectSPAFramework(page) {
7
+ return await page.evaluate(() => {
8
+ // Next.js detection (check before React)
9
+ const nextData = document.querySelector('script#__NEXT_DATA__');
10
+ if (nextData || window.__NEXT_DATA__) {
11
+ try {
12
+ const version = window.__NEXT_DATA__?.buildId;
13
+ return {
14
+ framework: 'next',
15
+ version,
16
+ router: 'next-router',
17
+ };
18
+ }
19
+ catch {
20
+ return { framework: 'next', router: 'next-router' };
21
+ }
22
+ }
23
+ // Nuxt detection (check before Vue)
24
+ if (window.__NUXT__ || document.querySelector('[data-n-head]')) {
25
+ try {
26
+ const version = window.__NUXT__?.config?.app?.version;
27
+ return {
28
+ framework: 'nuxt',
29
+ version,
30
+ router: 'nuxt-router',
31
+ };
32
+ }
33
+ catch {
34
+ return { framework: 'nuxt', router: 'nuxt-router' };
35
+ }
36
+ }
37
+ // React detection
38
+ const reactContainer = Object.keys(document.body || {}).find((k) => k.startsWith('__reactContainer'));
39
+ const reactDevtools = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
40
+ if (reactContainer || reactDevtools) {
41
+ try {
42
+ const version = reactDevtools?.renderers?.values()?.next()?.value?.version;
43
+ return {
44
+ framework: 'react',
45
+ version,
46
+ router: 'react-router',
47
+ };
48
+ }
49
+ catch {
50
+ return { framework: 'react', router: 'react-router' };
51
+ }
52
+ }
53
+ // Vue detection
54
+ const vueAttr = document.querySelector('[data-v-]');
55
+ if (window.__VUE__ || window.Vue || vueAttr) {
56
+ try {
57
+ const version = window.Vue?.version || window.__VUE__?.version;
58
+ return {
59
+ framework: 'vue',
60
+ version,
61
+ router: 'vue-router',
62
+ };
63
+ }
64
+ catch {
65
+ return { framework: 'vue', router: 'vue-router' };
66
+ }
67
+ }
68
+ // Angular detection
69
+ const ngVersion = document.querySelector('[ng-version]');
70
+ if (window.ng || ngVersion || document.querySelector('app-root')) {
71
+ try {
72
+ const version = ngVersion?.getAttribute('ng-version') || window.ng?.version?.full;
73
+ return {
74
+ framework: 'angular',
75
+ version,
76
+ router: 'angular-router',
77
+ };
78
+ }
79
+ catch {
80
+ return { framework: 'angular', router: 'angular-router' };
81
+ }
82
+ }
83
+ // Svelte detection
84
+ if (document.querySelector('[data-svelte-h]')) {
85
+ return {
86
+ framework: 'svelte',
87
+ router: 'sveltekit',
88
+ };
89
+ }
90
+ return { framework: 'none' };
91
+ });
92
+ }
93
+ /**
94
+ * Intercept History API and discover client-side routes by clicking navigation elements
95
+ * Returns array of unique discovered route URLs
96
+ */
97
+ export async function interceptRouteChanges(page) {
98
+ const discoveredRoutes = new Set();
99
+ const startUrl = page.url();
100
+ const startHostname = new URL(startUrl).hostname;
101
+ // Set hard timeout for entire function (30 seconds)
102
+ const timeoutMs = 30000;
103
+ const startTime = Date.now();
104
+ try {
105
+ // Inject History API interceptor into the current document.
106
+ await page.evaluate(() => {
107
+ const windowWithAfterburn = window;
108
+ if (windowWithAfterburn.__afterburn_history_hook_installed) {
109
+ return;
110
+ }
111
+ windowWithAfterburn.__afterburn_history_hook_installed = true;
112
+ windowWithAfterburn.__afterburn_routes = windowWithAfterburn.__afterburn_routes || [];
113
+ const originalPushState = history.pushState;
114
+ const originalReplaceState = history.replaceState;
115
+ history.pushState = function (data, unused, url) {
116
+ if (url) {
117
+ windowWithAfterburn.__afterburn_routes.push(url);
118
+ }
119
+ return originalPushState.apply(history, arguments);
120
+ };
121
+ history.replaceState = function (data, unused, url) {
122
+ if (url) {
123
+ windowWithAfterburn.__afterburn_routes.push(url);
124
+ }
125
+ return originalReplaceState.apply(history, arguments);
126
+ };
127
+ window.addEventListener('popstate', () => {
128
+ windowWithAfterburn.__afterburn_routes.push(location.pathname + location.search);
129
+ });
130
+ });
131
+ // Find all navigation-like elements
132
+ const navLinks = await page.locator('nav a, [role="navigation"] a, header a').all();
133
+ const allLinks = await page.getByRole('link').all();
134
+ const navigationElements = [...new Set([...navLinks, ...allLinks])];
135
+ // Filter for internal navigation (skip destructive actions)
136
+ const skipWords = [
137
+ 'delete',
138
+ 'remove',
139
+ 'destroy',
140
+ 'reset',
141
+ 'clear',
142
+ 'drop',
143
+ 'purge',
144
+ 'revoke',
145
+ 'terminate',
146
+ 'unsubscribe',
147
+ 'cancel-account',
148
+ 'close-account',
149
+ 'cancel',
150
+ 'submit',
151
+ 'download',
152
+ 'logout',
153
+ 'log out',
154
+ 'sign out',
155
+ ];
156
+ for (const element of navigationElements) {
157
+ // Check timeout
158
+ if (Date.now() - startTime > timeoutMs) {
159
+ break;
160
+ }
161
+ try {
162
+ const href = await element.getAttribute('href');
163
+ const text = (await element.textContent())?.toLowerCase() || '';
164
+ // Skip if no href or external link
165
+ if (!href)
166
+ continue;
167
+ // Skip if text suggests it's not navigation
168
+ if (skipWords.some((word) => text.includes(word))) {
169
+ continue;
170
+ }
171
+ // Check if internal (starts with / or same hostname)
172
+ let isInternal = false;
173
+ if (href.startsWith('/')) {
174
+ isInternal = true;
175
+ }
176
+ else {
177
+ try {
178
+ const linkUrl = new URL(href, startUrl);
179
+ isInternal = linkUrl.hostname === startHostname;
180
+ }
181
+ catch {
182
+ continue;
183
+ }
184
+ }
185
+ if (!isInternal)
186
+ continue;
187
+ // Store current URL
188
+ const beforeUrl = page.url();
189
+ // Try clicking the element
190
+ try {
191
+ await element.click({ timeout: 2000 });
192
+ // Wait for navigation or route change
193
+ try {
194
+ await page.waitForLoadState('domcontentloaded', { timeout: 3000 });
195
+ }
196
+ catch {
197
+ // Timeout is okay - route might have changed without full page load
198
+ }
199
+ // Check if navigation left the original origin
200
+ const afterUrl = page.url();
201
+ const afterOrigin = new URL(afterUrl).origin;
202
+ const startOrigin = new URL(startUrl).origin;
203
+ if (afterOrigin !== startOrigin) {
204
+ // Off-origin navigation detected - go back
205
+ try {
206
+ await page.goBack({ timeout: 3000, waitUntil: 'domcontentloaded' });
207
+ }
208
+ catch {
209
+ // If goBack fails, navigate directly
210
+ try {
211
+ await page.goto(beforeUrl, { timeout: 5000, waitUntil: 'domcontentloaded' });
212
+ }
213
+ catch {
214
+ // Can't go back, break loop
215
+ break;
216
+ }
217
+ }
218
+ continue; // Skip this link
219
+ }
220
+ // Collect discovered routes from window.__afterburn_routes
221
+ const routes = await page.evaluate(() => {
222
+ return window.__afterburn_routes || [];
223
+ });
224
+ for (const route of routes) {
225
+ try {
226
+ const absoluteUrl = new URL(route, startUrl).href;
227
+ discoveredRoutes.add(absoluteUrl);
228
+ }
229
+ catch {
230
+ // Invalid URL, skip
231
+ }
232
+ }
233
+ // Add current URL if it changed
234
+ if (afterUrl !== beforeUrl) {
235
+ discoveredRoutes.add(afterUrl);
236
+ }
237
+ // Navigate back to original page
238
+ if (page.url() !== beforeUrl) {
239
+ try {
240
+ await page.goBack({ timeout: 3000, waitUntil: 'domcontentloaded' });
241
+ }
242
+ catch {
243
+ // If goBack fails, navigate directly
244
+ try {
245
+ await page.goto(beforeUrl, { timeout: 5000, waitUntil: 'domcontentloaded' });
246
+ }
247
+ catch {
248
+ // If we can't go back, break the loop
249
+ break;
250
+ }
251
+ }
252
+ }
253
+ }
254
+ catch (error) {
255
+ // Click failed - element might trigger download, alert, or be stale
256
+ // Continue to next element
257
+ continue;
258
+ }
259
+ }
260
+ catch {
261
+ // Failed to get attributes or interact with element
262
+ continue;
263
+ }
264
+ }
265
+ }
266
+ catch (error) {
267
+ // Entire function failed - return what we have
268
+ }
269
+ return Array.from(discoveredRoutes);
270
+ }
271
+ //# sourceMappingURL=spa-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spa-detector.js","sourceRoot":"","sources":["../../src/discovery/spa-detector.ts"],"names":[],"mappings":"AAAA,uFAAuF;AAKvF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAU;IACjD,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QAC9B,yCAAyC;QACzC,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,sBAAsB,CAAC,CAAC;QAChE,IAAI,QAAQ,IAAK,MAAc,CAAC,aAAa,EAAE,CAAC;YAC9C,IAAI,CAAC;gBACH,MAAM,OAAO,GAAI,MAAc,CAAC,aAAa,EAAE,OAAO,CAAC;gBACvD,OAAO;oBACL,SAAS,EAAE,MAAe;oBAC1B,OAAO;oBACP,MAAM,EAAE,aAAa;iBACtB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,MAAe,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,oCAAoC;QACpC,IAAK,MAAc,CAAC,QAAQ,IAAI,QAAQ,CAAC,aAAa,CAAC,eAAe,CAAC,EAAE,CAAC;YACxE,IAAI,CAAC;gBACH,MAAM,OAAO,GAAI,MAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC;gBAC/D,OAAO;oBACL,SAAS,EAAE,MAAe;oBAC1B,OAAO;oBACP,MAAM,EAAE,aAAa;iBACtB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,MAAe,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,kBAAkB;QAClB,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CACjE,CAAC,CAAC,UAAU,CAAC,kBAAkB,CAAC,CACjC,CAAC;QACF,MAAM,aAAa,GAAI,MAAc,CAAC,8BAA8B,CAAC;QACrE,IAAI,cAAc,IAAI,aAAa,EAAE,CAAC;YACpC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC;gBAC3E,OAAO;oBACL,SAAS,EAAE,OAAgB;oBAC3B,OAAO;oBACP,MAAM,EAAE,cAAc;iBACvB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,OAAgB,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YACjE,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,MAAM,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;QACpD,IAAK,MAAc,CAAC,OAAO,IAAK,MAAc,CAAC,GAAG,IAAI,OAAO,EAAE,CAAC;YAC9D,IAAI,CAAC;gBACH,MAAM,OAAO,GAAI,MAAc,CAAC,GAAG,EAAE,OAAO,IAAK,MAAc,CAAC,OAAO,EAAE,OAAO,CAAC;gBACjF,OAAO;oBACL,SAAS,EAAE,KAAc;oBACzB,OAAO;oBACP,MAAM,EAAE,YAAY;iBACrB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,KAAc,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,oBAAoB;QACpB,MAAM,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QACzD,IAAK,MAAc,CAAC,EAAE,IAAI,SAAS,IAAI,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1E,IAAI,CAAC;gBACH,MAAM,OAAO,GACX,SAAS,EAAE,YAAY,CAAC,YAAY,CAAC,IAAK,MAAc,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC;gBAC7E,OAAO;oBACL,SAAS,EAAE,SAAkB;oBAC7B,OAAO;oBACP,MAAM,EAAE,gBAAgB;iBACzB,CAAC;YACJ,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,SAAS,EAAE,SAAkB,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;YACrE,CAAC;QACH,CAAC;QAED,mBAAmB;QACnB,IAAI,QAAQ,CAAC,aAAa,CAAC,iBAAiB,CAAC,EAAE,CAAC;YAC9C,OAAO;gBACL,SAAS,EAAE,QAAiB;gBAC5B,MAAM,EAAE,WAAW;aACpB,CAAC;QACJ,CAAC;QAED,OAAO,EAAE,SAAS,EAAE,MAAe,EAAE,CAAC;IACxC,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAU;IACpD,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAU,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC5B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;IAEjD,oDAAoD;IACpD,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAE7B,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;YACvB,MAAM,mBAAmB,GAAG,MAAa,CAAC;YAE1C,IAAI,mBAAmB,CAAC,kCAAkC,EAAE,CAAC;gBAC3D,OAAO;YACT,CAAC;YAED,mBAAmB,CAAC,kCAAkC,GAAG,IAAI,CAAC;YAC9D,mBAAmB,CAAC,kBAAkB,GAAG,mBAAmB,CAAC,kBAAkB,IAAI,EAAE,CAAC;YAEtF,MAAM,iBAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;YAC5C,MAAM,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC;YAElD,OAAO,CAAC,SAAS,GAAG,UAAU,IAAS,EAAE,MAAc,EAAE,GAAyB;gBAChF,IAAI,GAAG,EAAE,CAAC;oBACR,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACnD,CAAC;gBACD,OAAO,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,SAAgB,CAAC,CAAC;YAC5D,CAAC,CAAC;YAEF,OAAO,CAAC,YAAY,GAAG,UAAU,IAAS,EAAE,MAAc,EAAE,GAAyB;gBACnF,IAAI,GAAG,EAAE,CAAC;oBACR,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACnD,CAAC;gBACD,OAAO,oBAAoB,CAAC,KAAK,CAAC,OAAO,EAAE,SAAgB,CAAC,CAAC;YAC/D,CAAC,CAAC;YAEF,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE;gBACvC,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;YACnF,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,oCAAoC;QACpC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC,GAAG,EAAE,CAAC;QACpF,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC;QACpD,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEpE,4DAA4D;QAC5D,MAAM,SAAS,GAAG;YAChB,QAAQ;YACR,QAAQ;YACR,SAAS;YACT,OAAO;YACP,OAAO;YACP,MAAM;YACN,OAAO;YACP,QAAQ;YACR,WAAW;YACX,aAAa;YACb,gBAAgB;YAChB,eAAe;YACf,QAAQ;YACR,QAAQ;YACR,UAAU;YACV,QAAQ;YACR,SAAS;YACT,UAAU;SACX,CAAC;QAEF,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;YACzC,gBAAgB;YAChB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,EAAE,CAAC;gBACvC,MAAM;YACR,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;gBAChD,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;gBAEhE,mCAAmC;gBACnC,IAAI,CAAC,IAAI;oBAAE,SAAS;gBAEpB,4CAA4C;gBAC5C,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;oBAClD,SAAS;gBACX,CAAC;gBAED,qDAAqD;gBACrD,IAAI,UAAU,GAAG,KAAK,CAAC;gBACvB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzB,UAAU,GAAG,IAAI,CAAC;gBACpB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC;wBACH,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;wBACxC,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,aAAa,CAAC;oBAClD,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;gBAED,IAAI,CAAC,UAAU;oBAAE,SAAS;gBAE1B,oBAAoB;gBACpB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAE7B,2BAA2B;gBAC3B,IAAI,CAAC;oBACH,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;oBAEvC,sCAAsC;oBACtC,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;oBACrE,CAAC;oBAAC,MAAM,CAAC;wBACP,oEAAoE;oBACtE,CAAC;oBAED,+CAA+C;oBAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;oBAC5B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;oBAC7C,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;oBAE7C,IAAI,WAAW,KAAK,WAAW,EAAE,CAAC;wBAChC,2CAA2C;wBAC3C,IAAI,CAAC;4BACH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBACtE,CAAC;wBAAC,MAAM,CAAC;4BACP,qCAAqC;4BACrC,IAAI,CAAC;gCACH,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;4BAC/E,CAAC;4BAAC,MAAM,CAAC;gCACP,4BAA4B;gCAC5B,MAAM;4BACR,CAAC;wBACH,CAAC;wBACD,SAAS,CAAC,iBAAiB;oBAC7B,CAAC;oBAED,2DAA2D;oBAC3D,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;wBACtC,OAAQ,MAAc,CAAC,kBAAkB,IAAI,EAAE,CAAC;oBAClD,CAAC,CAAC,CAAC;oBAEH,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;wBAC3B,IAAI,CAAC;4BACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC;4BAClD,gBAAgB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;wBACpC,CAAC;wBAAC,MAAM,CAAC;4BACP,oBAAoB;wBACtB,CAAC;oBACH,CAAC;oBAED,gCAAgC;oBAChC,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;wBAC3B,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;oBACjC,CAAC;oBAED,iCAAiC;oBACjC,IAAI,IAAI,CAAC,GAAG,EAAE,KAAK,SAAS,EAAE,CAAC;wBAC7B,IAAI,CAAC;4BACH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBACtE,CAAC;wBAAC,MAAM,CAAC;4BACP,qCAAqC;4BACrC,IAAI,CAAC;gCACH,MAAM,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;4BAC/E,CAAC;4BAAC,MAAM,CAAC;gCACP,sCAAsC;gCACtC,MAAM;4BACR,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBACf,oEAAoE;oBACpE,2BAA2B;oBAC3B,SAAS;gBACX,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,oDAAoD;gBACpD,SAAS;YACX,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,+CAA+C;IACjD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { Page } from 'playwright';
2
+ import { ErrorCollector } from '../types/execution.js';
3
+ /**
4
+ * Set up page event listeners to capture errors passively
5
+ * Returns collector and cleanup function
6
+ */
7
+ export declare function setupErrorListeners(page: Page): {
8
+ collector: ErrorCollector;
9
+ cleanup: () => void;
10
+ };
@@ -0,0 +1,87 @@
1
+ // Page event listeners for passive error detection during workflow execution
2
+ import { redactSensitiveData, redactSensitiveUrl } from '../utils/sanitizer.js';
3
+ /**
4
+ * Set up page event listeners to capture errors passively
5
+ * Returns collector and cleanup function
6
+ */
7
+ export function setupErrorListeners(page) {
8
+ const collector = {
9
+ consoleErrors: [],
10
+ networkFailures: [],
11
+ brokenImages: [],
12
+ };
13
+ // Console error capture (only console.error, not warnings/logs)
14
+ const consoleHandler = (msg) => {
15
+ if (msg.type() === 'error') {
16
+ collector.consoleErrors.push({
17
+ message: redactSensitiveData(msg.text()),
18
+ url: page.url(),
19
+ timestamp: new Date().toISOString(),
20
+ });
21
+ }
22
+ };
23
+ // Network failure capture (4xx and 5xx responses)
24
+ const responseHandler = async (response) => {
25
+ const status = response.status();
26
+ const url = response.url();
27
+ // Capture failed HTTP requests (4xx and 5xx)
28
+ if (status >= 400) {
29
+ const resourceType = response.request().resourceType();
30
+ // Add to network failures
31
+ collector.networkFailures.push({
32
+ url: redactSensitiveUrl(url),
33
+ status,
34
+ method: response.request().method(),
35
+ resourceType,
36
+ });
37
+ // Detect broken images specifically
38
+ const isImage = resourceType === 'image' || /\.(jpg|jpeg|png|gif|svg|webp|ico)$/i.test(url);
39
+ if (isImage) {
40
+ // Find selector by matching img src or background-image
41
+ let selector = `img[src*="${url.split('/').pop()}"]`;
42
+ // Try to find more specific selector if possible
43
+ try {
44
+ const imgElement = await page.locator(`img[src="${url}"]`).first().elementHandle({ timeout: 1000 });
45
+ if (imgElement) {
46
+ const id = await imgElement.getAttribute('id');
47
+ const className = await imgElement.getAttribute('class');
48
+ if (id) {
49
+ selector = `img#${id}`;
50
+ }
51
+ else if (className) {
52
+ selector = `img.${className.split(' ')[0]}`;
53
+ }
54
+ }
55
+ }
56
+ catch {
57
+ // Fallback to generic selector if element not found
58
+ }
59
+ collector.brokenImages.push({
60
+ url,
61
+ selector,
62
+ status,
63
+ });
64
+ }
65
+ }
66
+ };
67
+ // Uncaught exception capture (TypeError, ReferenceError, SyntaxError, etc.)
68
+ const pageErrorHandler = (error) => {
69
+ collector.consoleErrors.push({
70
+ message: redactSensitiveData(`Uncaught ${error.name}: ${error.message}`),
71
+ url: page.url(),
72
+ timestamp: new Date().toISOString(),
73
+ });
74
+ };
75
+ // Register listeners
76
+ page.on('console', consoleHandler);
77
+ page.on('response', responseHandler);
78
+ page.on('pageerror', pageErrorHandler);
79
+ // Cleanup function to remove listeners
80
+ const cleanup = () => {
81
+ page.off('console', consoleHandler);
82
+ page.off('response', responseHandler);
83
+ page.off('pageerror', pageErrorHandler);
84
+ };
85
+ return { collector, cleanup };
86
+ }
87
+ //# sourceMappingURL=error-detector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-detector.js","sourceRoot":"","sources":["../../src/execution/error-detector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Page } from 'playwright-core';
2
+ import type { ErrorEvidence, ErrorCollector } from '../types/execution.js';
3
+ import { ScreenshotManager } from '../screenshots/index.js';
4
+ /**
5
+ * Capture error evidence when a workflow step fails
6
+ * Collects screenshot, console errors, and network failures
7
+ */
8
+ export declare function captureErrorEvidence(page: Page, collector: ErrorCollector, screenshotManager: ScreenshotManager, stepIndex: number): Promise<ErrorEvidence>;
@@ -0,0 +1,37 @@
1
+ // Error evidence capture: screenshot + context collection on step failure
2
+ import { redactSensitiveUrl } from '../utils/sanitizer.js';
3
+ /**
4
+ * Capture error evidence when a workflow step fails
5
+ * Collects screenshot, console errors, and network failures
6
+ */
7
+ export async function captureErrorEvidence(page, collector, screenshotManager, stepIndex) {
8
+ try {
9
+ // Capture screenshot with error label
10
+ const screenshotRef = await screenshotManager.capture(page, `error-step-${stepIndex}`);
11
+ // Get last 5 console errors
12
+ const recentConsoleErrors = collector.consoleErrors
13
+ .slice(-5)
14
+ .map((err) => err.message);
15
+ // Get last 5 network failures
16
+ const recentNetworkFailures = collector.networkFailures
17
+ .slice(-5)
18
+ .map((fail) => ({ url: fail.url, status: fail.status }));
19
+ return {
20
+ screenshotRef,
21
+ consoleErrors: recentConsoleErrors,
22
+ networkFailures: recentNetworkFailures,
23
+ pageUrl: redactSensitiveUrl(page.url()),
24
+ timestamp: new Date().toISOString(),
25
+ };
26
+ }
27
+ catch (error) {
28
+ // Return safe defaults if evidence capture fails
29
+ return {
30
+ consoleErrors: [],
31
+ networkFailures: [],
32
+ pageUrl: redactSensitiveUrl(page.url()),
33
+ timestamp: new Date().toISOString(),
34
+ };
35
+ }
36
+ }
37
+ //# sourceMappingURL=evidence-capture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"evidence-capture.js","sourceRoot":"","sources":["../../src/execution/evidence-capture.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAK1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,IAAU,EACV,SAAyB,EACzB,iBAAoC,EACpC,SAAiB;IAEjB,IAAI,CAAC;QACH,sCAAsC;QACtC,MAAM,aAAa,GAAG,MAAM,iBAAiB,CAAC,OAAO,CACnD,IAAI,EACJ,cAAc,SAAS,EAAE,CAC1B,CAAC;QAEF,4BAA4B;QAC5B,MAAM,mBAAmB,GAAG,SAAS,CAAC,aAAa;aAChD,KAAK,CAAC,CAAC,CAAC,CAAC;aACT,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAE7B,8BAA8B;QAC9B,MAAM,qBAAqB,GAAG,SAAS,CAAC,eAAe;aACpD,KAAK,CAAC,CAAC,CAAC,CAAC;aACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAE3D,OAAO;YACL,aAAa;YACb,aAAa,EAAE,mBAAmB;YAClC,eAAe,EAAE,qBAAqB;YACtC,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACvC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iDAAiD;QACjD,OAAO;YACL,aAAa,EAAE,EAAE;YACjB,eAAe,EAAE,EAAE;YACnB,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACvC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './test-data.js';
2
+ export * from './error-detector.js';
3
+ export * from './step-handlers.js';
4
+ export * from './evidence-capture.js';
5
+ export * from './workflow-executor.js';
@@ -0,0 +1,7 @@
1
+ // Barrel exports for execution module
2
+ export * from './test-data.js';
3
+ export * from './error-detector.js';
4
+ export * from './step-handlers.js';
5
+ export * from './evidence-capture.js';
6
+ export * from './workflow-executor.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/execution/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC"}