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.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/ai/gemini-client.d.ts +21 -0
- package/dist/ai/gemini-client.js +105 -0
- package/dist/ai/gemini-client.js.map +1 -0
- package/dist/ai/index.d.ts +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/analysis/diagnosis-schema.d.ts +106 -0
- package/dist/analysis/diagnosis-schema.js +54 -0
- package/dist/analysis/diagnosis-schema.js.map +1 -0
- package/dist/analysis/error-analyzer.d.ts +9 -0
- package/dist/analysis/error-analyzer.js +573 -0
- package/dist/analysis/error-analyzer.js.map +1 -0
- package/dist/analysis/index.d.ts +4 -0
- package/dist/analysis/index.js +6 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/source-mapper.d.ts +19 -0
- package/dist/analysis/source-mapper.js +329 -0
- package/dist/analysis/source-mapper.js.map +1 -0
- package/dist/analysis/ui-auditor.d.ts +9 -0
- package/dist/analysis/ui-auditor.js +104 -0
- package/dist/analysis/ui-auditor.js.map +1 -0
- package/dist/artifacts/artifact-storage.d.ts +44 -0
- package/dist/artifacts/artifact-storage.js +99 -0
- package/dist/artifacts/artifact-storage.js.map +1 -0
- package/dist/artifacts/index.d.ts +1 -0
- package/dist/artifacts/index.js +3 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/browser/browser-manager.d.ts +45 -0
- package/dist/browser/browser-manager.js +88 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/challenge-detector.d.ts +10 -0
- package/dist/browser/challenge-detector.js +58 -0
- package/dist/browser/challenge-detector.js.map +1 -0
- package/dist/browser/cookie-dismisser.d.ts +18 -0
- package/dist/browser/cookie-dismisser.js +76 -0
- package/dist/browser/cookie-dismisser.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +6 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/stealth-browser.d.ts +13 -0
- package/dist/browser/stealth-browser.js +59 -0
- package/dist/browser/stealth-browser.js.map +1 -0
- package/dist/cli/commander-cli.d.ts +2 -0
- package/dist/cli/commander-cli.js +150 -0
- package/dist/cli/commander-cli.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -0
- package/dist/cli/doctor.js +124 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/first-run.d.ts +6 -0
- package/dist/cli/first-run.js +58 -0
- package/dist/cli/first-run.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/progress.d.ts +11 -0
- package/dist/cli/progress.js +30 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/core/engine.d.ts +33 -0
- package/dist/core/engine.js +269 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/validation.d.ts +52 -0
- package/dist/core/validation.js +228 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/discovery/crawler.d.ts +58 -0
- package/dist/discovery/crawler.js +240 -0
- package/dist/discovery/crawler.js.map +1 -0
- package/dist/discovery/discovery-pipeline.d.ts +22 -0
- package/dist/discovery/discovery-pipeline.js +256 -0
- package/dist/discovery/discovery-pipeline.js.map +1 -0
- package/dist/discovery/element-mapper.d.ts +21 -0
- package/dist/discovery/element-mapper.js +422 -0
- package/dist/discovery/element-mapper.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.js +8 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/link-validator.d.ts +15 -0
- package/dist/discovery/link-validator.js +137 -0
- package/dist/discovery/link-validator.js.map +1 -0
- package/dist/discovery/sitemap-builder.d.ts +19 -0
- package/dist/discovery/sitemap-builder.js +166 -0
- package/dist/discovery/sitemap-builder.js.map +1 -0
- package/dist/discovery/spa-detector.d.ts +12 -0
- package/dist/discovery/spa-detector.js +271 -0
- package/dist/discovery/spa-detector.js.map +1 -0
- package/dist/execution/error-detector.d.ts +10 -0
- package/dist/execution/error-detector.js +87 -0
- package/dist/execution/error-detector.js.map +1 -0
- package/dist/execution/evidence-capture.d.ts +8 -0
- package/dist/execution/evidence-capture.js +37 -0
- package/dist/execution/evidence-capture.js.map +1 -0
- package/dist/execution/index.d.ts +5 -0
- package/dist/execution/index.js +7 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/step-handlers.d.ts +48 -0
- package/dist/execution/step-handlers.js +349 -0
- package/dist/execution/step-handlers.js.map +1 -0
- package/dist/execution/test-data.d.ts +50 -0
- package/dist/execution/test-data.js +160 -0
- package/dist/execution/test-data.js.map +1 -0
- package/dist/execution/workflow-executor.d.ts +56 -0
- package/dist/execution/workflow-executor.js +331 -0
- package/dist/execution/workflow-executor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +5 -0
- package/dist/mcp/entry.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +19 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +162 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/planning/heuristic-planner.d.ts +7 -0
- package/dist/planning/heuristic-planner.js +238 -0
- package/dist/planning/heuristic-planner.js.map +1 -0
- package/dist/planning/index.d.ts +3 -0
- package/dist/planning/index.js +5 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-schema.d.ts +74 -0
- package/dist/planning/plan-schema.js +39 -0
- package/dist/planning/plan-schema.js.map +1 -0
- package/dist/planning/workflow-planner.d.ts +39 -0
- package/dist/planning/workflow-planner.js +211 -0
- package/dist/planning/workflow-planner.js.map +1 -0
- package/dist/reports/health-scorer.d.ts +14 -0
- package/dist/reports/health-scorer.js +88 -0
- package/dist/reports/health-scorer.js.map +1 -0
- package/dist/reports/html-generator.d.ts +10 -0
- package/dist/reports/html-generator.js +155 -0
- package/dist/reports/html-generator.js.map +1 -0
- package/dist/reports/index.d.ts +4 -0
- package/dist/reports/index.js +6 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/markdown-generator.d.ts +10 -0
- package/dist/reports/markdown-generator.js +334 -0
- package/dist/reports/markdown-generator.js.map +1 -0
- package/dist/reports/priority-ranker.d.ts +22 -0
- package/dist/reports/priority-ranker.js +608 -0
- package/dist/reports/priority-ranker.js.map +1 -0
- package/dist/screenshots/dual-format.d.ts +14 -0
- package/dist/screenshots/dual-format.js +59 -0
- package/dist/screenshots/dual-format.js.map +1 -0
- package/dist/screenshots/index.d.ts +2 -0
- package/dist/screenshots/index.js +4 -0
- package/dist/screenshots/index.js.map +1 -0
- package/dist/screenshots/screenshot-manager.d.ts +33 -0
- package/dist/screenshots/screenshot-manager.js +86 -0
- package/dist/screenshots/screenshot-manager.js.map +1 -0
- package/dist/testing/accessibility-auditor.d.ts +23 -0
- package/dist/testing/accessibility-auditor.js +44 -0
- package/dist/testing/accessibility-auditor.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/meta-auditor.d.ts +16 -0
- package/dist/testing/meta-auditor.js +268 -0
- package/dist/testing/meta-auditor.js.map +1 -0
- package/dist/testing/performance-monitor.d.ts +15 -0
- package/dist/testing/performance-monitor.js +64 -0
- package/dist/testing/performance-monitor.js.map +1 -0
- package/dist/types/artifacts.d.ts +58 -0
- package/dist/types/artifacts.js +3 -0
- package/dist/types/artifacts.js.map +1 -0
- package/dist/types/discovery.d.ts +124 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/execution.d.ts +154 -0
- package/dist/types/execution.js +3 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/sanitizer.d.ts +25 -0
- package/dist/utils/sanitizer.js +98 -0
- package/dist/utils/sanitizer.js.map +1 -0
- package/package.json +86 -0
- package/templates/report.hbs +202 -0
- 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":"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"}
|
|
@@ -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,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"}
|