Running in CI
This page is about keeping the CI you already have. If you'd rather run your own control plane instead, that's Self-hosting — and if you want FlowWright itself to react to a push, that's Integrations, which is a different mechanism that shares every proper noun with this page.
FlowWright doesn't replace GitHub Actions or GitLab CI. It replaces the body of the
job: instead of twenty YAML steps, one step that runs flow run, with the actual work
described in pipeline.ts.
The payoff is that the same pipeline runs on your laptop. flow run locally and
flow run in CI execute the identical plan — no "works on my machine" gap because
there's no second definition of the build.
What the job has to provide
Four things, in every provider:
- A checkout of your repository.
- Node.js 24 or newer.
- Your dependencies, if
pipeline.tsimports anything beyond@flowwright/core. flow run.
That's the whole integration. Everything else — ordering, parallelism, caching, containers — is in your pipeline, not in the CI config.
flow export prints a starting point for each provider — see
Project setup.
Output adapts automatically
flow run detects the provider from the environment and picks a matching reporter:
| Environment | Reporter | What you get |
|---|---|---|
GITHUB_ACTIONS=true | github | Collapsible log groups, failure annotations, a job summary |
GITLAB_CI=true | gitlab | Collapsed sections per stage |
CI=true or CI=1 | ci | Plain text, no color |
| otherwise | pretty | Colored, live |
You don't normally set anything. Override with --reporter when the detection guesses
wrong. Full detail on Reporters.
Caching between jobs
FlowWright caches inside the pipeline — cache.restore/cache.save in a stage body,
covered in Artifacts, cache and stash. That cache
lives in .flowwright/cache, relative to the directory you run from. The path is
fixed; there's no flag or environment variable to move it.
To make it survive between CI jobs, persist that directory with your provider's own cache action. The per-provider recipes are on each page.
Two things worth knowing before you write the key:
- Entries are content-addressed and never overwritten. A key that already exists is
a no-op on save, so a
restore-keysprefix chain can only ever hit an entry that was written for exactly that key. Prefix restores are safe. - The directory only grows. There's no eviction. If your runner's cache quota bites, put something that rotates into the key — a date, or a lockfile hash.
Test reports
--junit <path> writes a JUnit XML file alongside whatever the console reporter is
doing. Every provider on this list can ingest it, and it's the one integration that
works everywhere:
flow run --junit report.xml
One stage becomes one test case. Failures carry the failing command and its exit code.
Upload it unconditionally — if: always() on GitHub, when: always on GitLab.
The run that failed is the one whose report you want, and a step that only runs on
success will skip exactly then.
Cancellation
When a runner cancels a job it sends SIGTERM. FlowWright stops the pipeline, finishes
writing its reports — the JUnit file and the GitHub job summary — and exits 130. So a
cancelled job still produces a report showing which stages had completed.
What each provider gives you
| GitHub Actions | GitLab CI | Jenkins | |
|---|---|---|---|
| Log grouping | ::group:: per stage | collapsed sections | — |
| Failure annotations | yes, on the diff | — | — |
| Job summary | yes | — | — |
| Test report | via --junit | via --junit | via --junit |
| Dedicated reporter | yes | yes | no — uses ci |
Jenkins has no dedicated reporter: it gets the plain ci output plus JUnit, which is
the whole integration.
Anything else
For a provider with no page here, --reporter ci plus --junit is the whole
integration, and it works everywhere. If you want to drive your own dashboard,
--reporter json streams the run as newline-delimited
events.