Files
SnapOtter/tests/qa/settings-extended.qa.spec.ts
T
SnapOtterandGitHub bd1838e40b fix: repair docker validation QA tooling, dispatcher crash-accounting, and image-enhancement RAW hang (#391)
Found and fixed during a full local Docker build validation (amd64/arm64, all
four fleet targets, AI bundle installs, QA harness) and the follow-up bug
sweep requested afterward. None of the affected scripts run in CI, so these
had been silently broken indefinitely.

- docker/feature-manifest.json: pythonVersion was a flat "3.11", but the
  amd64 base (Ubuntu 24.04) ships Python 3.12 while arm64 (Debian bookworm)
  ships 3.11. Changed to a per-arch object matching the file's existing
  convention.
- tests/qa/api-sweep.mts and verify-ai.mts: bare "@snapotter/shared" import
  can't resolve since tests/ is not a pnpm workspace member, making both
  silently unrunnable via their own documented command on any fresh
  checkout. Switched to a relative import.
- tests/qa/generate-ledger.mts: wrote to docs/qa/ without creating the
  directory first; docs/ is gitignored except COMMUNITY_GUIDE.md, so a fresh
  checkout threw ENOENT.
- Seven QA Playwright spec files (input-preview, settings,
  settings-extended, multifile, output-preview, pipeline-ui, smoke) had
  ~115 fixture() calls using directory names that don't exist. Resolved
  every call programmatically against the real fixture tree.
- packages/ai/src/bridge.ts: AI dispatcher restart (happens on every bundle
  install) was falsely counted as a crash, risking permanent dispatcher
  disable after enough legitimate restarts within the crash window. Added a
  shuttingDown flag checked at all three recordCrash() call sites.
- packages/image-engine/src/operations/auto-enhance.ts: image-enhancement
  hung 40+ seconds on large RAW photos (confirmed on a real 20.2MP file) in
  Sharp's .clahe() step, whose cost scales with total pixel count regardless
  of tile size. Added a 16-megapixel cap above which CLAHE is skipped;
  verified against the real file (40+s -> 2.0s) with no regression to other
  RAW formats or normal-sized images. Fixing this surfaced a second,
  smaller bug where the saturation step's CLAHE compensation boost was
  keyed off the raw toggle instead of whether CLAHE actually ran.
- Two QA-harness robustness gaps closed per "fix everything, even the small
  bugs": the passport-photo/erase-object input-preview tests now skip
  cleanly with a clear reason on a container without their AI bundle
  installed, and docker-compose.qa.yml's hardcoded project/container name
  (the actual root cause of a mid-validation container swap between two
  concurrent sessions) is now parameterized via QA_PROJECT_NAME.

Full validation report is local-only per repo convention.
2026-07-02 14:21:13 +08:00

1616 lines
48 KiB
TypeScript

/**
* SETTINGS-SEMANTIC QA sweep -- EXTENDED: covers tools NOT in settings.qa.spec.ts.
*
* For each tool we vary settings, process, download, and assert the output
* changed as expected via decode oracles (imageInfo, imageLuma, imageSaturation,
* probeMedia, mediaDuration, magicMatches). Where a setting's effect is NOT
* decode-verifiable, we confirm "valid output + no crash" and label it honestly.
*
* Run:
* pnpm playwright test --config tests/qa/playwright.qa.config.ts \
* tests/qa/settings-extended.qa.spec.ts --workers=1
*/
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { expect, type Page, test } from "@playwright/test";
import {
downloadResult,
fixture,
gotoTool,
imageInfo,
imageLuma,
imageSaturation,
instrument,
isClean,
issuesSummary,
magicMatches,
mediaDuration,
type PageIssues,
probeMedia,
processTool,
signalStats,
uploadFiles,
} from "./qa-helpers";
// ── Findings accumulator ─────────────────────────────────────────────
interface Finding {
tool: string;
setting: string;
value: string;
expected: string;
actual: string;
severity: "bug" | "warn";
}
const findings: Finding[] = [];
const coverage: Record<
string,
{ tested: number; total: number; level: "semantic" | "valid-output" }
> = {};
function bug(f: Omit<Finding, "severity">) {
findings.push({ ...f, severity: "bug" });
}
function warn(f: Omit<Finding, "severity">) {
findings.push({ ...f, severity: "warn" });
}
// ── Fixtures ─────────────────────────────────────────────────────────
const IMG = fixture("image", "valid", "test-200x150.png");
const IMG_JPG = fixture("image", "valid", "sample-photo.jpg");
const VID = fixture("video", "formats", "tiny.mp4");
const AUD = fixture("audio", "formats", "tiny.mp3");
const AUD_WAV = fixture("audio", "formats", "tone-stereo.wav");
const AUD_GAP = fixture("audio", "formats", "tone-gap.wav");
const PDF3 = fixture("document", "valid", "test-3page.pdf");
const GIF = fixture("image", "valid", "animated.gif");
const CSV = fixture("data", "valid", "tiny.csv");
const JSON_F = fixture("data", "valid", "tiny.json");
const YAML_F = fixture("data", "valid", "tiny.yaml");
const XML_F = fixture("data", "valid", "tiny.xml");
const ENCRYPTED_PDF = fixture("document", "valid", "encrypted.pdf");
// ── Helpers ──────────────────────────────────────────────────────────
const TOOL_TIMEOUT = 180_000;
async function fillInput(page: Page, id: string, value: string | number) {
const input = page.locator(`#${id}`);
await input.waitFor({ state: "visible", timeout: 10_000 });
await input.fill(String(value));
}
async function selectOption(page: Page, id: string, value: string) {
const sel = page.locator(`#${id}`);
await sel.waitFor({ state: "visible", timeout: 10_000 });
await sel.selectOption(value);
}
async function setupTool(page: Page, toolId: string, file: string) {
await gotoTool(page, toolId);
await uploadFiles(page, file);
const submit = page.getByTestId(`${toolId}-submit`);
await submit.first().waitFor({ state: "visible", timeout: 30_000 });
}
async function processAndDownload(page: Page, toolId: string, hint: "fast" | "long" = "fast") {
const result = await processTool(page, toolId, hint);
if (!result.ok) {
return { ok: false as const, error: result.error, path: "", size: 0, buf: Buffer.alloc(0) };
}
const dl = await downloadResult(page, toolId);
return { ok: true as const, ...dl };
}
// ── Write findings report ────────────────────────────────────────────
test.afterAll(async () => {
const reportPath = path.join(
__dirname,
"..",
"..",
"docs",
"qa",
"findings-settings-extended.md",
);
const semTools = Object.entries(coverage).filter(([, c]) => c.level === "semantic");
const voTools = Object.entries(coverage).filter(([, c]) => c.level === "valid-output");
const lines: string[] = [
"# Settings-Semantic QA Findings (Extended)",
"",
`Generated: ${new Date().toISOString()}`,
"",
"## Coverage Summary",
"",
`- **New tools covered in this file**: ${Object.keys(coverage).length}`,
`- **Semantic verification**: ${semTools.length} tools`,
`- **Valid-output-only**: ${voTools.length} tools`,
`- **Original file (settings.qa.spec.ts)**: 33 tools`,
`- **Running total**: ${33 + Object.keys(coverage).length} of 240 tools`,
"",
];
const bugs = findings.filter((f) => f.severity === "bug");
const warns = findings.filter((f) => f.severity === "warn");
if (bugs.length === 0 && warns.length === 0) {
lines.push("## No bugs found", "");
} else {
lines.push(`## Summary: ${bugs.length} bug(s), ${warns.length} warning(s)`, "");
if (bugs.length) {
lines.push("## Bugs", "");
lines.push("| Tool | Setting | Value | Expected | Actual |");
lines.push("|------|---------|-------|----------|--------|");
for (const f of bugs) {
lines.push(`| ${f.tool} | ${f.setting} | ${f.value} | ${f.expected} | ${f.actual} |`);
}
}
if (warns.length) {
lines.push("", "## Warnings", "");
lines.push("| Tool | Setting | Value | Expected | Actual |");
lines.push("|------|---------|-------|----------|--------|");
for (const f of warns) {
lines.push(`| ${f.tool} | ${f.setting} | ${f.value} | ${f.expected} | ${f.actual} |`);
}
}
}
lines.push("", "## Per-Tool Coverage", "");
lines.push("| Tool | Settings Tested | Total Settings | Verification Level |");
lines.push("|------|----------------|----------------|--------------------|");
for (const [tool, cov] of Object.entries(coverage).sort((a, b) => a[0].localeCompare(b[0]))) {
lines.push(`| ${tool} | ${cov.tested} | ${cov.total} | ${cov.level} |`);
}
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
fs.writeFileSync(reportPath, lines.join("\n") + "\n");
});
// =====================================================================
// VIDEO TOOLS
// =====================================================================
test.describe("VIDEO: convert-video", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const fmt of ["mp4", "webm"] as const) {
test(`format=${fmt} -> output magic matches`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "convert-video", VID);
await selectOption(page, "cv-format", fmt);
const dl = await processAndDownload(page, "convert-video", "long");
if (!dl.ok) {
bug({
tool: "convert-video",
setting: "format",
value: fmt,
expected: `valid ${fmt}`,
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, fmt);
if (!matches)
bug({
tool: "convert-video",
setting: "format",
value: fmt,
expected: `${fmt} magic bytes`,
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
}
test("quality=small -> smaller output than quality=high", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "convert-video", VID);
await selectOption(page, "cv-format", "mp4");
await selectOption(page, "cv-quality", "high");
const dlHigh = await processAndDownload(page, "convert-video", "long");
await setupTool(page, "convert-video", VID);
await selectOption(page, "cv-format", "mp4");
await selectOption(page, "cv-quality", "small");
const dlSmall = await processAndDownload(page, "convert-video", "long");
if (!dlHigh.ok || !dlSmall.ok) {
bug({
tool: "convert-video",
setting: "quality",
value: "high vs small",
expected: "both succeed",
actual: `high:${dlHigh.ok} small:${dlSmall.ok}`,
});
return;
}
// Small quality should produce a smaller (or equal) file
if (dlSmall.size > dlHigh.size * 1.2)
warn({
tool: "convert-video",
setting: "quality",
value: "small vs high",
expected: "small <= high",
actual: `small=${dlSmall.size} high=${dlHigh.size}`,
});
});
coverage["convert-video"] = { tested: 3, total: 3, level: "semantic" };
});
test.describe("VIDEO: compress-video", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const q of ["light", "balanced", "strong"] as const) {
test(`quality=${q} -> valid video output`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "compress-video", VID);
await selectOption(page, "cpv-quality", q);
const dl = await processAndDownload(page, "compress-video", "long");
if (!dl.ok) {
bug({
tool: "compress-video",
setting: "quality",
value: q,
expected: "valid video",
actual: dl.error ?? "error",
});
return;
}
expect(dl.size).toBeGreaterThan(0);
expect(magicMatches(dl.buf, "mp4")).toBe(true);
});
}
coverage["compress-video"] = { tested: 3, total: 4, level: "valid-output" };
});
test.describe("VIDEO: mute-video", () => {
test.setTimeout(TOOL_TIMEOUT);
test("mute -> output has no audio stream", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "mute-video", VID);
const dl = await processAndDownload(page, "mute-video", "long");
if (!dl.ok) {
bug({
tool: "mute-video",
setting: "default",
value: "mute",
expected: "no audio stream",
actual: dl.error ?? "error",
});
return;
}
const probe = probeMedia(dl.path);
const audioStream = probe.streams?.find((s) => s.codec_type === "audio");
if (audioStream)
bug({
tool: "mute-video",
setting: "default",
value: "mute",
expected: "no audio stream",
actual: "audio stream present",
});
expect(audioStream).toBeUndefined();
});
coverage["mute-video"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("VIDEO: reverse-video", () => {
test.setTimeout(TOOL_TIMEOUT);
test("reverse -> valid video, duration preserved", async ({ page }) => {
const issues = instrument(page);
const baseDur = mediaDuration(VID);
await setupTool(page, "reverse-video", VID);
const dl = await processAndDownload(page, "reverse-video", "long");
if (!dl.ok) {
bug({
tool: "reverse-video",
setting: "default",
value: "reverse",
expected: "valid video",
actual: dl.error ?? "error",
});
return;
}
const dur = mediaDuration(dl.path);
if (Math.abs(dur - baseDur) > 0.5)
warn({
tool: "reverse-video",
setting: "default",
value: "reverse",
expected: `dur ~${baseDur.toFixed(2)}s`,
actual: `dur=${dur.toFixed(2)}s`,
});
expect(dl.size).toBeGreaterThan(0);
});
coverage["reverse-video"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("VIDEO: video-color", () => {
test.setTimeout(TOOL_TIMEOUT);
test("brightness=0.5 -> valid video output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "video-color", VID);
await fillInput(page, "vc-brightness", 0.5);
const dl = await processAndDownload(page, "video-color", "long");
if (!dl.ok) {
bug({
tool: "video-color",
setting: "brightness",
value: "0.5",
expected: "valid video",
actual: dl.error ?? "error",
});
return;
}
expect(dl.size).toBeGreaterThan(0);
});
test("saturation=0 (grayscale) -> valid video output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "video-color", VID);
await fillInput(page, "vc-saturation", 0);
const dl = await processAndDownload(page, "video-color", "long");
if (!dl.ok) {
bug({
tool: "video-color",
setting: "saturation",
value: "0",
expected: "valid video",
actual: dl.error ?? "error",
});
return;
}
expect(dl.size).toBeGreaterThan(0);
});
coverage["video-color"] = { tested: 2, total: 4, level: "valid-output" };
});
test.describe("VIDEO: extract-audio", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const fmt of ["mp3", "wav"] as const) {
test(`format=${fmt} -> audio output with correct magic`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "extract-audio", VID);
await selectOption(page, "ea-format", fmt);
const dl = await processAndDownload(page, "extract-audio", "long");
if (!dl.ok) {
bug({
tool: "extract-audio",
setting: "format",
value: fmt,
expected: `${fmt} audio`,
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, fmt);
if (!matches)
bug({
tool: "extract-audio",
setting: "format",
value: fmt,
expected: `${fmt} magic bytes`,
actual: "magic mismatch",
});
expect(matches).toBe(true);
// Verify it's an audio file (no video stream)
const probe = probeMedia(dl.path);
const audioS = probe.streams?.find((s) => s.codec_type === "audio");
expect(audioS).toBeDefined();
});
}
coverage["extract-audio"] = { tested: 2, total: 3, level: "semantic" };
});
test.describe("VIDEO: video-to-gif", () => {
test.setTimeout(TOOL_TIMEOUT);
test("default settings -> gif magic bytes", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "video-to-gif", VID);
await fillInput(page, "vtg-fps", 8);
await fillInput(page, "vtg-width", 64);
const dl = await processAndDownload(page, "video-to-gif", "long");
if (!dl.ok) {
bug({
tool: "video-to-gif",
setting: "default",
value: "fps=8,width=64",
expected: "gif output",
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, "gif");
if (!matches)
bug({
tool: "video-to-gif",
setting: "format",
value: "gif",
expected: "GIF magic bytes",
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
coverage["video-to-gif"] = { tested: 1, total: 4, level: "semantic" };
});
test.describe("VIDEO: video-to-webp", () => {
test.setTimeout(TOOL_TIMEOUT);
test("default -> webp magic bytes", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "video-to-webp", VID);
await fillInput(page, "v2w-fps", 8);
await fillInput(page, "v2w-width", 64);
const dl = await processAndDownload(page, "video-to-webp", "long");
if (!dl.ok) {
bug({
tool: "video-to-webp",
setting: "default",
value: "fps=8,width=64",
expected: "webp output",
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, "webp");
if (!matches)
bug({
tool: "video-to-webp",
setting: "format",
value: "webp",
expected: "WebP magic bytes",
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
coverage["video-to-webp"] = { tested: 1, total: 4, level: "semantic" };
});
test.describe("VIDEO: video-to-frames", () => {
test.setTimeout(TOOL_TIMEOUT);
test("mode=all -> zip output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "video-to-frames", VID);
await selectOption(page, "v2f-mode", "all");
const dl = await processAndDownload(page, "video-to-frames", "long");
if (!dl.ok) {
bug({
tool: "video-to-frames",
setting: "mode",
value: "all",
expected: "zip output",
actual: dl.error ?? "error",
});
return;
}
const isZip = magicMatches(dl.buf, "zip");
if (!isZip)
bug({
tool: "video-to-frames",
setting: "mode",
value: "all",
expected: "zip magic bytes",
actual: "not a zip",
});
expect(isZip).toBe(true);
});
coverage["video-to-frames"] = { tested: 1, total: 3, level: "semantic" };
});
test.describe("VIDEO: video-loudnorm", () => {
test.setTimeout(TOOL_TIMEOUT);
test("normalize -> valid video, duration preserved", async ({ page }) => {
const issues = instrument(page);
const baseDur = mediaDuration(VID);
await setupTool(page, "video-loudnorm", VID);
const dl = await processAndDownload(page, "video-loudnorm", "long");
if (!dl.ok) {
bug({
tool: "video-loudnorm",
setting: "default",
value: "normalize",
expected: "valid video",
actual: dl.error ?? "error",
});
return;
}
const dur = mediaDuration(dl.path);
if (Math.abs(dur - baseDur) > 0.5)
warn({
tool: "video-loudnorm",
setting: "default",
value: "normalize",
expected: `dur ~${baseDur.toFixed(2)}s`,
actual: `dur=${dur.toFixed(2)}s`,
});
expect(dl.size).toBeGreaterThan(0);
});
coverage["video-loudnorm"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("VIDEO: aspect-pad", () => {
test.setTimeout(TOOL_TIMEOUT);
test("target=1:1 -> output is square", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "aspect-pad", VID);
await selectOption(page, "ap-target", "1:1");
const dl = await processAndDownload(page, "aspect-pad", "long");
if (!dl.ok) {
bug({
tool: "aspect-pad",
setting: "target",
value: "1:1",
expected: "square video",
actual: dl.error ?? "error",
});
return;
}
const probe = probeMedia(dl.path);
const vs = probe.streams?.find((s) => s.codec_type === "video");
if (vs && vs.width !== vs.height)
bug({
tool: "aspect-pad",
setting: "target",
value: "1:1",
expected: "w==h",
actual: `${vs.width}x${vs.height}`,
});
expect(vs?.width).toBe(vs?.height);
});
test("target=16:9 -> output ratio ~16:9", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "aspect-pad", VID);
await selectOption(page, "ap-target", "16:9");
const dl = await processAndDownload(page, "aspect-pad", "long");
if (!dl.ok) {
bug({
tool: "aspect-pad",
setting: "target",
value: "16:9",
expected: "16:9 ratio",
actual: dl.error ?? "error",
});
return;
}
const probe = probeMedia(dl.path);
const vs = probe.streams?.find((s) => s.codec_type === "video");
if (vs) {
const ratio = vs.width! / vs.height!;
if (Math.abs(ratio - 16 / 9) > 0.1)
bug({
tool: "aspect-pad",
setting: "target",
value: "16:9",
expected: `ratio ~${(16 / 9).toFixed(2)}`,
actual: `ratio=${ratio.toFixed(2)}`,
});
}
});
coverage["aspect-pad"] = { tested: 2, total: 2, level: "semantic" };
});
test.describe("VIDEO: blur-pad", () => {
test.setTimeout(TOOL_TIMEOUT);
test("target=1:1 -> output is square", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "blur-pad", VID);
await selectOption(page, "bp-target", "1:1");
const dl = await processAndDownload(page, "blur-pad", "long");
if (!dl.ok) {
bug({
tool: "blur-pad",
setting: "target",
value: "1:1",
expected: "square video",
actual: dl.error ?? "error",
});
return;
}
const probe = probeMedia(dl.path);
const vs = probe.streams?.find((s) => s.codec_type === "video");
if (vs && vs.width !== vs.height)
bug({
tool: "blur-pad",
setting: "target",
value: "1:1",
expected: "w==h",
actual: `${vs.width}x${vs.height}`,
});
expect(vs?.width).toBe(vs?.height);
});
coverage["blur-pad"] = { tested: 1, total: 2, level: "semantic" };
});
test.describe("VIDEO: gif-to-video", () => {
test.setTimeout(TOOL_TIMEOUT);
test("format=mp4 -> mp4 magic bytes", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "gif-to-video", GIF);
await selectOption(page, "g2v-format", "mp4");
const dl = await processAndDownload(page, "gif-to-video", "long");
if (!dl.ok) {
bug({
tool: "gif-to-video",
setting: "format",
value: "mp4",
expected: "mp4 output",
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, "mp4");
if (!matches)
bug({
tool: "gif-to-video",
setting: "format",
value: "mp4",
expected: "mp4 magic bytes",
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
coverage["gif-to-video"] = { tested: 1, total: 2, level: "semantic" };
});
// =====================================================================
// AUDIO TOOLS
// =====================================================================
test.describe("AUDIO: convert-audio", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const fmt of ["wav", "ogg", "flac"] as const) {
test(`format=${fmt} -> output magic matches`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "convert-audio", AUD);
await selectOption(page, "ca-format", fmt);
const dl = await processAndDownload(page, "convert-audio", "long");
if (!dl.ok) {
bug({
tool: "convert-audio",
setting: "format",
value: fmt,
expected: `${fmt} audio`,
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, fmt);
if (!matches)
bug({
tool: "convert-audio",
setting: "format",
value: fmt,
expected: `${fmt} magic bytes`,
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
}
coverage["convert-audio"] = { tested: 3, total: 5, level: "semantic" };
});
test.describe("AUDIO: reverse-audio", () => {
test.setTimeout(TOOL_TIMEOUT);
test("reverse -> same duration, valid audio", async ({ page }) => {
const issues = instrument(page);
const baseDur = mediaDuration(AUD);
await setupTool(page, "reverse-audio", AUD);
const dl = await processAndDownload(page, "reverse-audio", "long");
if (!dl.ok) {
bug({
tool: "reverse-audio",
setting: "default",
value: "reverse",
expected: "valid audio",
actual: dl.error ?? "error",
});
return;
}
const dur = mediaDuration(dl.path);
if (Math.abs(dur - baseDur) > 0.5)
warn({
tool: "reverse-audio",
setting: "default",
value: "reverse",
expected: `dur ~${baseDur.toFixed(2)}s`,
actual: `dur=${dur.toFixed(2)}s`,
});
expect(dl.size).toBeGreaterThan(0);
});
coverage["reverse-audio"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("AUDIO: pitch-shift", () => {
test.setTimeout(TOOL_TIMEOUT);
test("semitones=6 -> same duration, valid audio", async ({ page }) => {
const issues = instrument(page);
const baseDur = mediaDuration(AUD);
await setupTool(page, "pitch-shift", AUD);
await fillInput(page, "ps-semi", 6);
const dl = await processAndDownload(page, "pitch-shift", "long");
if (!dl.ok) {
bug({
tool: "pitch-shift",
setting: "semitones",
value: "6",
expected: "valid audio, same duration",
actual: dl.error ?? "error",
});
return;
}
const dur = mediaDuration(dl.path);
// Pitch shift should preserve duration
if (Math.abs(dur - baseDur) > 0.5)
bug({
tool: "pitch-shift",
setting: "semitones",
value: "6",
expected: `dur ~${baseDur.toFixed(2)}s`,
actual: `dur=${dur.toFixed(2)}s`,
});
expect(dl.size).toBeGreaterThan(0);
});
test("semitones=-6 -> valid audio", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "pitch-shift", AUD);
await fillInput(page, "ps-semi", -6);
const dl = await processAndDownload(page, "pitch-shift", "long");
if (!dl.ok)
bug({
tool: "pitch-shift",
setting: "semitones",
value: "-6",
expected: "success",
actual: dl.error ?? "error",
});
expect(dl.ok).toBe(true);
});
coverage["pitch-shift"] = { tested: 2, total: 2, level: "semantic" };
});
test.describe("AUDIO: silence-removal", () => {
test.setTimeout(TOOL_TIMEOUT);
test("default threshold on tone-gap -> valid audio output", async ({ page }) => {
const issues = instrument(page);
// tone-gap.wav has silent gaps; removal should produce a shorter file
await setupTool(page, "silence-removal", AUD_GAP);
await fillInput(page, "sr-threshold", -40);
await fillInput(page, "sr-minsilence", 0.2);
const dl = await processAndDownload(page, "silence-removal", "long");
if (!dl.ok) {
bug({
tool: "silence-removal",
setting: "threshold",
value: "-40dB",
expected: "valid audio",
actual: dl.error ?? "error",
});
return;
}
expect(dl.size).toBeGreaterThan(0);
});
coverage["silence-removal"] = { tested: 1, total: 2, level: "valid-output" };
});
test.describe("AUDIO: noise-reduction", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const strength of ["light", "medium", "strong"] as const) {
test(`strength=${strength} -> valid audio output`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "noise-reduction", AUD);
await selectOption(page, "nr-strength", strength);
const dl = await processAndDownload(page, "noise-reduction", "long");
if (!dl.ok) {
bug({
tool: "noise-reduction",
setting: "strength",
value: strength,
expected: "valid audio",
actual: dl.error ?? "error",
});
return;
}
expect(dl.size).toBeGreaterThan(0);
});
}
coverage["noise-reduction"] = { tested: 3, total: 3, level: "valid-output" };
});
test.describe("AUDIO: split-audio", () => {
test.setTimeout(TOOL_TIMEOUT);
test("mode=parts, parts=2 -> zip output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "split-audio", AUD_WAV);
await selectOption(page, "sa-mode", "parts");
// Wait for parts input to appear
await page.locator("#sa-parts").waitFor({ state: "visible", timeout: 5_000 });
await fillInput(page, "sa-parts", 2);
const dl = await processAndDownload(page, "split-audio", "long");
if (!dl.ok) {
bug({
tool: "split-audio",
setting: "mode",
value: "parts=2",
expected: "zip output",
actual: dl.error ?? "error",
});
return;
}
const isZip = magicMatches(dl.buf, "zip");
if (!isZip)
bug({
tool: "split-audio",
setting: "mode",
value: "parts=2",
expected: "zip magic bytes",
actual: "not a zip",
});
expect(isZip).toBe(true);
});
coverage["split-audio"] = { tested: 1, total: 3, level: "semantic" };
});
test.describe("AUDIO: ringtone-maker", () => {
test.setTimeout(TOOL_TIMEOUT);
test("startS=0, durationS=1 -> output dur <= 2s", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "ringtone-maker", AUD);
await fillInput(page, "rm-start", 0);
await fillInput(page, "rm-duration", 1);
const dl = await processAndDownload(page, "ringtone-maker", "long");
if (!dl.ok) {
bug({
tool: "ringtone-maker",
setting: "duration",
value: "1s",
expected: "short audio",
actual: dl.error ?? "error",
});
return;
}
const dur = mediaDuration(dl.path);
if (dur > 3.0)
bug({
tool: "ringtone-maker",
setting: "duration",
value: "1s",
expected: "dur <= 3s",
actual: `dur=${dur.toFixed(2)}s`,
});
expect(dur).toBeLessThan(3.0);
});
coverage["ringtone-maker"] = { tested: 1, total: 2, level: "semantic" };
});
test.describe("AUDIO: waveform-image", () => {
test.setTimeout(TOOL_TIMEOUT);
test("width=512, height=128 -> image with specified dims", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "waveform-image", AUD);
await fillInput(page, "wi-width", 512);
await fillInput(page, "wi-height", 128);
const dl = await processAndDownload(page, "waveform-image", "long");
if (!dl.ok) {
bug({
tool: "waveform-image",
setting: "dims",
value: "512x128",
expected: "image output 512x128",
actual: dl.error ?? "error",
});
return;
}
const info = imageInfo(dl.path);
if (info.width !== 512 || info.height !== 128)
bug({
tool: "waveform-image",
setting: "dims",
value: "512x128",
expected: "512x128",
actual: `${info.width}x${info.height}`,
});
expect(info.width).toBe(512);
expect(info.height).toBe(128);
});
coverage["waveform-image"] = { tested: 1, total: 3, level: "semantic" };
});
// =====================================================================
// DOCUMENT / PDF TOOLS
// =====================================================================
test.describe("DOCUMENT: remove-pages", () => {
test.setTimeout(TOOL_TIMEOUT);
test("remove page 2 from 3-page PDF -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "remove-pages", PDF3);
await fillInput(page, "rp-pages", "2");
const dl = await processAndDownload(page, "remove-pages", "long");
if (!dl.ok) {
bug({
tool: "remove-pages",
setting: "pages",
value: "2",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["remove-pages"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("DOCUMENT: protect-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("set password -> valid PDF output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "protect-pdf", PDF3);
await fillInput(page, "pp-user-pw", "test123");
const dl = await processAndDownload(page, "protect-pdf", "long");
if (!dl.ok) {
bug({
tool: "protect-pdf",
setting: "password",
value: "test123",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
// The output should be larger or at least different (encryption overhead)
expect(dl.size).toBeGreaterThan(0);
});
coverage["protect-pdf"] = { tested: 1, total: 2, level: "valid-output" };
});
test.describe("DOCUMENT: unlock-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("unlock with correct password -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "unlock-pdf", ENCRYPTED_PDF);
await fillInput(page, "up-password", "test123");
const dl = await processAndDownload(page, "unlock-pdf", "long");
if (!dl.ok) {
// May fail if fixture password is different; log as warning
warn({
tool: "unlock-pdf",
setting: "password",
value: "test123",
expected: "valid PDF (fixture user password is test123)",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["unlock-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: booklet-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("booklet from 3-page PDF -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "booklet-pdf", PDF3);
const dl = await processAndDownload(page, "booklet-pdf", "long");
if (!dl.ok) {
bug({
tool: "booklet-pdf",
setting: "default",
value: "default",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["booklet-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: watermark-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("text=DRAFT, position=c -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "watermark-pdf", PDF3);
await fillInput(page, "wm-text", "DRAFT");
const dl = await processAndDownload(page, "watermark-pdf", "long");
if (!dl.ok) {
bug({
tool: "watermark-pdf",
setting: "text",
value: "DRAFT",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
// Watermarked PDF should be at least as large
expect(dl.size).toBeGreaterThan(0);
});
coverage["watermark-pdf"] = { tested: 1, total: 5, level: "valid-output" };
});
test.describe("DOCUMENT: pdf-page-numbers", () => {
test.setTimeout(TOOL_TIMEOUT);
for (const pos of ["bc", "tr"] as const) {
test(`position=${pos} -> valid PDF`, async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "pdf-page-numbers", PDF3);
await selectOption(page, "pn-position", pos);
const dl = await processAndDownload(page, "pdf-page-numbers", "long");
if (!dl.ok) {
bug({
tool: "pdf-page-numbers",
setting: "position",
value: pos,
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
}
coverage["pdf-page-numbers"] = { tested: 2, total: 2, level: "valid-output" };
});
test.describe("DOCUMENT: flatten-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("flatten -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "flatten-pdf", PDF3);
const dl = await processAndDownload(page, "flatten-pdf", "long");
if (!dl.ok) {
bug({
tool: "flatten-pdf",
setting: "default",
value: "default",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["flatten-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: repair-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("repair -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "repair-pdf", PDF3);
const dl = await processAndDownload(page, "repair-pdf", "long");
if (!dl.ok) {
bug({
tool: "repair-pdf",
setting: "default",
value: "default",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["repair-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: linearize-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("linearize -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "linearize-pdf", PDF3);
const dl = await processAndDownload(page, "linearize-pdf", "long");
if (!dl.ok) {
bug({
tool: "linearize-pdf",
setting: "default",
value: "default",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["linearize-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: pdfa-convert", () => {
test.setTimeout(TOOL_TIMEOUT);
test("convert to PDF/A -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "pdfa-convert", PDF3);
const dl = await processAndDownload(page, "pdfa-convert", "long");
if (!dl.ok) {
bug({
tool: "pdfa-convert",
setting: "default",
value: "default",
expected: "valid PDF/A",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["pdfa-convert"] = { tested: 1, total: 1, level: "valid-output" };
});
test.describe("DOCUMENT: crop-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("margin=20 -> valid PDF", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "crop-pdf", PDF3);
await fillInput(page, "cpdf-margin", 20);
const dl = await processAndDownload(page, "crop-pdf", "long");
if (!dl.ok) {
bug({
tool: "crop-pdf",
setting: "margin",
value: "20",
expected: "valid PDF",
actual: dl.error ?? "error",
});
return;
}
expect(magicMatches(dl.buf, "pdf")).toBe(true);
});
coverage["crop-pdf"] = { tested: 1, total: 1, level: "valid-output" };
});
// =====================================================================
// IMAGE TOOLS
// =====================================================================
test.describe("IMAGE: color-blindness", () => {
test.setTimeout(TOOL_TIMEOUT);
test("achromatopsia -> near-zero saturation", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "color-blindness", IMG);
await selectOption(page, "cb-simulation-type", "achromatopsia");
const dl = await processAndDownload(page, "color-blindness");
if (!dl.ok) {
bug({
tool: "color-blindness",
setting: "type",
value: "achromatopsia",
expected: "grayscale output",
actual: dl.error ?? "error",
});
return;
}
const sat = imageSaturation(dl.path);
if (sat > 15)
bug({
tool: "color-blindness",
setting: "type",
value: "achromatopsia",
expected: "saturation < 15",
actual: `sat=${sat}`,
});
expect(sat).toBeLessThan(15);
});
test("protanopia -> valid image, saturation changes", async ({ page }) => {
const issues = instrument(page);
const baseSat = imageSaturation(IMG);
await setupTool(page, "color-blindness", IMG);
await selectOption(page, "cb-simulation-type", "protanopia");
const dl = await processAndDownload(page, "color-blindness");
if (!dl.ok) {
bug({
tool: "color-blindness",
setting: "type",
value: "protanopia",
expected: "valid image",
actual: dl.error ?? "error",
});
return;
}
const info = imageInfo(dl.path);
expect(info.width).toBe(200);
});
test("deuteranopia -> valid image", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "color-blindness", IMG);
await selectOption(page, "cb-simulation-type", "deuteranopia");
const dl = await processAndDownload(page, "color-blindness");
if (!dl.ok)
bug({
tool: "color-blindness",
setting: "type",
value: "deuteranopia",
expected: "success",
actual: dl.error ?? "error",
});
expect(dl.ok).toBe(true);
});
test("tritanopia -> valid image", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "color-blindness", IMG);
await selectOption(page, "cb-simulation-type", "tritanopia");
const dl = await processAndDownload(page, "color-blindness");
if (!dl.ok)
bug({
tool: "color-blindness",
setting: "type",
value: "tritanopia",
expected: "success",
actual: dl.error ?? "error",
});
expect(dl.ok).toBe(true);
});
coverage["color-blindness"] = { tested: 4, total: 8, level: "semantic" };
});
test.describe("IMAGE: image-to-pdf", () => {
test.setTimeout(TOOL_TIMEOUT);
test("default -> PDF output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "image-to-pdf", IMG);
const dl = await processAndDownload(page, "image-to-pdf");
if (!dl.ok) {
bug({
tool: "image-to-pdf",
setting: "default",
value: "default",
expected: "PDF magic bytes",
actual: dl.error ?? "error",
});
return;
}
const matches = magicMatches(dl.buf, "pdf");
if (!matches)
bug({
tool: "image-to-pdf",
setting: "format",
value: "pdf",
expected: "PDF magic bytes",
actual: "magic mismatch",
});
expect(matches).toBe(true);
});
coverage["image-to-pdf"] = { tested: 1, total: 3, level: "semantic" };
});
test.describe("IMAGE: image-enhancement", () => {
test.setTimeout(TOOL_TIMEOUT);
test("auto mode -> valid image, same dims", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "image-enhancement", IMG_JPG);
// Auto mode is the default
const dl = await processAndDownload(page, "image-enhancement");
if (!dl.ok) {
bug({
tool: "image-enhancement",
setting: "mode",
value: "auto",
expected: "valid image",
actual: dl.error ?? "error",
});
return;
}
const info = imageInfo(dl.path);
expect(info.width).toBeGreaterThan(0);
expect(info.height).toBeGreaterThan(0);
});
coverage["image-enhancement"] = { tested: 1, total: 6, level: "valid-output" };
});
// =====================================================================
// FILE / DATA TOOLS
// =====================================================================
test.describe("FILE: csv-json", () => {
test.setTimeout(TOOL_TIMEOUT);
test("pretty=true -> valid JSON output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "csv-json", CSV);
const dl = await processAndDownload(page, "csv-json");
if (!dl.ok) {
bug({
tool: "csv-json",
setting: "pretty",
value: "true",
expected: "valid JSON",
actual: dl.error ?? "error",
});
return;
}
// Verify it's valid JSON
const content = dl.buf.toString("utf8");
try {
const parsed = JSON.parse(content);
expect(Array.isArray(parsed) || typeof parsed === "object").toBe(true);
} catch {
bug({
tool: "csv-json",
setting: "pretty",
value: "true",
expected: "parseable JSON",
actual: "JSON parse error",
});
}
});
coverage["csv-json"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("FILE: json-xml", () => {
test.setTimeout(TOOL_TIMEOUT);
test("default -> valid XML output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "json-xml", JSON_F);
const dl = await processAndDownload(page, "json-xml");
if (!dl.ok) {
bug({
tool: "json-xml",
setting: "pretty",
value: "true",
expected: "valid XML",
actual: dl.error ?? "error",
});
return;
}
const content = dl.buf.toString("utf8");
// XML should start with <?xml or <
if (!content.trimStart().startsWith("<"))
bug({
tool: "json-xml",
setting: "format",
value: "xml",
expected: "starts with <",
actual: `starts with ${content.substring(0, 20)}`,
});
expect(content.trimStart().startsWith("<")).toBe(true);
});
coverage["json-xml"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("FILE: yaml-json", () => {
test.setTimeout(TOOL_TIMEOUT);
test("yaml -> valid JSON output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "yaml-json", YAML_F);
const dl = await processAndDownload(page, "yaml-json");
if (!dl.ok) {
bug({
tool: "yaml-json",
setting: "default",
value: "yaml-to-json",
expected: "valid JSON",
actual: dl.error ?? "error",
});
return;
}
const content = dl.buf.toString("utf8");
try {
JSON.parse(content);
} catch {
bug({
tool: "yaml-json",
setting: "default",
value: "yaml-to-json",
expected: "parseable JSON",
actual: "JSON parse error",
});
}
expect(dl.size).toBeGreaterThan(0);
});
coverage["yaml-json"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("FILE: csv-excel", () => {
test.setTimeout(TOOL_TIMEOUT);
test("csv -> xlsx (zip magic)", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "csv-excel", CSV);
// Process but check for inline errors too (the csv-excel tool surfaces
// errors in a <p class="text-red-500"> that waitForResult doesn't catch).
const submit = page.getByTestId("csv-excel-submit");
await submit.first().click();
// Wait up to 60s for either download button or an inline error
const deadline = Date.now() + 60_000;
let gotResult = false;
let inlineError = "";
while (Date.now() < deadline) {
const errEl = page.locator(".text-red-500").first();
if (await errEl.isVisible().catch(() => false)) {
inlineError = await errEl.innerText().catch(() => "unknown error");
gotResult = true;
break;
}
const dlBtn = page.getByTestId("csv-excel-download").first();
const genDl = page.locator("[data-download-button]").first();
if (
(await dlBtn.isVisible().catch(() => false)) ||
(await genDl.isVisible().catch(() => false))
) {
gotResult = true;
break;
}
await page.waitForTimeout(500);
}
if (inlineError) {
bug({
tool: "csv-excel",
setting: "default",
value: "csv-to-xlsx",
expected: "xlsx output",
actual: `BUG: ${inlineError}`,
});
// Do not fail test -- bug is recorded
return;
}
if (!gotResult) {
bug({
tool: "csv-excel",
setting: "default",
value: "csv-to-xlsx",
expected: "xlsx output",
actual: "timeout waiting for result (60s)",
});
return;
}
const dl = await downloadResult(page, "csv-excel");
if (dl.size === 0) {
bug({
tool: "csv-excel",
setting: "default",
value: "csv-to-xlsx",
expected: "xlsx output",
actual: "empty file",
});
return;
}
// xlsx files are zip archives
const isZip = magicMatches(dl.buf, "zip");
if (!isZip)
bug({
tool: "csv-excel",
setting: "format",
value: "xlsx",
expected: "zip/xlsx magic bytes",
actual: "magic mismatch",
});
expect(isZip).toBe(true);
});
coverage["csv-excel"] = { tested: 1, total: 1, level: "semantic" };
});
test.describe("FILE: split-csv", () => {
test.setTimeout(TOOL_TIMEOUT);
test("rowsPerFile=1 -> zip output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "split-csv", CSV);
await fillInput(page, "sc-rows", 1);
const dl = await processAndDownload(page, "split-csv");
if (!dl.ok) {
bug({
tool: "split-csv",
setting: "rowsPerFile",
value: "1",
expected: "zip output",
actual: dl.error ?? "error",
});
return;
}
const isZip = magicMatches(dl.buf, "zip");
// Could be a single CSV if only one row, or a zip
expect(dl.size).toBeGreaterThan(0);
});
coverage["split-csv"] = { tested: 1, total: 2, level: "semantic" };
});
test.describe("FILE: xml-to-csv", () => {
test.setTimeout(TOOL_TIMEOUT);
test("xml -> csv output", async ({ page }) => {
const issues = instrument(page);
await setupTool(page, "xml-to-csv", XML_F);
const dl = await processAndDownload(page, "xml-to-csv");
if (!dl.ok) {
bug({
tool: "xml-to-csv",
setting: "default",
value: "xml-to-csv",
expected: "CSV output",
actual: dl.error ?? "error",
});
return;
}
const content = dl.buf.toString("utf8");
// CSV should have comma-separated values or at least be text
expect(content.length).toBeGreaterThan(0);
});
coverage["xml-to-csv"] = { tested: 1, total: 1, level: "semantic" };
});