@yasserkhanorg/e2e-agents 1.8.3 → 1.8.5
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.
|
@@ -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;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;
|
|
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,CAsJnF;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"}
|
|
@@ -85,25 +85,37 @@ async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
85
85
|
timings: result.timings,
|
|
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
|
+
}
|
|
88
94
|
function buildCrewMarkdown(crew, plan) {
|
|
89
95
|
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
90
96
|
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);
|
|
96
97
|
const lines = [
|
|
97
|
-
'### Crew
|
|
98
|
+
'### Crew Analysis — What to Test',
|
|
99
|
+
'',
|
|
100
|
+
`Crew analyzed the diff and recommends what to verify before merging.`,
|
|
98
101
|
'',
|
|
99
|
-
`
|
|
100
|
-
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
101
|
-
`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
|
|
102
|
+
`Impacted flows: **${crew.summary.impactedFlows}** | Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
102
103
|
];
|
|
103
|
-
if (
|
|
104
|
-
|
|
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)`);
|
|
105
117
|
}
|
|
106
|
-
lines.push(`
|
|
118
|
+
lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
107
119
|
if (crew.strategyEntries.length > 0) {
|
|
108
120
|
lines.push('');
|
|
109
121
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -151,14 +163,18 @@ function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
|
151
163
|
*/
|
|
152
164
|
function buildCrewTestPlan(crew, plan) {
|
|
153
165
|
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
166
|
+
const hasTestDesigns = crew.testDesigns.length > 0;
|
|
154
167
|
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
155
|
-
// Split
|
|
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
|
|
156
174
|
const gapDesigns = [];
|
|
157
175
|
const coveredDesigns = [];
|
|
158
176
|
for (const td of crew.testDesigns) {
|
|
159
|
-
|
|
160
|
-
const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
161
|
-
if (isGap) {
|
|
177
|
+
if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
|
|
162
178
|
gapDesigns.push(td);
|
|
163
179
|
}
|
|
164
180
|
else {
|
|
@@ -168,90 +184,117 @@ function buildCrewTestPlan(crew, plan) {
|
|
|
168
184
|
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
169
185
|
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
170
186
|
const lines = [
|
|
171
|
-
'# Crew Test Plan',
|
|
187
|
+
'# Crew Test Plan — What to Verify',
|
|
172
188
|
'',
|
|
173
|
-
|
|
189
|
+
'> **This is a test recommendation, not a test execution report.**',
|
|
190
|
+
'> Crew analyzed the code diff and identified what needs to be tested.',
|
|
191
|
+
'> Use this plan to guide manual QA or write automated E2E tests.',
|
|
192
|
+
'',
|
|
193
|
+
`_Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)_`,
|
|
174
194
|
'',
|
|
175
195
|
'## Summary',
|
|
176
196
|
'',
|
|
177
197
|
`| Metric | Count |`,
|
|
178
198
|
`|--------|-------|`,
|
|
179
|
-
`| Gap flows
|
|
180
|
-
`| Covered flows
|
|
181
|
-
`| Total | ${crew.
|
|
199
|
+
`| Gap flows — **no existing tests, must verify** | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
|
|
200
|
+
`| Covered flows — **has tests, verify no regressions** | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
|
|
201
|
+
`| Total | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
|
|
182
202
|
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
183
203
|
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
184
204
|
'',
|
|
185
205
|
];
|
|
186
|
-
//
|
|
187
|
-
if (
|
|
188
|
-
lines.push('##
|
|
206
|
+
// ── Gap flows ──
|
|
207
|
+
if (gapStrategies.length > 0) {
|
|
208
|
+
lines.push('## Action Required: Gap Flows (No Existing Tests)');
|
|
189
209
|
lines.push('');
|
|
190
|
-
lines.push('These flows have **no
|
|
210
|
+
lines.push('These flows have **no automated E2E coverage**. Before merging, either:');
|
|
211
|
+
lines.push('1. **Write E2E tests** for the critical scenarios below, or');
|
|
212
|
+
lines.push('2. **Manually verify** each flow works as expected');
|
|
191
213
|
lines.push('');
|
|
192
|
-
for (const
|
|
193
|
-
const
|
|
194
|
-
|
|
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}`);
|
|
214
|
+
for (const strategy of gapStrategies) {
|
|
215
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
216
|
+
lines.push(`### ${strategy.flowName}`);
|
|
199
217
|
lines.push('');
|
|
200
|
-
|
|
218
|
+
const actionVerb = strategy.approach === 'full-test' ? 'Write full E2E test or verify manually'
|
|
219
|
+
: strategy.approach === 'smoke-test' ? 'Smoke-test manually or add basic E2E coverage'
|
|
220
|
+
: strategy.approach === 'manual-review' ? 'Manual review required'
|
|
221
|
+
: 'Can skip — low risk';
|
|
222
|
+
lines.push(`**${strategy.priority}** | Recommended: **${strategy.approach}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
|
|
223
|
+
lines.push(`> ${actionVerb}`);
|
|
224
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
225
|
+
lines.push(`> Rationale: ${strategy.rationale}`);
|
|
226
|
+
}
|
|
227
|
+
if (strategy.testCategories.length > 0) {
|
|
228
|
+
lines.push(`> Test types to cover: ${strategy.testCategories.join(', ')}`);
|
|
229
|
+
}
|
|
201
230
|
lines.push('');
|
|
202
|
-
//
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
lines.push(
|
|
208
|
-
|
|
209
|
-
|
|
231
|
+
// If test designs exist, show P0/P1 cases
|
|
232
|
+
if (td && td.testCases.length > 0) {
|
|
233
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
234
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
235
|
+
if (p0Cases.length > 0) {
|
|
236
|
+
lines.push('**P0 — Must test:**');
|
|
237
|
+
lines.push('');
|
|
238
|
+
for (const tc of p0Cases) {
|
|
239
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
240
|
+
if (tc.preconditions.length > 0) {
|
|
241
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
242
|
+
}
|
|
243
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
244
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
210
245
|
}
|
|
211
|
-
lines.push(
|
|
212
|
-
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
246
|
+
lines.push('');
|
|
213
247
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
lines.push(
|
|
248
|
+
if (p1Cases.length > 0) {
|
|
249
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
250
|
+
lines.push('');
|
|
251
|
+
for (const tc of p1Cases) {
|
|
252
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
253
|
+
}
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push('</details>');
|
|
256
|
+
lines.push('');
|
|
222
257
|
}
|
|
223
|
-
lines.push('');
|
|
224
|
-
lines.push('</details>');
|
|
225
|
-
lines.push('');
|
|
226
258
|
}
|
|
227
259
|
}
|
|
228
260
|
}
|
|
229
|
-
// Covered
|
|
230
|
-
if (
|
|
231
|
-
lines.push('## Covered Flows (
|
|
261
|
+
// ── Covered flows ──
|
|
262
|
+
if (coveredStrategies.length > 0) {
|
|
263
|
+
lines.push('## Regression Check: Covered Flows (Already Have Tests)');
|
|
232
264
|
lines.push('');
|
|
233
|
-
lines.push('These flows
|
|
265
|
+
lines.push('These flows have existing E2E specs. The existing tests should catch regressions automatically.');
|
|
266
|
+
lines.push('**No manual action required** unless CI tests fail on these flows.');
|
|
234
267
|
lines.push('');
|
|
235
|
-
for (const
|
|
236
|
-
const
|
|
237
|
-
const
|
|
238
|
-
const
|
|
239
|
-
lines.push(`<details><summary><strong>${
|
|
268
|
+
for (const strategy of coveredStrategies) {
|
|
269
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
270
|
+
const caseCount = td ? td.testCases.length : 0;
|
|
271
|
+
const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
|
|
272
|
+
lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
|
|
240
273
|
lines.push('');
|
|
241
|
-
|
|
242
|
-
|
|
274
|
+
lines.push(`Existing tests should cover this. Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
275
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
276
|
+
lines.push(`> ${strategy.rationale}`);
|
|
277
|
+
}
|
|
278
|
+
if (strategy.testCategories.length > 0) {
|
|
279
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
280
|
+
}
|
|
281
|
+
if (td && td.testCases.length > 0) {
|
|
282
|
+
lines.push('');
|
|
283
|
+
for (const tc of td.testCases) {
|
|
284
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
285
|
+
}
|
|
243
286
|
}
|
|
244
287
|
lines.push('');
|
|
245
288
|
lines.push('</details>');
|
|
246
289
|
lines.push('');
|
|
247
290
|
}
|
|
248
291
|
}
|
|
249
|
-
// Cross-impacts
|
|
292
|
+
// ── Cross-impacts ──
|
|
250
293
|
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
251
294
|
if (highRisk.length > 0) {
|
|
252
|
-
lines.push('## High-Risk Cross-Impacts');
|
|
295
|
+
lines.push('## High-Risk Cross-Impacts — Verify Before Release');
|
|
253
296
|
lines.push('');
|
|
254
|
-
lines.push('
|
|
297
|
+
lines.push('Changes in one area may break these related areas. Manually verify or ensure E2E tests cover both sides:');
|
|
255
298
|
lines.push('');
|
|
256
299
|
for (const ci of highRisk) {
|
|
257
300
|
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
|
@@ -78,25 +78,37 @@ export async function runPlanCrewAnalysis(plan, config, args) {
|
|
|
78
78
|
timings: result.timings,
|
|
79
79
|
};
|
|
80
80
|
}
|
|
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
|
+
}
|
|
81
87
|
export function buildCrewMarkdown(crew, plan) {
|
|
82
88
|
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
83
89
|
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);
|
|
89
90
|
const lines = [
|
|
90
|
-
'### Crew
|
|
91
|
+
'### Crew Analysis — What to Test',
|
|
92
|
+
'',
|
|
93
|
+
`Crew analyzed the diff and recommends what to verify before merging.`,
|
|
91
94
|
'',
|
|
92
|
-
`
|
|
93
|
-
`Impacted flows: **${crew.summary.impactedFlows}**`,
|
|
94
|
-
`Structured test designs: **${crew.summary.testDesigns}** flows, **${totalCases}** test cases`,
|
|
95
|
+
`Impacted flows: **${crew.summary.impactedFlows}** | Strategy entries: **${crew.summary.strategyEntries}**`,
|
|
95
96
|
];
|
|
96
|
-
if (
|
|
97
|
-
|
|
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)`);
|
|
98
110
|
}
|
|
99
|
-
lines.push(`
|
|
111
|
+
lines.push(`Estimated AI cost: **$${crew.summary.totalCostUSD.toFixed(4)}**`);
|
|
100
112
|
if (crew.strategyEntries.length > 0) {
|
|
101
113
|
lines.push('');
|
|
102
114
|
lines.push('Top Strategy Recommendations:');
|
|
@@ -144,14 +156,18 @@ export function appendCrewToSummary(baseMarkdown, crew, plan) {
|
|
|
144
156
|
*/
|
|
145
157
|
export function buildCrewTestPlan(crew, plan) {
|
|
146
158
|
const gapFamilies = new Set((plan?.gapDetails ?? []).map((g) => g.id));
|
|
159
|
+
const hasTestDesigns = crew.testDesigns.length > 0;
|
|
147
160
|
const totalCases = crew.testDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
148
|
-
// Split
|
|
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
|
|
149
167
|
const gapDesigns = [];
|
|
150
168
|
const coveredDesigns = [];
|
|
151
169
|
for (const td of crew.testDesigns) {
|
|
152
|
-
|
|
153
|
-
const isGap = Array.from(gapFamilies).some((fam) => td.flowId.startsWith(fam) || td.flowName.toLowerCase().includes(fam.replace(/_/g, ' ')));
|
|
154
|
-
if (isGap) {
|
|
170
|
+
if (matchesGapFamily(td.flowId, td.flowName, gapFamilies)) {
|
|
155
171
|
gapDesigns.push(td);
|
|
156
172
|
}
|
|
157
173
|
else {
|
|
@@ -161,90 +177,117 @@ export function buildCrewTestPlan(crew, plan) {
|
|
|
161
177
|
const gapCases = gapDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
162
178
|
const coveredCases = coveredDesigns.reduce((n, td) => n + td.testCases.length, 0);
|
|
163
179
|
const lines = [
|
|
164
|
-
'# Crew Test Plan',
|
|
180
|
+
'# Crew Test Plan — What to Verify',
|
|
165
181
|
'',
|
|
166
|
-
|
|
182
|
+
'> **This is a test recommendation, not a test execution report.**',
|
|
183
|
+
'> Crew analyzed the code diff and identified what needs to be tested.',
|
|
184
|
+
'> Use this plan to guide manual QA or write automated E2E tests.',
|
|
185
|
+
'',
|
|
186
|
+
`_Auto-generated by e2e-agents crew (\`${crew.workflow}\` workflow)_`,
|
|
167
187
|
'',
|
|
168
188
|
'## Summary',
|
|
169
189
|
'',
|
|
170
190
|
`| Metric | Count |`,
|
|
171
191
|
`|--------|-------|`,
|
|
172
|
-
`| Gap flows
|
|
173
|
-
`| Covered flows
|
|
174
|
-
`| Total | ${crew.
|
|
192
|
+
`| Gap flows — **no existing tests, must verify** | ${gapStrategies.length} flows${hasTestDesigns ? `, **${gapCases} test cases**` : ''} |`,
|
|
193
|
+
`| Covered flows — **has tests, verify no regressions** | ${coveredStrategies.length} flows${hasTestDesigns ? `, ${coveredCases} test cases` : ''} |`,
|
|
194
|
+
`| Total | ${crew.strategyEntries.length} flows${hasTestDesigns ? `, ${totalCases} test cases` : ''} |`,
|
|
175
195
|
`| High-risk cross-impacts | ${crew.summary.highRiskCrossImpacts} |`,
|
|
176
196
|
`| AI cost | $${crew.summary.totalCostUSD.toFixed(4)} |`,
|
|
177
197
|
'',
|
|
178
198
|
];
|
|
179
|
-
//
|
|
180
|
-
if (
|
|
181
|
-
lines.push('##
|
|
199
|
+
// ── Gap flows ──
|
|
200
|
+
if (gapStrategies.length > 0) {
|
|
201
|
+
lines.push('## Action Required: Gap Flows (No Existing Tests)');
|
|
182
202
|
lines.push('');
|
|
183
|
-
lines.push('These flows have **no
|
|
203
|
+
lines.push('These flows have **no automated E2E coverage**. Before merging, either:');
|
|
204
|
+
lines.push('1. **Write E2E tests** for the critical scenarios below, or');
|
|
205
|
+
lines.push('2. **Manually verify** each flow works as expected');
|
|
184
206
|
lines.push('');
|
|
185
|
-
for (const
|
|
186
|
-
const
|
|
187
|
-
|
|
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}`);
|
|
207
|
+
for (const strategy of gapStrategies) {
|
|
208
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
209
|
+
lines.push(`### ${strategy.flowName}`);
|
|
192
210
|
lines.push('');
|
|
193
|
-
|
|
211
|
+
const actionVerb = strategy.approach === 'full-test' ? 'Write full E2E test or verify manually'
|
|
212
|
+
: strategy.approach === 'smoke-test' ? 'Smoke-test manually or add basic E2E coverage'
|
|
213
|
+
: strategy.approach === 'manual-review' ? 'Manual review required'
|
|
214
|
+
: 'Can skip — low risk';
|
|
215
|
+
lines.push(`**${strategy.priority}** | Recommended: **${strategy.approach}** | Cross-impact risk: **${strategy.crossImpactRisk}**`);
|
|
216
|
+
lines.push(`> ${actionVerb}`);
|
|
217
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
218
|
+
lines.push(`> Rationale: ${strategy.rationale}`);
|
|
219
|
+
}
|
|
220
|
+
if (strategy.testCategories.length > 0) {
|
|
221
|
+
lines.push(`> Test types to cover: ${strategy.testCategories.join(', ')}`);
|
|
222
|
+
}
|
|
194
223
|
lines.push('');
|
|
195
|
-
//
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
lines.push(
|
|
201
|
-
|
|
202
|
-
|
|
224
|
+
// If test designs exist, show P0/P1 cases
|
|
225
|
+
if (td && td.testCases.length > 0) {
|
|
226
|
+
const p0Cases = td.testCases.filter((tc) => tc.priority === 'P0');
|
|
227
|
+
const p1Cases = td.testCases.filter((tc) => tc.priority === 'P1');
|
|
228
|
+
if (p0Cases.length > 0) {
|
|
229
|
+
lines.push('**P0 — Must test:**');
|
|
230
|
+
lines.push('');
|
|
231
|
+
for (const tc of p0Cases) {
|
|
232
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type})`);
|
|
233
|
+
if (tc.preconditions.length > 0) {
|
|
234
|
+
lines.push(` - Preconditions: ${tc.preconditions.join('; ')}`);
|
|
235
|
+
}
|
|
236
|
+
lines.push(` - Steps: ${tc.steps.join(' → ')}`);
|
|
237
|
+
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
203
238
|
}
|
|
204
|
-
lines.push(
|
|
205
|
-
lines.push(` - Expected: ${tc.expectedOutcome}`);
|
|
239
|
+
lines.push('');
|
|
206
240
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
lines.push(
|
|
241
|
+
if (p1Cases.length > 0) {
|
|
242
|
+
lines.push(`<details><summary>P1 — Should test (${p1Cases.length})</summary>`);
|
|
243
|
+
lines.push('');
|
|
244
|
+
for (const tc of p1Cases) {
|
|
245
|
+
lines.push(`- [ ] **${tc.name}** (${tc.type}) — ${tc.expectedOutcome}`);
|
|
246
|
+
}
|
|
247
|
+
lines.push('');
|
|
248
|
+
lines.push('</details>');
|
|
249
|
+
lines.push('');
|
|
215
250
|
}
|
|
216
|
-
lines.push('');
|
|
217
|
-
lines.push('</details>');
|
|
218
|
-
lines.push('');
|
|
219
251
|
}
|
|
220
252
|
}
|
|
221
253
|
}
|
|
222
|
-
// Covered
|
|
223
|
-
if (
|
|
224
|
-
lines.push('## Covered Flows (
|
|
254
|
+
// ── Covered flows ──
|
|
255
|
+
if (coveredStrategies.length > 0) {
|
|
256
|
+
lines.push('## Regression Check: Covered Flows (Already Have Tests)');
|
|
225
257
|
lines.push('');
|
|
226
|
-
lines.push('These flows
|
|
258
|
+
lines.push('These flows have existing E2E specs. The existing tests should catch regressions automatically.');
|
|
259
|
+
lines.push('**No manual action required** unless CI tests fail on these flows.');
|
|
227
260
|
lines.push('');
|
|
228
|
-
for (const
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
lines.push(`<details><summary><strong>${
|
|
261
|
+
for (const strategy of coveredStrategies) {
|
|
262
|
+
const td = crew.testDesigns.find((d) => d.flowId === strategy.flowId);
|
|
263
|
+
const caseCount = td ? td.testCases.length : 0;
|
|
264
|
+
const detail = caseCount > 0 ? ` | ${caseCount} cases` : '';
|
|
265
|
+
lines.push(`<details><summary><strong>${strategy.flowName}</strong> — ${strategy.approach}${detail} (${strategy.priority})</summary>`);
|
|
233
266
|
lines.push('');
|
|
234
|
-
|
|
235
|
-
|
|
267
|
+
lines.push(`Existing tests should cover this. Cross-impact risk: ${strategy.crossImpactRisk}`);
|
|
268
|
+
if (strategy.rationale && !strategy.rationale.includes('Default strategy')) {
|
|
269
|
+
lines.push(`> ${strategy.rationale}`);
|
|
270
|
+
}
|
|
271
|
+
if (strategy.testCategories.length > 0) {
|
|
272
|
+
lines.push(`Test categories: ${strategy.testCategories.join(', ')}`);
|
|
273
|
+
}
|
|
274
|
+
if (td && td.testCases.length > 0) {
|
|
275
|
+
lines.push('');
|
|
276
|
+
for (const tc of td.testCases) {
|
|
277
|
+
lines.push(`- [ ] **${tc.name}** (${tc.priority}, ${tc.type}) — ${tc.expectedOutcome}`);
|
|
278
|
+
}
|
|
236
279
|
}
|
|
237
280
|
lines.push('');
|
|
238
281
|
lines.push('</details>');
|
|
239
282
|
lines.push('');
|
|
240
283
|
}
|
|
241
284
|
}
|
|
242
|
-
// Cross-impacts
|
|
285
|
+
// ── Cross-impacts ──
|
|
243
286
|
const highRisk = crew.crossImpacts.filter((ci) => ci.riskLevel === 'high');
|
|
244
287
|
if (highRisk.length > 0) {
|
|
245
|
-
lines.push('## High-Risk Cross-Impacts');
|
|
288
|
+
lines.push('## High-Risk Cross-Impacts — Verify Before Release');
|
|
246
289
|
lines.push('');
|
|
247
|
-
lines.push('
|
|
290
|
+
lines.push('Changes in one area may break these related areas. Manually verify or ensure E2E tests cover both sides:');
|
|
248
291
|
lines.push('');
|
|
249
292
|
for (const ci of highRisk) {
|
|
250
293
|
lines.push(`- **${ci.sourceFamily}** → **${ci.affectedFamily}**: ${ci.sharedDependency}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yasserkhanorg/e2e-agents",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.5",
|
|
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",
|