feat: run-all.js --fail-only 모드 추가 및 8·9차 E2E 테스트 결과
- --fail-only: 최근 요약 리포트에서 실패 시나리오만 추출하여 집중 실행 - getFailedScenarioIds(): E2E_FULL_TEST_SUMMARY에서 실패 ID 파싱 - --fail-only --iterate 조합으로 실패분만 반복 타격 가능 - 8차 전체 검증: 184개 중 176 PASS / 8 FAIL (95.7%) - 9차 실패 집중 검증: 8개 × 4라운드 모두 FAIL (확정적 앱 버그) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
* node e2e/runner/run-all.js --iterate # 실패 시나리오 자동 재실행 (최대 3회)
|
||||
* node e2e/runner/run-all.js --iterate 5 # 실패 시나리오 자동 재실행 (최대 5회)
|
||||
* node e2e/runner/run-all.js --stage # 카테고리별 단계 실행 (accessibility → edge → perf → workflow → functional)
|
||||
* node e2e/runner/run-all.js --fail-only # 최근 요약 리포트의 실패 시나리오만 실행
|
||||
* node e2e/runner/run-all.js --fail-only --iterate # 실패 시나리오만 반복 실행
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -59,6 +61,7 @@ const MAX_ITERATIONS = (() => {
|
||||
return 3;
|
||||
})();
|
||||
const STAGE_MODE = args.includes('--stage');
|
||||
const FAIL_ONLY = args.includes('--fail-only');
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────
|
||||
|
||||
@@ -96,6 +99,67 @@ function getPassedScenarioIds() {
|
||||
return passed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근 E2E_FULL_TEST_SUMMARY 리포트에서 실패한 시나리오 ID 집합을 반환.
|
||||
* --fail-only 모드에서 사용.
|
||||
*
|
||||
* 파싱 전략:
|
||||
* 1차: "### ❌ {name} ({scenario-id})" 형식에서 ID 추출 (가장 신뢰)
|
||||
* 2차: Fail-{scenario-id}_{timestamp}.md 파일명에서 추출 (보완)
|
||||
*/
|
||||
function getFailedScenarioIds() {
|
||||
const failed = new Set();
|
||||
if (!fs.existsSync(RESULTS_DIR)) return { ids: failed, source: null };
|
||||
|
||||
// E2E_FULL_TEST_SUMMARY_*.md 파일 중 가장 최근 것 찾기
|
||||
const summaryFiles = fs.readdirSync(RESULTS_DIR)
|
||||
.filter(f => f.startsWith('E2E_FULL_TEST_SUMMARY_') && f.endsWith('.md'))
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
if (summaryFiles.length === 0) {
|
||||
console.log(' (이전 요약 리포트 없음 - Fail- 파일에서 추출)');
|
||||
// Fail- 리포트에서 추출
|
||||
const failFiles = fs.readdirSync(RESULTS_DIR)
|
||||
.filter(f => f.startsWith('Fail-') && f.endsWith('.md'));
|
||||
for (const f of failFiles) {
|
||||
const withoutPrefix = f.substring(5); // remove "Fail-"
|
||||
const tsMatch = withoutPrefix.match(/_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.md$/);
|
||||
if (tsMatch) {
|
||||
failed.add(withoutPrefix.substring(0, tsMatch.index));
|
||||
}
|
||||
}
|
||||
return { ids: failed, source: 'Fail-*.md files' };
|
||||
}
|
||||
|
||||
const latestSummary = summaryFiles[0];
|
||||
const content = fs.readFileSync(path.join(RESULTS_DIR, latestSummary), 'utf-8');
|
||||
|
||||
// "### ❌ 시나리오명 (scenario-id)" 패턴에서 ID 추출
|
||||
const lines = content.split('\n');
|
||||
for (const line of lines) {
|
||||
const match = line.match(/^###\s*❌\s*.+\(([a-zA-Z0-9_-]+)\)\s*$/);
|
||||
if (match) {
|
||||
failed.add(match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// 보완: Fail- 리포트에서도 추출 (요약 파싱 실패 시)
|
||||
if (failed.size === 0) {
|
||||
const failFiles = fs.readdirSync(RESULTS_DIR)
|
||||
.filter(f => f.startsWith('Fail-') && f.endsWith('.md'));
|
||||
for (const f of failFiles) {
|
||||
const withoutPrefix = f.substring(5);
|
||||
const tsMatch = withoutPrefix.match(/_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.md$/);
|
||||
if (tsMatch) {
|
||||
failed.add(withoutPrefix.substring(0, tsMatch.index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ids: failed, source: latestSummary };
|
||||
}
|
||||
|
||||
/** Color console helpers */
|
||||
const C = {
|
||||
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
||||
@@ -1285,6 +1349,7 @@ async function main() {
|
||||
if (SKIP_PASSED) console.log(`스킵: 이미 성공한 시나리오 건너뛰기 (--skip-passed)`);
|
||||
if (ITERATE) console.log(`반복 모드: 실패 시나리오 자동 재실행 (최대 ${MAX_ITERATIONS}회)`);
|
||||
if (STAGE_MODE) console.log(`단계 모드: 카테고리별 순차 실행`);
|
||||
if (FAIL_ONLY) console.log(`실패 집중 모드: 이전 실패 시나리오만 실행 (--fail-only)`);
|
||||
console.log('');
|
||||
|
||||
// Ensure directories
|
||||
@@ -1330,6 +1395,25 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// --fail-only: 이전 실패 시나리오만 실행
|
||||
if (FAIL_ONLY) {
|
||||
const { ids: failedIds, source } = getFailedScenarioIds();
|
||||
if (failedIds.size === 0) {
|
||||
console.log(C.green('이전 실패 시나리오 없음! 전체 PASS 상태입니다.'));
|
||||
process.exit(0);
|
||||
}
|
||||
const before = scenarioFiles.length;
|
||||
scenarioFiles = scenarioFiles.filter((f) => {
|
||||
const id = f.replace('.json', '');
|
||||
return failedIds.has(id);
|
||||
});
|
||||
const excluded = before - scenarioFiles.length;
|
||||
console.log(C.yellow(`실패 집중 모드: ${failedIds.size}개 실패 시나리오 대상`));
|
||||
console.log(C.dim(` 소스: ${source}`));
|
||||
console.log(C.dim(` 대상: ${Array.from(failedIds).join(', ')}`));
|
||||
console.log(C.dim(` 건너뜀: ${excluded}개 성공 시나리오\n`));
|
||||
}
|
||||
|
||||
const totalScenarios = scenarioFiles.length;
|
||||
console.log(`시나리오: ${totalScenarios}개 발견\n`);
|
||||
|
||||
@@ -1646,6 +1730,7 @@ async function main() {
|
||||
const failCount = allResults.length - passCount;
|
||||
|
||||
console.log(C.bold('\n=== 테스트 완료 ==='));
|
||||
if (FAIL_ONLY) console.log(C.yellow(`[실패 집중 모드] 이전 실패 시나리오 ${totalScenarios}개만 실행`));
|
||||
console.log(`전체: ${totalScenarios} | ${C.green(`성공: ${passCount}`)} | ${failCount > 0 ? C.red(`실패: ${failCount}`) : '실패: 0'}`);
|
||||
if (ITERATE && iterationHistory.length > 1) {
|
||||
const lastIter = iterationHistory[iterationHistory.length - 1];
|
||||
|
||||
Reference in New Issue
Block a user