Skip to content

Latest commit

 

History

History
531 lines (350 loc) · 35.9 KB

File metadata and controls

531 lines (350 loc) · 35.9 KB
type Reference
title Running e2e tests
description The canonical, minimal command set for running React Native Firebase e2e tests on every platform.
tags
testing
e2e
detox
jet
ios
android
macos
coverage
timestamp 2026-06-25 00:00:00 UTC

Running e2e tests

Canonical local e2e commands. Use only these commands. -ci variants are CI-only. Avoid :test-cover-reuse, :test-cover-and-process, :test-reuse (stale native risk). If another doc disagrees, this wins.

All e2e how-to lives here; other docs link here — they do not define alternate entrypoints or commands.

Agent rule (read first)

Never invoke the test runner (Jet), Detox, Metro, or emulators directly. Use only the repo-root yarn tests:* commands defined in this document (for example yarn tests:packager:jet, yarn tests:emulator:start, yarn tests:<platform>:test-cover). Do not run jet, npx jet, yarn jet, detox test, cd tests && …, or ad-hoc Metro/emulator start commands. When another doc mentions e2e, Jet, Detox, or pre-flight, follow the link to this runbook — do not infer commands from log output or implementation details.

Install, prepare, and validation commands are not in this doc — they live in agent command policy (read before any non-e2e shell command).

Prerequisites (once per checkout)

yarn   # applies .yarn/patches (jet, mocha-remote-*, detox); installs tests devDeps incl. babel-plugin-istanbul

Rules

  1. Packager (background):
yarn tests:packager:jet
  1. Emulators (background, always):
yarn tests:emulator:start
  1. Rebuild when needed

    • Native changed → yarn tests:ios:build / yarn tests:android:build before e2e. macOS uses firebase-js-sdk only — no native rebuild.
    • packages/*/lib/** changed → yarn lerna:prepare must run to completion (exit 0) before anything else — Metro serves dist/module/**, not lib/**. See prepare completion gate and agent command policy § prepare must finish first. After prepare finishes, restart the packager with yarn tests:packager:jet-reset-cache when Metro was already running (Rules §1).
    • TS coverage: iOS/Android embed JS at build time; run :build before :test-cover so Istanbul + patched test-runner coverage upload is in app. macOS loads from Metro live; after test-runner patch changes, restart the packager with yarn tests:packager:jet-reset-cache (Rules §1).
  2. Always run with coverage:

yarn tests:ios:test-cover
yarn tests:android:test-cover
yarn tests:macos:test-cover

Clean :build + :test-cover each time — not reuse variants.

  1. Report locationsCoverage design.

  2. One e2e at a time — never overlap :test-cover runs on one host. All platforms share Metro :8081 and the test-runner WebSocket port (default 8090); parallel runs race on coverage/device/emulator state. Every run starts after clean pre-flight. Log triage for port/orchestration markers: test-runner host orchestration.

  3. No source edits during e2e — wait/cancel cleanly before editing packages/**, tests/**, or bundle-affecting OKF docs. Saves can hot reload/rebundle and invalidate tests/coverage.

Serialized e2e loops (shared dev host)

Use validation tiers: unit-focused, area-focused, full. Match tier to work type. Runs are serial from clean pre-flight. Log long output; upstream gets exit code + short summary.

Policy: OKF documentation and commit policy. Terms: iteration vocabulary.

How a platform run is structured (Android/iOS)

Internal only — do not invoke sub-commands. Wait on the single repo-root :test-cover command; Detox/Jest and the test runner start automatically.

yarn tests:android:test-cover   # only command you run
  └─ (internal) detox → jest → firebase.test.js → test runner on :8090 → app

macOS: yarn tests:macos:test-cover only — same :8090 transport, no Detox.

Do not poll pgrep, detox, process names, or :8090 for completion. They match stale wrappers, orphans, zombies, and contention.

Test-runner host orchestration (log triage only)

No commands to run from this section — for interpreting :test-cover logs and CI artifacts only. Patch workflow: detox-patches.md. CI triage: iOS orchestration.

Port Protocol Role
8090 (default JET_REMOTE_PORT) WebSocket (mocha-remote-*) App ↔ host test transport; drives Mocha in the app
8091 (default JET_REMOTE_PORT + 1, override RNFB_JET_CONTROL_PORT) HTTP POST only Host ↔ test-runner control plane — not used by the app

Why two ports — Port 8090 is a WebSocket server (ws library). Plain HTTP POST to that socket (e.g. /launch-ready) gets 426 Upgrade Required and can crash the runner with ERR_HTTP_HEADERS_SENT if a control handler shares the same HTTP stack. Control endpoints therefore live on a separate small HTTP server (startControlHttpServer in the test-runner patch).

Launch gate (orchestration race fix)firebase.test.js starts the test runner with RNFB_JET_DEFER_RUN=1. It listens on 8090 and defers server.run() until the host signals launch success:

  1. Host waits for TCP 8090, then Metro (debug) if needed, then launchAppWithRetry.
  2. Host POSTs /orchestrate-state ({ "phase": "launch-pending" | "launch-ok" | … }) to the control port (best-effort diagnostics).
  3. After launchApp succeeds, host POSTs /launch-ready → test runner calls server.run() and the app may receive the mocha-remote run action.
  4. Mocha tests must not start during a stuck or retried launchApp; on inner launch retry the host may kill and respawn the test runner before terminateApp/simulator reboot.

Log markers[rnfb-e2e] orchestrate-state=…, [jet-control] deferring server.run until POST /launch-ready, [jet-control] launch-ready received, [jet-control] listening on http://…:8091, [jet-coverage] …, Jet client connected.

Pre-flightHost-clear probes check 8090 only (stray test-runner WS listener). 8091 may be open during a run; do not treat it as a stale-process signal by itself.

CI iOS instrumentation (not local)

GitHub Actions Testing E2E iOS adds CI-only steps local :test-cover does not run: pre-boot (boot-simulator.sh), one filtered sim-app.log stream, wait-for-load-settle.sh (threshold 20) immediately before Detox, and optional video when record_screens: true. Host syslog and unfiltered simulator logs are disabled to reduce runner baseload.

Canonical owner: iOS CI baseload policy. Artifact names and triage: simulator logging and video.

Running one iteration

  1. Pre-flight; if host-clear probes fail, pre-flight recovery first.
  2. One foreground Shell command; set block_until_ms large enough (~15m macOS, ~45–60m iOS/Android). Do not background/poll.
  3. From repo root, tee canonical command:
yarn tests:android:test-cover 2>&1 | tee /tmp/rnfb-e2e-android.log
yarn tests:ios:test-cover     2>&1 | tee /tmp/rnfb-e2e-ios.log
yarn tests:macos:test-cover   2>&1 | tee /tmp/rnfb-e2e-macos.log

Use /tmp/rnfb-e2e-<platform>.log (overwrite each iteration). Do not substitute other entrypoints — see agent rule.

  1. Completion = shell exit code. 0 finished; non-zero failed/aborted. Read log for counts.
  2. Parse log tail; do not infer from processes:
rg 'passing|failing' /tmp/rnfb-e2e-<platform>.log | tail -1
rg '^\s+\d+\)' /tmp/rnfb-e2e-<platform>.log          # failure blocks, if any
rg 'Tests Complete|jet-coverage.*merged' /tmp/rnfb-e2e-<platform>.log | tail -3

Markers: ✨ Tests Complete ✨, Jest N passing / N failing, [jet-coverage] merged … before NYC shutdown, [rnfb-e2e] orchestrate-state=, [jet-control] launch-ready received.

  1. Return only platform, exit code, pass/fail line, failing tests, log path, optional coverage-gap line. No full log upstream.

Pre-flight: is the host clear to start?

Canonical owner for host-clear probes, recovery after abort, and service checks. Other OKF docs link here by reference — do not duplicate commands or probes elsewhere.

Run all four steps before every :test-cover. After an interrupted run, run pre-flight recovery and re-run the probes.

0. Prepare complete (when packages/*/lib/** changed)

If product code under packages/*/lib/** was edited in this session, yarn lerna:prepare (or scoped yarn lerna run prepare --scope …) must have fully finished with exit code 0 before pre-flight steps 1–3 or any :test-cover / :build.

  • Wait for the prepare shell to return — do not batch prepare in parallel with Metro restart, pre-flight probes, or e2e in the same agent turn.
  • Then restart Metro when it was already running: yarn tests:packager:jet-reset-cache (Rules §3).
  • Then continue with host-clear probes and service checks below.

Skipping this gate causes missing or half-written dist/module/** while Metro /status still returns 200 — a common source of bundle-load and module-not-found failures that look like product bugs.

Owner for install/prepare serialization: agent command policy § prepare must finish first.

1. Host clear

No in-flight test run on the target platform:

Platform Clear when
Android Host-clear probes pass (no instrumentation PID)
iOS Host-clear probes pass — zero booted simulators and no stray listener on :8090. Detox boots iPhone 17 from tests/.detoxrc.js; do not pre-boot or leave simulators running.
macOS Host-clear probes pass (no io.invertase.testing process)

Also wait for any visible unfinished yarn tests:*:test-cover.

Host-clear probes — run the block for your platform; exit 0 = clear (chain with &&):

# iOS — booted-device count must be 0
test "$(xcrun simctl list devices booted | grep -c '(Booted)' || true)" -eq 0
test -z "$(lsof -nP -iTCP:8090 -sTCP:LISTEN -t 2>/dev/null || true)"

# Android
! adb -s emulator-5554 shell pidof com.invertase.testing.test >/dev/null 2>&1

# macOS
! pgrep -x io.invertase.testing >/dev/null 2>&1

Pre-flight recovery — when probes fail after abort, kill, or EADDRINUSE on :8090. Then re-run host-clear probes.

# Android
adb -s emulator-5554 shell am force-stop com.invertase.testing
adb -s emulator-5554 shell am force-stop com.invertase.testing.test

# iOS — Detox re-boots iPhone 17 after shutdown booted
lsof -nP -iTCP:8090 -sTCP:LISTEN -t | xargs kill 2>/dev/null || true
pkill -f 'detox test --configuration ios' 2>/dev/null || true
pkill -f 'jet.js --target=ios' 2>/dev/null || true
xcrun simctl shutdown booted

Do not use boot-simulator.sh or simctl shutdown all as routine prep (what not to do).

2. Services ready

Metro and emulators must be running and responsive — do not assume from a prior session or background start.

curl -sf http://127.0.0.1:8081/status >/dev/null   # Metro (127.0.0.1 matches test app bundle URL)
curl -sf http://127.0.0.1:8080 >/dev/null          # Firestore emulator

If either fails: start yarn tests:packager:jet and yarn tests:emulator:start (background); re-check until both pass. After yarn lerna:prepare has finished (step 0) or test-runner patch edits, restart the packager with yarn tests:packager:jet-reset-cache (Rules §1) — never restart Metro while prepare is still running.

A listener on :8081 or :8080 is not sufficient — HTTP checks must succeed.

3. Harness matches validation tier

Confirm tests/app.js / tests/globals.js match the item's validation_tier (iteration vocabulary), not the branch's committed harness.

Tier Harness before :test-cover
Unit-focused (implementation) Area narrowing required — trim modules + load only the spec under change (e.g. firestore + Pipeline.e2e.js); .only OK locally. Set RNFBDebug = true locally in tests/globals.js (§ fail-fast).
Area-focused (independent-review, baseline-capture) Area narrowing required — same module/spec trim as unit-focused; load full spec file(s) for the package area; no .only. Set RNFBDebug = true locally (§ fail-fast).
Full (pre-merge-validation) Revert all narrowing — full app (require.context, all modules); RNFBDebug = false (committed default).

Committed full harness on the branch does not override unit-focused or area-focused tier for local runs. Package workflows define area setup (e.g. pipelines § area harness). How to edit tests/app.js: two platform blocks. Never commit narrowing until full tier.

See Harness narrowing gate (blocking) — a run that skips step 3 does not close implementation_gate or review_gate.

Stalled run detection

Completion = shell exit code + log markers — not open-ended log tailing.

Platform Early markers (≈2–3 min) Done
macOS Jet client connected ✨ Tests Complete ✨, Jest N passing
iOS/Android Detox launch done, Jet client connected Same

If stalled — no new markers for 5 minutes, or past tier budget (~15m macOS, ~45–60m iOS/Android) without Tests Complete: treat as interrupted run. Run pre-flight recovery, confirm host-clear probes and services ready, retry. Do not keep watching flat tee output.

Do not poll pgrep, process names, or :8090 for completion (above). Stall detection uses missing progress markers, not exit polling.

Harness narrowing gate (blocking)

Both unit-focused and area-focused tiers require area narrowing in tests/app.js / tests/globals.js before the first :test-cover. The only difference between those tiers is whether .only is allowed and whether the full package-area spec loads — not whether the harness stays at full app load.

Mistake Symptom Gate impact
Run :test-cover on committed full harness during implementation or independent-review macOS/iOS/Android pass counts in the hundreds or thousands (all modules via require.context) Run is invalid — does not close implementation_gate or review_gate
Narrow only if (Platform.other) or only set initial platformSupportedModules while if (!Platform.other) still pushes full native list macOS ~700 firestore tests pass; iOS/Android logs show database, crashlytics, etc.; thousands of tests / Jet WS 1006 under load Run is invalid on iOS/Android — see two platform blocks
Correct pipeline area harness ~100 passing per platform for Pipeline.e2e.js only (pipeline workflow) Expected for pipeline area runs

Apply locally before every :test-cover at unit-focused or area-focused tier — even when git shows the full push harness. Revert tests/app.js / tests/globals.js after the run if the branch commit keeps full harness (typical until phase R).

Validation report must state: harness narrowed (yes/no), which module/spec loads, whether pass counts match area scope, and which platforms ran with exit codes. A green full-app run is not a substitute.

Platform coverage gate (blocking — no shortcuts)

Both unit-focused (implementation) and area-focused (baseline-capture, independent-review) require e2e on every platform where the changed module loads in the committed harness — not a subset for convenience.

Determine required platforms from tests/app.js (two platform blocks — use committed lists when deciding macOS vs native requirement, not a narrowed local harness):

Platform class When required
macOS (Platform.other) Module appears in the committed if (Platform.other) list (or your narrowed list includes it on macOS)
iOS and Android Module appears in the committed if (!Platform.other) list (or your narrowed list includes it on native)

Area-focused (baseline-capture, independent-review) — closes review_gate / baseline only when:

  1. Full loaded package spec(s) with area narrowing (no .only).
  2. Serial :test-cover on each required platform above — pre-flight before every run.
  3. Native platforms: yarn tests:<platform>:build before first :test-cover when product/native JS changed (Rules §4).
  4. Subagent/orchestrator return includes a platform matrix: platform, exit code, pass/fail/pending counts, log path.

Invalid shortcuts (do not close gates):

  • “macOS + iOS minimum”; skipping Android when the module loads on Android.
  • “Skip Android if time tight” or “Android fallback only if iOS failures look env-related” without a fresh Android run.
  • Substituting a prior implementer log for independent-review on the frozen tree.

Module-specific skip: only when the module is absent from that platform’s harness list (e.g. messaging is not on macOS). Record in the work-queue Notes — not an oral exception.

Unit-focused (implementation) — native touched: macOS first when the path is TS/web-only; when the module loads on iOS and Android, run both before closing implementation_gate (same narrowing; .only OK locally; never commit).

See also: coverage design § platform parity, validation checklist § handoff.

Checklist (copy before first run):

  1. Both platform blocks narrowed or disabled — not just macOS / not just initial array.
  2. platformSupportedModules lists only the package under change (e.g. firestore + app).
  3. Spec load uses direct require of the area spec — not require.context for all packages — when sub-suite narrowing applies; otherwise full package require.context is OK when the module list is narrowed.
  4. No .only when tier is area-focused; .only optional when tier is unit-focused.
  5. Grep log: pass count consistent with area scope (~100 for pipeline-only, ~700 for full firestore package on macOS), not full app (~141+ macOS baseline with full load per work queue).

Unit-focused-tier iteration loop

For implementation work type — validation tier unit-focused (change authoring workflow):

  1. Pre-flightprepare completion gate when lib/** changed, host-clear probes, services ready, harness narrowed (step 3), RNFBDebug = true locally; if probes fail, pre-flight recovery first.
  2. Edit e2e/spec; add .only if needed; never commit narrowing.
  3. macOS first when TS-only: yarn tests:macos:test-cover 2>&1 | tee /tmp/rnfb-e2e-macos.log — wait for exit code (stalled run if markers stop).
  4. If macOS green and native touched: yarn tests:<platform>:build && yarn tests:<platform>:test-cover 2>&1 | tee /tmp/rnfb-e2e-<platform>.log; one platform at a time.
  5. Grep log tail → fix → repeat from step 1.
  6. When implementation_gate closes, next work type is independent-review at area-focused tier — frozen tree; no .only; area narrowing per package workflow.

Serialized e2e dispatch

Never overlap runs that use :test-cover. See host rule.

Rule Requirement
One e2e run at a time Wait for prior shell exit code + short log summary
No overlapping tiers Never run unit-focused-tier and area-focused-tier :test-cover concurrently on one host
Clean pre-flight every run Pre-flighthost-clear probes, services, harness tier
Phase J loop implementation (Jest + unit-focused) → independent-review (area-focused, frozen tree) → commitwork queue protocol
Validation tier E2e scope Narrowing allowed Typical work type
Unit-focused Backpressure while product code is changing it.only / describe.only / tight area narrowing in tests/app.jsnever commit implementation
Area-focused Full loaded spec(s) for the package/area under change Area narrowing required in tests/app.js / tests/globals.js; no .only baseline-capture, independent-review
Full All modules, all platforms None — revert all narrowing pre-merge-validation

Each run owns its blocking :test-cover and returns summaries only.

Interrupted run (abort, killed terminal, EADDRINUSE on :8090)

Run pre-flight recovery, confirm host-clear probes pass, then rerun from repo root: yarn tests:<platform>:build && yarn tests:<platform>:test-cover (foreground; tee if logging).

What not to do

  • Do not invoke the test runner (Jet), Detox, Metro, or emulators except through repo-root yarn tests:* commands in this doc — see agent rule.
  • Do not run :test-cover, :build, Metro restart, or pre-flight while yarn / yarn lerna:prepare is still in progress — wait for exit 0 first (prepare completion gate).
  • Do not background :test-cover and poll pgrep, detox, or process names for completion.
  • Do not use :test-cover-reuse, :test-cover-and-process, or :test-reuse when measuring coverage or closing review gates.
  • Do not use :8090 listening as “e2e still running” without the platform active signal above.
  • Do not start iOS/Android/macOS :test-cover concurrently on one host.
  • Do not edit source while a tee'd run is still in progress.
  • Do not passively tail tee output when progress markers stop — follow stalled run detection.
  • Do not run full harness (require.context, all modules) for unit-focused/area-focused tier — match harness to tier.
  • Do not run .github/workflows/scripts/boot-simulator.sh, simctl shutdown all, or kill -9 on :8090 as prep. boot-simulator.sh is CI-only or internal to iOS test-runner retry.

Typical loop

# Background (once):
yarn tests:emulator:start
yarn tests:packager:jet

# Per platform (rebuild when native changed):
yarn tests:ios:build && yarn tests:ios:test-cover
yarn tests:android:build && yarn tests:android:test-cover
yarn tests:macos:test-cover

Fast iteration: test narrowing

Full e2e loads every package. Narrow locally; never commit narrowing.

Kind Mechanism Scope
Area narrowing tests/app.js + tests/globals.js Which modules/specs load (e.g. trim platformSupportedModules; require one spec file instead of require.context)
Single-test narrowing it.only(...) One case in a loaded file
Single-suite narrowing describe.only(...) One block in a loaded file

Area narrowing = tests/app.js / tests/globals.js only; not test-runner --grep or packager --target.

Area harness in tests/app.js (two platform blocks)

Canonical owner for how to narrow the test app. Package workflows name which module/spec; this section defines how to edit tests/app.js so narrowing works on every platform.

Why two blocks

Committed tests/app.js builds platformSupportedModules from two separate blocks — only one runs per platform at bundle time:

Block Runs on Committed role
if (Platform.other) { … push … } macOS / Other Full macOS module list
if (!Platform.other) { … push … } iOS / Android Full native module list

Committed shape: const platformSupportedModules = [], then both blocks push their full lists.

Common agent mistake: set a narrowed initial array (e.g. ['app', 'firestore']) but leave if (!Platform.other) pushing every native module → macOS looks narrowed (~700 firestore tests) while iOS/Android still run the full app (thousands of tests, unrelated modules like database appear in logs). That invalidates harness narrowing gate on native platforms.

Apply narrowing (pick one pattern)

Pattern A — initial array + disable both blocks (recommended for one area on all platforms):

  1. Replace const platformSupportedModules = [] with the narrowed list (almost always include 'app').
  2. Change both populate blocks to if (false && Platform.other) and if (false && !Platform.other) so neither block re-expands the list.
  3. Add // TEMP: <module> area harness — never commit above your edits.
// TEMP: firestore area harness — never commit
const platformSupportedModules = ['app', 'firestore'];

if (false && Platform.other) {
  // committed macOS list — disabled while narrowed
  platformSupportedModules.push('app');
  // …
}

if (false && !Platform.other) {
  // committed iOS/Android list — disabled while narrowed
  platformSupportedModules.push('app');
  // …
}

Pattern B — trim inside each block (when macOS and native lists must differ):

  1. Keep const platformSupportedModules = [].
  2. Edit if (Platform.other) pushes to only the modules needed on macOS.
  3. Edit if (!Platform.other) pushes to only the modules needed on iOS/Android.
  4. Both blocks must be edited before :test-cover when the work item requires native platforms — editing only one block is invalid.

Do not use if (false && Platform.other) on one block while leaving the other block active unless that asymmetry is intentional.

Spec loading (optional second narrowing)

Module specs load later in loadTests() via platformSupportedModules.includes('<module>') — usually require.context('../packages/<module>/e2e', …).

Goal Change
Full package area Leave require.context as-is; narrowing the module list is enough
Single spec file Replace require.context for that module with require('../packages/<module>/e2e/<Spec>.e2e.js') (sub-suite — unit-focused diagnosis only unless package workflow says otherwise)

Revert before full tier or commit

Restore committed harness on both blocks:

  1. const platformSupportedModules = []
  2. if (Platform.other) { … } — full macOS list, no false &&
  3. if (!Platform.other) { … } — full native list, no false &&
  4. Restore any require.context edits; remove // TEMP comments
  5. Revert tests/globals.js (RNFBDebug = false) per before merge

Pre-flight check: grep tests/app.js — if either block is if (Platform.other) / if (!Platform.other) without false && and pushes modules outside your area, native or macOS runs are not narrowed.

Sanity check by platform

Platform Narrowed firestore-only (full packages/firestore/e2e) Pipeline-only (Pipeline.e2e.js)
macOS ~700 passing ~100 passing
iOS / Android Same order of magnitude as macOS for the same spec scope ~100 passing

Pass counts in the thousands or unrelated suites (database, crashlytics, …) in the log → re-apply both blocks and re-run.

Area example (Pattern A): firestore-only platformSupportedModules + both blocks disabled; full firestore specs via existing require.context in loadTests.

Package-specific spec names: Firestore pipeline harness, namespace removal § module area harness.

Fail-fast (RNFBDebug) and sub-suite narrowing

RNFBDebug (tests/globals.js): for unit-focused and area-focused tiers, set globalThis.RNFBDebug = true locally before the first :test-cover — not optional. It prints per-case start/finish and disables Mocha retry/backoff, so failures surface immediately instead of burning time on retries. Committed default must stay false. Revert to false with other harness edits before full tier or commit (§ before merge).

Kind Mechanism When
Sub-suite narrowing describe.only / require one e2e file (e.g. Aggregate/count.e2e.js only) Unit-focused diagnosis only — after pre-flight is clean and the same failure repeats on back-to-back runs without assertion progress. Never for area-focused gate closure (no .only). Never commit.
Single-test narrowing it.only(...) Same as sub-suite — unit-focused diagnosis only

Package workflows may name default area specs (e.g. Firestore pipeline harness); sub-suite narrowing is tighter than area narrowing for iteration speed.

E2e diagnosis escalation (cross-package): change authoring § implementation inner loop.

Package workflows may further restrict narrowing per validation tier.

E2e validation tiers (unit-focused / area-focused / full)

All tiers use canonical commands, host rule, and clean pre-flight. Tier names describe scope, not who runs the commands — see iteration vocabulary.

Validation tier E2e scope Narrowing allowed Typical work type
Unit-focused Fast loop while product code is changing it.only / describe.only / tight area narrowing in tests/app.jsnever commit implementation
Area-focused Full loaded spec(s) for the package/area under change Area narrowing required in tests/app.js / tests/globals.js; no .only baseline-capture, independent-review
Full Unfocused — all modules, all platforms None — revert all narrowing pre-merge-validation

Universal rules:

  • E2e is always serial — one :test-cover at a time on the host.
  • Every run starts from verified pre-flight; if probes fail, pre-flight recovery before another run.
  • Use only canonical commands from this doc.
  • Never overlap unit-focused-tier and area-focused-tier :test-cover on one host.

See also: unit-focused-tier loop, dispatch, pre-merge.

Environment

  • Devices — Detox boots simulator/emulator (iPhone 17 on iOS, TestingAVD on Android); host-clear probes require zero booted iOS simulators before :test-cover. macOS auto-starts app.
  • adb emptyadb kill-server && adb start-server && adb devices
  • Stale processes — one Metro (:8081), one emulator set (:8080, :9099, :9000, :4400, …). Stray listener on :8090 after a run → pre-flight recovery, then restart background services with Rules §1–2 (yarn tests:packager:jet, yarn tests:emulator:start).

Diagnosing hangs

Local stalls — see stalled run detection first (Metro /status, Jet client connected markers).

Native / device logs (remove instrumentation before merge):

  • macOSlog show --predicate 'process == "io.invertase.testing"' --last 10m --style compact; bundle errors → other.md
  • iOSxcrun simctl spawn booted log stream --level debug --style compact --predicate 'process == "testing"'; silent hangs: sample <pid> on testing
  • Androidadb logcat (filter your tags)

Benign noise: iOS Detox EXEC_FAIL "xcrun simctl terminate … com.invertase.testing" … found nothing to terminate — app wasn't running; ignore.

Cloud API pressure — Installations / Remote Config failures with FIS 503 or “Too many server requests” are live-project quota on any platform, not emulator issues. See Firebase testing project — CI triage.

Before merge (PR handoff)

Pre-merge applies once to the branch commit stream before merge/push intended for merge, not after every commit.

  1. Revert all narrowing (full tier): restore tests/app.js (platformSupportedModules + require.context), default RNFBDebug in tests/globals.js, remove all .only, remove native instrumentation.
  2. Pre-flighthost-clear probes pass before each platform run.
  3. Rebuild if needed (tests:<platform>:build; yarn lerna:prepare for lib/**).
  4. Full unfocused suite with coverage on iOS, Android, macOS — one platform at a time, all green.

Notes

  • Stale native build after native edits → rebuild first.
  • All three platforms required; macOS exercises the JS SDK path.