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:
김보곤
2026-02-26 20:50:26 +09:00
parent 95b7c4afe3
commit 3e9c638b04
243 changed files with 11484 additions and 0 deletions

View File

@@ -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];