@yasserkhanorg/e2e-agents 1.8.2 → 1.8.4
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 +166 -11
- package/dist/esm/cli/commands/plan.js +6 -2
- package/dist/esm/cli/commands/plan_crew.js +165 -11
- 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;AAWD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,MAAM,CAwEnF;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,CA0InF;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,37 @@ async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
84
85
|
timings: result.timings,
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Match a strategy/design entry against a set of gap family IDs.
|
|
90
|
+
*/
|
|
91
|
+
function matchesGapFamily(flowId, flowName, gapFamilies) {
|
|
92
|
+
return Array.from(gapFamilies).some((fam) => flowId.startsWith(fam) || flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
93
|
+
}
|
|
94
|
+
function buildCrewMarkdown(crew, plan) {
|
|
95
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
96
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
88
97
|
const lines = [
|
|
89
98
|
'### Crew Insights',
|
|
90
99
|
'',
|
|
91
100
|
`Workflow: \`${crew.workflow}\``,
|
|
92
|
-
`Provider override: \`${crew.providerOverride}\``,
|
|
93
101
|
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
94
102
|
`Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
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)}**`,
|
|
99
103
|
];
|
|
104
|
+
if (totalCases > 0) {
|
|
105
|
+
const gapDesigns = gapFamilies.size > 0
|
|
106
|
+
? crew.testDesigns.filter((td) => matchesGapFamily(td.flowId, td.flowName, gapFamilies))
|
|
107
|
+
: [];
|
|
108
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
109
|
+
const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
|
|
110
|
+
lines.push(`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`);
|
|
111
|
+
if (gapDesigns.length > 0) {
|
|
112
|
+
lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (crew.summary.crossImpacts > 0) {
|
|
116
|
+
lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`);
|
|
117
|
+
}
|
|
118
|
+
lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
100
119
|
if (crew.strategyEntries.length > 0) {
|
|
101
120
|
lines.push('');
|
|
102
121
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -135,15 +154,151 @@ function buildCrewMarkdown(crew) {
|
|
|
135
154
|
}
|
|
136
155
|
return lines.join('\n');
|
|
137
156
|
}
|
|
138
|
-
function appendCrewToSummary(baseMarkdown, crew) {
|
|
139
|
-
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
|
|
157
|
+
function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
158
|
+
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Build a full structured test plan as a Markdown document.
|
|
162
|
+
* Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
|
|
163
|
+
*/
|
|
164
|
+
function buildCrewTestPlan(crew, plan) {
|
|
165
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
166
|
+
const hasTestDesigns = crew.testDesigns.length > 0;
|
|
167
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
168
|
+
// Split strategy entries into gap-related and covered
|
|
169
|
+
const gapStrategies = gapFamilies.size > 0
|
|
170
|
+
? crew.strategyEntries.filter((s) => matchesGapFamily(s.flowId, s.flowName, gapFamilies))
|
|
171
|
+
: [];
|
|
172
|
+
const coveredStrategies = crew.strategyEntries.filter((s) => !gapStrategies.includes(s));
|
|
173
|
+
// Split test designs (if present) into gap-related and covered
|
|
174
|
+
const gapDesigns = [];
|
|
175
|
+
const coveredDesigns = [];
|
|
176
|
+
for (const td of crew.testDesigns) {
|
|
177
|
+
if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
|
|
178
|
+
gapDesigns.push(td);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
coveredDesigns.push(td);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
185
|
+
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
186
|
+
const lines = [
|
|
187
|
+
'# Crew Test Plan',
|
|
188
|
+
'',
|
|
189
|
+
`> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
|
|
190
|
+
'',
|
|
191
|
+
'## Summary',
|
|
192
|
+
'',
|
|
193
|
+
`| Metric | Count |`,
|
|
194
|
+
`|--------|-------|`,
|
|
195
|
+
`| Gap flows (missing tests) | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
|
|
196
|
+
`| Covered flows (expansion) | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
|
|
197
|
+
`| Total strategy entries | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
|
|
198
|
+
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
199
|
+
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
200
|
+
'',
|
|
201
|
+
];
|
|
202
|
+
// ── Gap flows ──
|
|
203
|
+
if (gapStrategies.length > 0) {
|
|
204
|
+
lines.push('## Priority: Gap Flows (Missing Tests)');
|
|
205
|
+
lines.push('');
|
|
206
|
+
lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
|
|
207
|
+
lines.push('');
|
|
208
|
+
for (const strategy of gapStrategies) {
|
|
209
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
210
|
+
lines.push(`### ${strategy.flowName}`);
|
|
211
|
+
lines.push('');
|
|
212
|
+
lines.push(`Strategy: **${strategy.approach}** | Priority: **${strategy.priority}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
|
|
213
|
+
if (strategy.rationale) {
|
|
214
|
+
lines.push(`> ${strategy.rationale}`);
|
|
215
|
+
}
|
|
216
|
+
if (strategy.testCategories.length > 0) {
|
|
217
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
218
|
+
}
|
|
219
|
+
lines.push('');
|
|
220
|
+
// If test designs exist, show P0/P1 cases
|
|
221
|
+
if (td && td.testCases.length > 0) {
|
|
222
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
223
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
224
|
+
if (p0Cases.length > 0) {
|
|
225
|
+
lines.push('**P0 — Must test:**');
|
|
226
|
+
lines.push('');
|
|
227
|
+
for (const tc of p0Cases) {
|
|
228
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
229
|
+
if (tc.preconditions.length > 0) {
|
|
230
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
231
|
+
}
|
|
232
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
233
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
234
|
+
}
|
|
235
|
+
lines.push('');
|
|
236
|
+
}
|
|
237
|
+
if (p1Cases.length > 0) {
|
|
238
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
239
|
+
lines.push('');
|
|
240
|
+
for (const tc of p1Cases) {
|
|
241
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
242
|
+
}
|
|
243
|
+
lines.push('');
|
|
244
|
+
lines.push('</details>');
|
|
245
|
+
lines.push('');
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// ── Covered flows ──
|
|
251
|
+
if (coveredStrategies.length > 0) {
|
|
252
|
+
lines.push('## Covered Flows (Regression / Expansion)');
|
|
253
|
+
lines.push('');
|
|
254
|
+
lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
|
|
255
|
+
lines.push('');
|
|
256
|
+
for (const strategy of coveredStrategies) {
|
|
257
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
258
|
+
const caseCount = td ? td.testCases.length : 0;
|
|
259
|
+
const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
|
|
260
|
+
lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
|
|
261
|
+
lines.push('');
|
|
262
|
+
lines.push(`Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
263
|
+
if (strategy.rationale) {
|
|
264
|
+
lines.push(`> ${strategy.rationale}`);
|
|
265
|
+
}
|
|
266
|
+
if (strategy.testCategories.length > 0) {
|
|
267
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
268
|
+
}
|
|
269
|
+
if (td && td.testCases.length > 0) {
|
|
270
|
+
lines.push('');
|
|
271
|
+
for (const tc of td.testCases) {
|
|
272
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
lines.push('');
|
|
276
|
+
lines.push('</details>');
|
|
277
|
+
lines.push('');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// ── Cross-impacts ──
|
|
281
|
+
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
282
|
+
if (highRisk.length > 0) {
|
|
283
|
+
lines.push('## High-Risk Cross-Impacts');
|
|
284
|
+
lines.push('');
|
|
285
|
+
lines.push('These cross-family dependencies should be verified during release testing:');
|
|
286
|
+
lines.push('');
|
|
287
|
+
for (const ci of highRisk) {
|
|
288
|
+
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
289
|
+
}
|
|
290
|
+
lines.push('');
|
|
291
|
+
}
|
|
292
|
+
return lines.join('\n');
|
|
140
293
|
}
|
|
141
|
-
function writeCrewArtifacts(reportRoot, crew) {
|
|
294
|
+
function writeCrewArtifacts(reportRoot, crew, plan) {
|
|
142
295
|
const outputDir = (0, path_1.join)(reportRoot, '.e2e-ai-agents');
|
|
143
296
|
(0, fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
144
297
|
const crewSummaryPath = (0, path_1.join)(outputDir, 'crew-summary.json');
|
|
145
298
|
const crewMarkdownPath = (0, path_1.join)(outputDir, 'crew-summary.md');
|
|
299
|
+
const crewTestPlanPath = (0, path_1.join)(outputDir, 'crew-test-plan.md');
|
|
146
300
|
(0, fs_1.writeFileSync)(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
|
|
147
|
-
(0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
|
|
148
|
-
|
|
301
|
+
(0, fs_1.writeFileSync)(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
|
|
302
|
+
(0, fs_1.writeFileSync)(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
|
|
303
|
+
return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
|
|
149
304
|
}
|
|
@@ -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,37 @@ export async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
78
78
|
timings: result.timings,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Match a strategy/design entry against a set of gap family IDs.
|
|
83
|
+
*/
|
|
84
|
+
function matchesGapFamily(flowId, flowName, gapFamilies) {
|
|
85
|
+
return Array.from(gapFamilies).some((fam) => flowId.startsWith(fam) || flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
86
|
+
}
|
|
87
|
+
export function buildCrewMarkdown(crew, plan) {
|
|
88
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
89
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
82
90
|
const lines = [
|
|
83
91
|
'### Crew Insights',
|
|
84
92
|
'',
|
|
85
93
|
`Workflow: \`${crew.workflow}\``,
|
|
86
|
-
`Provider override: \`${crew.providerOverride}\``,
|
|
87
94
|
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
88
95
|
`Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
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)}**`,
|
|
93
96
|
];
|
|
97
|
+
if (totalCases > 0) {
|
|
98
|
+
const gapDesigns = gapFamilies.size > 0
|
|
99
|
+
? crew.testDesigns.filter((td) => matchesGapFamily(td.flowId, td.flowName, gapFamilies))
|
|
100
|
+
: [];
|
|
101
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
102
|
+
const gapP0Cases = gapDesigns.reduce((n, td) => n + td.testCases.filter((tc) => tc.priority === 'P0').length, 0);
|
|
103
|
+
lines.push(`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`);
|
|
104
|
+
if (gapDesigns.length > 0) {
|
|
105
|
+
lines.push(`Gap-focused: **${gapDesigns.length}** flows, **${gapCases}** test cases (**${gapP0Cases}** P0)`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (crew.summary.crossImpacts > 0) {
|
|
109
|
+
lines.push(`Cross-impacts: **${crew.summary.crossImpacts}** (${crew.summary.highRiskCrossImpacts} high risk)`);
|
|
110
|
+
}
|
|
111
|
+
lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
94
112
|
if (crew.strategyEntries.length > 0) {
|
|
95
113
|
lines.push('');
|
|
96
114
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -129,15 +147,151 @@ export function buildCrewMarkdown(crew) {
|
|
|
129
147
|
}
|
|
130
148
|
return lines.join('\n');
|
|
131
149
|
}
|
|
132
|
-
export function appendCrewToSummary(baseMarkdown, crew) {
|
|
133
|
-
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew)}`;
|
|
150
|
+
export function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
151
|
+
return `${baseMarkdown}\n\n---\n\n${buildCrewMarkdown(crew, plan)}`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Build a full structured test plan as a Markdown document.
|
|
155
|
+
* Sections: gap flows first (P0 cases expanded), then covered-flow smoke tests.
|
|
156
|
+
*/
|
|
157
|
+
export function buildCrewTestPlan(crew, plan) {
|
|
158
|
+
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
159
|
+
const hasTestDesigns = crew.testDesigns.length > 0;
|
|
160
|
+
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
161
|
+
// Split strategy entries into gap-related and covered
|
|
162
|
+
const gapStrategies = gapFamilies.size > 0
|
|
163
|
+
? crew.strategyEntries.filter((s) => matchesGapFamily(s.flowId, s.flowName, gapFamilies))
|
|
164
|
+
: [];
|
|
165
|
+
const coveredStrategies = crew.strategyEntries.filter((s) => !gapStrategies.includes(s));
|
|
166
|
+
// Split test designs (if present) into gap-related and covered
|
|
167
|
+
const gapDesigns = [];
|
|
168
|
+
const coveredDesigns = [];
|
|
169
|
+
for (const td of crew.testDesigns) {
|
|
170
|
+
if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
|
|
171
|
+
gapDesigns.push(td);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
coveredDesigns.push(td);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
178
|
+
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
179
|
+
const lines = [
|
|
180
|
+
'# Crew Test Plan',
|
|
181
|
+
'',
|
|
182
|
+
`> Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)`,
|
|
183
|
+
'',
|
|
184
|
+
'## Summary',
|
|
185
|
+
'',
|
|
186
|
+
`| Metric | Count |`,
|
|
187
|
+
`|--------|-------|`,
|
|
188
|
+
`| Gap flows (missing tests) | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
|
|
189
|
+
`| Covered flows (expansion) | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
|
|
190
|
+
`| Total strategy entries | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
|
|
191
|
+
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
192
|
+
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
193
|
+
'',
|
|
194
|
+
];
|
|
195
|
+
// ── Gap flows ──
|
|
196
|
+
if (gapStrategies.length > 0) {
|
|
197
|
+
lines.push('## Priority: Gap Flows (Missing Tests)');
|
|
198
|
+
lines.push('');
|
|
199
|
+
lines.push('These flows have **no existing E2E coverage** and should be addressed first.');
|
|
200
|
+
lines.push('');
|
|
201
|
+
for (const strategy of gapStrategies) {
|
|
202
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
203
|
+
lines.push(`### ${strategy.flowName}`);
|
|
204
|
+
lines.push('');
|
|
205
|
+
lines.push(`Strategy: **${strategy.approach}** | Priority: **${strategy.priority}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
|
|
206
|
+
if (strategy.rationale) {
|
|
207
|
+
lines.push(`> ${strategy.rationale}`);
|
|
208
|
+
}
|
|
209
|
+
if (strategy.testCategories.length > 0) {
|
|
210
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
211
|
+
}
|
|
212
|
+
lines.push('');
|
|
213
|
+
// If test designs exist, show P0/P1 cases
|
|
214
|
+
if (td && td.testCases.length > 0) {
|
|
215
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
216
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
217
|
+
if (p0Cases.length > 0) {
|
|
218
|
+
lines.push('**P0 — Must test:**');
|
|
219
|
+
lines.push('');
|
|
220
|
+
for (const tc of p0Cases) {
|
|
221
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
222
|
+
if (tc.preconditions.length > 0) {
|
|
223
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
224
|
+
}
|
|
225
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
226
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
227
|
+
}
|
|
228
|
+
lines.push('');
|
|
229
|
+
}
|
|
230
|
+
if (p1Cases.length > 0) {
|
|
231
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
232
|
+
lines.push('');
|
|
233
|
+
for (const tc of p1Cases) {
|
|
234
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
235
|
+
}
|
|
236
|
+
lines.push('');
|
|
237
|
+
lines.push('</details>');
|
|
238
|
+
lines.push('');
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// ── Covered flows ──
|
|
244
|
+
if (coveredStrategies.length > 0) {
|
|
245
|
+
lines.push('## Covered Flows (Regression / Expansion)');
|
|
246
|
+
lines.push('');
|
|
247
|
+
lines.push('These flows already have specs. Verify changes haven\'t introduced regressions.');
|
|
248
|
+
lines.push('');
|
|
249
|
+
for (const strategy of coveredStrategies) {
|
|
250
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
251
|
+
const caseCount = td ? td.testCases.length : 0;
|
|
252
|
+
const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
|
|
253
|
+
lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push(`Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
256
|
+
if (strategy.rationale) {
|
|
257
|
+
lines.push(`> ${strategy.rationale}`);
|
|
258
|
+
}
|
|
259
|
+
if (strategy.testCategories.length > 0) {
|
|
260
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
261
|
+
}
|
|
262
|
+
if (td && td.testCases.length > 0) {
|
|
263
|
+
lines.push('');
|
|
264
|
+
for (const tc of td.testCases) {
|
|
265
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
lines.push('');
|
|
269
|
+
lines.push('</details>');
|
|
270
|
+
lines.push('');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// ── Cross-impacts ──
|
|
274
|
+
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
275
|
+
if (highRisk.length > 0) {
|
|
276
|
+
lines.push('## High-Risk Cross-Impacts');
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push('These cross-family dependencies should be verified during release testing:');
|
|
279
|
+
lines.push('');
|
|
280
|
+
for (const ci of highRisk) {
|
|
281
|
+
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
282
|
+
}
|
|
283
|
+
lines.push('');
|
|
284
|
+
}
|
|
285
|
+
return lines.join('\n');
|
|
134
286
|
}
|
|
135
|
-
export function writeCrewArtifacts(reportRoot, crew) {
|
|
287
|
+
export function writeCrewArtifacts(reportRoot, crew, plan) {
|
|
136
288
|
const outputDir = join(reportRoot, '.e2e-ai-agents');
|
|
137
289
|
mkdirSync(outputDir, { recursive: true });
|
|
138
290
|
const crewSummaryPath = join(outputDir, 'crew-summary.json');
|
|
139
291
|
const crewMarkdownPath = join(outputDir, 'crew-summary.md');
|
|
292
|
+
const crewTestPlanPath = join(outputDir, 'crew-test-plan.md');
|
|
140
293
|
writeFileSync(crewSummaryPath, JSON.stringify(crew, null, 2), 'utf-8');
|
|
141
|
-
writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew), 'utf-8');
|
|
142
|
-
|
|
294
|
+
writeFileSync(crewMarkdownPath, buildCrewMarkdown(crew, plan), 'utf-8');
|
|
295
|
+
writeFileSync(crewTestPlanPath, buildCrewTestPlan(crew, plan), 'utf-8');
|
|
296
|
+
return { crewSummaryPath, crewMarkdownPath, crewTestPlanPath };
|
|
143
297
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.4",
|
|
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",
|