ADR-0007: Flashlight as an opt-in hook of the Android E2E job, not a lane
- Status: Accepted
- Date: 2026-09-13
- Issue: #63 (D4 Flashlight in the Android E2E lane; decided with the owner 2026-09-13)
Context
PLAN.md decision 7 lists Flashlight as the "later epic" of the performance story and D4 places it "in the Android E2E lane". Flashlight (BAM) profiles an Android app process over adb while a command runs, repeats it N times, and scores CPU, RAM and FPS into a static HTML report; it has no iOS profiler. It is the only layer here that measures a release build on a device before release — Reassure (perf-tests.md) measures render cost under Jest, EAS Observe (observe.md) measures real installs after the fact, and the bundle budget measures bytes. Two facts shape the decision: the maestro_android job of e2e.yml already leaves an emulator running with the e2e build installed (that is how device logs and the a11y audit, ADR-0005, run from its after_maestro_tests hook), and Flashlight's npm packages were last published in mid-2024 (v0.18) although the repository still receives commits — a maintenance risk. Emulator numbers on a shared worker are also noisy, and the variance is unknown until runs exist.
Decision
Flashlight is an after_maestro_tests hook step of the existing maestro_android job in .eas/workflows/e2e.yml, after the device-log and a11y steps: scripts/flashlight.js (bun run perf:flashlight, Node built-ins only, shares the device / app-id preflight with the a11y audit) installs Flashlight on the worker, runs flashlight test on one flow — .maestro/flows/fetch.yaml, whose clearState launch gives each iteration a cold-ish start — for 5 iterations of 10 s (the hook's time budget, not Flashlight's default of 10), renders flashlight report, and prints a summary. results.json, report/ and a README.txt upload as Flashlight (android). It is informational (--no-fail: a skip or a failed run is a notice, never a red job; no budget) and opt-in behind the FLASHLIGHT repo constant (disabled) in the IOS_MODE idiom: a workflow_dispatch input whose default equals the || 'disabled' literal. The literal is passed to the step as env.FLASHLIGHT and the script gates on it rather than a step if, so the upload step always has a directory and an expression that does not resolve at runtime degrades to "skipped", not to a failed hook. No custom job, no new workflow, no iOS.
Consequences
- One dispatch (
-F flashlight=enabled) gives a PR a release-build CPU / RAM / FPS report with no extra build, boot or install; flipping the default runs it on every PR for about five extra flow runs of hook time. - Numbers are relative: comparable only run-to-run on the same worker class (
linux-large-nested-virtualization) or the same laptop, never to a phone. Compare withflashlight report a.json b.json. - Nothing has run on EAS yet:
eas workflow:validateaccepts the file, the script's pure parts are unit-tested, and the local path needs an Android emulator with the e2e build. Whetheradb/maestroare on PATH inside the hook, whether the installer works on the worker, and whether the profiler sees the emulator's app process are confirmed on the first enabled run — every one of those is a printed skip, not a failure. docs/performance.mdgains a layer row and section;docs/native-e2e.mdan artifact row;docs/ci-overview.mda constant row.
Rejected
- A custom
linuxjob that boots its own emulator and installs the build — duplicates what the maestro job already does (download, install, boot) for a second copy of the same device. - A budget / gate now — the run-to-run spread on an emulator is unknown; a threshold picked before a baseline would only add noise to the PR check.
- Flashlight Cloud — app-start only, a separate account, and it does not run the project's own Maestro flows.
- Retiring D4 — loses the single pre-release, on-device signal the perf story has; the cost of keeping it as an opt-in hook is one script and two steps.
- Profiling the whole workspace — four flows × N iterations does not fit a hook; one representative flow per run does, and the flow is a
--flowflag away from changing.
Follow-ups
- First enabled run: confirm PATH / installer / profiler on the worker, record findings in
docs/performance.md, and open an issue for anything that turned into a skip. - After a handful of runs: a baseline plus
flashlight-budget.jsonand a gate in the style ofobserve-budget.json/observe:check, once the variance is known. - Revisit the tool choice if Flashlight has had no release by mid-2027.