Original Reddit post

It could be a common knowledge but i finally realized the power of asking Claude to run through user flows and take screenshots and maintain all main flows for different type of users of the app. Few benefits that I’ve noticed:

  • you don’t have to run yourself as it’s time consuming.
  • It’s possible to just review the artifact with the flow of screenshots.
  • It’s something that you can ask daily and keep this as a reference book of app features. How it’s set up (details below are written up with Claude) Claude writes the flows once; a script runs them. Claude doesn’t click through the app each time. It writes each user flow as a Playwright test, so a run costs no tokens and doesn’t depend on an AI clicking correctly. Real app, nothing faked. Each run rebuilds the whole app in Docker from the current code, with Stripe in test mode. One screenshot per step, plus a check. Each step takes a full-page screenshot with a caption and asserts with expect() that it did what it claims, so a green run can’t hide a broken page. A storyboard per flow. Each run writes a markdown page per flow: numbered steps, each with a caption and a screenshot. I review that instead of clicking through myself. Organized by user type. One doc lists what each kind of user does and which flow covers it, so gaps are obvious. Numbers today: 28 flows across 4+ user types, 36 storyboards (7 at phone width), about 300 screenshots per full run. The helper (Playwright, TypeScript): import mkdirSync, rmSync, writeFileSync } from ‘node:fs’ import path from ‘node:path’ import { test, type Page, type TestInfo } from ‘@playwright/test’ const OUT = path.resolve(‘e2e/storyboards’) export class Story { private n = 0 private lines: string[] = [] private dir: string constructor(private info: TestInfo, private flow: string, private title: string) { this.dir = path.join(OUT, flow) rmSync(this.dir, { recursive: true, force: true }) // one run per storyboard mkdirSync(this.dir, { recursive: true }) } async step(caption: string, page: Page, body: () => Promise<Page void>) { const shown = (await test.step(caption, body)) ?? page const file = `${String(++this.n).padStart(2, ‘0’).png await shown.evaluate(() => window.scrollTo(0, 0)) await shown.screenshot({ path: path.join(this.dir, file), fullPage: true }) await this.info.attach(${this.n}. ${caption}, { path: path.join(this.dir, file), contentType: 'image/png' }) this.lines.push(## ${this.n}. ${caption}\n\n${caption}\n) writeFileSync(path.join(OUT, ${this.flow}.md), # ${this.title}\n\n${this.lines.join(‘\n’)}`) } } A flow using it: test(‘a buyer moves and cancels a booking’, async ({ page }, info) => { const story = new Story(info, ‘buyer-moves-and-cancels’, ‘A buyer moves and cancels a booking’) await story.step(“The buyer books today’s departure”, page, async () => { // …fill the form, submit await expect(page.getByText(‘Booking confirmed’)).toBeVisible() }) await story.step(‘They move it to tomorrow’, page, async () => { /* … / }) await story.step(‘Then they cancel’, page, async () => { / … */ }) }) What one run produces (storyboards/buyer-moves-and-cancels.md):

A buyer moves and cancels a booking

1. The buyer books today’s departure

2. They pay at the counter; the receipt offers to change the day

3. They move it to tomorrow

4. Then they cancel

5. The counter sees the order cancelled on tomorrow’s list

Each heading is followed by its screenshot, so you read the flow the way the user sees it. Caveats: you need seeded test accounts and data for each user type, and runs share one copy of the app’s data, so they take turns. You still have to skim the storyboards, because the checks only cover what you thought to assert. Anyone doing something similar in CI, or comparing screenshots between runs to catch visual changes? submitted by /u/svet07

Originally posted by u/svet07 on r/ClaudeCode