diff --git a/src/detect.ts b/src/detect.ts index abc5609..f550149 100644 --- a/src/detect.ts +++ b/src/detect.ts @@ -14,6 +14,7 @@ const FRAMEWORK_NAMES: Record = { playwright: "Playwright", "go-test": "Go test", "cargo-test": "Cargo test", + tap: "TAP", node: "Node/npm generic", unknown: "Unknown" }; @@ -76,6 +77,10 @@ export function detectFramework(command: string[] = [], output = ""): DetectedFr add("cargo-test", 3, "output contains Rust failure references"); } + if (/^TAP version \d+/m.test(output)) add("tap", 4, "output contains TAP version header"); + if (/^not ok \d+\b/m.test(output)) add("tap", 4, "output contains TAP failing test lines"); + if (/^# (?:tests|pass|fail|skip|todo)\b/im.test(output)) add("tap", 2, "output contains TAP summary lines"); + if (/\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|unit|e2e|spec|check)\b/.test(commandText)) { add("node", 2, "command is a Node package script"); } diff --git a/src/extract.ts b/src/extract.ts index ddb740b..67a6827 100644 --- a/src/extract.ts +++ b/src/extract.ts @@ -67,7 +67,8 @@ function findAnchorLines(lines: string[], framework: DetectedFramework): number[ pytest: [/=+\s*FAILURES\s*=+/i, /^_{3,}\s+.+\s+_{3,}$/], playwright: [/^\s*\d+\)\s+\[.*\]\s+.+\s+›\s+/, /Error:\s+expect\(.+\)\./], "go-test": [/^FAIL\t/, /^\s+.+\.go:\d+:/], - "cargo-test": [/^----\s+.+\s+stdout\s+----/, /^failures:\s*$/i] + "cargo-test": [/^----\s+.+\s+stdout\s+----/, /^failures:\s*$/i], + tap: [/^not ok \d+\b/, /^\s{2,}---\s*$/, /^\s{2,}\.\.\.\s*$/] }; const allPatterns = [...patterns, ...(frameworkPatterns[framework.id] ?? [])]; @@ -142,7 +143,9 @@ function collectAssertionMessages(lines: string[]): string[] { /Error:\s+expect\(.*/i, /^\s*E\s+AssertionError:.*/i, /thread '.*' panicked at.*/i, - /\bpanic:.*/i + /\bpanic:.*/i, + /^not ok \d+\b.*/i, + /^\s{4}(?:message|severity|operator|expected|actual):\s+.*/i ]; for (const line of lines) { @@ -243,7 +246,10 @@ function collectSummaryLines(lines: string[]): string[] { /^FAIL\s+.+/i, /^test result: FAILED\./i, /^error: test failed/i, - /^failures:\s*$/i + /^failures:\s*$/i, + /^TAP version \d+/i, + /^not ok \d+\b/i, + /^# (?:tests|pass|fail|skip|todo)\b/i ]; return uniqueStrings( diff --git a/src/types.ts b/src/types.ts index c966a71..d51d21b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,6 +7,7 @@ export type FrameworkId = | "playwright" | "go-test" | "cargo-test" + | "tap" | "node" | "unknown"; diff --git a/test/detect.test.ts b/test/detect.test.ts index ac627ba..b253be2 100644 --- a/test/detect.test.ts +++ b/test/detect.test.ts @@ -13,4 +13,10 @@ describe("detectFramework", () => { expect(framework.id).toBe("pytest"); expect(framework.confidence).not.toBe("low"); }); + + it("detects TAP from version header and failing test lines", () => { + const framework = detectFramework([], "TAP version 13\nnot ok 2 should validate payload\n# fail 1"); + expect(framework.id).toBe("tap"); + expect(framework.confidence).toBe("high"); + }); }); diff --git a/test/extract.test.ts b/test/extract.test.ts index 2e3c376..e09d9e1 100644 --- a/test/extract.test.ts +++ b/test/extract.test.ts @@ -22,6 +22,27 @@ const playwrightLog = ` attachment #3: trace (application/zip) - test-results/login-works-chromium/trace.zip `; +const tapLog = ` +TAP version 13 +# Subtest: api validation + ok 1 accepts valid payload + not ok 2 rejects missing user id + --- + message: "expected validation error" + severity: fail + operator: equal + expected: true + actual: false + ... + ok 3 accepts optional metadata +not ok 1 api validation +ok 2 config loads +1..2 +# tests 2 +# pass 1 +# fail 1 +`; + describe("extractFailures", () => { it("extracts blocks, assertion messages, summaries, and file references", () => { const extraction = extractFailures(vitestLog, detectFramework(["vitest"], vitestLog)); @@ -41,4 +62,13 @@ describe("extractFailures", () => { expect.objectContaining({ kind: "trace", path: "test-results/login-works-chromium/trace.zip" }) ]); }); + + it("extracts TAP failing tests, diagnostics, and summary counts", () => { + const extraction = extractFailures(tapLog, detectFramework([], tapLog)); + + expect(extraction.blocks[0]?.text).toContain("not ok 2 rejects missing user id"); + expect(extraction.assertionMessages).toContain("not ok 2 rejects missing user id"); + expect(extraction.assertionMessages).toContain("message: \"expected validation error\""); + expect(extraction.summaryLines).toEqual(expect.arrayContaining(["TAP version 13", "# tests 2", "# fail 1"])); + }); });