@yasserkhanorg/e2e-agents 0.3.2
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 +168 -0
- package/README.md +620 -0
- package/dist/agent/analysis.d.ts +62 -0
- package/dist/agent/analysis.d.ts.map +1 -0
- package/dist/agent/analysis.js +292 -0
- package/dist/agent/blast_radius.d.ts +4 -0
- package/dist/agent/blast_radius.d.ts.map +1 -0
- package/dist/agent/blast_radius.js +37 -0
- package/dist/agent/cache_utils.d.ts +38 -0
- package/dist/agent/cache_utils.d.ts.map +1 -0
- package/dist/agent/cache_utils.js +67 -0
- package/dist/agent/config.d.ts +148 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +640 -0
- package/dist/agent/dependency_graph.d.ts +14 -0
- package/dist/agent/dependency_graph.d.ts.map +1 -0
- package/dist/agent/dependency_graph.js +227 -0
- package/dist/agent/feedback.d.ts +55 -0
- package/dist/agent/feedback.d.ts.map +1 -0
- package/dist/agent/feedback.js +257 -0
- package/dist/agent/flags.d.ts +23 -0
- package/dist/agent/flags.d.ts.map +1 -0
- package/dist/agent/flags.js +171 -0
- package/dist/agent/flow_catalog.d.ts +25 -0
- package/dist/agent/flow_catalog.d.ts.map +1 -0
- package/dist/agent/flow_catalog.js +106 -0
- package/dist/agent/flow_mapping.d.ts +10 -0
- package/dist/agent/flow_mapping.d.ts.map +1 -0
- package/dist/agent/flow_mapping.js +84 -0
- package/dist/agent/framework.d.ts +13 -0
- package/dist/agent/framework.d.ts.map +1 -0
- package/dist/agent/framework.js +149 -0
- package/dist/agent/gap_suggestions.d.ts +14 -0
- package/dist/agent/gap_suggestions.d.ts.map +1 -0
- package/dist/agent/gap_suggestions.js +101 -0
- package/dist/agent/generator.d.ts +10 -0
- package/dist/agent/generator.d.ts.map +1 -0
- package/dist/agent/generator.js +115 -0
- package/dist/agent/git.d.ts +11 -0
- package/dist/agent/git.d.ts.map +1 -0
- package/dist/agent/git.js +90 -0
- package/dist/agent/handoff.d.ts +22 -0
- package/dist/agent/handoff.d.ts.map +1 -0
- package/dist/agent/handoff.js +180 -0
- package/dist/agent/impact-analyzer.d.ts +114 -0
- package/dist/agent/impact-analyzer.d.ts.map +1 -0
- package/dist/agent/impact-analyzer.js +557 -0
- package/dist/agent/index.d.ts +21 -0
- package/dist/agent/index.d.ts.map +1 -0
- package/dist/agent/index.js +38 -0
- package/dist/agent/model-router.d.ts +57 -0
- package/dist/agent/model-router.d.ts.map +1 -0
- package/dist/agent/model-router.js +154 -0
- package/dist/agent/operational_insights.d.ts +41 -0
- package/dist/agent/operational_insights.d.ts.map +1 -0
- package/dist/agent/operational_insights.js +126 -0
- package/dist/agent/pipeline.d.ts +23 -0
- package/dist/agent/pipeline.d.ts.map +1 -0
- package/dist/agent/pipeline.js +609 -0
- package/dist/agent/plan.d.ts +91 -0
- package/dist/agent/plan.d.ts.map +1 -0
- package/dist/agent/plan.js +331 -0
- package/dist/agent/playwright_report.d.ts +8 -0
- package/dist/agent/playwright_report.d.ts.map +1 -0
- package/dist/agent/playwright_report.js +126 -0
- package/dist/agent/report-generator.d.ts +24 -0
- package/dist/agent/report-generator.d.ts.map +1 -0
- package/dist/agent/report-generator.js +250 -0
- package/dist/agent/report.d.ts +81 -0
- package/dist/agent/report.d.ts.map +1 -0
- package/dist/agent/report.js +147 -0
- package/dist/agent/runner.d.ts +7 -0
- package/dist/agent/runner.d.ts.map +1 -0
- package/dist/agent/runner.js +576 -0
- package/dist/agent/selectors.d.ts +10 -0
- package/dist/agent/selectors.d.ts.map +1 -0
- package/dist/agent/selectors.js +75 -0
- package/dist/agent/spec-bridge.d.ts +101 -0
- package/dist/agent/spec-bridge.d.ts.map +1 -0
- package/dist/agent/spec-bridge.js +273 -0
- package/dist/agent/spec-builder.d.ts +102 -0
- package/dist/agent/spec-builder.d.ts.map +1 -0
- package/dist/agent/spec-builder.js +273 -0
- package/dist/agent/subsystem_risk.d.ts +23 -0
- package/dist/agent/subsystem_risk.d.ts.map +1 -0
- package/dist/agent/subsystem_risk.js +207 -0
- package/dist/agent/telemetry.d.ts +84 -0
- package/dist/agent/telemetry.d.ts.map +1 -0
- package/dist/agent/telemetry.js +220 -0
- package/dist/agent/test_path.d.ts +2 -0
- package/dist/agent/test_path.d.ts.map +1 -0
- package/dist/agent/test_path.js +23 -0
- package/dist/agent/tests.d.ts +18 -0
- package/dist/agent/tests.d.ts.map +1 -0
- package/dist/agent/tests.js +106 -0
- package/dist/agent/traceability.d.ts +22 -0
- package/dist/agent/traceability.d.ts.map +1 -0
- package/dist/agent/traceability.js +183 -0
- package/dist/agent/traceability_capture.d.ts +18 -0
- package/dist/agent/traceability_capture.d.ts.map +1 -0
- package/dist/agent/traceability_capture.js +313 -0
- package/dist/agent/traceability_ingest.d.ts +21 -0
- package/dist/agent/traceability_ingest.d.ts.map +1 -0
- package/dist/agent/traceability_ingest.js +237 -0
- package/dist/agent/utils.d.ts +13 -0
- package/dist/agent/utils.d.ts.map +1 -0
- package/dist/agent/utils.js +152 -0
- package/dist/agent/validators/selector-validator.d.ts +74 -0
- package/dist/agent/validators/selector-validator.d.ts.map +1 -0
- package/dist/agent/validators/selector-validator.js +165 -0
- package/dist/anthropic_provider.d.ts +65 -0
- package/dist/anthropic_provider.d.ts.map +1 -0
- package/dist/anthropic_provider.js +332 -0
- package/dist/api.d.ts +48 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +113 -0
- package/dist/base_provider.d.ts +53 -0
- package/dist/base_provider.d.ts.map +1 -0
- package/dist/base_provider.js +81 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +843 -0
- package/dist/custom_provider.d.ts +20 -0
- package/dist/custom_provider.d.ts.map +1 -0
- package/dist/custom_provider.js +276 -0
- package/dist/e2e-test-gen/index.d.ts +51 -0
- package/dist/e2e-test-gen/index.d.ts.map +1 -0
- package/dist/e2e-test-gen/index.js +57 -0
- package/dist/e2e-test-gen/spec_parser.d.ts +142 -0
- package/dist/e2e-test-gen/spec_parser.d.ts.map +1 -0
- package/dist/e2e-test-gen/spec_parser.js +786 -0
- package/dist/e2e-test-gen/types.d.ts +185 -0
- package/dist/e2e-test-gen/types.d.ts.map +1 -0
- package/dist/e2e-test-gen/types.js +4 -0
- package/dist/esm/agent/analysis.js +287 -0
- package/dist/esm/agent/blast_radius.js +34 -0
- package/dist/esm/agent/cache_utils.js +63 -0
- package/dist/esm/agent/config.js +637 -0
- package/dist/esm/agent/dependency_graph.js +224 -0
- package/dist/esm/agent/feedback.js +253 -0
- package/dist/esm/agent/flags.js +160 -0
- package/dist/esm/agent/flow_catalog.js +103 -0
- package/dist/esm/agent/flow_mapping.js +81 -0
- package/dist/esm/agent/framework.js +145 -0
- package/dist/esm/agent/gap_suggestions.js +98 -0
- package/dist/esm/agent/generator.js +112 -0
- package/dist/esm/agent/git.js +87 -0
- package/dist/esm/agent/handoff.js +177 -0
- package/dist/esm/agent/impact-analyzer.js +548 -0
- package/dist/esm/agent/index.js +22 -0
- package/dist/esm/agent/model-router.js +150 -0
- package/dist/esm/agent/operational_insights.js +123 -0
- package/dist/esm/agent/pipeline.js +605 -0
- package/dist/esm/agent/plan.js +324 -0
- package/dist/esm/agent/playwright_report.js +123 -0
- package/dist/esm/agent/report-generator.js +247 -0
- package/dist/esm/agent/report.js +144 -0
- package/dist/esm/agent/runner.js +572 -0
- package/dist/esm/agent/selectors.js +71 -0
- package/dist/esm/agent/spec-bridge.js +267 -0
- package/dist/esm/agent/spec-builder.js +267 -0
- package/dist/esm/agent/subsystem_risk.js +204 -0
- package/dist/esm/agent/telemetry.js +216 -0
- package/dist/esm/agent/test_path.js +20 -0
- package/dist/esm/agent/tests.js +101 -0
- package/dist/esm/agent/traceability.js +180 -0
- package/dist/esm/agent/traceability_capture.js +310 -0
- package/dist/esm/agent/traceability_ingest.js +234 -0
- package/dist/esm/agent/utils.js +138 -0
- package/dist/esm/agent/validators/selector-validator.js +160 -0
- package/dist/esm/anthropic_provider.js +324 -0
- package/dist/esm/api.js +105 -0
- package/dist/esm/base_provider.js +77 -0
- package/dist/esm/cli.js +841 -0
- package/dist/esm/custom_provider.js +272 -0
- package/dist/esm/e2e-test-gen/index.js +50 -0
- package/dist/esm/e2e-test-gen/spec_parser.js +782 -0
- package/dist/esm/e2e-test-gen/types.js +3 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/logger.js +89 -0
- package/dist/esm/mcp-server.js +465 -0
- package/dist/esm/ollama_provider.js +300 -0
- package/dist/esm/openai_provider.js +242 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/plan-and-test-constants.js +126 -0
- package/dist/esm/provider_factory.js +336 -0
- package/dist/esm/provider_interface.js +23 -0
- package/dist/esm/provider_utils.js +96 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/logger.d.ts +23 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +93 -0
- package/dist/mcp-server.d.ts +35 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +469 -0
- package/dist/ollama_provider.d.ts +65 -0
- package/dist/ollama_provider.d.ts.map +1 -0
- package/dist/ollama_provider.js +308 -0
- package/dist/openai_provider.d.ts +23 -0
- package/dist/openai_provider.d.ts.map +1 -0
- package/dist/openai_provider.js +250 -0
- package/dist/plan-and-test-constants.d.ts +110 -0
- package/dist/plan-and-test-constants.d.ts.map +1 -0
- package/dist/plan-and-test-constants.js +132 -0
- package/dist/provider_factory.d.ts +99 -0
- package/dist/provider_factory.d.ts.map +1 -0
- package/dist/provider_factory.js +341 -0
- package/dist/provider_interface.d.ts +358 -0
- package/dist/provider_interface.d.ts.map +1 -0
- package/dist/provider_interface.js +28 -0
- package/dist/provider_utils.d.ts +39 -0
- package/dist/provider_utils.d.ts.map +1 -0
- package/dist/provider_utils.js +103 -0
- package/package.json +101 -0
- package/schemas/gap.schema.json +18 -0
- package/schemas/impact.schema.json +418 -0
- package/schemas/plan.schema.json +285 -0
- package/schemas/subsystem-risk-map.schema.json +62 -0
- package/schemas/traceability-input.schema.json +122 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.runImpact = runImpact;
|
|
6
|
+
exports.runGap = runGap;
|
|
7
|
+
const fs_1 = require("fs");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const analysis_js_1 = require("./analysis.js");
|
|
10
|
+
const blast_radius_js_1 = require("./blast_radius.js");
|
|
11
|
+
const framework_js_1 = require("./framework.js");
|
|
12
|
+
const git_js_1 = require("./git.js");
|
|
13
|
+
const report_js_1 = require("./report.js");
|
|
14
|
+
const flags_js_1 = require("./flags.js");
|
|
15
|
+
const selectors_js_1 = require("./selectors.js");
|
|
16
|
+
const tests_js_1 = require("./tests.js");
|
|
17
|
+
const generator_js_1 = require("./generator.js");
|
|
18
|
+
const flow_catalog_js_1 = require("./flow_catalog.js");
|
|
19
|
+
const flow_mapping_js_1 = require("./flow_mapping.js");
|
|
20
|
+
const pipeline_js_1 = require("./pipeline.js");
|
|
21
|
+
const gap_suggestions_js_1 = require("./gap_suggestions.js");
|
|
22
|
+
const dependency_graph_js_1 = require("./dependency_graph.js");
|
|
23
|
+
const traceability_js_1 = require("./traceability.js");
|
|
24
|
+
const PRIORITY_RANK = {
|
|
25
|
+
P0: 0,
|
|
26
|
+
P1: 1,
|
|
27
|
+
P2: 2,
|
|
28
|
+
};
|
|
29
|
+
function ensureAppRoot(path) {
|
|
30
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
31
|
+
throw new Error(`App path does not exist: ${path}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function computeGaps(flows, coverageMap) {
|
|
35
|
+
return flows.filter((flow) => {
|
|
36
|
+
if (flow.priority !== 'P0' && flow.priority !== 'P1') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const coveredBy = coverageMap.get(flow.id) || [];
|
|
40
|
+
return coveredBy.length === 0;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function normalizeChangedFiles(appRoot, files) {
|
|
44
|
+
const normalizedRoot = appRoot.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
45
|
+
const baseName = normalizedRoot.split('/').pop() || '';
|
|
46
|
+
return files
|
|
47
|
+
.map((file) => file.replace(/\\/g, '/'))
|
|
48
|
+
.map((file) => {
|
|
49
|
+
if (baseName && file.startsWith(`${baseName}/`)) {
|
|
50
|
+
return file.slice(baseName.length + 1);
|
|
51
|
+
}
|
|
52
|
+
return file;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function sortFlows(flows) {
|
|
56
|
+
const priorityRank = { P0: 0, P1: 1, P2: 2 };
|
|
57
|
+
return [...flows].sort((a, b) => {
|
|
58
|
+
const rankDiff = (priorityRank[a.priority] ?? 3) - (priorityRank[b.priority] ?? 3);
|
|
59
|
+
if (rankDiff !== 0) {
|
|
60
|
+
return rankDiff;
|
|
61
|
+
}
|
|
62
|
+
return b.score - a.score;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function applyPriorityThresholds(flows, config) {
|
|
66
|
+
return flows.map((flow) => {
|
|
67
|
+
const priority = flow.score >= config.risk.p0Threshold
|
|
68
|
+
? 'P0'
|
|
69
|
+
: flow.score >= config.risk.p1Threshold
|
|
70
|
+
? 'P1'
|
|
71
|
+
: 'P2';
|
|
72
|
+
const boundedPriority = flow.priorityFloor && PRIORITY_RANK[flow.priorityFloor] < PRIORITY_RANK[priority]
|
|
73
|
+
? flow.priorityFloor
|
|
74
|
+
: priority;
|
|
75
|
+
return { ...flow, priority: boundedPriority };
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function buildRecommendedTestsWithFlags(flows, testsByFlow) {
|
|
79
|
+
const testNotes = new Map();
|
|
80
|
+
for (const flow of flows) {
|
|
81
|
+
if (flow.priority !== 'P0' && flow.priority !== 'P1') {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const tests = testsByFlow.get(flow.id) || [];
|
|
85
|
+
const flagSummary = (0, flags_js_1.formatFlags)(flow.flags || []);
|
|
86
|
+
for (const test of tests) {
|
|
87
|
+
if (!testNotes.has(test)) {
|
|
88
|
+
testNotes.set(test, new Set());
|
|
89
|
+
}
|
|
90
|
+
if (flagSummary !== 'none') {
|
|
91
|
+
testNotes.get(test)?.add(flagSummary);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return Array.from(testNotes.entries())
|
|
96
|
+
.map(([test, notes]) => {
|
|
97
|
+
if (notes.size === 0) {
|
|
98
|
+
return test;
|
|
99
|
+
}
|
|
100
|
+
return `${test} (flags: ${Array.from(notes).join(', ')})`;
|
|
101
|
+
})
|
|
102
|
+
.sort();
|
|
103
|
+
}
|
|
104
|
+
function buildRecommendedTestsFromCoverage(flows, coverage) {
|
|
105
|
+
const flowMap = new Map();
|
|
106
|
+
for (const flow of flows) {
|
|
107
|
+
flowMap.set(flow.id, flow);
|
|
108
|
+
}
|
|
109
|
+
const testNotes = new Map();
|
|
110
|
+
for (const entry of coverage) {
|
|
111
|
+
if (entry.priority !== 'P0' && entry.priority !== 'P1') {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const flow = flowMap.get(entry.flowId);
|
|
115
|
+
const flagSummary = (0, flags_js_1.formatFlags)(flow?.flags || []);
|
|
116
|
+
for (const test of entry.coveredBy) {
|
|
117
|
+
if (!testNotes.has(test)) {
|
|
118
|
+
testNotes.set(test, new Set());
|
|
119
|
+
}
|
|
120
|
+
if (flagSummary !== 'none') {
|
|
121
|
+
testNotes.get(test)?.add(flagSummary);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return Array.from(testNotes.entries())
|
|
126
|
+
.map(([test, notes]) => {
|
|
127
|
+
if (notes.size === 0) {
|
|
128
|
+
return test;
|
|
129
|
+
}
|
|
130
|
+
return `${test} (flags: ${Array.from(notes).join(', ')})`;
|
|
131
|
+
})
|
|
132
|
+
.sort();
|
|
133
|
+
}
|
|
134
|
+
function uniquePaths(paths) {
|
|
135
|
+
return Array.from(new Set(paths.map((value) => value.replace(/\\/g, '/')).filter(Boolean)));
|
|
136
|
+
}
|
|
137
|
+
function mergeCoverageWithHeuristicFallback(traceability, heuristic) {
|
|
138
|
+
const byFlow = new Map();
|
|
139
|
+
for (const entry of traceability) {
|
|
140
|
+
byFlow.set(entry.flowId, entry);
|
|
141
|
+
}
|
|
142
|
+
for (const entry of heuristic) {
|
|
143
|
+
const existing = byFlow.get(entry.flowId);
|
|
144
|
+
if (!existing) {
|
|
145
|
+
byFlow.set(entry.flowId, entry);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (existing.coveredBy.length === 0 && entry.coveredBy.length > 0) {
|
|
149
|
+
byFlow.set(entry.flowId, entry);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return Array.from(byFlow.values());
|
|
153
|
+
}
|
|
154
|
+
function classifyImpactModelConfidence(flowMapping, testMapping, dependencyGraph, traceability, warnings) {
|
|
155
|
+
let score = 0;
|
|
156
|
+
if (flowMapping === 'catalog') {
|
|
157
|
+
score += 2;
|
|
158
|
+
}
|
|
159
|
+
if (testMapping === 'catalog') {
|
|
160
|
+
score += 2;
|
|
161
|
+
}
|
|
162
|
+
else if (testMapping === 'traceability') {
|
|
163
|
+
score += 3;
|
|
164
|
+
}
|
|
165
|
+
if (traceability) {
|
|
166
|
+
if (!traceability.manifestFound) {
|
|
167
|
+
score -= 1;
|
|
168
|
+
}
|
|
169
|
+
else if (traceability.coverageRatio >= 0.7) {
|
|
170
|
+
score += 1;
|
|
171
|
+
}
|
|
172
|
+
else if (traceability.coverageRatio < 0.4) {
|
|
173
|
+
score -= 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (dependencyGraph && dependencyGraph.expandedFiles.length > 0) {
|
|
177
|
+
score += 1;
|
|
178
|
+
}
|
|
179
|
+
if (dependencyGraph && dependencyGraph.truncated) {
|
|
180
|
+
score -= 1;
|
|
181
|
+
}
|
|
182
|
+
if (warnings.length > 0) {
|
|
183
|
+
score -= 1;
|
|
184
|
+
}
|
|
185
|
+
if (score >= 5) {
|
|
186
|
+
return 'high';
|
|
187
|
+
}
|
|
188
|
+
if (score >= 3) {
|
|
189
|
+
return 'medium';
|
|
190
|
+
}
|
|
191
|
+
return 'low';
|
|
192
|
+
}
|
|
193
|
+
async function runImpact(_config, _options) {
|
|
194
|
+
ensureAppRoot(_config.path);
|
|
195
|
+
if (_config.testsRoot) {
|
|
196
|
+
ensureAppRoot(_config.testsRoot);
|
|
197
|
+
}
|
|
198
|
+
const deadline = Date.now() + _config.timeLimitMinutes * 60 * 1000;
|
|
199
|
+
const warnings = [];
|
|
200
|
+
const testsRoot = _config.testsRoot || _config.path;
|
|
201
|
+
const frameworkDetection = (0, framework_js_1.detectFramework)(testsRoot, _config.framework);
|
|
202
|
+
const testPatterns = (0, framework_js_1.resolveTestPatterns)(testsRoot, frameworkDetection, _config.testDiscovery.patterns);
|
|
203
|
+
if (frameworkDetection.framework === 'unknown' && testPatterns.patterns.length === 0) {
|
|
204
|
+
throw new Error('No framework config found. Provide testDiscovery.patterns in config or --patterns.');
|
|
205
|
+
}
|
|
206
|
+
const gitResult = (0, git_js_1.getChangedFiles)(_config.path, _config.git.since, {
|
|
207
|
+
includeUncommitted: _config.git.includeUncommitted,
|
|
208
|
+
});
|
|
209
|
+
const changedFiles = normalizeChangedFiles(_config.path, gitResult.files);
|
|
210
|
+
if (gitResult.error) {
|
|
211
|
+
warnings.push(`Git diff failed: ${gitResult.error}`);
|
|
212
|
+
}
|
|
213
|
+
if (changedFiles.length === 0 && !_config.impact.allowFallback) {
|
|
214
|
+
throw new Error('No changed files detected. Provide --since or use gap mode (or --allow-fallback).');
|
|
215
|
+
}
|
|
216
|
+
let analysisTargets = changedFiles.filter((file) => !(0, analysis_js_1.isTestFilePath)(file));
|
|
217
|
+
if (analysisTargets.length === 0 && _config.impact.allowFallback) {
|
|
218
|
+
warnings.push('No changed files detected. Falling back to repository scan for screens.');
|
|
219
|
+
analysisTargets = (0, analysis_js_1.scanRepositoryFlows)(_config.path, 250, _config.flowDiscovery.patterns, _config.flowDiscovery.exclude);
|
|
220
|
+
}
|
|
221
|
+
let dependencyGraph;
|
|
222
|
+
if (analysisTargets.length > 0 && _config.impact.dependencyGraph.enabled) {
|
|
223
|
+
dependencyGraph = (0, dependency_graph_js_1.expandByDependencyGraph)(_config.path, analysisTargets, _config.impact.dependencyGraph);
|
|
224
|
+
warnings.push(...dependencyGraph.warnings);
|
|
225
|
+
if (dependencyGraph.expandedFiles.length > 0) {
|
|
226
|
+
analysisTargets = uniquePaths([...analysisTargets, ...dependencyGraph.expandedFiles]);
|
|
227
|
+
warnings.push(`Dependency graph expanded impacted files by ${dependencyGraph.expandedFiles.length} (depth=${dependencyGraph.maxDepth}).`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const analysis = (0, analysis_js_1.analyzeFiles)(_config.path, analysisTargets, _config);
|
|
231
|
+
warnings.push(...analysis.warnings);
|
|
232
|
+
if (Date.now() > deadline) {
|
|
233
|
+
warnings.push('Time limit exceeded after impact analysis. Skipping coverage and selector steps.');
|
|
234
|
+
}
|
|
235
|
+
let coverage = [];
|
|
236
|
+
let gaps = [];
|
|
237
|
+
let dataTestIds = [];
|
|
238
|
+
let flows = [];
|
|
239
|
+
let flowCatalogSource;
|
|
240
|
+
let recommendedTests = [];
|
|
241
|
+
let testsByFlow;
|
|
242
|
+
let testSuggestions = [];
|
|
243
|
+
const catalog = (0, flow_catalog_js_1.loadFlowCatalog)(_config);
|
|
244
|
+
const flowMappingSource = catalog ? 'catalog' : 'heuristic';
|
|
245
|
+
let testMappingSource = 'heuristic';
|
|
246
|
+
let traceabilityStats;
|
|
247
|
+
if (catalog) {
|
|
248
|
+
flowCatalogSource = catalog.source;
|
|
249
|
+
const mapping = (0, flow_mapping_js_1.mapChangesToCatalogFlows)(catalog, analysisTargets, 'impact', _config);
|
|
250
|
+
flows = mapping.flows;
|
|
251
|
+
testsByFlow = mapping.testsByFlow;
|
|
252
|
+
warnings.push(...mapping.warnings);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
flows = analysis.flows;
|
|
256
|
+
}
|
|
257
|
+
flows = (0, blast_radius_js_1.applyBlastRadius)(flows, analysis.files, _config);
|
|
258
|
+
if (!catalog) {
|
|
259
|
+
flows = applyPriorityThresholds(flows, _config);
|
|
260
|
+
}
|
|
261
|
+
if (Date.now() <= deadline) {
|
|
262
|
+
if (catalog && testsByFlow) {
|
|
263
|
+
coverage = (0, tests_js_1.mapCatalogTestsToFlows)(flows, testsRoot, testsByFlow);
|
|
264
|
+
testMappingSource = 'catalog';
|
|
265
|
+
const coverageMap = new Map();
|
|
266
|
+
for (const entry of coverage) {
|
|
267
|
+
coverageMap.set(entry.flowId, entry.coveredBy);
|
|
268
|
+
}
|
|
269
|
+
gaps = computeGaps(flows, coverageMap);
|
|
270
|
+
recommendedTests = buildRecommendedTestsWithFlags(flows, testsByFlow);
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const traceability = (0, traceability_js_1.mapTraceabilityToFlows)(testsRoot, _config.impact.traceability, flows);
|
|
274
|
+
warnings.push(...traceability.warnings);
|
|
275
|
+
traceabilityStats = traceability.stats;
|
|
276
|
+
if (traceability.stats.manifestFound && traceability.stats.matchedFlows > 0) {
|
|
277
|
+
coverage = traceability.coverage;
|
|
278
|
+
testMappingSource = 'traceability';
|
|
279
|
+
if (traceability.stats.coverageRatio < 0.8) {
|
|
280
|
+
const tests = (0, tests_js_1.discoverTests)(testsRoot, testPatterns.patterns);
|
|
281
|
+
const heuristicCoverage = (0, tests_js_1.mapTestsToFlows)(flows, tests);
|
|
282
|
+
coverage = mergeCoverageWithHeuristicFallback(coverage, heuristicCoverage);
|
|
283
|
+
warnings.push('Applied heuristic fallback for flows not covered by traceability mapping.');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
const tests = (0, tests_js_1.discoverTests)(testsRoot, testPatterns.patterns);
|
|
288
|
+
coverage = (0, tests_js_1.mapTestsToFlows)(flows, tests);
|
|
289
|
+
}
|
|
290
|
+
const coverageMap = new Map();
|
|
291
|
+
for (const entry of coverage) {
|
|
292
|
+
coverageMap.set(entry.flowId, entry.coveredBy);
|
|
293
|
+
}
|
|
294
|
+
gaps = computeGaps(flows, coverageMap);
|
|
295
|
+
recommendedTests = buildRecommendedTestsFromCoverage(flows, coverage);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (Date.now() <= deadline) {
|
|
299
|
+
testSuggestions = (0, gap_suggestions_js_1.buildGapTestSuggestions)(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
|
|
300
|
+
}
|
|
301
|
+
if (Date.now() <= deadline) {
|
|
302
|
+
dataTestIds = analysis.files
|
|
303
|
+
.filter((file) => file.isUI && file.content)
|
|
304
|
+
.flatMap((file) => (0, selectors_js_1.findDataTestIdSuggestions)(file.relativePath, file.content, file.flowId));
|
|
305
|
+
}
|
|
306
|
+
if (_config.specPDF) {
|
|
307
|
+
warnings.push('Spec PDF provided but parsing is not implemented in v1.');
|
|
308
|
+
}
|
|
309
|
+
const applied = _options.apply && Date.now() <= deadline
|
|
310
|
+
? applyChanges(_config, analysis.files, dataTestIds, gaps, frameworkDetection.framework, testPatterns.patterns)
|
|
311
|
+
: undefined;
|
|
312
|
+
let pipelineSummary;
|
|
313
|
+
if (_config.pipeline.enabled && frameworkDetection.framework === 'playwright' && gaps.length > 0) {
|
|
314
|
+
pipelineSummary = (0, pipeline_js_1.runPlaywrightPipeline)(testsRoot, gaps, _config.pipeline);
|
|
315
|
+
if (pipelineSummary.warnings.length > 0) {
|
|
316
|
+
warnings.push(...pipelineSummary.warnings);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
const reportRoot = testsRoot;
|
|
320
|
+
const report = (0, report_js_1.writeReport)(reportRoot, _config, {
|
|
321
|
+
mode: 'impact',
|
|
322
|
+
changedFiles,
|
|
323
|
+
flows: sortFlows(flows),
|
|
324
|
+
coverage,
|
|
325
|
+
gaps,
|
|
326
|
+
dataTestIds,
|
|
327
|
+
framework: frameworkDetection.framework,
|
|
328
|
+
testPatterns: testPatterns.patterns,
|
|
329
|
+
specPDF: _config.specPDF,
|
|
330
|
+
warnings,
|
|
331
|
+
flowCatalog: flowCatalogSource,
|
|
332
|
+
recommendedTests,
|
|
333
|
+
impactModel: {
|
|
334
|
+
schemaVersion: '1.0.0',
|
|
335
|
+
flowMapping: flowMappingSource,
|
|
336
|
+
testMapping: testMappingSource,
|
|
337
|
+
confidenceClass: classifyImpactModelConfidence(flowMappingSource, testMappingSource, dependencyGraph, traceabilityStats, warnings),
|
|
338
|
+
traceability: traceabilityStats,
|
|
339
|
+
dependencyGraph: dependencyGraph
|
|
340
|
+
? {
|
|
341
|
+
source: dependencyGraph.source,
|
|
342
|
+
enabled: _config.impact.dependencyGraph.enabled,
|
|
343
|
+
seedFiles: dependencyGraph.seedFiles.length,
|
|
344
|
+
expandedFiles: dependencyGraph.expandedFiles.length,
|
|
345
|
+
analyzedFiles: dependencyGraph.analyzedFiles,
|
|
346
|
+
analyzedEdges: dependencyGraph.analyzedEdges,
|
|
347
|
+
maxDepth: dependencyGraph.maxDepth,
|
|
348
|
+
truncated: dependencyGraph.truncated,
|
|
349
|
+
}
|
|
350
|
+
: undefined,
|
|
351
|
+
subsystemRisk: analysis.subsystemRisk.enabled ? analysis.subsystemRisk : undefined,
|
|
352
|
+
},
|
|
353
|
+
testSuggestions,
|
|
354
|
+
pipeline: pipelineSummary,
|
|
355
|
+
applied,
|
|
356
|
+
});
|
|
357
|
+
// eslint-disable-next-line no-console
|
|
358
|
+
console.log(`Impact report: ${report.markdownPath}`);
|
|
359
|
+
// eslint-disable-next-line no-console
|
|
360
|
+
console.log(`Impact data: ${report.jsonPath}`);
|
|
361
|
+
}
|
|
362
|
+
async function runGap(_config, _options) {
|
|
363
|
+
ensureAppRoot(_config.path);
|
|
364
|
+
if (_config.testsRoot) {
|
|
365
|
+
ensureAppRoot(_config.testsRoot);
|
|
366
|
+
}
|
|
367
|
+
const deadline = Date.now() + _config.timeLimitMinutes * 60 * 1000;
|
|
368
|
+
const warnings = [];
|
|
369
|
+
const testsRoot = _config.testsRoot || _config.path;
|
|
370
|
+
const frameworkDetection = (0, framework_js_1.detectFramework)(testsRoot, _config.framework);
|
|
371
|
+
const testPatterns = (0, framework_js_1.resolveTestPatterns)(testsRoot, frameworkDetection, _config.testDiscovery.patterns);
|
|
372
|
+
if (frameworkDetection.framework === 'unknown' && testPatterns.patterns.length === 0) {
|
|
373
|
+
throw new Error('No framework config found. Provide testDiscovery.patterns in config or --patterns.');
|
|
374
|
+
}
|
|
375
|
+
const gitResult = (0, git_js_1.getChangedFiles)(_config.path, _config.git.since, {
|
|
376
|
+
includeUncommitted: _config.git.includeUncommitted,
|
|
377
|
+
});
|
|
378
|
+
const changedFiles = normalizeChangedFiles(_config.path, gitResult.files);
|
|
379
|
+
if (gitResult.error) {
|
|
380
|
+
warnings.push(`Git diff failed: ${gitResult.error}`);
|
|
381
|
+
}
|
|
382
|
+
let analysisTargets = changedFiles.filter((file) => !(0, analysis_js_1.isTestFilePath)(file));
|
|
383
|
+
if (analysisTargets.length === 0) {
|
|
384
|
+
analysisTargets = (0, analysis_js_1.scanRepositoryFlows)(_config.path, 250, _config.flowDiscovery.patterns, _config.flowDiscovery.exclude);
|
|
385
|
+
}
|
|
386
|
+
if (analysisTargets.length === 0) {
|
|
387
|
+
warnings.push('No flow candidates found. Ensure pages/screens exist or provide changed files.');
|
|
388
|
+
}
|
|
389
|
+
let dependencyGraph;
|
|
390
|
+
if (analysisTargets.length > 0 && _config.impact.dependencyGraph.enabled) {
|
|
391
|
+
dependencyGraph = (0, dependency_graph_js_1.expandByDependencyGraph)(_config.path, analysisTargets, _config.impact.dependencyGraph);
|
|
392
|
+
warnings.push(...dependencyGraph.warnings);
|
|
393
|
+
if (dependencyGraph.expandedFiles.length > 0) {
|
|
394
|
+
analysisTargets = uniquePaths([...analysisTargets, ...dependencyGraph.expandedFiles]);
|
|
395
|
+
warnings.push(`Dependency graph expanded impacted files by ${dependencyGraph.expandedFiles.length} (depth=${dependencyGraph.maxDepth}).`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const analysis = (0, analysis_js_1.analyzeFiles)(_config.path, analysisTargets, _config);
|
|
399
|
+
warnings.push(...analysis.warnings);
|
|
400
|
+
if (Date.now() > deadline) {
|
|
401
|
+
warnings.push('Time limit exceeded after gap analysis. Skipping coverage and selector steps.');
|
|
402
|
+
}
|
|
403
|
+
let coverage = [];
|
|
404
|
+
let gaps = [];
|
|
405
|
+
let dataTestIds = [];
|
|
406
|
+
let flows = [];
|
|
407
|
+
let flowCatalogSource;
|
|
408
|
+
let recommendedTests = [];
|
|
409
|
+
let testsByFlow;
|
|
410
|
+
let testSuggestions = [];
|
|
411
|
+
const catalog = (0, flow_catalog_js_1.loadFlowCatalog)(_config);
|
|
412
|
+
const flowMappingSource = catalog ? 'catalog' : 'heuristic';
|
|
413
|
+
let testMappingSource = 'heuristic';
|
|
414
|
+
let traceabilityStats;
|
|
415
|
+
if (catalog) {
|
|
416
|
+
flowCatalogSource = catalog.source;
|
|
417
|
+
const mapping = (0, flow_mapping_js_1.mapChangesToCatalogFlows)(catalog, analysisTargets, 'gap', _config);
|
|
418
|
+
flows = mapping.flows;
|
|
419
|
+
testsByFlow = mapping.testsByFlow;
|
|
420
|
+
warnings.push(...mapping.warnings);
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
flows = analysis.flows;
|
|
424
|
+
}
|
|
425
|
+
flows = (0, blast_radius_js_1.applyBlastRadius)(flows, analysis.files, _config);
|
|
426
|
+
if (!catalog) {
|
|
427
|
+
flows = applyPriorityThresholds(flows, _config);
|
|
428
|
+
}
|
|
429
|
+
if (Date.now() <= deadline) {
|
|
430
|
+
if (catalog && testsByFlow) {
|
|
431
|
+
coverage = (0, tests_js_1.mapCatalogTestsToFlows)(flows, testsRoot, testsByFlow);
|
|
432
|
+
testMappingSource = 'catalog';
|
|
433
|
+
const coverageMap = new Map();
|
|
434
|
+
for (const entry of coverage) {
|
|
435
|
+
coverageMap.set(entry.flowId, entry.coveredBy);
|
|
436
|
+
}
|
|
437
|
+
gaps = computeGaps(flows, coverageMap);
|
|
438
|
+
recommendedTests = buildRecommendedTestsWithFlags(flows, testsByFlow);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
const traceability = (0, traceability_js_1.mapTraceabilityToFlows)(testsRoot, _config.impact.traceability, flows);
|
|
442
|
+
warnings.push(...traceability.warnings);
|
|
443
|
+
traceabilityStats = traceability.stats;
|
|
444
|
+
if (traceability.stats.manifestFound && traceability.stats.matchedFlows > 0) {
|
|
445
|
+
coverage = traceability.coverage;
|
|
446
|
+
testMappingSource = 'traceability';
|
|
447
|
+
if (traceability.stats.coverageRatio < 0.8) {
|
|
448
|
+
const tests = (0, tests_js_1.discoverTests)(testsRoot, testPatterns.patterns);
|
|
449
|
+
const heuristicCoverage = (0, tests_js_1.mapTestsToFlows)(flows, tests);
|
|
450
|
+
coverage = mergeCoverageWithHeuristicFallback(coverage, heuristicCoverage);
|
|
451
|
+
warnings.push('Applied heuristic fallback for flows not covered by traceability mapping.');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
const tests = (0, tests_js_1.discoverTests)(testsRoot, testPatterns.patterns);
|
|
456
|
+
coverage = (0, tests_js_1.mapTestsToFlows)(flows, tests);
|
|
457
|
+
}
|
|
458
|
+
const coverageMap = new Map();
|
|
459
|
+
for (const entry of coverage) {
|
|
460
|
+
coverageMap.set(entry.flowId, entry.coveredBy);
|
|
461
|
+
}
|
|
462
|
+
gaps = computeGaps(flows, coverageMap);
|
|
463
|
+
recommendedTests = buildRecommendedTestsFromCoverage(flows, coverage);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (Date.now() <= deadline) {
|
|
467
|
+
testSuggestions = (0, gap_suggestions_js_1.buildGapTestSuggestions)(testsRoot, gaps, frameworkDetection.framework, testPatterns.patterns);
|
|
468
|
+
}
|
|
469
|
+
if (Date.now() <= deadline) {
|
|
470
|
+
dataTestIds = analysis.files
|
|
471
|
+
.filter((file) => file.isUI && file.content)
|
|
472
|
+
.flatMap((file) => (0, selectors_js_1.findDataTestIdSuggestions)(file.relativePath, file.content, file.flowId));
|
|
473
|
+
}
|
|
474
|
+
if (_config.specPDF) {
|
|
475
|
+
warnings.push('Spec PDF provided but parsing is not implemented in v1.');
|
|
476
|
+
}
|
|
477
|
+
const applied = _options.apply && Date.now() <= deadline
|
|
478
|
+
? applyChanges(_config, analysis.files, dataTestIds, gaps, frameworkDetection.framework, testPatterns.patterns)
|
|
479
|
+
: undefined;
|
|
480
|
+
let pipelineSummary;
|
|
481
|
+
if (_config.pipeline.enabled && frameworkDetection.framework === 'playwright' && gaps.length > 0) {
|
|
482
|
+
pipelineSummary = (0, pipeline_js_1.runPlaywrightPipeline)(testsRoot, gaps, _config.pipeline);
|
|
483
|
+
if (pipelineSummary.warnings.length > 0) {
|
|
484
|
+
warnings.push(...pipelineSummary.warnings);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
const reportRoot = testsRoot;
|
|
488
|
+
const report = (0, report_js_1.writeReport)(reportRoot, _config, {
|
|
489
|
+
mode: 'gap',
|
|
490
|
+
changedFiles,
|
|
491
|
+
flows: sortFlows(flows),
|
|
492
|
+
coverage,
|
|
493
|
+
gaps,
|
|
494
|
+
dataTestIds,
|
|
495
|
+
framework: frameworkDetection.framework,
|
|
496
|
+
testPatterns: testPatterns.patterns,
|
|
497
|
+
specPDF: _config.specPDF,
|
|
498
|
+
warnings,
|
|
499
|
+
flowCatalog: flowCatalogSource,
|
|
500
|
+
recommendedTests,
|
|
501
|
+
impactModel: {
|
|
502
|
+
schemaVersion: '1.0.0',
|
|
503
|
+
flowMapping: flowMappingSource,
|
|
504
|
+
testMapping: testMappingSource,
|
|
505
|
+
confidenceClass: classifyImpactModelConfidence(flowMappingSource, testMappingSource, dependencyGraph, traceabilityStats, warnings),
|
|
506
|
+
traceability: traceabilityStats,
|
|
507
|
+
dependencyGraph: dependencyGraph
|
|
508
|
+
? {
|
|
509
|
+
source: dependencyGraph.source,
|
|
510
|
+
enabled: _config.impact.dependencyGraph.enabled,
|
|
511
|
+
seedFiles: dependencyGraph.seedFiles.length,
|
|
512
|
+
expandedFiles: dependencyGraph.expandedFiles.length,
|
|
513
|
+
analyzedFiles: dependencyGraph.analyzedFiles,
|
|
514
|
+
analyzedEdges: dependencyGraph.analyzedEdges,
|
|
515
|
+
maxDepth: dependencyGraph.maxDepth,
|
|
516
|
+
truncated: dependencyGraph.truncated,
|
|
517
|
+
}
|
|
518
|
+
: undefined,
|
|
519
|
+
subsystemRisk: analysis.subsystemRisk.enabled ? analysis.subsystemRisk : undefined,
|
|
520
|
+
},
|
|
521
|
+
testSuggestions,
|
|
522
|
+
pipeline: pipelineSummary,
|
|
523
|
+
applied,
|
|
524
|
+
});
|
|
525
|
+
// eslint-disable-next-line no-console
|
|
526
|
+
console.log(`Gap report: ${report.markdownPath}`);
|
|
527
|
+
// eslint-disable-next-line no-console
|
|
528
|
+
console.log(`Gap data: ${report.jsonPath}`);
|
|
529
|
+
}
|
|
530
|
+
function applyChanges(config, files, dataTestIds, gaps, framework, testPatterns) {
|
|
531
|
+
const patchedFiles = [];
|
|
532
|
+
const suggestionsByFile = new Map();
|
|
533
|
+
for (const suggestion of dataTestIds) {
|
|
534
|
+
const bucket = suggestionsByFile.get(suggestion.file) || [];
|
|
535
|
+
bucket.push(suggestion);
|
|
536
|
+
suggestionsByFile.set(suggestion.file, bucket);
|
|
537
|
+
}
|
|
538
|
+
if (config.selectors.patchOnApply) {
|
|
539
|
+
for (const file of files) {
|
|
540
|
+
const suggestions = suggestionsByFile.get(file.relativePath);
|
|
541
|
+
if (!suggestions || !file.content) {
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
const updated = (0, selectors_js_1.applyDataTestIdSuggestions)(file.content, suggestions);
|
|
545
|
+
if (updated !== file.content) {
|
|
546
|
+
const fullPath = (0, path_1.join)(config.path, file.relativePath);
|
|
547
|
+
(0, fs_1.writeFileSync)(fullPath, updated, 'utf-8');
|
|
548
|
+
patchedFiles.push(file.relativePath);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const fileToFlow = new Map();
|
|
553
|
+
for (const file of files) {
|
|
554
|
+
fileToFlow.set(file.relativePath, file.flowId);
|
|
555
|
+
}
|
|
556
|
+
const testIdsByFlow = new Map();
|
|
557
|
+
for (const suggestion of dataTestIds) {
|
|
558
|
+
const flowId = fileToFlow.get(suggestion.file);
|
|
559
|
+
if (!flowId) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
const bucket = testIdsByFlow.get(flowId) || [];
|
|
563
|
+
bucket.push(suggestion.testId);
|
|
564
|
+
testIdsByFlow.set(flowId, bucket);
|
|
565
|
+
}
|
|
566
|
+
let generatedTests = [];
|
|
567
|
+
let skippedTests = [];
|
|
568
|
+
if (!config.pipeline.enabled) {
|
|
569
|
+
const frameworkType = framework === 'playwright' || framework === 'cypress' || framework === 'selenium' ? framework : 'playwright';
|
|
570
|
+
const testsRoot = config.testsRoot || config.path;
|
|
571
|
+
const generated = (0, generator_js_1.generateTests)(testsRoot, gaps, frameworkType, testPatterns, testIdsByFlow);
|
|
572
|
+
generatedTests = generated.filter((entry) => entry.created).map((entry) => entry.path);
|
|
573
|
+
skippedTests = generated.filter((entry) => !entry.created).map((entry) => entry.path);
|
|
574
|
+
}
|
|
575
|
+
return { patchedFiles, generatedTests, skippedTests };
|
|
576
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface DataTestIdSuggestion {
|
|
2
|
+
file: string;
|
|
3
|
+
line: number;
|
|
4
|
+
tag: string;
|
|
5
|
+
testId: string;
|
|
6
|
+
snippet: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function findDataTestIdSuggestions(relativePath: string, content: string | null, flowId: string): DataTestIdSuggestion[];
|
|
9
|
+
export declare function applyDataTestIdSuggestions(content: string, suggestions: DataTestIdSuggestion[]): string;
|
|
10
|
+
//# sourceMappingURL=selectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../../src/agent/selectors.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,oBAAoB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACnB;AAKD,wBAAgB,yBAAyB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,EAAE,MAAM,GACf,oBAAoB,EAAE,CA8CxB;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,oBAAoB,EAAE,GAAG,MAAM,CA6BvG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
3
|
+
// See LICENSE.txt for license information.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.findDataTestIdSuggestions = findDataTestIdSuggestions;
|
|
6
|
+
exports.applyDataTestIdSuggestions = applyDataTestIdSuggestions;
|
|
7
|
+
const utils_js_1 = require("./utils.js");
|
|
8
|
+
const INTERACTIVE_TAGS = ['button', 'input', 'select', 'textarea', 'form', 'a'];
|
|
9
|
+
const INTERACTIVE_HINT = /(onClick|onSubmit|onChange|type=['"]submit['"]|role=['"]button['"])/;
|
|
10
|
+
function findDataTestIdSuggestions(relativePath, content, flowId) {
|
|
11
|
+
if (!content) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
const suggestions = [];
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
let counter = 1;
|
|
17
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
const trimmed = line.trim();
|
|
20
|
+
if (!trimmed.startsWith('<')) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (trimmed.includes('data-testid')) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const tagMatch = trimmed.match(/^<([a-z][a-z0-9-]*)\b/);
|
|
27
|
+
if (!tagMatch) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const tag = tagMatch[1];
|
|
31
|
+
if (!INTERACTIVE_TAGS.includes(tag)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!INTERACTIVE_HINT.test(trimmed)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const testId = `${flowId}-${tag}-${counter}`;
|
|
38
|
+
counter += 1;
|
|
39
|
+
suggestions.push({
|
|
40
|
+
file: (0, utils_js_1.normalizePath)(relativePath),
|
|
41
|
+
line: i + 1,
|
|
42
|
+
tag,
|
|
43
|
+
testId,
|
|
44
|
+
snippet: trimmed.slice(0, 200),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return suggestions;
|
|
48
|
+
}
|
|
49
|
+
function applyDataTestIdSuggestions(content, suggestions) {
|
|
50
|
+
if (suggestions.length === 0) {
|
|
51
|
+
return content;
|
|
52
|
+
}
|
|
53
|
+
const lines = content.split('\n');
|
|
54
|
+
const suggestionsByLine = new Map();
|
|
55
|
+
for (const suggestion of suggestions) {
|
|
56
|
+
const bucket = suggestionsByLine.get(suggestion.line) || [];
|
|
57
|
+
bucket.push(suggestion);
|
|
58
|
+
suggestionsByLine.set(suggestion.line, bucket);
|
|
59
|
+
}
|
|
60
|
+
for (const [lineNumber, lineSuggestions] of suggestionsByLine.entries()) {
|
|
61
|
+
const index = lineNumber - 1;
|
|
62
|
+
if (index < 0 || index >= lines.length) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
let line = lines[index];
|
|
66
|
+
for (const suggestion of lineSuggestions) {
|
|
67
|
+
const pattern = new RegExp(`<${suggestion.tag}(\\s|>)`);
|
|
68
|
+
if (pattern.test(line) && !line.includes('data-testid')) {
|
|
69
|
+
line = line.replace(pattern, `<${suggestion.tag} data-testid="${suggestion.testId}"$1`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
lines[index] = line;
|
|
73
|
+
}
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|