Skip to content
228 changes: 214 additions & 14 deletions .github/workflows/ios-e2e.yml

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,9 @@ device (the shared device is reseeded via one channel, so parallel workers would
swipe, and the simctl launch sequencing — confirmed against the installed `idb` /
`idb_companion` by running the showcase scenarios, evidence capture, and the triage self-heal
loop on-device (`make -C demos/showcase run-swiftui`; the `ios-e2e.yml` CI workflow also exercises the idb smoke path).
- `back` and device control (`setLocation` / clipboard / `push`) on the idb backend, exercised
on-device. The `ios-e2e.yml` `actuation (idb)` job runs both per PR
([BE-0281](../roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md)).
- The XCUITest backend's resident runner — element resolution by snapshot handle, semantic tap, and
the `pinch`/`rotate` multi-touch gestures idb cannot run — confirmed on-device via the `ios-e2e.yml`
`xcuitest (multi-touch)` job (`demos/showcase/scenarios/gestures_multitouch.yaml`, `--backend xcuitest`).
Expand Down
2 changes: 1 addition & 1 deletion docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This document covers two separate topics:
| [`web-e2e.yml`](../.github/workflows/web-e2e.yml) | Linux | manual + every PR + merge queue (required `E2E (web)` check) | the **web (Playwright) backend** lane (BE-0279), four jobs against a headless Chromium: **smoke (playwright)** runs the `demos/web` scenarios (`make -C demos/web e2e`); **dogfood (serve UI)** drives Bajutsu's own serve SPA (BE-0058); **conformance (playwright)** runs the BE-0114 driver contract against the real browser; **network (playwright)** drives the real network path — `page.route` interception, `requestfinished` capture, the `mocked` flag, and redaction of really-captured evidence (`make -C demos/web e2e-network`, BE-0282). **No Mac / Simulator**, so it proves the core is platform-neutral. All four are path-gated by the same `changes` detector (`scripts/e2e_changes.py`, `E2E_LANE=web`), but only the first three — deterministic and host-independent — feed the always-reporting `E2E (web)` job, the required check; a PR that can't affect the web path skips the browser jobs and the gate passes. **network (playwright)** is deliberately excluded from `E2E (web)`'s `needs:` for now: it lands as a per-PR signal first and is promoted to required once it proves stable (BE-0282) |
| [`dependency-audit.yml`](../.github/workflows/dependency-audit.yml) | Linux | manual + weekly + push to `main` / PRs touching `pyproject.toml` / `uv.lock` | audit the locked dependency graph (`uv export` → `pip-audit --no-deps`) against the advisory DB. The result is a function of the lockfile and the DB, so it runs on a dependency change and on a weekly schedule that catches advisories newly disclosed against unchanged pins |
| [`swift.yml`](../.github/workflows/swift.yml) | macOS | push to `main` + PRs touching `BajutsuKit/**` | `swift build` + `swift test` for [BajutsuKit](../BajutsuKit). Unit-tests the pure-Foundation logic (request matching / mock parsing) with no Simulator — the on-device interception itself is covered by `ios-e2e.yml` |
| [`ios-e2e.yml`](../.github/workflows/ios-e2e.yml) | macOS | manual + every PR + merge queue (required `E2E` check) | the **iOS (idb / XCUITest) backend** lane, six jobs against the showcase: **smoke (idb)** builds the showcase, boots a Simulator, and runs a scenario through the idb backend (driver + simctl + idb); **golden (idb)** runs the BE-0006 element-tree golden over idb (`golden.yaml`) — the iOS twin of `android-e2e.yml`'s `golden (adb)`; **xcuitest (codegen)** generates a native XCUITest from a scenario (`make -C demos/showcase ui-test`) and runs it with `xcodebuild` (no bajutsu / idb / AI at test time); **xcuitest (multi-touch)** runs a pinch/rotate scenario through the full `bajutsu run` path on the XCUITest backend (BE-0019) — two-finger gestures idb cannot actuate; **conformance (idb + xcuitest)** runs the driver conformance suite (BE-0114) on-device against both backends; and **visual (idb)** pixel-compares the Stable catalog against the committed `baselines_ios/` baseline (`make -C demos/showcase e2e-visual`), masking the status bar + the "Liquid Glass" tab bar. The smoke, xcuitest (codegen), xcuitest (multi-touch), and conformance jobs are path-gated by a `changes` detector and feed a single always-reporting `E2E` job — the required check. `golden` and `visual` are gated by the same detector but deliberately excluded from `E2E`'s `needs:`: the element-tree `golden` runs against an upstream `idb_companion` whose drift could redden it independently of any Bajutsu change, and a `visual` pixel baseline is host-specific (the Simulator renderer varies by Xcode / device / OS), so each surfaces a drift as a signal on its own job (visual's captured screenshot uploads as `ios-e2e-visual-run` to re-record the baseline from) rather than blocking a merge. The element-tree golden also runs weekly on idb-monitor, against the latest `idb_companion` |
| [`ios-e2e.yml`](../.github/workflows/ios-e2e.yml) | macOS | manual + every PR + merge queue (required `E2E` check) | the **iOS (idb / XCUITest) backend** lane, seven jobs against the showcase: **smoke (idb)** builds the showcase, boots a Simulator, and runs a scenario through the idb backend (driver + simctl + idb); **actuation (idb)** drives real idb actuation beyond the conformance job's `tap` (BE-0281) — `back` (`navigation.yaml`) and device control (`setLocation` / clipboard / `push`, `device.yaml` + `push.yaml`) on the Stable launch tab, the surface idb reaches (idb collapses the native tab bar into one opaque group, so the tab-crossing gesture / text scenarios stay on XCUITest); **golden (idb)** runs the BE-0006 element-tree golden over idb (`golden.yaml`) — the iOS twin of `android-e2e.yml`'s `golden (adb)`; **xcuitest (codegen)** generates a native XCUITest from a scenario (`make -C demos/showcase ui-test`) and runs it with `xcodebuild` (no bajutsu / idb / AI at test time); **xcuitest (multi-touch)** runs a pinch/rotate scenario through the full `bajutsu run` path on the XCUITest backend (BE-0019) — two-finger gestures idb cannot actuate — and also runs the runner channel's `/type` (`search.yaml`) and `/swipe` + `/back` (`notices.yaml`) actuation (BE-0281), tab-crossing scenarios idb cannot reach; **conformance (idb + xcuitest)** runs the driver conformance suite (BE-0114) on-device against both backends; and **visual (idb)** pixel-compares the Stable catalog against the committed `baselines_ios/` baseline (`make -C demos/showcase e2e-visual`), masking the status bar + the "Liquid Glass" tab bar. The smoke, xcuitest (codegen), xcuitest (multi-touch), and conformance jobs are path-gated by a `changes` detector and feed a single always-reporting `E2E` job — the required check. `actuation`, `golden`, and `visual` are gated by the same detector but deliberately excluded from `E2E`'s `needs:`: newly-wired idb actuation lands as a per-PR signal first (the Simulator lane has a flakiness history, BE-0218) and is promoted to the gate only once stable, the element-tree `golden` runs against an upstream `idb_companion` whose drift could redden it independently of any Bajutsu change, and a `visual` pixel baseline is host-specific (the Simulator renderer varies by Xcode / device / OS), so each surfaces a drift or a flake as a signal on its own job (visual's captured screenshot uploads as `ios-e2e-visual-run` to re-record the baseline from) rather than blocking a merge. The element-tree golden also runs weekly on idb-monitor, against the latest `idb_companion` |
| [`android-e2e.yml`](../.github/workflows/android-e2e.yml) | Linux | manual + every PR + merge queue (required `E2E (android)` check) | the **Android (adb) backend** lane (BE-0208), four per-concern jobs mirroring the iOS and web lanes' job splits, each booting its own x86_64 API 34 AVD under KVM (`reactivecircus/android-emulator-runner`): **smoke (adb)** builds the Compose + Views showcase APKs and runs the Stable-tab scenarios — the core id/tap/type/value flows plus a push/pop back-navigation flow — through `--backend android` (`make -C demos/showcase/android e2e`); **golden (adb)** runs the on-device golden element-tree check for the Compose Stable catalog (`make -C demos/showcase/android e2e-golden`, BE-0006 / BE-0208 unit 4), then re-runs it with the resident channel forced off (`make -C demos/showcase/android e2e-fallback`, BE-0245) so both read channels are proven to agree; **conformance (adb)** runs the driver conformance suite (BE-0114) against the real adb backend — the Android twin of `ios-e2e.yml`'s `conformance (idb + xcuitest)`; **visual (adb)** runs the pixel VRT (below). **No Mac / Simulator** — the third backend's Linux twin of the idb and web e2e lanes. Path-gated by a `changes` detector (`scripts/e2e_changes.py`, `E2E_LANE=android`), feeding the required `E2E (android)` aggregator (BE-0279). The AVD is x86_64 (not the local validation's arm64) so KVM accelerates it on the x86_64 runner; the golden's baseline is recorded on arm64 yet passes on x86_64 because the comparison is field-level with a tolerant frame check. The sheet/cover flows (`components`, `modals`) are included by raising the condition-wait ceiling for this lane only — `make -C demos/showcase/android e2e` exports `BAJUTSU_MIN_WAIT_TIMEOUT` (default 15s), a floor under each wait's own timeout — because the software-rendered emulator draws a modal slower than the shared scenarios' 5s waits allow. A condition wait returns the instant it is satisfied, so the larger ceiling is a safe upper bound, not a fixed delay, and the shared scenarios stay untouched (their `timeout: 5` is the same on every backend). The deep-scroll flows (`controls`, `notices`) join the lane too: the default directional swipe now travels a fraction of the screen rather than a fixed coordinate count, so a swipe reaches the same proportion of Android's dense screen (2400px) as of iOS's (~900pt) — a fixed count scrolled far less on Android and never brought the far target (the segmented-control value node, a bottom list row) into view (`bajutsu/orchestrator/actions/handlers/gestures.py`, BE-0208 unit 5). The single-touch gesture flow (`gestures`) joins too: the adb driver drives a double-tap with a raw `sendevent` touch sequence on a rooted emulator (the `e2e` target runs `adb root` first), firing both taps inside the platform double-tap window that a per-tap `input` JVM overran; on a non-rooted device it falls back to `input tap` unchanged (BE-0208 unit 5). The multi-touch gesture flow (`gestures_multitouch`) joins too (BE-0232): the adb driver drives a pinch / rotate as a raw two-slot `sendevent` sweep on the rooted emulator — two contacts moving together across interleaved frames — so the shared scenario that iOS runs on XCUITest runs unchanged on Android; unlike the single-touch double-tap there is no `input` fallback (a two-finger gesture cannot be approximated), so it requires root and fails loudly otherwise. The runtime-permission flow (`permission`) joins the lane too (BE-0208 unit 6, exercising BE-0210's up-front grant): it is the **same** `permission.yaml` the idb lane runs — the grant mechanism lives in config, not the scenario, so one file serves both. `showcase-compose` grants `POST_NOTIFICATIONS` up front (`grantPermissions` → `pm grant` at lease time), so Android's `RequestPermission` contract short-circuits to granted with no dialog; the scenario's `dismissAlerts` guard therefore never fires here, keeping the flow deterministic (no LLM, no fixed sleep) on the lane (on iOS, where notifications can't be pre-granted, that guard taps "Allow" instead). The device-control flow (`device`) joins the lane too (BE-0208 unit 5): it overrides the GPS location (`emu geo fix`), round-trips the clipboard, and re-asserts the settled screen — the **same** `device.yaml` the idb lane runs (unified across iOS/Android, since `setLocation` and the clipboard are advertised on both; the iOS-only `push` half lives in `push.yaml`), run on the Stable launch tab. Both `setLocation` and `clipboard` of the device-control family are exercised: `cmd clipboard` is a silent no-op on-device and since Android 10 only the foreground app may touch the clipboard, so the clipboard runs through an in-app receiver the showcase embeds from `BajutsuAndroid` (BE-0233) — the seed/read-back is the strong assertion PR #934 wanted. The `visual (adb)` job runs a pixel visual-regression check for the Compose Stable catalog (`make -C demos/showcase/android e2e-visual`, BE-0208 unit 4): unlike the element-tree golden, a pixel baseline is host-specific — the x86_64 software renderer (swiftshader) and a local arm64 emulator diverge per pixel — so this baseline is recorded on this x86_64 lane and committed (`demos/showcase/scenarios/visual/baselines_android/`), not on arm64; the top status bar is masked so the wall clock never churns the comparison. The deterministic host-independent jobs — `smoke (adb)` and `conformance (adb)` — feed the always-reporting `E2E (android)` aggregator, the required check (BE-0279); `golden (adb)` and `visual (adb)` are deliberately excluded from its `needs:` and stay per-PR signals (the element-tree golden can drift with an upstream dependency, and a pixel baseline is host-specific), the same gate boundary iOS's `E2E` draws |
| [`devicefarm.yml`](../.github/workflows/devicefarm.yml) | Linux | **manual only** (`workflow_dispatch`) | the **AWS Device Farm batch submit** (BE-0235) — builds the showcase Compose APK, packages Bajutsu + config + scenarios, and hands them to [`scripts/devicefarm_submit.py`](../scripts/devicefarm_submit.py), which uploads a custom-environment test spec that runs `bajutsu run --backend adb` on Device Farm's host, polls the run, downloads the artifacts, and surfaces **Bajutsu's own manifest verdict** (never Device Farm's classification). It is CI-side glue outside the deterministic core, so no LLM touches the verdict; it is `workflow_dispatch` only (never on push/PR, not a required check). Auth is a short-lived AWS credential from GitHub OIDC (`AWS_DEVICEFARM_ROLE_ARN`) scoped to a `devicefarm` Environment, with the project / device-pool ARNs in repository variables; with any unset the job is a green no-op, so it stays dormant until an operator wires up an account. The real-account serial-resolution PoC is a documented human procedure (see [AWS Device Farm](devicefarm.md)), deliberately kept off the deterministic gate |

Expand Down
1 change: 1 addition & 0 deletions docs/ja/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ adb の harness はその代わりに、新しい `SHOWCASE_CONFORMANCE` の int
### 実機 Simulator で検証済み(iPhone 17 Pro、近年の iOS)

- idb バックエンドの subprocess 実行(`describe-all` パース、フレーム中心の tap / text / swipe、`simctl` launch 手順)を、インストール済みの `idb` / `idb_companion` に対して確認しています。showcase シナリオの実行、証跡の取得、triage 自己修復ループを実機で走らせて検証しました(`make -C demos/showcase run-swiftui`。`ios-e2e.yml` CI も idb smoke を実行します)。
- idb バックエンドの `back` とデバイス制御(`setLocation` / クリップボード / `push`)を実機上で実行しています。`ios-e2e.yml` の `actuation (idb)` ジョブが PR ごとに検証します([BE-0281](../../roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md))。
- XCUITest バックエンドの常駐 runner を実機で検証しています。スナップショットハンドルによる要素解決、semantic tap、idb では動かせない `pinch`/`rotate` の multi-touch ジェスチャを、`ios-e2e.yml` の `xcuitest (multi-touch)` ジョブ(`demos/showcase/scenarios/gestures_multitouch.yaml`、`--backend xcuitest`)で確認済みです。

### ブラウザで検証済み(Linux で動作、Mac 不要)
Expand Down
Loading
Loading