@sudobility/testomniac_runner_service 0.1.35 → 0.1.37
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/dist/api/client.d.ts +61 -2
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +37 -18
- package/dist/api/client.js.map +1 -1
- package/dist/crawler/link-extractor.d.ts +8 -0
- package/dist/crawler/link-extractor.d.ts.map +1 -0
- package/dist/crawler/link-extractor.js +37 -0
- package/dist/crawler/link-extractor.js.map +1 -0
- package/dist/crawler/url-normalizer.d.ts +4 -0
- package/dist/crawler/url-normalizer.d.ts.map +1 -0
- package/dist/crawler/url-normalizer.js +27 -0
- package/dist/crawler/url-normalizer.js.map +1 -0
- package/dist/expertise/accessibility-expertise.d.ts +11 -0
- package/dist/expertise/accessibility-expertise.d.ts.map +1 -0
- package/dist/expertise/accessibility-expertise.js +131 -0
- package/dist/expertise/accessibility-expertise.js.map +1 -0
- package/dist/expertise/content-expertise.d.ts +10 -0
- package/dist/expertise/content-expertise.d.ts.map +1 -0
- package/dist/expertise/content-expertise.js +102 -0
- package/dist/expertise/content-expertise.js.map +1 -0
- package/dist/expertise/index.d.ts +3 -0
- package/dist/expertise/index.d.ts.map +1 -1
- package/dist/expertise/index.js +9 -4
- package/dist/expertise/index.js.map +1 -1
- package/dist/expertise/ui-expertise.d.ts +10 -0
- package/dist/expertise/ui-expertise.d.ts.map +1 -0
- package/dist/expertise/ui-expertise.js +76 -0
- package/dist/expertise/ui-expertise.js.map +1 -0
- package/dist/orchestrator/decomposition.d.ts.map +1 -1
- package/dist/orchestrator/decomposition.js +300 -65
- package/dist/orchestrator/decomposition.js.map +1 -1
- package/dist/orchestrator/direct-navigation.d.ts +4 -0
- package/dist/orchestrator/direct-navigation.d.ts.map +1 -0
- package/dist/orchestrator/direct-navigation.js +47 -0
- package/dist/orchestrator/direct-navigation.js.map +1 -0
- package/dist/orchestrator/discovery.d.ts +10 -0
- package/dist/orchestrator/discovery.d.ts.map +1 -0
- package/dist/orchestrator/discovery.js +77 -0
- package/dist/orchestrator/discovery.js.map +1 -0
- package/dist/orchestrator/expertise.d.ts +5 -0
- package/dist/orchestrator/expertise.d.ts.map +1 -0
- package/dist/orchestrator/expertise.js +157 -0
- package/dist/orchestrator/expertise.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.js +63 -37
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/orchestrator/page-capture.d.ts +22 -0
- package/dist/orchestrator/page-capture.d.ts.map +1 -0
- package/dist/orchestrator/page-capture.js +102 -0
- package/dist/orchestrator/page-capture.js.map +1 -0
- package/dist/orchestrator/test-execution.d.ts.map +1 -1
- package/dist/orchestrator/test-execution.js +17 -32
- package/dist/orchestrator/test-execution.js.map +1 -1
- package/dist/orchestrator/types.d.ts +10 -0
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expertise.d.ts","sourceRoot":"","sources":["../../src/orchestrator/expertise.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAmG5D,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CA4Gf"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { ExpectationType } from "../domain/types";
|
|
2
|
+
import { createDefaultExpertises } from "../expertise";
|
|
3
|
+
import { detectScaffoldRegions } from "../scanner/component-detector";
|
|
4
|
+
import { detectPatternsWithInstances } from "../scanner/pattern-detector";
|
|
5
|
+
const EXPERTISE_PREFIX = "Expertise:";
|
|
6
|
+
function buildDefaultExpectations() {
|
|
7
|
+
return [
|
|
8
|
+
{
|
|
9
|
+
expectationType: ExpectationType.PageLoaded,
|
|
10
|
+
description: "Page should load successfully",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
expectationType: ExpectationType.NoConsoleErrors,
|
|
14
|
+
description: "Page should not produce console errors",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
expectationType: ExpectationType.NoNetworkErrors,
|
|
18
|
+
description: "Page should not produce network errors",
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
function normalizeOutcomes(outcomes) {
|
|
23
|
+
const expectedOutcome = outcomes.length > 0
|
|
24
|
+
? outcomes.map(item => item.expected).join("\n")
|
|
25
|
+
: undefined;
|
|
26
|
+
const observedOutcome = outcomes.length > 0
|
|
27
|
+
? outcomes.map(item => `[${item.result}] ${item.observed}`).join("\n")
|
|
28
|
+
: undefined;
|
|
29
|
+
const hasErrors = outcomes.some(item => item.result === "error");
|
|
30
|
+
return {
|
|
31
|
+
expectedOutcome,
|
|
32
|
+
observedOutcome,
|
|
33
|
+
status: hasErrors ? "failed" : "completed",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function ensureExpertiseSuite(api, config, events, expertiseName) {
|
|
37
|
+
const title = `${EXPERTISE_PREFIX} ${expertiseName}`;
|
|
38
|
+
const suites = await api.getTestSuitesByRunner(config.runnerId);
|
|
39
|
+
const existing = suites.find(suite => suite.title === title);
|
|
40
|
+
if (existing) {
|
|
41
|
+
return existing.id;
|
|
42
|
+
}
|
|
43
|
+
const created = await api.insertTestSuite(config.runnerId, {
|
|
44
|
+
title,
|
|
45
|
+
description: `Auto-generated expertise suite for ${expertiseName} findings`,
|
|
46
|
+
startingPath: "/",
|
|
47
|
+
sizeClass: config.sizeClass,
|
|
48
|
+
priority: 2,
|
|
49
|
+
suite_tags: ["expertise", expertiseName],
|
|
50
|
+
});
|
|
51
|
+
events.onTestSuiteCreated({ suiteId: created.id, title: created.title });
|
|
52
|
+
return created.id;
|
|
53
|
+
}
|
|
54
|
+
async function ensureExpertiseCase(api, config, suiteId, expertiseName, pageStateId, pageId, relativePath) {
|
|
55
|
+
const testCase = await api.insertTestCase(config.runnerId, {
|
|
56
|
+
title: `${expertiseName}: ${relativePath || "/"}`,
|
|
57
|
+
type: "render",
|
|
58
|
+
sizeClass: config.sizeClass,
|
|
59
|
+
suite_tags: ["expertise", expertiseName],
|
|
60
|
+
page_id: pageId,
|
|
61
|
+
priority: 2,
|
|
62
|
+
startingPageStateId: pageStateId,
|
|
63
|
+
startingPath: relativePath || "/",
|
|
64
|
+
steps: [],
|
|
65
|
+
globalExpectations: [],
|
|
66
|
+
}, suiteId);
|
|
67
|
+
return testCase.id;
|
|
68
|
+
}
|
|
69
|
+
export async function executePageExpertises(adapter, config, api, events, pageStateId, pageId, relativePath) {
|
|
70
|
+
const html = await adapter.content();
|
|
71
|
+
const scaffolds = await detectScaffoldRegions(adapter);
|
|
72
|
+
const patterns = await detectPatternsWithInstances(adapter);
|
|
73
|
+
const context = {
|
|
74
|
+
html,
|
|
75
|
+
scaffolds,
|
|
76
|
+
patterns,
|
|
77
|
+
consoleLogs: [],
|
|
78
|
+
networkLogs: [],
|
|
79
|
+
expectations: buildDefaultExpectations(),
|
|
80
|
+
};
|
|
81
|
+
const expertises = createDefaultExpertises();
|
|
82
|
+
for (const expertise of expertises) {
|
|
83
|
+
const suiteId = await ensureExpertiseSuite(api, config, events, expertise.name);
|
|
84
|
+
const testCaseId = await ensureExpertiseCase(api, config, suiteId, expertise.name, pageStateId, pageId, relativePath);
|
|
85
|
+
const testCaseRun = await api.createTestCaseRun({ testCaseId });
|
|
86
|
+
const testRun = await api.createTestRun({
|
|
87
|
+
runnerId: config.runnerId,
|
|
88
|
+
testCaseRunId: testCaseRun.id,
|
|
89
|
+
testEnvironmentId: config.testEnvironmentId,
|
|
90
|
+
parentTestRunId: config.scanId,
|
|
91
|
+
rootTestRunId: config.scanId,
|
|
92
|
+
sizeClass: config.sizeClass,
|
|
93
|
+
runnerInstanceId: config.runnerInstanceId,
|
|
94
|
+
runnerInstanceName: config.runnerInstanceName,
|
|
95
|
+
});
|
|
96
|
+
const startedAt = Date.now();
|
|
97
|
+
try {
|
|
98
|
+
const outcomes = expertise.evaluate(context);
|
|
99
|
+
const { expectedOutcome, observedOutcome, status } = normalizeOutcomes(outcomes);
|
|
100
|
+
for (const outcome of outcomes) {
|
|
101
|
+
if (outcome.result === "warning" || outcome.result === "error") {
|
|
102
|
+
await api.createTestRunFinding({
|
|
103
|
+
testCaseRunId: testCaseRun.id,
|
|
104
|
+
type: outcome.result === "error" ? "error" : "warning",
|
|
105
|
+
title: `[${expertise.name}] ${outcome.expected}`,
|
|
106
|
+
description: outcome.observed,
|
|
107
|
+
});
|
|
108
|
+
events.onFindingCreated({
|
|
109
|
+
type: outcome.result,
|
|
110
|
+
title: `[${expertise.name}] ${outcome.expected}`,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
await api.completeTestCaseRun(testCaseRun.id, {
|
|
115
|
+
status,
|
|
116
|
+
durationMs: Date.now() - startedAt,
|
|
117
|
+
expectedOutcome,
|
|
118
|
+
observedOutcome,
|
|
119
|
+
});
|
|
120
|
+
await api.completeTestRun(testRun.id, {
|
|
121
|
+
status,
|
|
122
|
+
totalDurationMs: Date.now() - startedAt,
|
|
123
|
+
});
|
|
124
|
+
events.onTestRunCompleted({
|
|
125
|
+
testRunId: testRun.id,
|
|
126
|
+
passed: status !== "failed",
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : "Expertise evaluation failed";
|
|
131
|
+
await api.completeTestCaseRun(testCaseRun.id, {
|
|
132
|
+
status: "failed",
|
|
133
|
+
durationMs: Date.now() - startedAt,
|
|
134
|
+
errorMessage: message,
|
|
135
|
+
});
|
|
136
|
+
await api.completeTestRun(testRun.id, {
|
|
137
|
+
status: "failed",
|
|
138
|
+
totalDurationMs: Date.now() - startedAt,
|
|
139
|
+
});
|
|
140
|
+
await api.createTestRunFinding({
|
|
141
|
+
testCaseRunId: testCaseRun.id,
|
|
142
|
+
type: "error",
|
|
143
|
+
title: `[${expertise.name}] expertise execution failed`,
|
|
144
|
+
description: message,
|
|
145
|
+
});
|
|
146
|
+
events.onFindingCreated({
|
|
147
|
+
type: "error",
|
|
148
|
+
title: `[${expertise.name}] expertise execution failed`,
|
|
149
|
+
});
|
|
150
|
+
events.onTestRunCompleted({
|
|
151
|
+
testRunId: testRun.id,
|
|
152
|
+
passed: false,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=expertise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expertise.js","sourceRoot":"","sources":["../../src/orchestrator/expertise.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AA6CxE,wBAAsB,OAAO,CAC3B,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,YAAY,EAAE,gBAAgB,GAC7B,OAAO,CAAC,UAAU,CAAC,CA2LrB"}
|
|
@@ -1,14 +1,38 @@
|
|
|
1
|
+
import { discoverPublicPages } from "./discovery";
|
|
1
2
|
import { processDecompositionJob } from "./decomposition";
|
|
2
3
|
import { executeTestCases } from "./test-execution";
|
|
3
|
-
import {
|
|
4
|
-
import { extractActionableItems } from "../extractors";
|
|
4
|
+
import { captureCurrentPage, resetCapturedPagePaths } from "./page-capture";
|
|
5
5
|
const LOG = (...args) => console.warn("[orchestrator]", ...args);
|
|
6
|
+
function buildAiSummary(pagesFound, pageStatesFound, testRunsCompleted, findingsFound, expertiseSummary) {
|
|
7
|
+
const expertiseLines = Object.entries(expertiseSummary)
|
|
8
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
9
|
+
.map(([name, counts]) => {
|
|
10
|
+
const parts = [];
|
|
11
|
+
if (counts.errors > 0)
|
|
12
|
+
parts.push(`${counts.errors} error${counts.errors === 1 ? "" : "s"}`);
|
|
13
|
+
if (counts.warnings > 0) {
|
|
14
|
+
parts.push(`${counts.warnings} warning${counts.warnings === 1 ? "" : "s"}`);
|
|
15
|
+
}
|
|
16
|
+
return parts.length > 0
|
|
17
|
+
? `${name}: ${parts.join(", ")}`
|
|
18
|
+
: `${name}: no findings`;
|
|
19
|
+
});
|
|
20
|
+
const headline = findingsFound === 0
|
|
21
|
+
? `Scan completed with no findings across ${pagesFound} page${pagesFound === 1 ? "" : "s"}.`
|
|
22
|
+
: `Scan completed with ${findingsFound} finding${findingsFound === 1 ? "" : "s"} across ${pagesFound} page${pagesFound === 1 ? "" : "s"}.`;
|
|
23
|
+
const detail = `Captured ${pageStatesFound} page state${pageStatesFound === 1 ? "" : "s"} and completed ${testRunsCompleted} test run${testRunsCompleted === 1 ? "" : "s"}.`;
|
|
24
|
+
if (expertiseLines.length === 0) {
|
|
25
|
+
return `${headline} ${detail}`;
|
|
26
|
+
}
|
|
27
|
+
return `${headline} ${detail} Findings by expertise: ${expertiseLines.join("; ")}.`;
|
|
28
|
+
}
|
|
6
29
|
export async function runScan(adapter, config, api, eventHandler) {
|
|
7
30
|
const startTime = Date.now();
|
|
8
31
|
let pagesFound = 0;
|
|
9
32
|
let pageStatesFound = 0;
|
|
10
33
|
let testRunsCompleted = 0;
|
|
11
34
|
let findingsFound = 0;
|
|
35
|
+
const expertiseSummary = {};
|
|
12
36
|
const wrappedHandler = {
|
|
13
37
|
...eventHandler,
|
|
14
38
|
onPageFound(page) {
|
|
@@ -28,6 +52,21 @@ export async function runScan(adapter, config, api, eventHandler) {
|
|
|
28
52
|
},
|
|
29
53
|
onFindingCreated(finding) {
|
|
30
54
|
findingsFound++;
|
|
55
|
+
const match = /^\[([^\]]+)\]/.exec(finding.title);
|
|
56
|
+
if (match?.[1]) {
|
|
57
|
+
const expertiseName = match[1];
|
|
58
|
+
const counts = expertiseSummary[expertiseName] ?? {
|
|
59
|
+
warnings: 0,
|
|
60
|
+
errors: 0,
|
|
61
|
+
};
|
|
62
|
+
if (finding.type === "error") {
|
|
63
|
+
counts.errors += 1;
|
|
64
|
+
}
|
|
65
|
+
else if (finding.type === "warning") {
|
|
66
|
+
counts.warnings += 1;
|
|
67
|
+
}
|
|
68
|
+
expertiseSummary[expertiseName] = counts;
|
|
69
|
+
}
|
|
31
70
|
eventHandler.onFindingCreated(finding);
|
|
32
71
|
emitStats();
|
|
33
72
|
},
|
|
@@ -41,42 +80,23 @@ export async function runScan(adapter, config, api, eventHandler) {
|
|
|
41
80
|
});
|
|
42
81
|
}
|
|
43
82
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
resetCapturedPagePaths();
|
|
84
|
+
// 1. Crawl the site from the scan URL to inventory public pages
|
|
85
|
+
LOG(`Step 1: Discovering pages from ${config.scanUrl}`);
|
|
86
|
+
await discoverPublicPages(adapter, config, api, wrappedHandler);
|
|
87
|
+
// 2. Navigate back to scan URL and capture initial page state
|
|
88
|
+
LOG(`Step 2: Navigating to ${config.scanUrl}`);
|
|
46
89
|
await adapter.goto(config.scanUrl, { waitUntil: "networkidle0" });
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const relativePath = scanUrlObj.pathname;
|
|
52
|
-
const page = await api.findOrCreatePage(config.runnerId, relativePath);
|
|
53
|
-
LOG(`Page created/found: id=${page.id}, path=${relativePath}`);
|
|
54
|
-
wrappedHandler.onPageFound({ relativePath, pageId: page.id });
|
|
55
|
-
// Extract actionable items and compute hashes
|
|
56
|
-
const items = await extractActionableItems(adapter);
|
|
57
|
-
LOG(`Extracted ${items.length} actionable items from page`);
|
|
58
|
-
const hashes = await computeHashes(initialHtml, items);
|
|
59
|
-
LOG(`Computed hashes:`, hashes);
|
|
60
|
-
const initialPageState = await api.createPageState({
|
|
61
|
-
pageId: page.id,
|
|
62
|
-
sizeClass: config.sizeClass,
|
|
63
|
-
hashes,
|
|
64
|
-
screenshotPath: undefined,
|
|
65
|
-
contentText: initialHtml.slice(0, 5000),
|
|
66
|
-
});
|
|
67
|
-
LOG(`Created page state: id=${initialPageState.id}`);
|
|
68
|
-
wrappedHandler.onPageStateCreated({
|
|
69
|
-
pageStateId: initialPageState.id,
|
|
70
|
-
pageId: page.id,
|
|
71
|
-
});
|
|
72
|
-
// 2. Create initial AI Decomposition Job
|
|
73
|
-
const initialJob = await api.createDecompositionJob(config.scanId, initialPageState.id);
|
|
74
|
-
LOG(`Created decomposition job: id=${initialJob.id}`);
|
|
75
|
-
wrappedHandler.onDecompositionJobCreated({
|
|
76
|
-
jobId: initialJob.id,
|
|
77
|
-
pageStateId: initialPageState.id,
|
|
90
|
+
const initialCapture = await captureCurrentPage(adapter, config, api, wrappedHandler, {
|
|
91
|
+
testRunId: config.scanId,
|
|
92
|
+
markDiscovered: false,
|
|
93
|
+
createDecompositionJob: true,
|
|
78
94
|
});
|
|
79
|
-
|
|
95
|
+
if (!initialCapture?.pageStateId) {
|
|
96
|
+
throw new Error("Failed to capture initial page state");
|
|
97
|
+
}
|
|
98
|
+
LOG(`Captured initial page state ${initialCapture.pageStateId} for ${initialCapture.relativePath}`);
|
|
99
|
+
// 4. Generate/Run loop
|
|
80
100
|
let iteration = 0;
|
|
81
101
|
const MAX_ITERATIONS = 50;
|
|
82
102
|
while (iteration < MAX_ITERATIONS) {
|
|
@@ -113,10 +133,12 @@ export async function runScan(adapter, config, api, eventHandler) {
|
|
|
113
133
|
}
|
|
114
134
|
LOG("New pages discovered — continuing to next iteration");
|
|
115
135
|
}
|
|
116
|
-
//
|
|
136
|
+
// 5. Complete test run
|
|
117
137
|
const durationMs = Date.now() - startTime;
|
|
138
|
+
const aiSummary = buildAiSummary(pagesFound, pageStatesFound, testRunsCompleted, findingsFound, expertiseSummary);
|
|
118
139
|
await api.completeTestRun(config.scanId, {
|
|
119
140
|
status: "completed",
|
|
141
|
+
aiSummary,
|
|
120
142
|
totalDurationMs: durationMs,
|
|
121
143
|
pagesFound,
|
|
122
144
|
pageStatesFound,
|
|
@@ -129,11 +151,15 @@ export async function runScan(adapter, config, api, eventHandler) {
|
|
|
129
151
|
testRunsCompleted,
|
|
130
152
|
findingsFound,
|
|
131
153
|
durationMs,
|
|
154
|
+
aiSummary,
|
|
155
|
+
expertiseSummary,
|
|
132
156
|
};
|
|
133
157
|
wrappedHandler.onScanComplete({
|
|
134
158
|
totalPages: pagesFound,
|
|
135
159
|
totalFindings: findingsFound,
|
|
136
160
|
durationMs,
|
|
161
|
+
aiSummary,
|
|
162
|
+
expertiseSummary,
|
|
137
163
|
});
|
|
138
164
|
return result;
|
|
139
165
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/orchestrator/orchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BrowserAdapter } from "../adapter";
|
|
2
|
+
import type { ApiClient } from "../api/client";
|
|
3
|
+
import type { ScanConfig, ScanEventHandler } from "./types";
|
|
4
|
+
export interface CapturePageOptions {
|
|
5
|
+
testRunId?: number;
|
|
6
|
+
markDiscovered?: boolean;
|
|
7
|
+
captureState?: boolean;
|
|
8
|
+
createDecompositionJob?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface CapturePageResult {
|
|
11
|
+
pageId: number;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
pageStateId?: number;
|
|
14
|
+
decompositionJobId?: number;
|
|
15
|
+
createdNewState: boolean;
|
|
16
|
+
matchedExistingState: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function resetCapturedPagePaths(): void;
|
|
19
|
+
export declare function seedCapturedPagePath(relativePath: string): void;
|
|
20
|
+
export declare function hasCapturedPagePath(relativePath: string): boolean;
|
|
21
|
+
export declare function captureCurrentPage(adapter: BrowserAdapter, config: ScanConfig, api: ApiClient, events: ScanEventHandler, options?: CapturePageOptions): Promise<CapturePageResult | null>;
|
|
22
|
+
//# sourceMappingURL=page-capture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-capture.d.ts","sourceRoot":"","sources":["../../src/orchestrator/page-capture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAM5D,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,OAAO,CAAC;IACzB,oBAAoB,EAAE,OAAO,CAAC;CAC/B;AAID,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAgHnC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { computeHashes } from "../browser/page-utils";
|
|
2
|
+
import { toRelativePath } from "../crawler/url-normalizer";
|
|
3
|
+
import { extractActionableItems } from "../extractors";
|
|
4
|
+
import { executePageExpertises } from "./expertise";
|
|
5
|
+
const capturedPagePaths = new Set();
|
|
6
|
+
export function resetCapturedPagePaths() {
|
|
7
|
+
capturedPagePaths.clear();
|
|
8
|
+
}
|
|
9
|
+
export function seedCapturedPagePath(relativePath) {
|
|
10
|
+
capturedPagePaths.add(relativePath);
|
|
11
|
+
}
|
|
12
|
+
export function hasCapturedPagePath(relativePath) {
|
|
13
|
+
return capturedPagePaths.has(relativePath);
|
|
14
|
+
}
|
|
15
|
+
export async function captureCurrentPage(adapter, config, api, events, options = {}) {
|
|
16
|
+
const currentUrl = await adapter.getUrl();
|
|
17
|
+
const current = new URL(currentUrl);
|
|
18
|
+
const base = new URL(config.baseUrl);
|
|
19
|
+
if (current.origin !== base.origin) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const relativePath = toRelativePath(currentUrl);
|
|
23
|
+
const page = await api.findOrCreatePage(config.runnerId, relativePath);
|
|
24
|
+
if (options.markDiscovered) {
|
|
25
|
+
events.onPageFound({ relativePath, pageId: page.id });
|
|
26
|
+
if (config.testEnvironmentId) {
|
|
27
|
+
await api.createDiscoveredPages({
|
|
28
|
+
testEnvironmentId: config.testEnvironmentId,
|
|
29
|
+
pages: [{ relativePath, isPublic: true }],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (config.testEnvironmentId && options.testRunId) {
|
|
33
|
+
await api.createPageVisit({
|
|
34
|
+
testRunId: options.testRunId,
|
|
35
|
+
testEnvironmentId: config.testEnvironmentId,
|
|
36
|
+
relativePath,
|
|
37
|
+
status: "visited",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (hasCapturedPagePath(relativePath)) {
|
|
42
|
+
return {
|
|
43
|
+
pageId: page.id,
|
|
44
|
+
relativePath,
|
|
45
|
+
createdNewState: false,
|
|
46
|
+
matchedExistingState: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (options.captureState === false) {
|
|
50
|
+
return {
|
|
51
|
+
pageId: page.id,
|
|
52
|
+
relativePath,
|
|
53
|
+
createdNewState: false,
|
|
54
|
+
matchedExistingState: false,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const html = await adapter.content();
|
|
58
|
+
const items = await extractActionableItems(adapter);
|
|
59
|
+
const hashes = await computeHashes(html, items);
|
|
60
|
+
const existingState = await api.findMatchingPageState(page.id, hashes, config.sizeClass);
|
|
61
|
+
if (existingState) {
|
|
62
|
+
seedCapturedPagePath(relativePath);
|
|
63
|
+
return {
|
|
64
|
+
pageId: page.id,
|
|
65
|
+
relativePath,
|
|
66
|
+
pageStateId: existingState.id,
|
|
67
|
+
createdNewState: false,
|
|
68
|
+
matchedExistingState: true,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const pageState = await api.createPageState({
|
|
72
|
+
pageId: page.id,
|
|
73
|
+
sizeClass: config.sizeClass,
|
|
74
|
+
hashes,
|
|
75
|
+
contentText: html.slice(0, 5000),
|
|
76
|
+
createdByTestRunId: options.testRunId,
|
|
77
|
+
});
|
|
78
|
+
seedCapturedPagePath(relativePath);
|
|
79
|
+
events.onPageStateCreated({
|
|
80
|
+
pageStateId: pageState.id,
|
|
81
|
+
pageId: page.id,
|
|
82
|
+
});
|
|
83
|
+
await executePageExpertises(adapter, config, api, events, pageState.id, page.id, relativePath);
|
|
84
|
+
let decompositionJobId;
|
|
85
|
+
if (options.createDecompositionJob) {
|
|
86
|
+
const job = await api.createDecompositionJob(config.scanId, pageState.id);
|
|
87
|
+
decompositionJobId = job.id;
|
|
88
|
+
events.onDecompositionJobCreated({
|
|
89
|
+
jobId: job.id,
|
|
90
|
+
pageStateId: pageState.id,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
pageId: page.id,
|
|
95
|
+
relativePath,
|
|
96
|
+
pageStateId: pageState.id,
|
|
97
|
+
decompositionJobId,
|
|
98
|
+
createdNewState: true,
|
|
99
|
+
matchedExistingState: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=page-capture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-capture.js","sourceRoot":"","sources":["../../src/orchestrator/page-capture.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-execution.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-execution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"test-execution.d.ts","sourceRoot":"","sources":["../../src/orchestrator/test-execution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAiI5D,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,SAAS,EACd,MAAM,EAAE,gBAAgB,EACxB,WAAW,EAAE,MAAM,EAAE,GACpB,OAAO,CAAC,OAAO,CAAC,CA0NlB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { extractActionableItems } from "../extractors";
|
|
2
|
-
import {
|
|
2
|
+
import { toRelativePath } from "../crawler/url-normalizer";
|
|
3
|
+
import { captureCurrentPage, hasCapturedPagePath, seedCapturedPagePath, } from "./page-capture";
|
|
3
4
|
const LOG = (...args) => console.warn("[test-execution]", ...args);
|
|
4
5
|
/** Delay after each browser action to let the page settle. */
|
|
5
6
|
const POST_ACTION_DELAY_MS = 2000;
|
|
@@ -10,8 +11,6 @@ function checkAbort(signal) {
|
|
|
10
11
|
if (signal?.aborted)
|
|
11
12
|
throw new Error("Scan aborted");
|
|
12
13
|
}
|
|
13
|
-
/** Pages already discovered — only create decomposition jobs for NEW pages. */
|
|
14
|
-
const discoveredPagePaths = new Set();
|
|
15
14
|
/**
|
|
16
15
|
* After each action, check if we navigated to a new page (different path).
|
|
17
16
|
* Only new pages trigger decomposition jobs. Same-page DOM changes from
|
|
@@ -25,34 +24,19 @@ async function captureCurrentPageState(config, adapter, api, testRunId, events)
|
|
|
25
24
|
if (current.origin !== base.origin) {
|
|
26
25
|
return false;
|
|
27
26
|
}
|
|
28
|
-
const relativePath =
|
|
27
|
+
const relativePath = toRelativePath(currentUrl);
|
|
29
28
|
// Already seen this page path — no new work
|
|
30
|
-
if (
|
|
29
|
+
if (hasCapturedPagePath(relativePath)) {
|
|
31
30
|
return false;
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const html = await adapter.content();
|
|
38
|
-
const items = await extractActionableItems(adapter);
|
|
39
|
-
const hashes = await computeHashes(html, items);
|
|
40
|
-
const newState = await api.createPageState({
|
|
41
|
-
pageId: page.id,
|
|
42
|
-
sizeClass: config.sizeClass,
|
|
43
|
-
hashes,
|
|
44
|
-
contentText: html.slice(0, 5000),
|
|
45
|
-
createdByTestRunId: testRunId,
|
|
46
|
-
});
|
|
47
|
-
events.onPageStateCreated({
|
|
48
|
-
pageStateId: newState.id,
|
|
49
|
-
pageId: page.id,
|
|
50
|
-
});
|
|
51
|
-
const job = await api.createDecompositionJob(config.scanId, newState.id);
|
|
52
|
-
events.onDecompositionJobCreated({
|
|
53
|
-
jobId: job.id,
|
|
54
|
-
pageStateId: newState.id,
|
|
32
|
+
const captureResult = await captureCurrentPage(adapter, config, api, events, {
|
|
33
|
+
testRunId,
|
|
34
|
+
markDiscovered: true,
|
|
35
|
+
createDecompositionJob: true,
|
|
55
36
|
});
|
|
37
|
+
if (!captureResult?.createdNewState) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
56
40
|
return true;
|
|
57
41
|
}
|
|
58
42
|
async function executeStoredAction(adapter, config, playwrightCode, fallbackPath) {
|
|
@@ -107,8 +91,8 @@ async function executeStoredAction(adapter, config, playwrightCode, fallbackPath
|
|
|
107
91
|
}
|
|
108
92
|
export async function executeTestCases(config, adapter, api, events, testCaseIds) {
|
|
109
93
|
// Seed the initial scan URL path so we don't re-discover it
|
|
110
|
-
const scanPath =
|
|
111
|
-
|
|
94
|
+
const scanPath = toRelativePath(config.scanUrl);
|
|
95
|
+
seedCapturedPagePath(scanPath);
|
|
112
96
|
// Only fetch and execute the specific test cases from this scan's decomposition
|
|
113
97
|
LOG(`Fetching test cases for runner ${config.runnerId}`);
|
|
114
98
|
const allTestCases = await api.getTestCasesByRunner(config.runnerId);
|
|
@@ -142,13 +126,13 @@ export async function executeTestCases(config, adapter, api, events, testCaseIds
|
|
|
142
126
|
if (tc.startingPath) {
|
|
143
127
|
const targetUrl = new URL(tc.startingPath, config.baseUrl).toString();
|
|
144
128
|
const currentUrl = await adapter.getUrl();
|
|
145
|
-
if (
|
|
129
|
+
if (toRelativePath(currentUrl) !== toRelativePath(targetUrl)) {
|
|
146
130
|
LOG(`Navigating from ${currentUrl} to ${targetUrl}`);
|
|
147
131
|
await adapter.goto(targetUrl, { waitUntil: "networkidle0" });
|
|
148
132
|
await sleep(POST_ACTION_DELAY_MS);
|
|
149
133
|
}
|
|
150
134
|
else {
|
|
151
|
-
LOG(`Already on ${
|
|
135
|
+
LOG(`Already on ${toRelativePath(currentUrl)} — skipping navigation`);
|
|
152
136
|
}
|
|
153
137
|
}
|
|
154
138
|
// Re-inject data-tmnc-id attributes into the DOM (they are transient
|
|
@@ -166,7 +150,7 @@ export async function executeTestCases(config, adapter, api, events, testCaseIds
|
|
|
166
150
|
}
|
|
167
151
|
const gotoUrl = new URL(action.path, config.baseUrl).toString();
|
|
168
152
|
const nowUrl = await adapter.getUrl();
|
|
169
|
-
if (
|
|
153
|
+
if (toRelativePath(nowUrl) !== toRelativePath(gotoUrl)) {
|
|
170
154
|
await adapter.goto(gotoUrl, { waitUntil: "networkidle0" });
|
|
171
155
|
await sleep(POST_ACTION_DELAY_MS);
|
|
172
156
|
}
|
|
@@ -190,6 +174,7 @@ export async function executeTestCases(config, adapter, api, events, testCaseIds
|
|
|
190
174
|
await sleep(POST_ACTION_DELAY_MS);
|
|
191
175
|
break;
|
|
192
176
|
case "select":
|
|
177
|
+
case "selectOption":
|
|
193
178
|
if (!action.path || action.value == null) {
|
|
194
179
|
throw new Error("Select test action requires selector path and value");
|
|
195
180
|
}
|