ci(e2e): gate the emulator on window focus to fix the RootViewPicker flake (#468)
Root cause: intermittently the launched activity window has has-window-focus=false for the WHOLE instrumented run, so Espresso's RootViewPicker (onView().check(), Intents.intended(), pressBack(), focus-dependent clipboard) times out after 10s and fails EVERY focus-dependent test at once while the ~280 pure-Compose semantics tests (which don't need window focus) pass. A failing E2E (35) leg's logcat (PR #470, run 28985259521) shows has-window-focus=true ZERO times across the whole session and both the first attempt and the once-retry fail identically -- a persistent environmental state, not a per-test transient. The prior mitigation, a single fire-and-forget `adb shell input keyevent 82` (MENU) right after boot, is too weak: MENU no longer dismisses the modern (API 30+) keyguard and, delivered before SystemUI/keyguard comes up, is simply dropped -- so the insecure keyguard / non-interactive display persists and no app window ever takes focus. Fix: a single shared helper, .github/scripts/emulator_focus_gate.py, invoked identically by BOTH E2E jobs (the e2e API 29-36 matrix AND e2e-preview API 37) and by the local preflight runners (local_instrumented.py / api37_e2e.py), so it cannot drift. It wakes the display (KEYCODE_WAKEUP), dismisses + disables the keyguard (wm dismiss-keyguard, locksettings set-disabled true), keeps the screen on (svc power stayon true + max screen_off_timeout), zeroes the animation scales, then polls dumpsys power/window until the device is interactive AND a real window holds input focus (mCurrentFocus non-null) -- re-nudging each iteration -- before the suite runs. Applied uniformly, this also gives e2e-preview the animation-disable the matrix already had. The gate is soft (bounded wait, then proceeds with a ::warning:: and the final device state) and non-fatal (`|| true`), preserving #454's guarantee that the unlock never aborts the boot; it leaves #454's manual boot, #460's path-filter and #464's wedge-capture untouched. The pure readiness parser is unit-tested by test_emulator_focus_gate.py (run by the traffic-control-tests job). Determinism is validated by this PR's own matrix run. Closes #468
This commit is contained in:
@@ -632,12 +632,20 @@ jobs:
|
||||
for attempt in 1 2; do boot_emulator "$attempt" && { booted=1; break; }; done
|
||||
[ "$booted" = "1" ] || { echo "::error::API ${{ matrix.api-level }} emulator failed to boot after 2 attempts"; exit 1; }
|
||||
|
||||
# NON-FATAL unlock (the boot-race fix) + disable animations for deterministic UI tests
|
||||
# (parity with the replaced android-emulator-runner `disable-animations: true`).
|
||||
adb shell input keyevent 82 || true
|
||||
adb shell settings put global window_animation_scale 0.0 || true
|
||||
adb shell settings put global transition_animation_scale 0.0 || true
|
||||
adb shell settings put global animator_duration_scale 0.0 || true
|
||||
# Focus/readiness gate (issue #468): the durable fix for the intermittent app-window-
|
||||
# focus flake. Occasionally the launched activity window has has-window-focus=false for
|
||||
# the WHOLE run, so Espresso's RootViewPicker (onView().check(), Intents.intended(),
|
||||
# pressBack(), focus-dependent clipboard) times out after 10s and fails EVERY
|
||||
# focus-dependent test at once while the ~280 pure-Compose tests pass (evidence: a
|
||||
# failing E2E leg's logcat had has-window-focus=true ZERO times, both attempt + retry).
|
||||
# The single `input keyevent 82` here was too weak (MENU no longer dismisses the modern
|
||||
# keyguard, and races SystemUI coming up). This shared helper WAKES the display, dismisses
|
||||
# + disables the keyguard, keeps the screen on, disables animations (parity with the
|
||||
# replaced `disable-animations: true`), then WAITS until a real window holds input focus
|
||||
# before the suite runs. It is the IDENTICAL mechanism the e2e-preview job and the local
|
||||
# preflight runners invoke, so the fix cannot drift between jobs. Non-fatal (`|| true`),
|
||||
# preserving #454's guarantee that the unlock never aborts the boot.
|
||||
python3 .github/scripts/emulator_focus_gate.py --label "api${{ matrix.api-level }}" || true
|
||||
|
||||
# WEDGE (hang) smoking-gun capture (#404, restored to the matrix by #421). On the wrapper
|
||||
# `timeout` below (exit 124), grab the smoking gun WHILE this hand-provisioned emulator is
|
||||
@@ -968,7 +976,13 @@ jobs:
|
||||
for attempt in 1 2; do boot_emulator "$attempt" && { booted=1; break; }; done
|
||||
[ "$booted" = "1" ] || { echo "::error::API 37 preview emulator failed to boot after 2 attempts"; exit 1; }
|
||||
|
||||
adb shell input keyevent 82 || true
|
||||
# Focus/readiness gate (issue #468) -- the IDENTICAL shared mechanism the `e2e` matrix job
|
||||
# (and the local preflight runners) invoke, so the fix can't drift: wake the display,
|
||||
# dismiss + disable the keyguard, keep the screen on, disable animations, then WAIT until a
|
||||
# real window holds input focus before the suite runs. This replaces the lone `input
|
||||
# keyevent 82` and, applied UNIFORMLY, also gives this preview job the animation-disable
|
||||
# the matrix already had. Non-fatal (`|| true`) -- the unlock must never abort the boot.
|
||||
python3 .github/scripts/emulator_focus_gate.py --label "api37-shard${{ matrix.shard }}" || true
|
||||
|
||||
# WEDGE (hang) smoking-gun capture (#404). On the wrapper `timeout` below (exit 124), grab
|
||||
# the smoking gun WHILE this hand-provisioned emulator is still alive (it stays up until the
|
||||
|
||||
Reference in New Issue
Block a user