Skip to content

test: E2E confidence suite (iOS/Android scripted + agent evals)#47

Merged
onevcat merged 9 commits into
mainfrom
feat/e2e-confidence-suite
Jul 13, 2026
Merged

test: E2E confidence suite (iOS/Android scripted + agent evals)#47
onevcat merged 9 commits into
mainfrom
feat/e2e-confidence-suite

Conversation

@onevcat

@onevcat onevcat commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

A pre-release confidence layer that verifies sim-use itself — the CLI and the bundled agent skill — against live devices. The Playground apps are controlled fixtures that echo input back as assertable accessibility state; they are not the subject under test.

Why

The iOS E2E suites existed but had rotted invisibly: four suites (Key, KeyCombo, KeySequence, StreamVideo) failed continuously after the 0.5.x move of the five iOS-only verbs under the ios namespace — the runner aborted at the first failure and its hardcoded list had drifted, so nobody saw it. There was also no Android device E2E at all, and no way to catch skill-prose regressions that only surface with an agent in the loop.

Changes

Scripted E2E

  • Fix Key/KeyCombo/KeySequence/StreamVideo suites to call sim-use ios <verb>.
  • Fix KeyComboTests' Cmd+A assertion (an empty UITextField reports its placeholder as the AX value, so the cleared-field check never passed).
  • scripts/test-runner.sh runs every suite past a failure and prints a full pass/fail map; list corrected (+KeyboardStateTests, StreamVideoDebugTest name fix — the old plural matched zero tests).
  • iOS Playground: new paste-test, orientation-test, permissions-test screens + PasteTests / OrientationTests / PermissionAlertTests.
  • New Android Playground fixture (bridge/playground) + seven Android E2E suites gated by SIM_USE_E2E_ANDROID + ANDROID_SERIAL; make e2e-android.

Agent evals (e2e/agent-evals/)

  • Natural-language cases run by a headless claude -p agent using the bundled skill against the Playground apps, judged by deterministic device-side checks.

Docs: design note under docs/ai/; /release pre-flight now requires green make e2e / make e2e-android.

Verification

  • iOS scripted E2E: 19 suites green on iPhone 17 Pro / iOS 26.4 (the four repaired suites confirmed).
  • Android scripted E2E: 7 suites / 16 tests green on a Pixel 8 emulator.
  • Agent evals: iOS quick 2/2, Android 3/3 PASS live.
  • swift build --build-tests and make test green (one pre-existing load-sensitive latency test flakes under device contention; passes in isolation).

Open item

Android describe-ui --include-offscreen appears to be a no-op for recycled off-screen list cells (the outline renderer drops visibleToUser==false nodes before the geometric filter the flag relaxes), and the top-level --help text contradicts the code. AndroidDescribeUITests pins the honest behavior; the fix/doc decision is left to a follow-up.

🤖 Generated with Claude Code

onevcat added 9 commits July 11, 2026 00:25
Pre-release confidence tests that verify sim-use itself — the CLI and the
bundled agent skill — against live devices, with the Playground apps as
controlled fixtures.

Scripted E2E:
- Fix four iOS E2E suites (Key, KeyCombo, KeySequence, StreamVideo) that had
  failed since the 0.5.x move of the five iOS-only verbs under the `ios`
  namespace — they still invoked the top-level forms.
- Fix KeyComboTests' Cmd+A assertion: an empty UITextField exposes its
  placeholder as the accessibility value, so the cleared-field check could
  never pass.
- test-runner.sh now runs every suite past a failure and prints a full
  pass/fail map; the hardcoded list gains the missing KeyboardStateTests and
  the corrected StreamVideoDebugTest name (the old plural matched nothing).
- iOS Playground gains paste-test, orientation-test, and permissions-test
  screens with matching suites (PasteTests, OrientationTests,
  PermissionAlertTests): paste via Cmd+V and --via-menu, AX-selector taps
  after rotation, and system-alert dismissal via describe-ui + tap.
- New Android Playground fixture (bridge/playground) and seven Android device
  E2E suites gated by SIM_USE_E2E_ANDROID + ANDROID_SERIAL; `make e2e-android`
  builds, installs, inits the bridge, and runs them with a summary.

Agent evals (e2e/agent-evals/):
- Natural-language cases executed by a headless `claude -p` agent using the
  bundled skill against the Playground apps, judged by deterministic
  device-side post-condition checks. Catches skill-prose drift and
  verb-steering regressions (paste vs US-ASCII type, Android paste fallback)
  that the scripted suites cannot.

Docs: design note under docs/ai/; the release skill's pre-flight now requires
green make e2e / make e2e-android.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
One entry point that checks the environment, prints a cost estimate, and
prompts before running — since each case spins a real `claude -p` agent that
makes live API calls.

- scripts/eval.sh: verify claude + sim-use present, auto-detect reachable
  platform(s), count the selected cases (new `run.py --count`), print an
  order-of-magnitude cost warning, then confirm ([y/N] on a tty, -y or a
  non-interactive shell proceeds) before delegating to the runner.
- Makefile: `make eval` (env overrides PLATFORM/TAGS/DEVICE, or ARGS="…").
- Release pre-flight and the agent-evals README point at it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
PermissionAlertTests matched the alert buttons by ASCII label ("Don't
Allow"), but on an English simulator the button is "Don’t Allow" with a
U+2019 curly apostrophe, so the deny tap silently missed. Because the tap
was conditional on a label match, the prompt was left on screen — and a
SpringBoard permission alert survives an app terminate/relaunch, so it
covered the playground and swallowed every gesture in the suites that ran
after it (SwipeTests failing with Count: 0 was this cascade, not a swipe
bug). A JP simulator matched the JP label and never hit it, which is why it
passed in-house.

- SpringBoard alert buttons expose no accessibility id or resource-id (both
  null in describe-ui --json), only a localized label. Select them by
  positional list alias instead — order is stable across locales:
  #1@1 Allow Once / #2@1 Allow While Using App / #3@1 Don't Allow. The tap is
  now unconditional, so the alert is always dismissed regardless of locale.
- launchPlaygroundApp: if a system alert is frontmost after launch (a leak
  from an earlier suite), dismiss it (tap #1@1) and relaunch once, so suites
  stay independent of run order.
- PermissionAlertTests clears any stray alert before each test as well.

Verified on a JP simulator: PermissionAlertTests 2/2, and SwipeTests 5/5
even with a permission alert deliberately left up beforehand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
PasteTests wrapped the "--replace via menu overwrites" assertion in a plain
`withKnownIssue`, asserting the iOS-26 Select-All gap always reproduces. It
does not: on some simulators the menu path replaces correctly, so the block
passed unexpectedly and Swift Testing failed the suite with "Known issue was
not recorded" (seen on an English sim).

The Select-All edit-menu step is genuinely unreliable, not uniformly broken.
Assert only that the second paste is delivered (replaced or appended), and
check strict replacement under `withKnownIssue(isIntermittent: true)` so the
suite stays green whichever way the menu path behaves. Strict replacement is
still reliably covered on the Cmd+V path. Fix candidate noted:
IOSSimPasteCommand.pasteViaEditMenu select-all sequencing.

Verified green on iOS 26.4 JP (menu path appends → known issue recorded and
tolerated); the intermittent wrapper also absorbs the replace-works case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
…timing note

- `make e2e` now runs the iOS then Android suites in sequence, continuing past
  a platform failure and failing if either did. The iOS-only target is renamed
  to `make e2e-ios`; `make e2e-android` is unchanged.
- Document that a full green iOS E2E run is ~15 minutes (per-suite HID/animation
  waits dominate — expected, not a hang) so future runs/agents budget for it.
  Added to the Makefile target comments, `make help`, AGENTS.md (new
  "End-to-end tests" section — README stays user-facing and has none), the
  release pre-flight, CONTRIBUTING, and the design note.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
The type-append test polled for the combined "abcde" echo with the default
8s waitForOutline timeout. Android accessibility text is eventually
consistent — TYPE_VIEW_TEXT_CHANGED events are coalesced and the a11y node
cache can lag the field by several seconds on a loaded/cold emulator — so on
a slower host the echo still read "abc" (char_count 3) when the window
expired, even though the field already held "abcde".

- Poll the final "abcde" settle with a 20s timeout, and raise the shared
  waitForOutline default from 8s to 12s as slow-machine insurance (the
  predicate short-circuits, so fast runs pay nothing).
- Keep the two types in quick succession: `type` appends at the caret only
  while the caret is at the end; waiting for the first type to fully settle
  lets the IME commit and the selection reset, after which the second type
  replaces instead of appends (verified — the intermediate "wait for abc"
  variant produced "de"). Comment documents this so it is not reintroduced.

Verified: typeAppendsAtCaret green across repeated runs on emulator-5554.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
`resolve_device` read `candidates[0]["udid"]`, but `sim-use devices --json`
emits `deviceId` (the legacy `udid` key was removed in 0.10.0), so
auto-resolving a device crashed with KeyError. The bug was latent because
every prior run passed --device explicitly. Now it reads `deviceId`, exits
cleanly with the candidate ids listed when the platform has 0 or >1 reachable
devices, and `make eval` auto-resolves a single emulator/simulator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
A running case only printed "[reset] launching…" then went silent for 1-3
min while `claude -p` worked (its stream went to the transcript file, not the
console) and its cold start delays the first tokens — so a live run looked
hung. Emit a compact indented line per agent step (tool_use name + a short
command/selector preview, plus assistant text snippets and a start marker),
while the full stream still lands in the transcript. No behavior change to
the agent itself.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
The two test-fixture apps now sit symmetrically under Playgrounds/:
- SimUsePlaygroundApp/ -> Playgrounds/iOS/ (xcodegen project unchanged).
- bridge/playground/ (a module of the device-bridge Gradle build) ->
  Playgrounds/Android/ as its own standalone Gradle project. The bridge
  project is the shipped device-bridge product whose APK is bundled into the
  CLI; a test-only fixture had no business living inside it. It now has its
  own wrapper/settings and is fully decoupled — `:playground` is gone from
  bridge/settings.gradle.kts.

Bundle ids are unchanged (com.cameroncooke.SimUsePlayground /
com.linecorp.simuse.playground), so the E2E suites need no edits.

Updated: scripts/test-runner.sh (iOS project path), build-playground-android.sh
+ test-runner-android.sh (new project dir, `:app:assembleDebug`,
app-debug.apk), .gitignore, and the docs/CHANGELOG/CONTRIBUTING references.

Verified from the new layout: both playgrounds build; the Android runner
(build APK -> install -> init -> AndroidTapTests) and the iOS runner
(xcodegen -> build -> install -> TapTests) pass end-to-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: onevcat <onevcat@gmail.com>
@onevcat
onevcat merged commit bd8e3e8 into main Jul 13, 2026
4 checks passed
@onevcat
onevcat deleted the feat/e2e-confidence-suite branch July 13, 2026 07:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant