From 02a065b826031f99442c2c1f9d4933b80c2834ef Mon Sep 17 00:00:00 2001 From: Mathias Date: Fri, 2 Oct 2026 09:03:55 +0200 Subject: [PATCH] test(web): opt-in renderer of every progress state for a screenshot review (TAPIR_SHOT_DIR) Co-Authored-By: Claude Sonnet 5.5 --- internal/web/uploads_shots_test.go | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 internal/web/uploads_shots_test.go diff --git a/internal/web/uploads_shots_test.go b/internal/web/uploads_shots_test.go new file mode 100644 index 0000000..2a227d7 --- /dev/null +++ b/internal/web/uploads_shots_test.go @@ -0,0 +1,77 @@ +package web_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "git.d-ma.be/mathias/tapir/internal/domain" +) + +// TestRenderUploadProgressStatesForReview writes the upload page in each progress state +// to $TAPIR_SHOT_DIR as standalone HTML (light and dark), for a screenshot review of the +// visual claims tests cannot assert: that it looks like tapir and reads like a log of +// phases (ADR-035, #42). Opt-in: it does nothing unless the variable is set. The text is +// synthetic. Screenshot with any browser, for example: +// +// TAPIR_SHOT_DIR=/tmp/shots go test -run TestRenderUploadProgressStatesForReview ./internal/web +// chromium --headless --screenshot=/tmp/shots/running.png --window-size=900,900 /tmp/shots/running-light.html +func TestRenderUploadProgressStatesForReview(t *testing.T) { + dir := os.Getenv("TAPIR_SHOT_DIR") + if dir == "" { + t.Skip("set TAPIR_SHOT_DIR to render the progress states for a screenshot review") + } + require.NoError(t, os.MkdirAll(dir, 0o750)) + app, _ := newUploadApp(t, audioOK, 200<<20) + fixedClock(app) + + decide := twoEngineRun() + decide[3] = step("engine_b", "Transcribing with iguana/kb-whisper", domain.StepDone, 9*time.Minute, 4*time.Minute, 0, 0) + decide[4] = step("compare", "Comparing the two transcripts", domain.StepDone, 5*time.Minute, time.Second, 0, 0) + decide[5] = step("decide", "Deciding the differences", domain.StepRunning, 4*time.Minute+30*time.Second, 0, 142, 282) + + failed := twoEngineRun() + failed[3] = step("engine_b", "Transcribing with iguana/kb-whisper", domain.StepFailed, 3*time.Minute, time.Minute, 0, 0) + + done := []domain.ProgressStep{ + step("received", "Received", domain.StepDone, 22*time.Minute, 0, 0, 0), + step("audio", "Preparing the audio", domain.StepDone, 22*time.Minute, 41*time.Second, 0, 0), + step("engine_a", "Transcribing with iguana/pianissimo", domain.StepDone, 21*time.Minute, 2*time.Minute+10*time.Second, 0, 0), + step("engine_b", "Transcribing with iguana/kb-whisper", domain.StepDone, 19*time.Minute, 4*time.Minute+5*time.Second, 0, 0), + step("compare", "Comparing the two transcripts", domain.StepDone, 15*time.Minute, 2*time.Second, 0, 0), + step("decide", "Deciding the differences", domain.StepDone, 15*time.Minute, 14*time.Minute, 282, 282), + step("save", "Saving the transcript", domain.StepDone, time.Minute, time.Second, 0, 0), + } + + cases := []struct { + name, status, content string + steps []domain.ProgressStep + progressAgo, aliveAgo time.Duration + }{ + {"running", "transcribing", "", twoEngineRun(), 10 * time.Second, 4 * time.Second}, + {"deciding", "transcribing", "", decide, 3 * time.Second, 3 * time.Second}, + {"quiet", "transcribing", "", decide, 6 * time.Minute, 4 * time.Second}, + {"lost", "transcribing", "", twoEngineRun(), 3 * time.Minute, 3 * time.Minute}, + {"failed", "failed", "transcription_failed", failed, 2 * time.Minute, 2 * time.Minute}, + {"done", "done", "Synthetic transcript text for the review.", done, time.Minute, time.Minute}, + {"queued", "queued", "", nil, 0, 0}, + } + for _, c := range cases { + id := seedUpload(t, userID, "Project notes (synthetic)", c.status, c.content) + if c.steps != nil { + seedProgress(t, id, c.steps, progressNow.Add(-c.progressAgo), progressNow.Add(-c.aliveAgo)) + } + page := body(t, do(t, app, get("/uploads/"+id))) + for _, theme := range []string{"light", "dark"} { + html := page + if theme == "dark" { + html = strings.Replace(html, "