Skip to content

Architecture

brighttest is a thin orchestration layer over three mature tools. You write Rooibos specs once; the tool runs them headless (no device) or on a real Roku — and can diff the two to prove the fast lane is faithful.

--cross-check runs the headless and device lanes and diffs every test, so the fast lane can be trusted.

Components

ComponentRoleRuntime?
BrighterScript (bsc)Compiles/transpiles plain .brs + .bs; hosts the Rooibos plugin; builds/deploys.No (compiler)
rooibos-roku (bsc plugin)Turns @suite/@it/@params specs into runnable suites; injects the runtime + coverage instrumentation.No (build-time)
brs-node (brs-cli)BrightScript and SceneGraph simulator that runs .brs headlessly in Node. Broad component set incl. crypto, and a SceneGraph engine so @SGNode suites run without a device.Yes (headless)
brighttestThe CLI/orchestrator: generates configs, builds, runs the right lane, parses results, writes JUnit/LCOV.

The lanes

Headless — default (fast loop)

  1. bsc builds with the Rooibos plugin, SceneGraph off — the fastest configuration.
  2. A bundled driver (brs/headless_runner.brs) asks Rooibos's generated RuntimeConfig for the suite map, instantiates each suite, walks its groupsData → testCases, invokes each test method (handling @params), and reads the pass/fail state — reusing Rooibos's own assertions.
  3. Runs on brs-node (brs-cli -n). brighttest parses PASS/FAIL/__RESULT__ lines, writes optional JUnit, and sets a CI exit code. With a project that has @SGNode specs, the default lane boots a scene to run them too (pass --no-sgnode to skip that and stay on the pure SceneGraph-off driver).

Headless coverage — --coverage (no device)

  1. bsc builds with the Rooibos plugin and coverage instrumentation + SceneGraph on.
  2. brighttest runs the stock Rooibos scene runner on the brs-node simulator. Because the simulator has a SceneGraph engine, this runs @SGNode node suites and their onChange observer cascades in full, and the coverage collector's field observers work — yielding real LCOV with no device.
  3. brighttest scrapes the printed coverage blocks (ANSI-stripped) and writes lcov.info.

Device — --device (the reference)

  1. bsc builds with the Rooibos plugin and coverage on.
  2. brighttest hands off to the stock Rooibos CLI, which deploys to the Roku and runs the scene-based runner on hardware. Results + coverage come back over the network; --lcov writes lcov.info locally.
  3. The device lane is the fidelity reference — it runs everything, including behavior tied to real wall-clock timing that the simulator only approximates.

Cross-check — --cross-check

Builds once, runs both the headless and device lanes, matches tests by name, and reports agree / device-only / DIVERGENT. A run fails on any divergence, so the headless lane stays a trustworthy proxy.

Why one spec works in every lane

A Rooibos suite compiles to a plain object: test methods plus a metadata structure (testGroups → testCases, each with funcName, name, rawParams). Assertions set m.currentResult.isFail. The scene-based runner is just one way to drive that object; the headless driver is another. Same compiled suite, same assertions — only the driver and the run target differ.

What runs where

Design guideline: keep business logic in pure functions so most tests are trivially headless; the few tests that genuinely need real hardware timing get marked @deviceOnly and skipped on the fast lanes. More in Headless vs device.