GitHub PR Previews
The Contrast GitHub App attaches a playable preview to each pull request and keeps live branch builds available for inspection. A PR-diff-driven agent boots your app in headless SootSim, drives the surface your change touched, and posts a single sticky comment with the recording.
Your repo does not need a provider API key or SOOTSIM_API_KEY. The workflow
authenticates with the GitHub installation token, and Contrast cloud owns the
agent, model proxy, upload, and PR comment.
PR previews and branch builds require the Carbon plan. The agent runs on Contrast’s cloud and model keys, so the automated preview/build flows are gated to Carbon. A pull request on a repo without an entitled account gets a sticky comment noting that previews require Carbon, and no preview is generated.
Set up a repo
Install the Contrast GitHub App on the
repo. On first install it opens a bootstrap PR that adds
.github/workflows/sootsim.yml tuned to your repo. Merge it and you are done.
To write the workflow by hand, add one job that checks out and calls
sootbean/soot-preview-action@v1:
The action installs your dependencies, boots the dev server in the background,
waits for it to be ready, drives the app, and reports back. On a
pull_request it runs the preview agent; on a push to a tracked branch it runs
the branch build.
Package manager
The action auto-detects your package manager from the lockfile or the
package.json packageManager field and installs with it (npm ci, yarn install --immutable/--frozen-lockfile, pnpm install --frozen-lockfile, or
bun install --frozen-lockfile). Override with package-manager: or a custom
install: command for a monorepo.
If your workflow already runs its own setup (checkout, Node, install, e.g. a
repo ./.github/actions/setup composite), run it before this action and pass
package-manager: none so the action skips straight to the preview:
Fit previews into an existing CI job
Use the existing job when it already checks out the PR, installs the same dependencies, and runs on a machine that can also host the app. Pick placement from the app server’s lifecycle:
- Parallel: start the preview after install, run independent checks, then wait near the end. This avoids a second install and overlaps wall-clock time, but the preview and tests compete for CPU and memory.
- Before tests: use the normal action invocation when the preview needs a clean state or its app server would collide with test ports or processes.
- After tests: use the normal invocation when tests build the artifact or leave the exact staging server the preview needs running. Do not assume a server survives a completed action step unless the workflow deliberately keeps that process alive.
- Separate job: keep isolation when runner labels, permissions, secrets, process lifetime, or resource limits differ. This repeats checkout and often install, but avoids slowing or destabilizing the main test lane.
Parallel example:
Inputs
| input | default | description |
|---|---|---|
start | empty | command that boots your dev server in the background |
phase | run | run | start | wait; split start/wait to overlap the preview with other steps in one job |
port | 8081 | dev server port (Metro/Expo default) |
package-manager | auto | auto | npm | yarn | pnpm | bun | none |
install | empty | override the install command (monorepo filters, etc.) |
node-version | 24 | Node version installed via actions/setup-node |
mode | auto | auto | preview | build |
maestro | empty | Maestro flow paths to run against the preview bundle, or auto for every .maestro/*.yaml |
runner | local | where the recording executes: local (your CI runner) or hosted (a Contrast GPU recorder) |
build-env | empty | newline KEY=VALUE pairs baked into the captured bundle (Clerk / API keys); source from ${{ secrets.* }} |
Full input list and behavior: the action’s README.
Where the recording runs
The runner input decides where SootSim and the recording execute. Your app’s
install and dev server always run on your own CI runner either way — the choice
is only about the machine that drives and records it.
runner: local runs everything on your workflow’s runner. Nothing leaves
your CI beyond the finished upload, and there is no extra plan requirement
beyond previews themselves. The trade-off is hardware: standard hosted CI
runners have two CPU cores and no GPU, so SootSim renders through software.
Short walkthroughs and quick flows are fine; longer recordings come out
choppy, and on complex apps the recording can slow the interaction itself.
runner: hosted keeps your CI runner serving the dev server and moves the
driving and recording to a Contrast GPU recorder. Your workflow opens a
one-off tunnel for the run, gated by a per-run token so only the assigned
recorder can reach your dev server, and tears it down as soon as the run
ends. The recorder renders with a real GPU, which is what makes the
walkthrough video watchably smooth, and heavier Maestro suites do not eat
your CI minutes. It requires the Carbon plan.
Rules of thumb: start with local; switch to hosted when the videos matter
(design review, sharing with non-engineers) or when your flows are long enough
that software rendering visibly degrades them. If your policy forbids exposing
a dev server through any tunnel, even token-gated and per-run, stay on
local — that constraint outranks video quality.
Preview vs build
SootSim publishes two share types from GitHub Actions:
| type | URL | behavior |
|---|---|---|
| PR preview | /preview/<id> | recorded replay of the changed surface, including bundle, events, storage, network replay data, snapshots, and video when recording succeeds |
| branch build | /build/<owner>/<repo>/<branch>?platform=ios | live configured app build that keeps talking to the endpoint your workflow configured |
Keep those modes separate. A recorded PR preview should replay what CI captured; it should not silently fall through to a live backend for missing requests. A branch build is the live inspection mode, so auth and API failures there usually mean the app’s build-time configuration needs work.
What the bot does
For each non-draft PR, the workflow:
- reads the PR diff;
- asks Contrast cloud whether the change has an app surface worth previewing (and whether the account is entitled);
- boots your app and opens it in headless SootSim;
- lets the agent drive the relevant surface and save a flow;
- replays the flow with recording enabled;
- uploads the bundle, events, storage, snapshots, network replay data, and video when present;
- updates one sticky PR comment with the preview result and branch build links.
For pushes to configured branches, the build lane uploads the live branch build without running the PR agent.
Verify a preview
A terminal preview or build failure must fail the GitHub workflow. Do not add
continue-on-error to the Contrast action. A green check still is not visual proof:
it confirms the pipeline finished, but cannot confirm that the changed surface
rendered correctly.
Before treating a preview as ready:
- read the SootSim sticky comment and confirm it says ready, not failed or skipped;
- open the linked
/preview/<id>; - confirm the video loads when present;
- switch to Live and confirm the app catches up to the recorded point;
- scrub the timeline and confirm the live app follows without getting stuck;
- check that the frames show the changed surface, not just a login screen or the starting tab twice;
- open the branch build link separately when you need live staging data.
Skipped previews are valid for docs-only, CI-only, and backend-only changes. If the PR changed user-visible UI and the bot skipped it, adjust the app map or preview guidance instead of accepting the skip.
Troubleshooting
- “PR previews require a Carbon plan” in the sticky comment: the repo’s Contrast account is on Free. Subscribe to Carbon, then re-run the PR workflow.
- Preview skipped on a UI change: the relevance gate judged the diff had no
observable short-clip delta. Adjust your preview guidance, or open the
contrast-agent-logsartifact on the Actions run to see the decision reason. - Boot never became ready: the action waits for
port(default8081). If your dev server binds a different port, setport:; if it needs env to boot, pass it viabuild-env. - Auth failed inside the build: the captured bundle needs a runtime key
whose allowed origins include
sootsim.com. Bake it in withbuild-envsourced from a repo secret. - Deeper diagnostics: every run uploads a
contrast-agent-logsartifact (each probe’s stdout/stderr) to the Actions run page; download it for post-mortems.
Re-running previews
When you need to regenerate history, rerun the original pull request workflow run. That preserves the PR event shape the agent expects:
terminal
Do not use a push run when you need a PR preview; push runs only update branch
builds. Do not use workflow_dispatch unless your workflow was explicitly
written for it.

