Freesbe

Blog

Our docs screenshots are generated, never taken

By Freesbe3 min read

Every product team knows the problem. You ship a redesign of the settings page, and three months later a customer emails a support question with a screenshot from your own help center that shows the old one.

Screenshots go stale because taking them is manual, and nobody owns retaking them. Freesbe ships changes every day, so by hand we’d have lost that fight within a week.

So nobody at Freesbe takes screenshots. All of the more than 300 images in our help center and on our homepage come from one script.

How it works

pnpm screenshots does the whole thing:

  1. Starts Freesbe locally, the real server and the real apps, from the current code.
  2. Fills it with a made-up company. Northwind Traders, a tea and coffee wholesaler, with Maya Chen as owner and a dozen colleagues, channels full of believable conversations, meetings, recordings, a guest from Acme Corp, integrations, phone numbers, the lot.
  3. Opens the apps in a headless browser with Playwright, signs in as the right person, clicks to the right screen, and saves each shot. The same scene can shoot light and dark mode, the desktop window and the phone app.
  4. Rewrites only images whose pixels changed. A run after a small UI change leaves a diff of just the screenshots that show it.

Each screen is a few lines in a scene file. This is the one behind the picture of noise cancellation in our audio preferences: open the Audio page with made-up microphones and speakers, then pick the AI option.

shot("prefs-noise-cancellation", "Audio", {
  before: (s) => s.fakeDevices(),
  after: (s) => s.page.getByRole("radio", { name: "AI" }).click(),
}),

Making screenshots deterministic

The hard part of generating screenshots isn’t the browser automation, it’s making two runs produce the same pixels. If anything varies, every run rewrites every image, and the diff is useless.

  • The clock is frozen at the same afternoon, in the same time zone, for every run. Every “5 minutes ago” and every calendar is identical.
  • Server randomness is reseeded per scene, so ids, avatars’ colours and generated names come out the same.
  • Fonts are bundled, so the rendering doesn’t depend on what the machine has installed.
  • The demo company is saved and only rebuilt when its seed changes.

Once a day, not once per pull request

A full run takes about 7 minutes locally and 11 in CI. Running it on every pull request would cost hours of CI a day and, worse, every pull request would commit images, and images conflict with each other constantly.

So a feature pull request changes the guide text and, for a new screen, adds a scene, with the guide already pointing at the image file it will produce. It doesn’t commit images. Every morning at 11:00 UTC, a workflow runs the whole suite on main and commits every image that the day’s merges changed, in one commit.

Until that run makes a new image, the site build leaves it out of the page instead of showing a broken picture.

What we turned down

  • Screenshots by hand. They’re stale within weeks, they’re inconsistent (different window sizes, different data, someone’s real name in the corner), and nobody wants the job.
  • Retaking on every pull request. Accurate, but slow and expensive, with merge conflicts on binary files nobody can resolve.
  • No screenshots. Some teams give up and write text-only docs. Ours are deliberately full of pictures, because seeing the screen is how most people learn a product.

A side effect we didn’t expect

The demo company became the most-used thing in the repository. The same Northwind Traders seed fills our staging environment, so anyone can sign in and click around a realistic workspace. And because every feature has to add its own data to the seed to get its screenshot, the demo never falls behind the product.

What we’d tell another team

Treat screenshots like any other build output. Put the source (a scene and some seed data) in the pull request, and let a machine make the pixels. The first week is spent making it deterministic, and every week after that is free.

Try all of it free with up to 10 people.

No card, no time limit. Invite your team and see what sticks.