MidstreamDocs
Marking scenes

What the SDK does in each environment

Why the same line of code behaves differently in CI, on your laptop, and inside a deploy.

One scene call has four behaviors. It picks between them by reading the environment it is running in, which is why you never have to configure anything or branch on where the test is.

WhereWhat the call does
Inside a deployCaptures the browser session at the scene the reviewer opened
In CIRegisters the capture: a screenshot, a snapshot, and where in the suite it came from
Local debugPauses the test at the scene so you can poke at it
Local developmentNothing at all

They are checked in that order, and the first match wins.

Inside a deploy

Detected by MIDSTREAM_SANDBOX=true, which we set on the container running your app. A second variable, SCENE_SLUG, names the scene being deployed.

This is the only mode that captures a session. At the matching scene call, the SDK reads the page's cookies, localStorage, sessionStorage, and current URL, and hands them to the deploy, which stores them and marks the instance ready. That bundle is what gets replayed into a reviewer's browser so they land already signed in, already at that moment.

Every other scene call in the test returns immediately. A deploy targets one scene, so a test with four calls in it does the work at one of them and runs straight through the rest.

Two consequences worth knowing:

  • The test keeps running after the scene is captured. It is not stopped there. What was captured is fixed at that moment, so later steps cannot change it — but they do still run, against the same live app the reviewer is about to open. A cleanup step that deletes the record you just marked will delete it. Put teardown in an afterEach hook.
  • A test that never reaches the call produces nothing to serve, and the deploy fails. That is the most common deploy failure by a wide margin, and Why a deploy failed covers what causes it.

In CI

Detected by any of CI=true, GITHUB_ACTIONS=true, GITLAB_CI=true, or CIRCLECI=true.

The call takes the screenshot and accessibility snapshot, collects the test file path and title chain, and registers the capture. On GitHub Actions the credentials are the token the workflow already has, so there is nothing to set up. What exactly is recorded is in The scene call.

Two things can stop a capture from being registered, and both print a warning rather than failing anything: no commit could be determined, or the API call did not go through.

One gotcha: this is a check of your environment, not of your intent. If your shell exports CI=true, your local runs are CI runs as far as the SDK is concerned, and it will try to register captures from your laptop.

Local debug

Detected by MIDSTREAM_DEBUG_SCENE being set to a slug. The call with that slug pauses the test using Playwright's inspector; the others return immediately.

You do not normally set this yourself. npx midstream sets it for you — see Debugging a scene locally.

Local development

None of the above: the call returns immediately, having done nothing.

Why the default is to do nothing

This is the mode the call is in almost every time it runs, and getting it right matters more than the other three.

Adding scene calls to a test suite must not change the suite. So in a normal local run the call makes no network request, takes no screenshot, reads no state, and adds no measurable time. A test with fifteen scene calls in it runs exactly as it did before you added them. midstreamParameter() follows the same rule: it returns whatever your factory returns, so your test keeps using its own data.

What that buys you:

  • You can put a scene call anywhere without wondering what it costs.
  • Someone can check out your branch and run the suite with no Midstream account, no credentials, and no network, and see the same results you do.
  • Nothing in a scene call can make a test flaky, because in the mode you run most, there is nothing there to fail.

The call never fails your test

In every mode, the call catches its own errors. It prints a warning beginning with [Midstream] and returns; your test continues as though the line were not there. There is no configuration that makes it throw instead.

We chose that on purpose. Your suite is your suite, and instrumentation that can turn a build red is instrumentation people delete. The cost is that failures are quiet: if a scene you expected never shows up, search the CI log for [Midstream].