Skip to content

test(tui): screenshot snapshot tests for OutroScreen / RegionSelect / Logout / SettingsOverride#262

Merged
kelsonpw merged 3 commits into
mainfrom
kelsonpw/tui-screenshot-tests
Apr 26, 2026
Merged

test(tui): screenshot snapshot tests for OutroScreen / RegionSelect / Logout / SettingsOverride#262
kelsonpw merged 3 commits into
mainfrom
kelsonpw/tui-screenshot-tests

Conversation

@kelsonpw

@kelsonpw kelsonpw commented Apr 25, 2026

Copy link
Copy Markdown
Member

Summary

The TUI is a chain of Ink screens (src/ui/tui/screens/). Today there are no rendered-output tests — only assertions on a few pure helpers. A change to layout, copy, padding, or color silently slips through unless someone happens to run pnpm try and notice. Snapshot tests catch unintended visual regressions: every PR that changes a rendered frame fails until reviewed and updated with pnpm test -u.

This PR is independent of the sub-agent stack and the wizard-cleanup PR — lands straight onto main, can review and merge in parallel with everything else in the queue.

What's in

  • src/ui/tui/__tests__/snapshot-utils.tsx (new) — two small helpers:
    • renderSnapshot(element, store) — renders via ink-testing-library, strips ANSI color escapes, trims trailing whitespace per line. Output is identical on macOS / Linux / CI so snapshots don't drift.
    • makeStoreForSnapshot(patch) — fresh WizardStore with optional WizardSession overrides so tests can target specific branches (success/error/cancel, region forced, settings conflict, …) without running the wizard flow.
  • 8 snapshots across 4 screens — covers the highest-churn TUI surfaces:
    • OutroScreen — success (with changes + report link), error (with docs fallback), cancel
    • RegionSelectScreen — first-time picker, /region-forced switch
    • LogoutScreen — initial confirm prompt
    • SettingsOverrideScreen — empty (no overrides) and conflict modal
  • vitest.config.ts — include *.test.tsx so JSX tests are picked up
  • ink-testing-library added as devDep (5 KB, stable)

Why these 4 screens first

These are the highest-blast-radius surfaces today. Every PR that touches signup, region resolution, settings handling, or auth has touched one of them in the last month. Snapshot coverage here would have caught accidental copy regressions in #156, #234, #237, and the env-selection prompt rewording in #257.

Sample snapshot — what reviewers see in PRs

SettingsOverrideScreen > renders the conflict modal when settings override blocking env vars
"                  ╭──────────────────────────────────────────────────────────────╮
                   │                                                              │
                   │                     ◆ Settings Conflict                      │
                   │                                                              │
                   │   Your project's .claude/settings.json sets:                 │
                   │                                                              │
                   │     ● ANTHROPIC_BASE_URL                                     │
                   │     ● ANTHROPIC_AUTH_TOKEN                                   │
                   │                                                              │
                   │   These overrides prevent the Wizard from reaching the       │
                   │   Amplitude LLM Gateway. We can back up the file and         │
                   │   continue.                                                  │
                   │   ────────────────────────────────────────────────────────   │
                   │    Back up to .wizard-backup and continue?                   │
                   │       Backup & continue [Enter]                              │
                   │       Exit [Esc]                                             │
                   ╰──────────────────────────────────────────────────────────────╯"

Reviewer can read it, spot the change, and decide whether to approve or request rework — no need to checkout the branch and pnpm try.

Updating a snapshot intentionally

pnpm test -u       # vitest --update; review all diffs first
# or per-file:
pnpm vitest run src/ui/tui/screens/__tests__/OutroScreen.snap.test.tsx -u

Determinism guarantees

  • Width pinned by ink-testing-library's fake stdout (no real TTY)
  • ANSI escapes stripped (chalk colors don't pollute diffs)
  • Trailing whitespace trimmed (Ink right-pads to terminal width)
  • Spinners / async effects NOT exercised — caller pre-sets a stable session state via sessionPatch. Adding a screen with animation? Pass frame={0} to BrailleSpinner.

Test plan

  • pnpm test1245 passed, 17 skipped (12 new tests across 4 new files)
  • pnpm tsc --noEmit clean
  • pnpm lint clean
  • pnpm test -u regenerates all 8 snapshots cleanly
  • Smoke: deliberately break a copy string, verify snapshot test fails with a readable diff in PR review

Out of scope (follow-up — ~5 min per screen)

  • IntroScreen, AuthScreen, RunScreen, McpScreen, SetupScreen, CreateProjectScreen, DataSetupScreen, DataIngestionCheckScreen, LoginScreen, SlackScreen, OutageScreen, FeatureOptInScreen, ActivationOptionsScreen. Each takes ~1 sessionPatch + 1-3 it() blocks. Land them as the screens are touched.
  • Width-variant snapshots (e.g. narrow terminals where AmplitudeTextLogo collapses). The helper supports it via additional render options; not exercised here.
  • Visual regression CI integration (e.g. tag PRs with "✕ 2 snapshots changed"). Vitest already fails the build on diff; a custom CI annotation is gilding.

cc @amplitude/growth

🤖 Generated with Claude Code


Note

Low Risk
Low risk: changes are limited to dev dependencies and Vitest configuration, plus new snapshot tests/utilities that don’t affect production runtime behavior.

Overview
Adds deterministic snapshot testing for the Ink-based TUI by introducing a snapshot-utils helper that renders screens via ink-testing-library and normalizes output (strips ANSI escapes and trims trailing whitespace).

Introduces snapshot tests (and committed snapshots) for OutroScreen, RegionSelectScreen, LogoutScreen, and SettingsOverrideScreen across several key states, and updates vitest.config.ts to include *.test.tsx/*.spec.tsx so the new JSX tests run.

Reviewed by Cursor Bugbot for commit 45f7c6f. Bugbot is set up for automated code reviews on this repo. Configure here.

@kelsonpw kelsonpw requested a review from a team as a code owner April 25, 2026 23:47
@kelsonpw kelsonpw requested a review from a team April 25, 2026 23:47
@github-actions

Copy link
Copy Markdown
Contributor

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci django
  • /wizard-ci fastapi
  • /wizard-ci flask
  • /wizard-ci javascript-node
  • /wizard-ci javascript-web
  • /wizard-ci next-js
  • /wizard-ci python
  • /wizard-ci react-router
  • /wizard-ci vue

Test an individual app:

  • /wizard-ci django/django3-saas
  • /wizard-ci fastapi/fastapi3-ai-saas
  • /wizard-ci flask/flask3-social-media
Show more apps
  • /wizard-ci javascript-node/express-todo
  • /wizard-ci javascript-node/fastify-blog
  • /wizard-ci javascript-node/hono-links
  • /wizard-ci javascript-node/koa-notes
  • /wizard-ci javascript-node/native-http-contacts
  • /wizard-ci javascript-web/saas-dashboard
  • /wizard-ci next-js/15-app-router-saas
  • /wizard-ci next-js/15-app-router-todo
  • /wizard-ci next-js/15-pages-router-saas
  • /wizard-ci next-js/15-pages-router-todo
  • /wizard-ci python/meeting-summarizer
  • /wizard-ci react-router/react-router-v7-project
  • /wizard-ci react-router/rrv7-starter
  • /wizard-ci react-router/saas-template
  • /wizard-ci react-router/shopper
  • /wizard-ci vue/movies

Results will be posted here when complete.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Exported SnapshotOptions interface is unused dead code
    • Removed the unused SnapshotOptions interface and its sole consumer, the Flow type import, since neither was referenced anywhere in the codebase.

Create PR

Or push these changes by commenting:

@cursor push c3d530324c
Preview (c3d530324c)
diff --git a/src/ui/tui/__tests__/snapshot-utils.tsx b/src/ui/tui/__tests__/snapshot-utils.tsx
--- a/src/ui/tui/__tests__/snapshot-utils.tsx
+++ b/src/ui/tui/__tests__/snapshot-utils.tsx
@@ -40,7 +40,6 @@
 import type { ReactElement } from 'react';
 import { WizardStore } from '../store.js';
 import type { WizardSession } from '../store.js';
-import type { Flow } from '../router.js';
 
 /**
  * Strip ANSI color/style escapes so snapshots are readable in PR reviews.
@@ -62,20 +61,6 @@
     .map((line) => line.replace(/[ \t]+$/, ''))
     .join('\n');
 
-export interface SnapshotOptions {
-  /**
-   * Optional session patch applied before render. Use this to control
-   * which branch of the screen is exercised — e.g. `{ outroData: { ... } }`
-   * for OutroScreen success vs error vs cancel.
-   */
-  sessionPatch?: Partial<WizardSession>;
-  /**
-   * Flow to construct the WizardStore with. Defaults to `Wizard`. Override
-   * for screens that only render under specific flows (e.g. SUSI).
-   */
-  flow?: Flow;
-}
-
 export interface RenderedSnapshot {
   /** Sanitized terminal output, ready to feed `toMatchSnapshot()`. */
   frame: string;

You can send follow-ups to the cloud agent here.

Comment thread src/ui/tui/__tests__/snapshot-utils.tsx Outdated
… Logout / SettingsOverride

The TUI is a chain of Ink screens (`src/ui/tui/screens/`). Today there are
no rendered-output tests — only assertions on a few pure helpers. A
change to layout, copy, padding, or color silently slips through unless
someone happens to run `pnpm try` and notice. Snapshot tests catch
unintended visual regressions: every PR that changes a rendered frame
fails until reviewed and updated with `pnpm test -u`.

What's in:

  src/ui/tui/__tests__/snapshot-utils.tsx
    - renderSnapshot(element, store) — renders via ink-testing-library at
      a deterministic fake-stdout, strips ANSI color escapes, trims
      trailing whitespace per line. Output is identical on macOS / Linux /
      CI so snapshots don't drift.
    - makeStoreForSnapshot(patch) — builds a fresh WizardStore with an
      optional WizardSession patch so tests can target specific branches
      (success / error / cancel, region forced, settings conflict, ...)
      without exercising the full wizard flow.

  src/ui/tui/screens/__tests__/OutroScreen.snap.test.tsx        (3 snaps)
  src/ui/tui/screens/__tests__/RegionSelectScreen.snap.test.tsx (2 snaps)
  src/ui/tui/screens/__tests__/LogoutScreen.snap.test.tsx       (1 snap)
  src/ui/tui/screens/__tests__/SettingsOverrideScreen.snap.test.tsx (2 snaps)

  vitest.config.ts — include *.test.tsx so JSX tests are picked up.
  package.json     — adds ink-testing-library as devDep.

The 8 snapshots cover the highest-churn TUI surfaces today: the success
/ error / cancel outros, the first-time and /region-forced region
pickers, the logout confirmation, and the settings-override blocking
modal. Helper makes it ~5 minutes per additional screen to add coverage.

Updating a snapshot intentionally:

  pnpm test -u       # vitest --update; review all diffs first
  # or:
  pnpm vitest run src/ui/tui/screens/__tests__/OutroScreen.snap.test.tsx -u

Stable across runs:
  - Width pinned by ink-testing-library's fake stdout
  - ANSI escapes stripped (chalk colors)
  - Trailing whitespace trimmed (Ink right-pads to terminal width)
  - Spinners/animation NOT exercised — caller pre-sets stable session state

Tests: +12 new (1245 total). Suite green, lint clean.

Independent of the sub-agent stack and the wizard-cleanup PR — lands
straight onto main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kelsonpw kelsonpw force-pushed the kelsonpw/tui-screenshot-tests branch from 59a3f03 to 647fa0b Compare April 26, 2026 02:31
Bugbot caught: SnapshotOptions was exported but never imported by any
consumer. The Flow type import existed solely to type its `flow` field.
makeStoreForSnapshot accepts Partial<WizardSession> directly, so the
interface is dead code.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: ANSI regex misses OSC hyperlink sequences from TerminalLink
    • Expanded ANSI_REGEX to match OSC sequences (ESC ] ... BEL/ST) in addition to CSI sequences, ensuring terminal-link's OSC 8 hyperlink escapes are stripped for deterministic snapshots.

Create PR

Or push these changes by commenting:

@cursor push 6d9509edd4
Preview (6d9509edd4)
diff --git a/src/ui/tui/__tests__/snapshot-utils.tsx b/src/ui/tui/__tests__/snapshot-utils.tsx
--- a/src/ui/tui/__tests__/snapshot-utils.tsx
+++ b/src/ui/tui/__tests__/snapshot-utils.tsx
@@ -42,11 +42,12 @@
 import type { WizardSession } from '../store.js';
 
 /**
- * Strip ANSI color/style escapes so snapshots are readable in PR reviews.
- * Pattern matches CSI sequences: ESC [ <params> <command-byte>.
+ * Strip ANSI escape sequences so snapshots are readable and deterministic.
+ * Matches CSI sequences (ESC [ ... letter) and OSC sequences
+ * (ESC ] ... BEL/ST) such as the OSC 8 hyperlinks emitted by terminal-link.
  */
 // eslint-disable-next-line no-control-regex
-const ANSI_REGEX = /\x1b\[[0-9;]*[A-Za-z]/g;
+const ANSI_REGEX = /\x1b(?:\[[0-9;]*[A-Za-z]|\].*?(?:\x07|\x1b\\))/g;
 const stripAnsi = (s: string): string => s.replace(ANSI_REGEX, '');
 
 /**

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 1c754a1. Configure here.

Comment thread src/ui/tui/__tests__/snapshot-utils.tsx Outdated
Bugbot caught: ANSI_REGEX only stripped CSI sequences (ESC [ ...).
OutroScreen's error and cancel views render TerminalLink, which emits
OSC 8 hyperlink escapes (\x1b]8;;URL\x07text\x1b]8;;\x07) when
supports-hyperlinks detects a capable terminal. Updating snapshots
on iTerm2/VS Code (TTY=true) would have embedded unstripped OSC
sequences; CI (TTY=false) then diffs and fails with confusing output.

Fix: add a second regex pass for OSC sequences (ESC ] ... BEL) so
snapshots are deterministic regardless of whether the test was
generated on a TTY-capable terminal.
@kelsonpw kelsonpw merged commit 9b66016 into main Apr 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant