Files
wehub-resource-sync 426e9eeabd
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Waiting to run
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Waiting to run
Build Agent Image / build-and-push (push) Waiting to run
Chat shell gestures / Chat shell gesture + parity e2e (push) Waiting to run
ci / test (push) Waiting to run
ci / lint-and-format (push) Waiting to run
ci / build (push) Waiting to run
ci / dev-startup (push) Waiting to run
Cloud Gateway Discord / Test (push) Waiting to run
Cloud Gateway Webhook / Test (push) Waiting to run
Cloud Tests / lint-and-types (push) Waiting to run
Cloud Tests / unit-tests (push) Waiting to run
Cloud Tests / integration-tests (push) Waiting to run
Cloud Tests / e2e-tests (push) Blocked by required conditions
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
Deploy Apps Worker (Product 2) / Determine environment (push) Waiting to run
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Blocked by required conditions
Deploy Eliza Provisioning Worker / Determine environment (push) Waiting to run
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Blocked by required conditions
Dev Smoke / Classify changed paths (push) Waiting to run
Dev Smoke / bun run dev onboarding chat (push) Blocked by required conditions
Dev Smoke / Vite HMR dependency-level smoke (push) Blocked by required conditions
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Waiting to run
gitleaks / gitleaks (push) Waiting to run
Markdown Links / Relative Markdown Links (push) Waiting to run
Publish @elizaos/example-code / check_npm (push) Waiting to run
Publish @elizaos/example-code / publish_npm (push) Blocked by required conditions
Publish @elizaos/plugin-elizacloud / verify_version (push) Waiting to run
Publish @elizaos/plugin-elizacloud / publish_npm (push) Blocked by required conditions
Quality (Extended) / Homepage Build (PR smoke) (push) Waiting to run
Quality (Extended) / Comment-only diff guard (push) Waiting to run
Quality (Extended) / Format + Type Safety Ratchet (push) Waiting to run
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Waiting to run
Quality (Extended) / Develop Gate (lint) (push) Waiting to run
Sandbox Live Smoke / Sandbox live smoke (push) Waiting to run
Snap Build & Test / Build Snap (amd64) (push) Waiting to run
Snap Build & Test / Build Snap (arm64) (push) Waiting to run
supply-chain / sbom (push) Waiting to run
supply-chain / vulnerability-scan (push) Waiting to run
Build, Push & Deploy to Phala Cloud / build-and-push (push) Waiting to run
Test Packaging / Validate Packaging Configs (push) Waiting to run
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Waiting to run
Test Packaging / Pack & Test JS Tarballs (push) Waiting to run
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Waiting to run
UI Fixture E2E / ui-fixture-e2e (push) Waiting to run
UI Fixture E2E / fixture-e2e (push) Waiting to run
UI Story Gate / story-gate (push) Waiting to run
vault-ci / test (macos-latest) (push) Waiting to run
vault-ci / test (ubuntu-latest) (push) Waiting to run
vault-ci / test (windows-latest) (push) Waiting to run
vault-ci / app-core wiring tests (push) Waiting to run
verify-patches / verify patches/CHECKSUMS.sha256 (push) Waiting to run
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Waiting to run
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Waiting to run
Voice Benchmark Smoke / voice bench smoke summary (push) Blocked by required conditions
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Waiting to run
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Waiting to run
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Waiting to run
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Waiting to run
Test Packaging / Build & Test PyPI Package (push) Waiting to run
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:43:05 +08:00

240 lines
15 KiB
Markdown

# On-device testing pipeline (Android + iOS fleet)
## Summary
The MVP is the LifeOps Personal Assistant (GitHub project 15): chat, onboarding, the
current views, and LifeOps scheduling/reminders/goals/tasks, serving children, adults
with ADHD/ADD/Asperger's/autism, neurotypical adults, and elderly users through real-life
scenarios — no therapy language, no special rails. Every dev machine has Android and iOS
devices plugged in; those devices ARE the mobile e2e environment (CI has no iOS e2e lane
at all, and the Android CI lane is dispatch-only with a known x86_64 SIGSEGV limitation).
The MVP question for this workstream: can any engineer or coding agent, on any dev
machine, run one command per platform that builds current develop, installs it on the
plugged-in device, runs the e2e lane, and hands back a triage-ready artifact bundle —
and can they check at a glance whether every connected device runs develop HEAD?
Decision: **do not build new infrastructure.** Nearly every primitive already exists —
per-platform orchestrators, a physical-iPhone deploy pipeline, a content-derived renderer
build stamp carrying the git commit, capture scripts, an evidence-required gate. The work
is (1) fixing a verified stale-install bug in the Android runner, (2) wiring the existing
build stamp into a fleet-wide `devices:status` check, (3) collapsing scattered artifacts
into one run-scoped bundle in inline-postable formats (MP4/JPG), and (4) chaining the
already-proven physical-iPhone scripts into one command.
## Current state
What exists today, verified in code:
**Per-platform e2e orchestrators exist and are real (not mocks).**
- `packages/app/scripts/android-e2e.mjs:189-281` — single entrypoint: boots an
emulator or reuses an attached device (`lib/android-device.mjs:263-276` prefers the
attached physical device), installs the APK, runs the on-device local-model chat smoke,
drives every route on the real WebView via Playwright's Android driver, optional cloud
probe. Loud non-zero exits.
- `packages/app/scripts/ios-e2e.mjs:87-131` — simulator lane: boots a sim, builds by
default (`build:ios:local:sim`), runs the auth deep-link smoke and the full-Bun
on-device local chat smoke. **Simulator only** — there is no physical-iPhone e2e
entrypoint; the pieces exist but are not chained (see below).
**Verified bug: the Android runner runs stale builds by default.**
`android-e2e.mjs:202-214`: `--build` is opt-in, and the install step is
`if (!isInstalled(...))``isInstalled` (`lib/android-device.mjs:382-392`) only checks
`pm list packages`. Consequences: (a) the default run executes whatever APK was installed
last week; (b) even `--build` builds a fresh APK and then **skips installing it** when
any version of the app is already on the device. This is the exact Capacitor
bake-the-web-bundle-into-the-APK footgun the root `CLAUDE.md` warns about, live in the
runner itself.
**A build-identity stamp already exists — but is only enforced on the iOS simulator.**
- `packages/app/vite/renderer-build-manifest-plugin.ts:13-37` writes
`dist/eliza-renderer-build.json` on every production renderer build: content-derived
`buildId`, `commit` (git HEAD), variant, capacitorTarget, runtimeMode.
- `packages/app/src/renderer-build-stamp.ts:1-36` exposes it at runtime as
`window.__ELIZA_RENDERER_BUILD__` and at the web root.
- `packages/app/scripts/lib/ios-renderer-stamp.mjs:123-145`
(`assertInstalledIosAppRendererFresh`) compares the installed sim app's stamp against
fresh dist — used by `mobile-local-chat-smoke.mjs:336` and
`ios-onboarding-smoke.mjs:184-218`.
- `packages/app-core/scripts/lib/mobile-lane-stamp.mjs:1-31` guards against wrong-lane
dist reuse pre-Capacitor-sync (#11030).
- **Gaps:** no Android installed-freshness check anywhere (the stamp ships inside the APK
at `assets/public/eliza-renderer-build.json` since Capacitor `webDir: "dist"`,
`capacitor.config.ts:129`, but nothing reads it back); no physical-iOS freshness check;
no fleet-wide "what does each connected device run vs develop HEAD" report.
`ELIZAOS_VERSION_NAME`/`CODE` stamping in `run-mobile-build.mjs:807-887` only fires
when the env vars are set — dev builds all report `1.0`.
**Physical-iPhone pipeline exists and is proven, but is four separate commands.**
- `packages/app/scripts/ios-device-deploy.mjs` — unsigned build → provisioning-profile
auto-discovery → graft → explicit nested signing → `devicectl` install → launch.
`--skip-appexes` (lines 24-36, 429-441; PR #13174, merged 2026-07-04) strips the five
app extensions so the main app deploys with only the app's own team profile — the
unattended posture, since minting per-appex profiles needs an Xcode account session or
an ASC API key, neither available headlessly on dev machines.
- `packages/app/scripts/ios-device-capture.mjs` — XCUITest BootCapture harness
(screenshots via XCUIScreen, exports attachments + `test-summary.json` from the
.xcresult, lines 522-537); retry-in-isolation for flake cascades landed in #13698.
- `packages/app/scripts/ios-device-logs.mjs` — bounded console capture + boot-trace pull
(with the #11515 caveat: attached console SIGTRAPs the full-Bun engine, so engine
observability uses `--no-console --pull-boot-trace`).
- No deploy ledger: nothing records which buildId/commit was last deployed to which
physical device, and `ios-device-deploy.mjs` has no renderer-freshness assertion
(verified: zero matches for `renderer|stale|fresh` in the file).
**Capture and evidence tooling exists but targets the old evidence mechanics.**
- `packages/app/scripts/capture-ios-sim.mjs:48-79` records `.mov` via
`simctl io recordVideo` (h264 in a QuickTime container) — GitHub does **not** render
`.mov` inline in issues/PRs, so today's iOS captures cannot satisfy the new
inline-evidence rule without a remux. `capture-android-emu.mjs:84-158` records `.mp4`
(fine), plus `logcat -d -t 500` and a backend-log pull.
- All capture defaults write to `.github/issue-evidence/`
(`lib/issue-evidence.mjs:16-21`) — the repo is moving to inline PR/issue posting
(MP4 + JPG), so the committed-evidence directory stops being the destination.
- The green-with-nothing fix landed (#13624): `resolveRequireEvidence` +
`skip()` exiting non-zero when evidence is required
(`lib/issue-evidence.mjs:60-100,246-260`; sweep enforcement in
`scripts/e2e-recordings/run-all.mjs:56-61,388-402`).
**Failure surfacing is stdout-only and artifacts scatter across five locations.**
- `playwright.android.config.ts:36-43`: reporters are `list` + `html` — no JUnit, no
JSON; screenshots `only-on-failure`, trace `retain-on-failure`, landing in Playwright's
own `test-results/`.
- iOS smokes throw with a screenshot path embedded in the error string
(`mobile-local-chat-smoke.mjs:1475-1492`) — actionable only if you scroll the log.
- Artifact locations today: `.github/issue-evidence/`, `e2e-recordings/<suite>/test-results/`,
`packages/app/ios/build/{device-deploy-stage,device-logs,boot-capture}/`,
Playwright `test-results/`, `reports/walkthrough/`. No single per-run bundle.
- `packages/app/scripts/walkthrough-device-matrix.mjs:20-26` is the closest thing to a
fleet probe: it detects available platforms and writes an honest per-platform
`device-matrix.json` (run | n/a + reason) — but it reports availability, not
installed-build-vs-HEAD.
**CI cannot replace the dev-machine fleet.**
`.github/workflows/android-device-e2e.yml:1-35`: workflow_dispatch only, plus a minimal
PR slice behind the `ci:device` label; the file header documents that the on-device agent
SIGSEGVs on stock x86_64 emulators, so the local-model route only greens on real arm64
hardware. There is no iOS e2e workflow at all (`build-ios.yml` is build-only on
macos-15). The plugged-in device fleet is the only place the real mobile paths run.
**No device lease.** Concurrent agent sessions on one machine collide on the same
simulator/device — `ios-e2e.mjs:50-61` grabs the first booted simulator; nothing locks a
serial/udid. This has caused real contention between concurrent sessions on this fleet.
## Design considerations
- **Wire, don't build.** Every capability the mission asks for maps onto an existing
script; the deltas are defaults, one new ~200-line status script, and bundle plumbing.
- **Freshness must be stamp-driven, not rebuild-always.** The renderer stamp is
content-derived and carries the commit; comparing installed-vs-fresh `buildId` makes
"skip the 4-minute rebuild when nothing changed" safe, and makes "reinstall when stale"
automatic. Rebuild-always would make the runner too slow to be run habitually.
- **Inline evidence formats are a capture-time concern.** MP4 renders inline on GitHub;
MOV does not. The simctl recording is already h264, so `.mov → .mp4` is a container
remux (`ffmpeg -c copy`), not a re-encode. Screenshots stay PNG for pixel assertions;
the bundle emits JPG copies for posting.
- **JUnit-ish summary is nearly free.** Playwright ships `junit` and `json` reporters —
adding them to `playwright.android.config.ts` is config. The non-Playwright iOS smokes
need a small shared `summary.json` writer; `ios-device-capture.mjs` already writes
`test-summary.json`, so the schema should extend that shape.
- **Fail-fast doctrine applies to the runners.** On a fleet machine with devices
guaranteed plugged in, a runner that soft-skips "no device" is lying. The
`--require-evidence` contract (#13624) already encodes this; the per-platform runners
should arm it by default and keep the opt-out.
- **Point-of-failure forensics beat post-hoc archaeology.** The runner already owns the
child-process boundary (`android-e2e.mjs:162-171 run()`); catching a failed step there
and immediately capturing screenshot + `logcat -d` / boot-trace is a ~30-line change
per platform, not a framework.
## Open questions → answers
**Q1. Should the per-platform runner always rebuild?**
No — stamp-driven. Default flow: compute fresh-dist `buildId` (building only if dist is
missing/stale for the lane per `mobile-lane-stamp.mjs`), read the installed stamp, and
rebuild+reinstall only on mismatch. `--skip-build` stays for tight iteration,
`--force-build` for paranoia. Rationale: correctness of the Capacitor footgun without
making the habitual command cost 5+ minutes when nothing changed.
**Q2. Where do triage bundles live now that `.github/issue-evidence/` is deprecated as a
destination?**
A gitignored run-scoped directory: `packages/app/device-e2e-output/<platform>-<runId>/`
with `inline/` (the MP4s + JPGs sized for posting), `logs/`, `summary.json`, and
`junit.xml`. The runner prints the absolute path as its last line so an agent can post
`inline/` contents directly to the PR. Existing `.github/issue-evidence/` writers remain
untouched for old flows but the runners stop defaulting there.
**Q3. JUnit or JSON as the machine-readable summary?**
Both, JSON canonical. `summary.json` (shared schema: lane, device identity, installed
buildId/commit, steps[] with status/duration/artifact paths) is what coding agents parse;
`junit.xml` is a free byproduct of the Playwright reporter for anything expecting JUnit.
Undecidable-free: nothing consumes JUnit today, so JSON-canonical costs nothing.
**Q4. Do we try to green the CI emulator's local-model route for MVP?**
No. The x86_64 SIGSEGV is a hardware-emulation issue documented in
`android-device-e2e.yml`; the fix (self-hosted arm64 runner) is infrastructure the MVP
does not need while every dev machine has real arm64 devices attached. CI stays
dispatch/label-gated; the fleet is the mobile e2e environment of record.
**Q5. Do we need a device farm / fleet service?**
No. One machine, a handful of devices, multiple concurrent agent sessions — the failure
mode is contention, not scale. A lease lockfile keyed by serial/udid under the state dir
(stale-expiry, holder PID + session id) is sufficient and deletes nothing if unused.
**Q6. What about the iOS appex signing blocker for unattended runs?**
Accept `--skip-appexes` as the default unattended posture (already merged, PR #13174) and
have the runner log loudly that widgets/keyboard/device-activity surfaces are untested on
device. Minting per-appex profiles requires an ASC API key — a real owner decision
(credential provisioning), default: park it; the MVP surfaces (chat, onboarding, views,
LifeOps) do not live in appexes.
**Q7. How does a physical iPhone report its installed build when we can't read its app
container?**
Deploy-time ledger. `ios-device-deploy.mjs` stages the App.app locally, so it can read
the staged bundle's renderer stamp and append `{udid, buildId, commit, deployedAt}` to a
state-dir ledger; `devices:status` reads the ledger and reports "unknown — no ledger
entry" honestly when a device was flashed by other means. The boot-trace pull
(`ios:device:logs --pull-boot-trace`) remains the ground-truth cross-check.
## Recommendation
Minimal-scope MVP plan, in order:
1. **Fix the Android stale-install bug and make freshness stamp-driven** in
`android-e2e.mjs` (P0). Add the Android installed-stamp reader
(`pm path` → pull APK → read `assets/public/eliza-renderer-build.json`) to
`lib/android-device.mjs`; reinstall on mismatch; make `--build` actually reinstall.
2. **Ship `devices:status`** (P0): one read-only script enumerating `adb devices`,
`simctl list`, `devicectl list devices`; per device print installed
buildId/commit vs `origin/develop` HEAD and fresh dist; exit non-zero on `--require-fresh`.
3. **Triage bundle + machine-readable summary** (P1): run-scoped output dir, Playwright
junit+json reporters, shared `summary.json` writer, MOV→MP4 remux + JPG copies in
`inline/`.
4. **One-command physical-iPhone lane** (P1): chain deploy (`--skip-appexes` default) →
local-chat smoke → BootCapture → logs → bundle, and write the deploy ledger.
5. **Point-of-failure forensics** (P1): failed step ⇒ immediate screenshot + device log
snapshot into the bundle; runner exit summary names the failing step + artifact paths.
6. **Device lease lock** (P2): serial/udid-keyed lockfile so concurrent sessions queue
instead of colliding.
## Out of scope (MVP non-goals)
- Greening the CI x86_64 emulator local-model route or standing up self-hosted arm64
runners.
- Automating per-appex provisioning-profile minting (needs ASC API key — owner decision).
- Any device-farm service, remote-device brokering, or multi-machine fleet orchestration.
- Changes to desktop capture lanes (`capture-linux-desktop.mjs`,
`capture-windows-desktop.mjs`, `capture-macos-desktop.mjs`) — they already exist and
are not on the mobile-fleet path.
- New mobile test *content* (LifeOps scenario coverage is the scenarios workstream; this
workstream is the pipeline those tests run on).
- Store-release signing/TestFlight automation (`apple-store-release.yml`,
`android-release.yml` are separate release lanes).
## Proposed issues
1. [device-testing] android-e2e runs stale builds: install-if-missing skips fresh APKs — make freshness stamp-driven (P0)
2. [device-testing] devices:status — report installed buildId/commit per connected device vs develop HEAD (P0)
3. [device-testing] Triage-ready artifact bundle per device-e2e run: MP4 + JPG + logs + summary.json + junit.xml (P1)
4. [device-testing] One-command physical-iPhone e2e lane (deploy → smoke → capture → bundle) with deploy ledger (P1)
5. [device-testing] Point-of-failure forensics: auto-capture screenshot + device logs when a runner step fails (P1)
6. [device-testing] Device lease lock: stop concurrent agent sessions colliding on one simulator/device (P2)