CI is a good place to generate product screenshots. It is a dangerous place to publish them without review.
A browser can open the product, load a fixture, wait for the UI, capture the page, and store the result as a build artifact. Playwright supports page screenshots, full-page screenshots, element screenshots, and buffers. GitHub Actions can run the job on pull requests, scheduled builds, or docs changes. Tools like shot-scraper make website screenshot capture easy to script.
The capture problem is manageable. The publishing problem is where teams get hurt.
A CI screenshot can be technically fresh and still wrong for the docs.
Treat screenshots like build artifacts
A build artifact has provenance. It came from a commit, a workflow, an environment, and a set of inputs. A docs screenshot needs the same treatment.
If a screenshot appears in public documentation, the team should be able to answer:
- Which commit generated it?
- Which route did it capture?
- Which fixture or account state was loaded?
- Which viewport and theme were used?
- Which selector proved the page was ready?
- Which docs page uses the asset?
- Who approved the replacement?
Without that trail, the screenshot is just a generated PNG. It may be correct. It may be a loading state with nice pixels.
| Artifact field | Example |
|---|---|
| Commit | pull request head SHA |
| Route | /settings/api-keys |
| Fixture | team workspace with one active key |
| Viewport | 1440x900 |
| Ready condition | data-docs-ready="api-keys" |
| Capture target | API key table panel |
| Docs usage | docs/api-keys/create.mdx |
| Review state | approved, rejected, needs scenario fix |
This metadata is what makes CI screenshots safe to review.
Separate screenshot tests from docs screenshots
Visual regression tests and documentation screenshots overlap, but they are not the same job.
A visual regression test asks, "Did the UI change unexpectedly?" A docs screenshot asks, "Does this image still teach the product step correctly?"
The same browser capture may support both jobs, but the review rules are different. A pixel difference can fail a regression test. In docs, a pixel difference may be the expected result of a product improvement. Conversely, a screenshot can pass a pixel threshold and still be bad documentation if it shows the wrong user state or crop.
A clean CI design keeps the outputs separate:
| Output | Owner | Decision |
|---|---|---|
| Visual regression result | Engineering | Pass, fail, update baseline |
| Docs screenshot artifact | Docs, product, or support owner | Approve, reject, request scenario fix |
| Public docs asset | Docs publisher | Replace only after approval |
That separation prevents engineering test baselines from becoming public documentation by accident.
Make product state deterministic enough
Screenshot CI fails when the product state depends on live data, random accounts, timestamps, or whatever a developer had in their local workspace.
The fix is not always a full fake backend. Start with the state that matters for the screenshot. If the screenshot teaches API key creation, the page needs a workspace with a known key state. If it teaches billing settings, the page needs a known plan and payment state. If it teaches integration setup, the page needs connected and disconnected variants.
Useful controls include:
- seeded demo workspaces
- mocked network responses for unstable external data
- fixed dates and time zones
- explicit feature flag values
- test users with known roles
- data attributes for capture targets and ready states
The goal is not to make the entire product deterministic. The goal is to make the screenshot state reproducible.
Wait for the UI you mean to capture
A surprising number of bad screenshots are readiness bugs. The browser captured too early. A skeleton was still visible. A chart had not drawn. A toast covered the button. A font swapped after capture. A settings panel was still animating.
The wait condition should be part of the scenario, not an invisible sleep.
Weak:
- wait 3000ms
- take full-page screenshot
Better:
- open route
- wait for account fixture to load
- wait for data-docs-ready="billing-settings"
- capture locator for the billing panel
Playwright gives teams the browser primitives. The scenario should express what "ready" means for the docs image.
Do not auto-replace public docs on every run
The safest default is: CI generates, humans approve, publishing replaces.
Auto-replacement can work for low-risk internal references. It is risky for public product documentation, especially setup flows, compliance screens, billing pages, admin permissions, and integration guides. Those images carry trust. A wrong screenshot can make the docs look current while making the instruction worse.
Use review states:
| State | Meaning |
|---|---|
| Generated | CI produced the screenshot |
| Changed | The artifact differs from the current docs asset |
| Needs review | A human must approve replacement |
| Approved | The screenshot can publish |
| Rejected | The asset is wrong or docs need editing |
| Scenario fix needed | The automation captured the wrong state |
This gives the team a lane for screenshots instead of a folder of files.
Use Reshot between CI and the docs repo
Reshot keeps the screenshot scenario, CI artifact, review decision, and approved docs asset together. It does not replace Playwright. It does not replace your CI provider. It gives the screenshots a workflow.
A practical setup looks like this:
- Define scenarios for screenshots that matter.
- Run the scenarios in CI when product or docs inputs change.
- Store generated artifacts with commit, route, fixture, viewport, and docs usage.
- Send changed screenshots to review.
- Publish approved assets back to the docs repo or asset store.
This is how CI becomes useful for documentation screenshots without becoming reckless.
The browser can take the picture. The team still needs to decide whether the picture belongs in the docs.
Name artifacts so people can debug them
Screenshot artifacts often fail at the boring layer: naming. A folder called screenshots is not enough when a reviewer needs to know which product state produced an image.
Use names that carry useful context without becoming unreadable:
| Naming part | Example |
|---|---|
| Product area | settings-api-keys |
| Scenario state | one-active-key |
| Viewport | desktop-1440 |
| Theme or locale | light-en |
| Commit or run id | pr-1842 |
A generated file like settings-api-keys__one-active-key__desktop-1440__pr-1842.png is not beautiful, but it is debuggable. The reviewer can tell what it is before opening it. The CI logs, artifact store, and docs replacement step can all refer to the same object.
Pair the file name with a small metadata record. The metadata should include the route, fixture, ready condition, capture target, docs page, and reviewer state. That turns a PNG into a traceable artifact.
Record common failure modes
The first few CI screenshot failures will teach the team more than the happy path. Capture them as scenario fixes instead of one-off debugging.
Common failure modes include:
- app captured before hydration finished
- test account missing required plan or role
- modal or toast covering the target element
- external integration state unavailable
- browser viewport different from docs layout
- animation or chart rendering after capture
- feature flag exposing a different UI than production
Each failure should improve the scenario. Add a better ready signal. Seed the fixture. Mock the unstable response. Capture the locator instead of the full page. Pin the feature flag. The point is not to make screenshots flawless immediately. It is to make the workflow more deterministic each time it fails.
This is why review is so important. Review does not only protect the docs. It improves the screenshot system itself.
Sources
- [C1] Playwright screenshots - Official screenshot APIs for pages, locators, full-page captures, and buffers.
- [C2] GitHub Actions docs - Workflow automation and artifacts in CI.
- [C3] shot-scraper docs - Scriptable website screenshot capture.

