@yasserkhanorg/e2e-agents 1.8.2 → 1.8.3
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/cli/commands/plan.d.ts.map +1 -1
- package/dist/cli/commands/plan.js +6 -2
- package/dist/cli/commands/plan_crew.d.ts +9 -3
- package/dist/cli/commands/plan_crew.d.ts.map +1 -1
- package/dist/cli/commands/plan_crew.js +136 -12
- package/dist/esm/cli/commands/plan.js +6 -2
- package/dist/esm/cli/commands/plan_crew.js +135 -12
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAMzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,uBAAuB,CAAC;AAMzD,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAE5C,wBAAsB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA+GxJ"}
|
|
@@ -53,6 +53,7 @@ async function runPlanCommand(args, autoConfig, config) {
|
|
|
53
53
|
let combinedSummaryMarkdown = ciSummaryMarkdown;
|
|
54
54
|
let crewSummaryPath = '';
|
|
55
55
|
let crewMarkdownPath = '';
|
|
56
|
+
let crewTestPlanPath = '';
|
|
56
57
|
if (args.crew) {
|
|
57
58
|
try {
|
|
58
59
|
const crew = await (0, plan_crew_js_1.runPlanCrewAnalysis)(plan, config, args);
|
|
@@ -60,10 +61,11 @@ async function runPlanCommand(args, autoConfig, config) {
|
|
|
60
61
|
...plan,
|
|
61
62
|
crew,
|
|
62
63
|
};
|
|
63
|
-
combinedSummaryMarkdown = (0, plan_crew_js_1.appendCrewToSummary)(ciSummaryMarkdown, crew);
|
|
64
|
-
const artifacts = (0, plan_crew_js_1.writeCrewArtifacts)(reportRoot, crew);
|
|
64
|
+
combinedSummaryMarkdown = (0, plan_crew_js_1.appendCrewToSummary)(ciSummaryMarkdown, crew, plan);
|
|
65
|
+
const artifacts = (0, plan_crew_js_1.writeCrewArtifacts)(reportRoot, crew, plan);
|
|
65
66
|
crewSummaryPath = artifacts.crewSummaryPath;
|
|
66
67
|
crewMarkdownPath = artifacts.crewMarkdownPath;
|
|
68
|
+
crewTestPlanPath = artifacts.crewTestPlanPath;
|
|
67
69
|
(0, fs_1.writeFileSync)(planPath, JSON.stringify(planReport, null, 2), 'utf-8');
|
|
68
70
|
}
|
|
69
71
|
catch (error) {
|
|
@@ -95,6 +97,7 @@ async function runPlanCommand(args, autoConfig, config) {
|
|
|
95
97
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_workflow=${planReport.crew?.workflow || ''}\n`);
|
|
96
98
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_summary_path=${crewSummaryPath}\n`);
|
|
97
99
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_markdown_path=${crewMarkdownPath}\n`);
|
|
100
|
+
(0, fs_1.appendFileSync)(ghaOutput, `crew_test_plan_path=${crewTestPlanPath}\n`);
|
|
98
101
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_impacted_flows=${planReport.crew?.summary.impactedFlows || 0}\n`);
|
|
99
102
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_strategy_entries=${planReport.crew?.summary.strategyEntries || 0}\n`);
|
|
100
103
|
(0, fs_1.appendFileSync)(ghaOutput, `crew_test_designs=${planReport.crew?.summary.testDesigns || 0}\n`);
|
|
@@ -107,6 +110,7 @@ async function runPlanCommand(args, autoConfig, config) {
|
|
|
107
110
|
if (planReport.crew) {
|
|
108
111
|
console.log(`Crew workflow: ${planReport.crew.workflow} (impactedFlows=${planReport.crew.summary.impactedFlows}, strategyEntries=${planReport.crew.summary.strategyEntries}, testDesigns=${planReport.crew.summary.testDesigns})`);
|
|
109
112
|
console.log(`Crew summary: ${crewSummaryPath}`);
|
|
113
|
+
console.log(`Crew test plan: ${crewTestPlanPath}`);
|
|
110
114
|
}
|
|
111
115
|
console.log(`Plan metrics: ${metricsSummaryPath}`);
|
|
112
116
|
const failOnLegacyFlag = args.failOnMustAddTests && planReport.decision.action === 'must-add-tests';
|
|
@@ -2,10 +2,16 @@ import type { AgentConfig } from '../../agent/config.js';
|
|
|
2
2
|
import type { CrewPlanInsights, PlanReport } from '../../agent/plan.js';
|
|
3
3
|
import type { ParsedArgs } from '../types.js';
|
|
4
4
|
export declare function runPlanCrewAnalysis(plan: PlanReport, config: AgentConfig, args: ParsedArgs): Promise<CrewPlanInsights>;
|
|
5
|
-
export declare function buildCrewMarkdown(crew: CrewPlanInsights): string;
|
|
6
|
-
export declare function appendCrewToSummary(baseMarkdown: string, crew: CrewPlanInsights): string;
|
|
7
|
-
|
|
5
|
+
export declare function buildCrewMarkdown(crew: CrewPlanInsights, plan?: PlanReport): string;
|
|
6
|
+
export declare function appendCrewToSummary(baseMarkdown: string, crew: CrewPlanInsights, plan?: PlanReport): string;
|
|
7
|
+
/**
|
|
8
|
+
* Build a full structured test plan as a Markdown document.
|
|
9
|
+
* Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildCrewTestPlan(crew: CrewPlanInsights, plan?: PlanReport): string;
|
|
12
|
+
export declare function writeCrewArtifacts(reportRoot: string, crew: CrewPlanInsights, plan?: PlanReport): {
|
|
8
13
|
crewSummaryPath: string;
|
|
9
14
|
crewMarkdownPath: string;
|
|
15
|
+
crewTestPlanPath: string;
|
|
10
16
|
};
|
|
11
17
|
//# sourceMappingURL=plan_crew.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plan_crew.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan_crew.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"plan_crew.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/plan_crew.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAC,gBAAgB,EAAE,UAAU,EAAC,MAAM,qBAAqB,CAAC;AAUtE,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,aAAa,CAAC;AAgC5C,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAkD5H;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAuEnF;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAE3G;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAuHnF;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAC,CAa/K"}
|
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
5
5
|
exports.runPlanCrewAnalysis = runPlanCrewAnalysis;
|
|
6
6
|
exports.buildCrewMarkdown = buildCrewMarkdown;
|
|
7
7
|
exports.appendCrewToSummary = appendCrewToSummary;
|
|
8
|
+
exports.buildCrewTestPlan = buildCrewTestPlan;
|
|
8
9
|
exports.writeCrewArtifacts = writeCrewArtifacts;
|
|
9
10
|
const fs_1 = require("fs");
|
|
10
11
|
const path_1 = require("path");
|
|
@@ -84,19 +85,25 @@ async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
84
85
|
timings: result.timings,
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
|
-
function buildCrewMarkdown(crew) {
|
|
88
|
+
function buildCrewMarkdown(crew, plan) {
|
|
89
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
90
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
91
|
+
const gapDesigns = gapFamilies.size > 0
|
|
92
|
+
? crew.testDesigns.filter((td) => Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' '))))
|
|
93
|
+
: [];
|
|
94
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
95
|
+
const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
|
|
88
96
|
const lines = [
|
|
89
97
|
'### Crew Insights',
|
|
90
98
|
'',
|
|
91
99
|
`Workflow: \`${crew.workflow}\``,
|
|
92
|
-
`Provider override: \`${crew.providerOverride}\``,
|
|
93
100
|
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
94
|
-
`
|
|
95
|
-
`Structured test designs: **${crew.summary.testDesigns}**`,
|
|
96
|
-
`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`,
|
|
97
|
-
`Findings: **${crew.summary.findings}**`,
|
|
98
|
-
`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`,
|
|
101
|
+
`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
|
|
99
102
|
];
|
|
103
|
+
if (gapFamilies.size > 0 && gapDesigns.length > 0) {
|
|
104
|
+
lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
|
|
105
|
+
}
|
|
106
|
+
lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
100
107
|
if (crew.strategyEntries.length > 0) {
|
|
101
108
|
lines.push('');
|
|
102
109
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -135,15 +142,132 @@ function buildCrewMarkdown(crew) {
|
|
|
135
142
|
}
|
|
136
143
|
return lines.join('\n');
|
|
137
144
|
}
|
|
138
|
-
function appendCrewToSummary(baseMarkdown, crew) {
|
|
139
|
-
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
|
|
145
|
+
function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
146
|
+
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Build a full structured test plan as a Markdown document.
|
|
150
|
+
* Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
|
|
151
|
+
*/
|
|
152
|
+
function buildCrewTestPlan(crew, plan) {
|
|
153
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
154
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
155
|
+
// Split designs into gap-related and coverage-expansion
|
|
156
|
+
const gapDesigns = [];
|
|
157
|
+
const coveredDesigns = [];
|
|
158
|
+
for (const td of crew.testDesigns) {
|
|
159
|
+
// Match by flowId prefix against gap family ids
|
|
160
|
+
const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
161
|
+
if (isGap) {
|
|
162
|
+
gapDesigns.push(td);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
coveredDesigns.push(td);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
169
|
+
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
170
|
+
const lines = [
|
|
171
|
+
'# Crew Test Plan',
|
|
172
|
+
'',
|
|
173
|
+
`> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
|
|
174
|
+
'',
|
|
175
|
+
'## Summary',
|
|
176
|
+
'',
|
|
177
|
+
`| Metric | Count |`,
|
|
178
|
+
`|--------|-------|`,
|
|
179
|
+
`| Gap flows (missing tests) | ${gapDesigns.length} flows, **${gapCases} test cases** |`,
|
|
180
|
+
`| Covered flows (expansion) | ${coveredDesigns.length} flows, ${coveredCases} test cases |`,
|
|
181
|
+
`| Total | ${crew.testDesigns.length} flows, ${totalCases} test cases |`,
|
|
182
|
+
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
183
|
+
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
184
|
+
'',
|
|
185
|
+
];
|
|
186
|
+
// Priority action items
|
|
187
|
+
if (gapDesigns.length > 0) {
|
|
188
|
+
lines.push('## Priority: Gap Flows (Missing Tests)');
|
|
189
|
+
lines.push('');
|
|
190
|
+
lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
|
|
191
|
+
lines.push('');
|
|
192
|
+
for (const td of gapDesigns) {
|
|
193
|
+
const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
|
|
194
|
+
const approach = strategy?.approach ?? 'full-test';
|
|
195
|
+
const risk = strategy?.crossImpactRisk ?? 'unknown';
|
|
196
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
197
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
198
|
+
lines.push(`### ${td.flowName}`);
|
|
199
|
+
lines.push('');
|
|
200
|
+
lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
|
|
201
|
+
lines.push('');
|
|
202
|
+
// Show P0 cases expanded
|
|
203
|
+
if (p0Cases.length > 0) {
|
|
204
|
+
lines.push('**P0 — Must test:**');
|
|
205
|
+
lines.push('');
|
|
206
|
+
for (const tc of p0Cases) {
|
|
207
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
208
|
+
if (tc.preconditions.length > 0) {
|
|
209
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
210
|
+
}
|
|
211
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
212
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
213
|
+
}
|
|
214
|
+
lines.push('');
|
|
215
|
+
}
|
|
216
|
+
// Show P1 as a collapsed checklist
|
|
217
|
+
if (p1Cases.length > 0) {
|
|
218
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
219
|
+
lines.push('');
|
|
220
|
+
for (const tc of p1Cases) {
|
|
221
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
222
|
+
}
|
|
223
|
+
lines.push('');
|
|
224
|
+
lines.push('</details>');
|
|
225
|
+
lines.push('');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Covered flow expansion — collapsed by default
|
|
230
|
+
if (coveredDesigns.length > 0) {
|
|
231
|
+
lines.push('## Covered Flows (Regression / Expansion)');
|
|
232
|
+
lines.push('');
|
|
233
|
+
lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
|
|
234
|
+
lines.push('');
|
|
235
|
+
for (const td of coveredDesigns) {
|
|
236
|
+
const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
|
|
237
|
+
const approach = strategy?.approach ?? 'smoke-test';
|
|
238
|
+
const p0Count = td.testCases.filter((tc) => tc.priority === 'P0').length;
|
|
239
|
+
lines.push(`<details><summary><strong>${td.flowName}</strong> — ${approach} | ${td.testCases.length} cases (${p0Count} P0)</summary>`);
|
|
240
|
+
lines.push('');
|
|
241
|
+
for (const tc of td.testCases) {
|
|
242
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
243
|
+
}
|
|
244
|
+
lines.push('');
|
|
245
|
+
lines.push('</details>');
|
|
246
|
+
lines.push('');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// Cross-impacts section
|
|
250
|
+
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
251
|
+
if (highRisk.length > 0) {
|
|
252
|
+
lines.push('## High-Risk Cross-Impacts');
|
|
253
|
+
lines.push('');
|
|
254
|
+
lines.push('These cross-family dependencies should be verified during release testing:');
|
|
255
|
+
lines.push('');
|
|
256
|
+
for (const ci of highRisk) {
|
|
257
|
+
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
258
|
+
}
|
|
259
|
+
lines.push('');
|
|
260
|
+
}
|
|
261
|
+
return lines.join('\n');
|
|
140
262
|
}
|
|
141
|
-
function writeCrewArtifacts(reportRoot, crew) {
|
|
263
|
+
function writeCrewArtifacts(reportRoot, crew, plan) {
|
|
142
264
|
const outputDir = (0, path_1.join)(reportRoot, '.e2e-ai-agents');
|
|
143
265
|
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
144
266
|
const crewSummaryPath = (0, path_1.join)(outputDir, 'crew-summary.json');
|
|
145
267
|
const crewMarkdownPath = (0, path_1.join)(outputDir, 'crew-summary.md');
|
|
268
|
+
const crewTestPlanPath = (0, path_1.join)(outputDir, 'crew-test-plan.md');
|
|
146
269
|
(0, fs_1.writeFileSync)(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
|
|
147
|
-
(0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
|
|
148
|
-
|
|
270
|
+
(0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
|
|
271
|
+
(0, fs_1.writeFileSync)(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
|
|
272
|
+
return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
|
|
149
273
|
}
|
|
@@ -50,6 +50,7 @@ export async function runPlanCommand(args, autoConfig, config) {
|
|
|
50
50
|
let combinedSummaryMarkdown = ciSummaryMarkdown;
|
|
51
51
|
let crewSummaryPath = '';
|
|
52
52
|
let crewMarkdownPath = '';
|
|
53
|
+
let crewTestPlanPath = '';
|
|
53
54
|
if (args.crew) {
|
|
54
55
|
try {
|
|
55
56
|
const crew = await runPlanCrewAnalysis(plan, config, args);
|
|
@@ -57,10 +58,11 @@ export async function runPlanCommand(args, autoConfig, config) {
|
|
|
57
58
|
...plan,
|
|
58
59
|
crew,
|
|
59
60
|
};
|
|
60
|
-
combinedSummaryMarkdown = appendCrewToSummary(ciSummaryMarkdown, crew);
|
|
61
|
-
const artifacts = writeCrewArtifacts(reportRoot, crew);
|
|
61
|
+
combinedSummaryMarkdown = appendCrewToSummary(ciSummaryMarkdown, crew, plan);
|
|
62
|
+
const artifacts = writeCrewArtifacts(reportRoot, crew, plan);
|
|
62
63
|
crewSummaryPath = artifacts.crewSummaryPath;
|
|
63
64
|
crewMarkdownPath = artifacts.crewMarkdownPath;
|
|
65
|
+
crewTestPlanPath = artifacts.crewTestPlanPath;
|
|
64
66
|
writeFileSync(planPath, JSON.stringify(planReport, null, 2), 'utf-8');
|
|
65
67
|
}
|
|
66
68
|
catch (error) {
|
|
@@ -92,6 +94,7 @@ export async function runPlanCommand(args, autoConfig, config) {
|
|
|
92
94
|
appendFileSync(ghaOutput, `crew_workflow=${planReport.crew?.workflow || ''}\n`);
|
|
93
95
|
appendFileSync(ghaOutput, `crew_summary_path=${crewSummaryPath}\n`);
|
|
94
96
|
appendFileSync(ghaOutput, `crew_markdown_path=${crewMarkdownPath}\n`);
|
|
97
|
+
appendFileSync(ghaOutput, `crew_test_plan_path=${crewTestPlanPath}\n`);
|
|
95
98
|
appendFileSync(ghaOutput, `crew_impacted_flows=${planReport.crew?.summary.impactedFlows || 0}\n`);
|
|
96
99
|
appendFileSync(ghaOutput, `crew_strategy_entries=${planReport.crew?.summary.strategyEntries || 0}\n`);
|
|
97
100
|
appendFileSync(ghaOutput, `crew_test_designs=${planReport.crew?.summary.testDesigns || 0}\n`);
|
|
@@ -104,6 +107,7 @@ export async function runPlanCommand(args, autoConfig, config) {
|
|
|
104
107
|
if (planReport.crew) {
|
|
105
108
|
console.log(`Crew workflow: ${planReport.crew.workflow} (impactedFlows=${planReport.crew.summary.impactedFlows}, strategyEntries=${planReport.crew.summary.strategyEntries}, testDesigns=${planReport.crew.summary.testDesigns})`);
|
|
106
109
|
console.log(`Crew summary: ${crewSummaryPath}`);
|
|
110
|
+
console.log(`Crew test plan: ${crewTestPlanPath}`);
|
|
107
111
|
}
|
|
108
112
|
console.log(`Plan metrics: ${metricsSummaryPath}`);
|
|
109
113
|
const failOnLegacyFlag = args.failOnMustAddTests && planReport.decision.action === 'must-add-tests';
|
|
@@ -78,19 +78,25 @@ export async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
78
78
|
timings: result.timings,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
-
export function buildCrewMarkdown(crew) {
|
|
81
|
+
export function buildCrewMarkdown(crew, plan) {
|
|
82
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
83
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
84
|
+
const gapDesigns = gapFamilies.size > 0
|
|
85
|
+
? crew.testDesigns.filter((td) => Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' '))))
|
|
86
|
+
: [];
|
|
87
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
88
|
+
const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
|
|
82
89
|
const lines = [
|
|
83
90
|
'### Crew Insights',
|
|
84
91
|
'',
|
|
85
92
|
`Workflow: \`${crew.workflow}\``,
|
|
86
|
-
`Provider override: \`${crew.providerOverride}\``,
|
|
87
93
|
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
88
|
-
`
|
|
89
|
-
`Structured test designs: **${crew.summary.testDesigns}**`,
|
|
90
|
-
`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`,
|
|
91
|
-
`Findings: **${crew.summary.findings}**`,
|
|
92
|
-
`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`,
|
|
94
|
+
`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
|
|
93
95
|
];
|
|
96
|
+
if (gapFamilies.size > 0 && gapDesigns.length > 0) {
|
|
97
|
+
lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
|
|
98
|
+
}
|
|
99
|
+
lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`, `Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
94
100
|
if (crew.strategyEntries.length > 0) {
|
|
95
101
|
lines.push('');
|
|
96
102
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -129,15 +135,132 @@ export function buildCrewMarkdown(crew) {
|
|
|
129
135
|
}
|
|
130
136
|
return lines.join('\n');
|
|
131
137
|
}
|
|
132
|
-
export function appendCrewToSummary(baseMarkdown, crew) {
|
|
133
|
-
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
|
|
138
|
+
export function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
139
|
+
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build a full structured test plan as a Markdown document.
|
|
143
|
+
* Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
|
|
144
|
+
*/
|
|
145
|
+
export function buildCrewTestPlan(crew, plan) {
|
|
146
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
147
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
148
|
+
// Split designs into gap-related and coverage-expansion
|
|
149
|
+
const gapDesigns = [];
|
|
150
|
+
const coveredDesigns = [];
|
|
151
|
+
for (const td of crew.testDesigns) {
|
|
152
|
+
// Match by flowId prefix against gap family ids
|
|
153
|
+
const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
154
|
+
if (isGap) {
|
|
155
|
+
gapDesigns.push(td);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
coveredDesigns.push(td);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
162
|
+
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
163
|
+
const lines = [
|
|
164
|
+
'# Crew Test Plan',
|
|
165
|
+
'',
|
|
166
|
+
`> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
|
|
167
|
+
'',
|
|
168
|
+
'## Summary',
|
|
169
|
+
'',
|
|
170
|
+
`| Metric | Count |`,
|
|
171
|
+
`|--------|-------|`,
|
|
172
|
+
`| Gap flows (missing tests) | ${gapDesigns.length} flows, **${gapCases} test cases** |`,
|
|
173
|
+
`| Covered flows (expansion) | ${coveredDesigns.length} flows, ${coveredCases} test cases |`,
|
|
174
|
+
`| Total | ${crew.testDesigns.length} flows, ${totalCases} test cases |`,
|
|
175
|
+
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
176
|
+
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
177
|
+
'',
|
|
178
|
+
];
|
|
179
|
+
// Priority action items
|
|
180
|
+
if (gapDesigns.length > 0) {
|
|
181
|
+
lines.push('## Priority: Gap Flows (Missing Tests)');
|
|
182
|
+
lines.push('');
|
|
183
|
+
lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
|
|
184
|
+
lines.push('');
|
|
185
|
+
for (const td of gapDesigns) {
|
|
186
|
+
const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
|
|
187
|
+
const approach = strategy?.approach ?? 'full-test';
|
|
188
|
+
const risk = strategy?.crossImpactRisk ?? 'unknown';
|
|
189
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
190
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
191
|
+
lines.push(`### ${td.flowName}`);
|
|
192
|
+
lines.push('');
|
|
193
|
+
lines.push(`Strategy: **${approach}** | Cross-impact risk: **${risk}** | ${td.testCases.length} cases (${p0Cases.length} P0, ${p1Cases.length} P1)`);
|
|
194
|
+
lines.push('');
|
|
195
|
+
// Show P0 cases expanded
|
|
196
|
+
if (p0Cases.length > 0) {
|
|
197
|
+
lines.push('**P0 — Must test:**');
|
|
198
|
+
lines.push('');
|
|
199
|
+
for (const tc of p0Cases) {
|
|
200
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
201
|
+
if (tc.preconditions.length > 0) {
|
|
202
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
203
|
+
}
|
|
204
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
205
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
206
|
+
}
|
|
207
|
+
lines.push('');
|
|
208
|
+
}
|
|
209
|
+
// Show P1 as a collapsed checklist
|
|
210
|
+
if (p1Cases.length > 0) {
|
|
211
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
212
|
+
lines.push('');
|
|
213
|
+
for (const tc of p1Cases) {
|
|
214
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
215
|
+
}
|
|
216
|
+
lines.push('');
|
|
217
|
+
lines.push('</details>');
|
|
218
|
+
lines.push('');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Covered flow expansion — collapsed by default
|
|
223
|
+
if (coveredDesigns.length > 0) {
|
|
224
|
+
lines.push('## Covered Flows (Regression / Expansion)');
|
|
225
|
+
lines.push('');
|
|
226
|
+
lines.push('These flows already have specs. The test cases below extend coverage for changes in this PR.');
|
|
227
|
+
lines.push('');
|
|
228
|
+
for (const td of coveredDesigns) {
|
|
229
|
+
const strategy = crew.strategyEntries.find((s) => s.flowId === td.flowId);
|
|
230
|
+
const approach = strategy?.approach ?? 'smoke-test';
|
|
231
|
+
const p0Count = td.testCases.filter((tc) => tc.priority === 'P0').length;
|
|
232
|
+
lines.push(`<details><summary><strong>${td.flowName}</strong> — ${approach} | ${td.testCases.length} cases (${p0Count} P0)</summary>`);
|
|
233
|
+
lines.push('');
|
|
234
|
+
for (const tc of td.testCases) {
|
|
235
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
236
|
+
}
|
|
237
|
+
lines.push('');
|
|
238
|
+
lines.push('</details>');
|
|
239
|
+
lines.push('');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
// Cross-impacts section
|
|
243
|
+
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
244
|
+
if (highRisk.length > 0) {
|
|
245
|
+
lines.push('## High-Risk Cross-Impacts');
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push('These cross-family dependencies should be verified during release testing:');
|
|
248
|
+
lines.push('');
|
|
249
|
+
for (const ci of highRisk) {
|
|
250
|
+
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
251
|
+
}
|
|
252
|
+
lines.push('');
|
|
253
|
+
}
|
|
254
|
+
return lines.join('\n');
|
|
134
255
|
}
|
|
135
|
-
export function writeCrewArtifacts(reportRoot, crew) {
|
|
256
|
+
export function writeCrewArtifacts(reportRoot, crew, plan) {
|
|
136
257
|
const outputDir = join(reportRoot, '.e2e-ai-agents');
|
|
137
258
|
mkdirSync(outputDir, { recursive: true });
|
|
138
259
|
const crewSummaryPath = join(outputDir, 'crew-summary.json');
|
|
139
260
|
const crewMarkdownPath = join(outputDir, 'crew-summary.md');
|
|
261
|
+
const crewTestPlanPath = join(outputDir, 'crew-test-plan.md');
|
|
140
262
|
writeFileSync(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
|
|
141
|
-
writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
|
|
142
|
-
|
|
263
|
+
writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
|
|
264
|
+
writeFileSync(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
|
|
265
|
+
return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
|
|
143
266
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
4
4
|
"description": "AI-powered E2E test impact analysis, generation, and healing. Analyzes code changes to identify affected Playwright tests, detects coverage gaps, and generates or repairs specs using pluggable LLM providers (Claude, OpenAI, Ollama). Includes MCP server, traceability, and CI/CD integration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|