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.