Skip to main content

Reporters

A reporter decides what flow run writes to your terminal. There are five, and in CI you rarely pick one yourself.

KindFor
prettyA terminal. Colors, live streaming, per-stage timing.
ciLog files. Same layout, no color.
githubGitHub Actions. Collapsible ::group:: sections and a job summary.
gitlabGitLab CI. Collapsible section_start/section_end markers.
jsonMachines. One JSON event per line.

Automatic selection

FlowWright reads the environment and picks:

ConditionReporter
--reporter <kind>whatever you said
--jsonjson
GITHUB_ACTIONS=truegithub
GITLAB_CI=truegitlab
CI=true or CI=1ci
otherwisepretty

In order — the first match wins. The comparisons are exact string matches: CI=yes and CI=True don't count as CI, and a bare CI= doesn't either.

The upshot is that flow run, unchanged, produces folded groups on GitHub, folded sections on GitLab, plain text in other CI, and colored live output on your machine.

Overriding

flow run --reporter ci

Useful for reproducing CI output locally, or forcing plain text somewhere the detection guesses wrong.

GitHub

FlowWright · acme-api
::group::Install
$ npm ci
[07:11:42] added 1 package, and audited 2 packages in 315ms
::endgroup::
::group::Lint
$ npm run lint
[07:11:42] lint: 0 problems
::endgroup::

Each stage becomes a collapsible group in the Actions log. When GITHUB_STEP_SUMMARY is set, a run summary is appended to the job summary page too — best-effort, and silently skipped when the variable isn't there.

GitLab

FlowWright · acme-api
section_start:1785042703:install[0KInstall
$ npm ci
[07:11:43] added 1 package, and audited 2 packages in 324ms
section_end:1785042703:install[0K
✓ install

Same idea using GitLab's collapsible section markers.

JSON

flow run --json

One event per line, newline-delimited — pipe it straight into jq or read it incrementally:

{"type":"run.started","plan":{"version":2,"name":"acme-api","stages":[]},"at":1785042703668}
{"type":"stage.started","stageId":"install","name":"Install","at":1785042703669}
{"type":"step.started","stageId":"install","stepId":"install:0","display":"npm ci","at":1785042703669}
{"type":"step.output","stageId":"install","stepId":"install:0","stream":"stdout","chunk":"added 1 package\n","at":1785042703674}
{"type":"step.finished","stageId":"install","stepId":"install:0","status":"success","exitCode":0,"durationMs":393,"at":1785042704062}
{"type":"stage.finished","stageId":"install","status":"success","durationMs":393,"attempts":1,"at":1785042704062}

There are twelve event kinds. run.started carries the full execution plan, so a consumer knows the shape of the run before it begins. Every field of every event is on the Events reference.

Streaming vs grouped

-j changes how output is arranged, in every reporter that has a notion of a stage block — pretty, ci, github and gitlab:

  • -j 1 — output streams live as it's produced.
  • -j 2 and above — each stage's output is buffered and flushed as one block when it finishes, so concurrent stages don't interleave.

That matters most on the provider reporters: GitHub can't nest log groups and GitLab can't nest sections, so overlapping markers would swallow output rather than just scramble it. json is unaffected — every event carries its stageId.

JUnit

flow run --junit report.xml
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="acme-api" tests="5" failures="0" skipped="0" time="0.650">
<testsuite name="acme-api" tests="5" failures="0" skipped="0" time="0.650">
<testcase name="install" classname="acme-api" time="0.356"></testcase>
<testcase name="lint" classname="acme-api" time="0.085"></testcase>
<testcase name="typecheck" classname="acme-api" time="0.071"></testcase>
<testcase name="test" classname="acme-api" time="0.070"></testcase>
<testcase name="build" classname="acme-api" time="0.068"></testcase>
</testsuite>
</testsuites>

One test case per stage, and the suite time is the run's wall clock — not the sum of the stages, which would over-report under -j.

A failure carries the command and its exit code, and a skip carries its reason:

<testcase name="test" classname="acme-api" time="0.022">
<failure message="stage failed">command: node -e process.exit(2)
exit code: 2</failure>
</testcase>
<testcase name="deploy" classname="acme-api" time="0.000">
<skipped message="upstream-failed"/>
</testcase>

--junit is additive — it writes the file and keeps the console reporter, so you can combine it with any of the five. If the path's directory doesn't exist it's created; a write failure warns rather than failing the run.

Most CI systems render this as a test report if you point them at the file — see Running in CI.