test(android): restore full-tier lifecycle and observability scenarios (#1781 A1) - #1793
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. |
#1781 A1) The nightly Android job has failed on `click id="automation-request-microphone"` since the full tier landed: `settings permission reset microphone` runs `pm revoke`, and revoking a *granted* runtime permission kills the app process, so the round after an accept clicked into an empty launcher surface. Reproduced on the pinned CI image (android-36 google_apis_playstore, Pixel 7): pm revoke leaves pid 4259 alive when the permission is denied and kills it when it is granted, with NexusLauncher resumed afterwards. Fixing that exposed the rest of a scenario that had never executed end to end: the post-revoke readback cold-started on the tabs home instead of Automation lab, the relaunched Automation lab needed its controls revealed, the Form tab does not exist on the Automation root route, that section needs the system IME back, and the IME diagnostic sits above the bottom of the form. The observability scenario then failed the same way (reveal distances tuned for a taller device) plus an event-timeline walk whose page was smaller than the events each page read appends. Validated live against a local Pixel_7_CI emulator (API 36, same profile as the lane): the full tier now runs bootstrap -> inventory -> automation-system -> form-input -> keyboard-ime -> capture-close -> lifecycle-system -> observability-artifacts and stops only in full:fixture-replays.
def917d to
99d209e
Compare
|
Not ready yet. The nested batch regression is still not independent: the get step resolves dismiss-notice, while the is step checks release-notice, its parent. A successful child lookup already establishes that parent is present, so this cannot independently prove the nested is operation. Please use an independently co-visible target (for example gesture-lab-card), after confirming the pinned Pixel 7 snapshot. Before this can address #1781 A1, please also provide a successful exact-head Android Full Emulator Suite run (or equivalent uploaded full-tier artifact/evidence). full:fixture-replays remains red, so the linked A1 fix-or-disable sequence is incomplete until its fix or the separate disabling change is ready. Coverage is red due to an unhandled Vitest worker-fork exit and needs a clean rerun. Bundle Size is a GitHub 503 while posting its report, so I consider that infrastructure rather than a source finding. |
… canary (#1781 A1) Review follow-up. The nested batch regression now checks a sibling card instead of the notice that owns `dismiss-notice`: resolving a child already proves its parent is present, so the old target could not fail on its own. Confirmed on a Pixel 7 / API 36 emulator that `gesture-lab-card` and `dismiss-notice` are on screen together at the scenario's existing 0.3 reveal (both present at 0.2-0.4; the card is gone by 0.5). Getting a full-tier run to complete then required repairing what the lane had never executed: - `01-navigation-scroll.ad` clicked `label="Catalog, 0 new notifications"`. #1543 made the cart badge conditional, so the live label is `Catalog` — what the iOS twin already used. - The catalog scroll canary lives inside the scrolling content, and Android accessibility snapshots carry on-screen nodes only, so every state except the initial `top` was unobservable: `wait "Catalog scroll: down|bottom|up"` could never pass, whatever the swipe coordinates were. `stickyHeaderIndices` pins that one line, which makes all four states readable at any offset on both platforms rather than tuning the .ad around a canary that scrolls away. - `gesture-lab-android.ad` started its multi-pointer gestures at y=1040, inside the target when the file was last repaired but 90px from its top edge after #1567 moved the card (targets now span y=949-1525). The second pointer landed outside the view, which reads as "the gesture did nothing". Multi-pointer gestures now start at the target centre, and the header comment records the geometry they depend on. Evidence: the lane's own command (`AGENT_DEVICE_ANDROID_E2E_TIER=full` over smoke-android-emulator.test.ts) passes end to end on a Pixel 7 / API 36 AVD with a CI-equivalent fixture APK (cached native + head JS through the same repack the workflow runs): 9/9 scenarios, 153s.
|
All three addressed; the branch now carries a green full-tier run. Independent batch target. You were right that a child lookup already proves its parent, so Exact-head full-tier evidence. Run against a CI-equivalent fixture APK: the cached native build plus head JS through the same Three further drifts had to be repaired to get there, all of them invisible until the scenario ran that far:
One honest caveat: in three full runs, Coverage. Green on the current head after a clean re-run. The PR body keeps the analysis: three failures with no failed test, With |
|
Re-reviewed exact head |
|
Hosted dispatch done — that retires the "no hosted Replay Nightly dispatch for this branch" residual: run 32107665052 on this exact head. The full tier passed on the hosted emulator, all nine scenarios, no retries: That is the same result as the local Pixel 7 run, on the lane's own The job still ends red, and not on this PR's path. After the E2E the script runs a separate suite, Why I read that as not attributable to this branch:
I have left it alone rather than widen this PR into a third suite (system-app replays plus a system-surface recovery step are their own change, and the E2E evidence you asked for is complete without them). Happy to take it as a follow-up if you would rather have the whole job green in one go — say the word and I will pick it up in a separate PR. |
|
Summary
#1781A1. The nightly Android job (Android Full Emulator Suite) has failed every scheduled run since the full tier landed, always on the same assertion:Root cause
settings permission reset microphonemaps topm revoke(documented incommands.md), and Android kills the app process when a granted runtime permission is revoked. Theacceptround grants the permission; the reset that opens thedenyround therefore terminates the fixture, and the click lands on the launcher.Proven on the image the lane pins (
android-36 google_apis_playstore, Pixel 7 profile):The nightly artifacts agree: in run 31990896404 the
scroll bottomimmediately before the failing click reports"summary": "Already at bottom", "passes": 0(the launcher is not scrollable), while the same step in theacceptround scrolled 1440px.Why #1610 did not fix it
#1610 fixed a different defect in the same helper — a one-shot
alert getracing the asynchronous prompt — by switching toalert wait 10000. That made theacceptround reach its prompt, which moved the failure one step later, onto the revoke-kill. Before #1610 the job failed at{"alert":null,"message":"No alert visible"}(run 30974032723); from 2026-08-06 onwards it fails at thedenyclick (runs 31070190261, 31924041273, 31990896404).Why there was more than one defect
full:lifecycle-system(added in #1484) andfull:observability-artifactshave never executed end to end — #1484 explicitly deferred to the smoke tier for device validation, and the nightly has died inside the first of them every night since. Running them live surfaced the whole chain:Selector did not match: id="automation-request-microphone"home+openwait "Automation lab"times outget text id="automation-microphone-permission"finds nothing (Android snapshots are on-screen only)click label="Form"finds nothingopenre-activates the test IMEkeyboard statusreturnsvisible:false,inputMethodPackage: …imehelperscroll bottomovershoots the IME diagnosticwait "Android fill input was captured…"times outscroll down 0.7overshoots the quick actions on pixel_7press id="home-open-catalog"finds nothingget text id="dismiss-notice"finds nothingevents pagination exceeded 100 pagesWhat this changes
live-lifecycle-scenario.ts: oneopenAutomationLabhelper relaunches the deep-link route and reveals its controls; every permission reset and the post-revoke readback go through it. The IME section closes the session (the only thing that restores the previous IME), reopens the tabs root with--no-test-ime, and reveals the diagnostic with a bounded scroll.live-observability-scenario.ts: reveal distances corrected for the pinnedpixel_7profile, the batch'sistarget changed to an element co-visible withdismiss-notice, and the event page size raised above the tail each page read appends.examples/test-app/src/screens/CatalogScreen.tsx:stickyHeaderIndicespins the scroll-statecanary so Android can read it at any scroll offset.
examples/test-app/replays/gesture-lab-android.adandtest/integration/replays/android/fixture/01-navigation-scroll.ad: one selector and themulti-pointer start points re-derived from the live fixture on the pinned profile.
No production code is touched.
Validation
Live, on a local
Pixel_7_CIAVD (API 36,google_apis_playstore, same profile/API as the lane), with the fixture APK and the packaged snapshot/IME helpers:AGENT_DEVICE_ANDROID_E2E_SCENARIOS=full:lifecycle-systemreproduced the nightly assertion verbatim.pass 1,full:lifecycle-system: 38241ms).pnpm check:quick,pnpm format:check, andnode --test test/integration/smoke-android-emulator-coverage.test.ts(10/10) pass.Scope
Six files:
live-lifecycle-scenario.ts,live-observability-scenario.ts,live-form-scenario.ts(one constant exported), the
01-navigation-scroll.adandgesture-lab-android.adfixtures, andCatalogScreen.tsxin the fixture app. No production code.Full-tier evidence
Hosted, on the lane's own image — run 32107665052, a
workflow_dispatchof Replay Nightly on this exact head. All nine scenarios pass with no retries:That job still ends red on a separate suite that runs after the E2E —
pnpm gate replay-android,4 passed (8), 4 failed, every failure carrying agent-device's own "a system surface … covers the app" diagnosis (Silent, Collapse, Android System). It starts on two Settings-app replays this PR does not touch, clears again three files later, and the gate has not executed since 2026-07-30 because the workflow'sset -euscript never reached it while the E2E was failing. Detail in this comment; deliberately left to a follow-up rather than widening this PR into a third suite.Local reproduction
The lane's own command passes end to end on a Pixel 7 / API 36 AVD (the profile and image
replays-manual.ymlpins), against a CI-equivalent fixture APK — cached native plus head JSthrough the same
repack-android-apk.shthe workflow runs:Getting there needed three more repairs, each a drift the lane had never executed far enough to
see:
01-navigation-scroll.adclickedlabel="Catalog, 0 new notifications". fix: restore scheduled workflow health #1543 made the cartbadge conditional, so the live label is plain
Catalog— which the iOS twin of this filealready used.
on-screen nodes only, so every state except the initial
topwas unobservable —wait "Catalog scroll: down|bottom|up"could not pass at any swipe coordinates. Rather than tunethe .ad around a canary that scrolls away,
stickyHeaderIndicespins that one line, so allfour states are readable at any offset on both platforms.
gesture-lab-android.adstarted its multi-pointer gestures at y=1040 — inside the targetwhen the file was last repaired (fix(test): repair Android replay fixtures against live device reality #1538), but 90px from its top edge after feat: add selector-targeted drag gestures #1567 moved the
card (targets now span y=949-1525), so the second pointer landed outside the view and the
gesture read as a no-op. Multi-pointer gestures now start at the target centre, and the
header comment records the geometry they depend on. Isolated on device:
(220,1040)fails atdistance 180 and 500,
(300,1237)passes at both — position, not distance.One flake seen once in three full runs:
record stopreportedAndroid screenrecord ownership could not be confirmed for pid …infull:observability-artifacts. It passed on the scenariore-run and on the full re-run; it is a step this PR does not touch, so it is noted rather than
chased.
CI note: the Coverage red was not this branch (input for #1781 A4)
Coveragefailed three times on this PR with no failed test —Error: [vitest-pool]: Worker forks emitted error/Caused by: Error: Worker exited unexpectedly, last file logged before the ~36s gap alwayssubprocess-stub scripts/fuzz/corpus-replay.test.ts, summaryTest Files 942 passed (943). It passes on re-run (job95465796922). What the evidence says:git diff --stat origin/main...HEADis six files — four scenario/harness files undertest/integration/android-emulator-e2e/, one.adreplay fixture, and the fixture app screen — withorigin/mainan ancestor of HEAD and no lockfile or config change. (At the time this note was written the diff was the three scenario files; the fixture repairs below were added after.) And no Vitest project includes that directory (vitest.config.ts:unit-core,subprocess-stub,provider-integration,interaction-contract,output-economy) — these scenarios run undernode --test. The Coverage job executes the same 943 files with or without this diff.pnpm test:coverage:ci(the exact CI script) twice on this branch: run 1 exit 1 on an unrelated CPU-contention timeout (runner-client.test.ts > ensureXctestrunArtifact aborts only the disconnected request build,Test timed out in 5000ms) with zeroWorker exited unexpectedly; run 2 fully green,Test Files 943 passed (943), exit 0.claude/agent-device-issue-1783-9dcc8e(job95439415417, 16:58) andclaude/agent-device-1774-67603d(jobs95428461468at 16:07 and95421425175at 15:14).95428461468has this PR's exact shape — no failed test, one file unaccounted for (940 passed (941)).So it is (b): a
subprocess-stubfork being killed under runner load. Two properties make it a job-level red rather than a blip: that project runsfileParallelism: false,maxWorkers: 1,isolate: truewhile its tests spawn real processes, and a killed fork surfaces as an unhandled error, whichscripts/lib/contention-retry.tsdeliberately refuses to retry ("No retry: the run failed for a reason a rerun cannot re-check"). A human re-run then passes, which is the worst shape: the policy that exists to keep contention honest converts a killed worker into a red that only manual intervention clears. Handing that to #1781 A4 (the subprocess-stub / contention-retry decision) rather than widening the retry policy from this PR.How to verify
Trigger the workflow manually on this branch and watch the Android job:
Or locally, against a booted
pixel_7/API 36 AVD:Draft because the lane cannot go green until the replay fixture above is decided.