diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/ios-e2e.yml index e369f4890..a580fa4ea 100644 --- a/.github/workflows/ios-e2e.yml +++ b/.github/workflows/ios-e2e.yml @@ -5,6 +5,11 @@ # pins. Seven jobs, against the showcase suite (BE-0079, the single iOS fixture): # - smoke (idb): build the showcase, boot a Simulator, run a scenario through the idb backend # (driver + simctl + idb + real actuation) — the functional heart. +# - actuation (idb): real on-device idb actuation beyond the conformance job's `tap` (BE-0281) — +# `back` and device control (setLocation / clipboard / push) on the Stable launch tab, the +# surface idb reaches. Deliberately NOT part of the `E2E` gate's `needs:` (like `golden` / +# `visual`): new actuation lands as a per-PR signal first (Simulator flakiness, BE-0218) and is +# promoted to the gate only once stable. # - golden (idb): the BE-0006 element-tree golden over idb, the iOS twin of android-e2e.yml's # `golden (adb)`. Like `visual`, deliberately NOT part of the `E2E` gate's `needs:` — the tree # golden is deterministic, but `idb_companion` upstream drift could redden it independently of @@ -12,7 +17,9 @@ # - xcuitest (codegen): generate a native XCUITest from a scenario and run it with # xcodebuild — no bajutsu / idb / AI at test time (the codegen output path). # - xcuitest (multi-touch): a pinch/rotate scenario through the full `bajutsu run` path on the -# XCUITest backend (BE-0019) — two-finger gestures idb cannot actuate. +# XCUITest backend (BE-0019) — two-finger gestures idb cannot actuate. 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): the driver conformance suite (BE-0114) on-device against both # iOS backends. # - visual (idb): the iOS twin of android-e2e.yml's `visual` — a pixel VRT of the Stable @@ -181,6 +188,163 @@ jobs: udid: ${{ steps.sim.outputs.udid }} artifact-name: smoke-run + # Real on-device idb actuation beyond the conformance job's `tap` (BE-0281): drives the actuators + # idb can reach on the Stable launch tab, which the smoke / golden / visual jobs above never tap or + # otherwise drive the UI (they only `wait`/`expect`). idb collapses the native tab bar into one + # opaque group and cannot tap any tab (SPEC.md §3, BE-0107), so the tab-crossing gesture / text + # scenarios stay on XCUITest (the `xcuitest (multi-touch)` job below runs them); this job covers the + # launch-tab surface idb *does* drive — `back` and device control, unproven on any iOS lane before + # now: + # - navigation.yaml: tap a Stable row → push Horse Detail → `back` (the OS back button). idb `tap` + # is already the conformance job's contract, but `back` end-to-end over the idb companion is not. + # - device.yaml / push.yaml: `setLocation`, clipboard round-trip, and a simctl `push` — the + # device-control family idb advertises via DEVICE_CONTROL_ALL but no iOS lane had exercised it + # (only Android proved any device control on a real device, BE-0210). All three run on the + # launch tab. + # Deliberately NOT in the `E2E` gate's `needs:` below (like `golden` / `visual`): the Simulator lane + # has a flakiness history (BE-0218), so new actuation lands as a per-PR signal first and is promoted + # to the gate only once it proves stable. One build serves all three scenarios (cost discipline: the + # macOS runner bills at 10x) — split the interaction and device-control halves into their own jobs + # if one is promoted to the gate ahead of the other. + actuation: + name: actuation (idb) + needs: changes + if: needs.changes.outputs.relevant == 'true' + runs-on: macos-latest + # Cap the metered macOS job (10x billing): a hung Simulator/idb would otherwise run to the 6h + # default. Build + three short scenario runs is ~12min, so 25 catches a real hang with headroom. + timeout-minutes: 25 + # Raise the wait floor so a cold-boot Simulator has budget to render the first Stable row before + # navigation/device/push's own 10s first-wait — the same knob the `smoke` job above and the Android + # lane use. A condition wait returns the instant the row appears, so this is a ceiling, not a sleep. + env: + BAJUTSU_MIN_WAIT_TIMEOUT: "20" + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 + with: + xcode-version: latest-stable + + - name: Install uv + uses: astral-sh/setup-uv@f98e06938123ccabd21905ea5d0069192241f9f1 # v8.3.1 + with: + enable-cache: true + + - name: Install xcodegen + env: + HOMEBREW_NO_AUTO_UPDATE: "1" + HOMEBREW_NO_INSTALL_CLEANUP: "1" + run: brew install xcodegen + + # Start the boot first so it runs concurrently with the app build below (a fresh boot is ~80s + # and does not depend on the build). `wait: 'false'` returns once boot is initiated; the wait + # step blocks on "booted" right before installing the app. + - name: Boot a Simulator + id: sim + uses: ./.github/actions/boot-simulator + with: + wait: "false" + + # The DerivedData cache must not cross Xcode upgrades: a different Xcode's compiled modules + # cause stale-artifact build failures. Capture the active Xcode build version for the cache key. + - name: Resolve Xcode version (for the cache key) + id: xcode + run: echo "ver=$(xcodebuild -version | tr '\n ' '--')" >> "$GITHUB_OUTPUT" + + # Cache the showcase app's DerivedData so `swiftui-build` is incremental across runs, sharing + # the `-app-` scheme cache the smoke / golden / visual jobs use. A miss just rebuilds cold. + - name: Cache DerivedData (app) + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: demos/showcase/ios/swiftui/build/dd + key: xcode-dd-app-${{ runner.os }}-${{ runner.arch }}-${{ steps.xcode.outputs.ver }}-${{ hashFiles('BajutsuKit/**/*.swift', 'demos/showcase/ios/swiftui/**/*.swift', 'demos/showcase/ios/swiftui/**/project.yml') }} + restore-keys: | + xcode-dd-app-${{ runner.os }}-${{ runner.arch }}-${{ steps.xcode.outputs.ver }}- + + # Fetch the idb backend's deps concurrently with the app build (both independent of it). A + # build failure exits first under `set -e`; the trap stops the background fetches. + - name: Build the showcase app (fetch idb deps concurrently) + env: + HOMEBREW_NO_AUTO_UPDATE: "1" + HOMEBREW_NO_INSTALL_CLEANUP: "1" + run: | + brew list facebook/fb/idb-companion >/dev/null 2>&1 || brew install facebook/fb/idb-companion & + brew_pid=$! + uv sync --extra idb --no-dev & + sync_pid=$! + trap 'kill "$brew_pid" "$sync_pid" 2>/dev/null || true' EXIT + make -C demos/showcase swiftui-build + wait "$brew_pid" + wait "$sync_pid" + trap - EXIT + + - name: Wait for the Simulator + run: xcrun simctl bootstatus "${{ steps.sim.outputs.udid }}" -b + + - name: Install the app + run: xcrun simctl install "${{ steps.sim.outputs.udid }}" "demos/showcase/ios/swiftui/build/dd/Build/Products/Debug-iphonesimulator/BajutsuShowcaseSwiftUI.app" + + # Interaction (BE-0281): idb `tap` + `back` end-to-end, on the Stable launch tab so no tab tap + # idb cannot perform is needed. + # continue-on-error: the three scenarios below are independent and unrelated, bundled into one + # job purely for cost reasons (see the job header comment) — a normal step failure would skip + # every step after it, hiding the other two scenarios' pass/fail signal and artifacts even + # though they have nothing to do with the failure. The "Report actuation results" step below + # turns any step failure back into a job failure once all three have had a chance to run. + - name: Run the navigation scenario (idb — tap + back) + id: navigation + continue-on-error: true + uses: ./.github/actions/bajutsu-e2e + with: + scenarios: demos/showcase/scenarios/navigation.yaml + target: showcase-swiftui + config: demos/showcase/showcase.config.yaml + udid: ${{ steps.sim.outputs.udid }} + artifact-name: actuation-navigation-run + + # Device control (BE-0281): `setLocation` + clipboard round-trip. simctl-backed, on the launch + # tab. The iOS twin of android-e2e.yml's device-control coverage (BE-0210). + - name: Run the device-control scenario (idb — setLocation + clipboard) + id: device + continue-on-error: true + uses: ./.github/actions/bajutsu-e2e + with: + scenarios: demos/showcase/scenarios/device.yaml + target: showcase-swiftui + config: demos/showcase/showcase.config.yaml + udid: ${{ steps.sim.outputs.udid }} + artifact-name: actuation-device-run + + # Device control (BE-0281): a simctl `push` delivery — iOS-only (no faithful Android-emulator + # equivalent, so it lives apart from the cross-backend device.yaml, see push.yaml's header). + - name: Run the push scenario (idb — push notification) + id: push + continue-on-error: true + uses: ./.github/actions/bajutsu-e2e + with: + scenarios: demos/showcase/scenarios/push.yaml + target: showcase-swiftui + config: demos/showcase/showcase.config.yaml + udid: ${{ steps.sim.outputs.udid }} + artifact-name: actuation-push-run + + # Re-fail the job if any of the three continue-on-error steps above failed — they need to run + # independently of each other, but the job as a whole must still redden so the signal isn't lost. + - name: Report actuation results + if: always() + run: | + echo "navigation: ${{ steps.navigation.outcome }}" + echo "device: ${{ steps.device.outcome }}" + echo "push: ${{ steps.push.outcome }}" + if [ "${{ steps.navigation.outcome }}" = "failure" ] || \ + [ "${{ steps.device.outcome }}" = "failure" ] || \ + [ "${{ steps.push.outcome }}" = "failure" ]; then + echo "::error::at least one actuation scenario failed — see the step outcomes above" + exit 1 + fi + # The BE-0006 element-tree golden over idb: pins the normalized accessibility tree for the Stable # catalog's list rows, reached on the launch tab so idb needs no tab navigation. Runs the same # golden.yaml the weekly idb-monitor runs, now also per PR — the iOS twin of android-e2e.yml's @@ -371,21 +535,27 @@ jobs: # availability fn), and the idb actuation path by the `smoke (idb)` job above — a macOS runner # always has xcodebuild, so xcuitest never actually degrades to idb here to observe. # - # Also runs the deterministic scenario from `permission.yaml` (BE-0276): that scenario taps the - # "Permissions" tab bar item, which idb can never resolve (see docs/showcase.md), so it needs the - # same forced-XCUITest treatment as the multi-touch scenario above — this job's Simulator/app/ - # runner build already pays for that, so the second scenario is one extra `bajutsu run` rather - # than a whole new job. Retains the job's original name (`xcuitest (multi-touch)` is a - # branch-protection required check) even though it now covers a second scenario. + # Also runs the deterministic scenario from `permission.yaml` (BE-0276) and the runner-channel + # actuation scenarios search.yaml + notices.yaml (BE-0281): each taps a tab-bar item idb can never + # resolve (see docs/showcase.md), so all need the same forced-XCUITest treatment as the multi-touch + # scenario above — this job's Simulator/app/runner build already pays for that, so each is one extra + # `bajutsu run` rather than a whole new job. search.yaml exercises the `/type` endpoint and + # notices.yaml the `/swipe` + `/back` endpoints of the resident runner — the actuators on-device CI + # reached only for tap / pinch / rotate before BE-0281. Retains the job's original name + # (`xcuitest (multi-touch)` is a branch-protection required check) even though it now covers more + # than the multi-touch scenario. Unlike the `actuation` job above, these four steps don't get + # `continue-on-error` + a summary step: this job feeds the required `E2E` gate, so an early failure + # correctly stops the job rather than trading gate strictness for per-scenario signal the way the + # non-gating `actuation` job does. xcuitest-gestures: name: xcuitest (multi-touch) needs: changes if: needs.changes.outputs.relevant == 'true' runs-on: macos-latest # Cap the metered macOS job (10x billing): a hung Simulator/runner would otherwise run to the - # 6h default. Build + the gesture and permission scenario runs is ~13min, so 25 catches a real - # hang with headroom. - timeout-minutes: 25 + # 6h default. Build + the gesture, permission, search, and notices scenario runs (BE-0281 added + # the latter two) is ~21min, so 30 catches a real hang while keeping a comfortable buffer. + timeout-minutes: 30 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -496,6 +666,36 @@ jobs: udid: ${{ steps.sim.outputs.udid }} artifact-name: xcuitest-permission-run + # XCUITest actuation on the runner channel (BE-0281): the `/type`, `/swipe`, and `/back` + # endpoints of the resident BajutsuRunner, which on-device CI reached only for tap / pinch / + # rotate before now. Both scenarios open with a tab tap idb cannot resolve (they filter the + # Search list / walk the Notices list, screens behind the native tab bar), so they belong on + # XCUITest — reached via this job's already-built runner and app for one more `bajutsu run` + # each, the same cost-sharing the permission scenario above uses rather than a whole new job. + # - search.yaml: `/type` into the Search field, then assert the filtered count. + # - notices.yaml: `/swipe` to walk an off-screen row into view, then `/back` from + # the pushed Notice Detail. The Notices List is XCUITest's reliable scroll surface (the Log + # Form's deep rows are not, see gestures_multitouch.yaml's header). + - name: Run the search scenario (xcuitest — /type) + uses: ./.github/actions/bajutsu-e2e + with: + scenarios: demos/showcase/scenarios/search.yaml + target: showcase-swiftui + backend: xcuitest + config: demos/showcase/showcase.config.yaml + udid: ${{ steps.sim.outputs.udid }} + artifact-name: xcuitest-search-run + + - name: Run the notices scenario (xcuitest — /swipe + /back) + uses: ./.github/actions/bajutsu-e2e + with: + scenarios: demos/showcase/scenarios/notices.yaml + target: showcase-swiftui + backend: xcuitest + config: demos/showcase/showcase.config.yaml + udid: ${{ steps.sim.outputs.udid }} + artifact-name: xcuitest-notices-run + # The driver conformance suite (BE-0114): one backend-agnostic contract, run on-device against # BOTH iOS backends — idb (idb_companion) and XCUITest (the resident BajutsuRunner). It proves # the determinism-core invariants (ambiguous selector fails, zero-match fails, capabilities() @@ -712,10 +912,10 @@ jobs: # Single required status check for E2E. Always runs so it reports even when the macOS # jobs are skipped (docs-only PRs) — a skipped dependency is a pass; only a real - # failure or cancellation fails the gate. `golden` and `visual` are deliberately excluded - # from `needs:` (see each job's comment) so a host-specific pixel drift or an idb_companion - # upstream drift never blocks a merge. Make `E2E` the required check, not the path-gated - # macOS jobs. + # failure or cancellation fails the gate. `actuation`, `golden`, and `visual` are deliberately + # excluded from `needs:` (see each job's comment) so newly-wired idb actuation (BE-0281), a + # host-specific pixel drift, or an idb_companion upstream drift never blocks a merge. Make `E2E` + # the required check, not the path-gated macOS jobs. e2e: name: E2E (iOS) needs: [changes, smoke, xcuitest, xcuitest-gestures, conformance] diff --git a/docs/architecture.md b/docs/architecture.md index 3c460a017..de6fe1040 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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`). diff --git a/docs/ci.md b/docs/ci.md index 2243132f3..2813ad02d 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -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 | diff --git a/docs/ja/architecture.md b/docs/ja/architecture.md index 4aef36474..3f6a19add 100644 --- a/docs/ja/architecture.md +++ b/docs/ja/architecture.md @@ -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 不要) diff --git a/docs/ja/ci.md b/docs/ja/ci.md index 687e3b027..a3a0e5a31 100644 --- a/docs/ja/ci.md +++ b/docs/ja/ci.md @@ -13,7 +13,7 @@ | [`web-e2e.yml`](../../.github/workflows/web-e2e.yml) | Linux | 手動 + 全 PR + マージキュー(必須の `E2E (web)` チェック) | **web(Playwright)バックエンド**のレーン(BE-0279)で、ヘッドレス Chromium に対する 4 ジョブから成ります。**smoke (playwright)** は `demos/web` のシナリオを実行し(`make -C demos/web e2e`)、**dogfood (serve UI)** は Bajutsu 自身の serve SPA を駆動し(BE-0058)、**conformance (playwright)** は BE-0114 のドライバコントラクトを実ブラウザに対して実行します。**network (playwright)** は実ネットワーク経路(`page.route` の介入、`requestfinished` のキャプチャ、`mocked` フラグ、実際にキャプチャした証拠の redaction)を動かします(`make -C demos/web e2e-network`、BE-0282)。**Mac / Simulator 不要**で、コアがプラットフォーム非依存であることを示します。4 つとも同じ `changes` 検出ジョブ(`scripts/e2e_changes.py`、`E2E_LANE=web`)でパスゲートしますが、決定論的で実行環境に依存しない最初の 3 つだけが、常に結果を報告する `E2E (web)` ジョブ(必須チェック)に集約します。web 経路に影響しえない PR はブラウザジョブを飛ばし、ゲートは合格します。**network (playwright)** は今のところ意図的に `E2E (web)` の `needs:` から外れています。まずは PR ごとのシグナルとして着地させ、安定を確認してから必須へ昇格させます(BE-0282) | | [`dependency-audit.yml`](../../.github/workflows/dependency-audit.yml) | Linux | 手動 + 週次 + `pyproject.toml` / `uv.lock` を触る `main` への push / PR | ロックした依存グラフ(`uv export` → `pip-audit --no-deps`)を脆弱性 DB に照合します。結果はロックファイルと DB だけで決まるので、依存の変更時と、変わらない pin に対して新たに公表された脆弱性を拾う週次スケジュールで実行します | | [`swift.yml`](../../.github/workflows/swift.yml) | macOS | `main` への push + `BajutsuKit/**` を触る PR | [BajutsuKit](../../BajutsuKit) の `swift build` + `swift test` を実行します。純 Foundation のロジック(リクエスト照合 / モック解析)をシミュレータ無しで単体テストします。実機でのインターセプトそのものは `ios-e2e.yml` がカバーします | -| [`ios-e2e.yml`](../../.github/workflows/ios-e2e.yml) | macOS | 手動 + 全 PR + マージキュー(必須の `E2E` チェック) | **iOS(idb / XCUITest)バックエンド**のレーンで、showcase に対する 6 つのジョブから成ります。**smoke (idb)** は showcase をビルドしてシミュレータを起動し、idb バックエンドで `smoke.yaml` を実行します(driver + simctl + idb)。**golden (idb)** は BE-0006 の要素ツリー golden を idb で実行します(`golden.yaml`)。`android-e2e.yml` の `golden (adb)` に対応する iOS 版です。**xcuitest (codegen)** はシナリオからネイティブ XCUITest を生成し(`make -C demos/showcase ui-test`)、`xcodebuild` で実行します(テスト時に bajutsu / idb / AI は不要です)。**xcuitest (multi-touch)** は pinch/rotate シナリオを XCUITest バックエンド上で `bajutsu run` の経路そのまま実行し(BE-0019)、idb では actuate できない 2 本指ジェスチャを確認します。**conformance (idb + xcuitest)** はドライバ conformance スイート(BE-0114)を両バックエンドに対して実機で実行します。**visual (idb)** は Stable カタログをコミット済みの `baselines_ios/` ベースラインとピクセル比較します(`make -C demos/showcase e2e-visual`)。ステータスバーと「Liquid Glass」タブバーはマスクします。smoke、xcuitest (codegen)、xcuitest (multi-touch)、conformance の 4 ジョブは `changes` 検出ジョブでパスゲートされ、常に結果を報告する単一の `E2E` ジョブ(必須チェック)に集約されます。`golden` と `visual` も同じ検出ジョブでパスゲートされますが、`E2E` の `needs:` には意図的に含めていません。要素ツリーの `golden` は upstream の `idb_companion` に対して走り、そのドリフトが Bajutsu 側の変更と無関係に赤くしうるためであり、`visual` のピクセルのベースラインはホスト依存(Simulator のレンダラが Xcode / デバイス / OS で変わります)だからです。どちらもドリフトを PR のブロックではなく単独ジョブのシグナルとして出します(`visual` が採取したスクリーンショットは `ios-e2e-visual-run` としてアップロードし、ベースラインの再採取に使います)。要素ツリーの golden は週次の idb-monitor でも、最新の `idb_companion` に対して走ります | +| [`ios-e2e.yml`](../../.github/workflows/ios-e2e.yml) | macOS | 手動 + 全 PR + マージキュー(必須の `E2E` チェック) | **iOS(idb / XCUITest)バックエンド**のレーンで、showcase に対する 7 つのジョブから成ります。**smoke (idb)** は showcase をビルドしてシミュレータを起動し、idb バックエンドで `smoke.yaml` を実行します(driver + simctl + idb)。**actuation (idb)** は conformance ジョブの `tap` を超えて、実機で idb を実際にアクチュエートします(BE-0281)。idb が届く Stable の起動タブ上で、`back`(`navigation.yaml`)とデバイス制御(`setLocation` / クリップボード / `push`、`device.yaml` と `push.yaml`)を実行します。idb はネイティブのタブバーを 1 つの不透明なグループに畳んでしまうため、タブをまたぐジェスチャや文字入力のシナリオは XCUITest 側に残しています。**golden (idb)** は BE-0006 の要素ツリー golden を idb で実行します(`golden.yaml`)。`android-e2e.yml` の `golden (adb)` に対応する iOS 版です。**xcuitest (codegen)** はシナリオからネイティブ XCUITest を生成し(`make -C demos/showcase ui-test`)、`xcodebuild` で実行します(テスト時に bajutsu / idb / AI は不要です)。**xcuitest (multi-touch)** は pinch/rotate シナリオを XCUITest バックエンド上で `bajutsu run` の経路そのまま実行し(BE-0019)、idb では actuate できない 2 本指ジェスチャを確認します。さらに、ランナーチャネルの `/type`(`search.yaml`)と `/swipe` + `/back`(`notices.yaml`)のアクチュエーションも実行します(BE-0281)。どちらも idb が届かないタブをまたぐシナリオです。**conformance (idb + xcuitest)** はドライバ conformance スイート(BE-0114)を両バックエンドに対して実機で実行します。**visual (idb)** は Stable カタログをコミット済みの `baselines_ios/` ベースラインとピクセル比較します(`make -C demos/showcase e2e-visual`)。ステータスバーと「Liquid Glass」タブバーはマスクします。smoke、xcuitest (codegen)、xcuitest (multi-touch)、conformance の 4 ジョブは `changes` 検出ジョブでパスゲートされ、常に結果を報告する単一の `E2E` ジョブ(必須チェック)に集約されます。`actuation`、`golden`、`visual` も同じ検出ジョブでパスゲートされますが、`E2E` の `needs:` には意図的に含めていません。新しく配線した idb のアクチュエーションは、まずは参考シグナルとして着地させ(Simulator レーンにはフレーキーの実績があるため、BE-0218)、安定を確認してからゲートに昇格させます。要素ツリーの `golden` は upstream の `idb_companion` に対して走り、そのドリフトが Bajutsu 側の変更と無関係に赤くしうるためであり、`visual` のピクセルのベースラインはホスト依存(Simulator のレンダラが Xcode / デバイス / OS で変わります)だからです。いずれもドリフトやフレーキーを PR のブロックではなく単独ジョブのシグナルとして出します(`visual` が採取したスクリーンショットは `ios-e2e-visual-run` としてアップロードし、ベースラインの再採取に使います)。要素ツリーの golden は週次の idb-monitor でも、最新の `idb_companion` に対して走ります | | [`android-e2e.yml`](../../.github/workflows/android-e2e.yml) | Linux | 手動 + 全 PR + マージキュー(必須の `E2E (android)` チェック) | **Android(adb)バックエンド**のレーン(BE-0208)で、iOS や web のレーンのジョブ分割にならった観点ごとの 4 ジョブから成り、各ジョブが自前の x86_64 API 34 AVD を KVM のもとで起動します(`reactivecircus/android-emulator-runner`)。**smoke (adb)** は Compose と Views の showcase APK をビルドし、Stable タブのシナリオ(コアの id/tap/type/value のフローに、詳細画面への push と pop で戻る back ナビゲーションを加えたもの)を `--backend android` で実行します(`make -C demos/showcase/android e2e`)。**golden (adb)** は Compose の Stable カタログの golden 要素ツリーを実機でチェックし(`make -C demos/showcase/android e2e-golden`、BE-0006 / BE-0208 ユニット 4)、続いて resident チャネルを切って再実行して(`make -C demos/showcase/android e2e-fallback`、BE-0245)両方の読み取り経路が一致することを確かめます。**conformance (adb)** はドライバ conformance スイート(BE-0114)を実 adb バックエンドに対して実行します。`ios-e2e.yml` の `conformance (idb + xcuitest)` の Android 版です。**visual (adb)** はピクセル VRT を実行します(後述)。**Mac / Simulator 不要**で、idb と web の e2e レーンに並ぶ 3 つ目のバックエンドの Linux 版です。`changes` 検出ジョブ(`scripts/e2e_changes.py`、`E2E_LANE=android`)でパスゲートし、必須の集約ジョブ `E2E (android)` に集約します(BE-0279)。AVD は(ローカル検証の arm64 ではなく)x86_64 にして、x86_64 ランナー上で KVM が加速できるようにしています。golden のベースラインは arm64 で採取していますが、比較がフィールド単位で frame は健全性チェックだけのため、x86_64 でも通ります。sheet/cover のフロー(`components`、`modals`)も、このレーンに限って条件待ちの上限を引き上げることで含めています。`make -C demos/showcase/android e2e` が `BAJUTSU_MIN_WAIT_TIMEOUT`(既定 15 秒)を各待ちのタイムアウトの下限として渡すためです。ソフトウェアレンダリングのエミュレータは、共有シナリオの 5 秒の待ちに収まらないほど遅くモーダルを描画します。条件待ちは条件が満たされた瞬間に返るので、上限を広げても固定の待ち時間にはならず安全な上限にとどまり、共有シナリオには手を入れません(`timeout: 5` はどのバックエンドでも同じです)。深いスクロールのフロー(`controls`、`notices`)もこのレーンに加えました。既定の方向スワイプが固定の座標量ではなく画面に対する割合分を移動するようになったので、密度の高い Android の画面(2400px)でも iOS(約 900pt)と同じ割合まで届きます。固定量では Android のスクロールがはるかに小さく、遠くの対象(segmented control の値ノードや一覧下端の行)を表示できませんでした(`bajutsu/orchestrator/actions/handlers/gestures.py`、BE-0208 ユニット 5)。単一タッチのジェスチャのフロー(`gestures`)もこのレーンに加えました。adb ドライバが、root 化したエミュレータでは生の `sendevent` によるタッチ列でダブルタップを実行するようになったためです(`e2e` ターゲットが先に `adb root` を実行します)。2 回のタップが 1 回の `adb shell` のなかで発火するので、タップごとに `input` の JVM を起動していたときには超過していたプラットフォームのダブルタップの受付時間に収まります。root 化していないデバイスでは、従来どおり `input tap` にフォールバックします(BE-0208 ユニット 5)。マルチタッチのジェスチャのフロー(`gestures_multitouch`)もこのレーンに加えました(BE-0232)。adb ドライバが、root 化したエミュレータでピンチ / 回転を生の 2 スロットの `sendevent` スイープ(2 つの接点が複数フレームにわたって一緒に動きます)として実行するので、iOS が XCUITest で動かす共有シナリオが Android でもそのまま動きます。単一タッチのダブルタップと違って `input` へのフォールバックはなく(2 本指のジェスチャは近似できません)、root を要し、なければ明確に失敗します。ランタイム権限のフロー(`permission`)もこのレーンに加えました(BE-0208 ユニット 6。BE-0210 の事前付与を検証します)。これは idb レーンが走らせるのと同じ `permission.yaml` です。付与の仕組みはシナリオではなく config にあるので、1 つのファイルで両方をまかなえます。`showcase-compose` が `POST_NOTIFICATIONS` を事前に付与するため(`grantPermissions` により lease 時に `pm grant` を実行します)、Android の `RequestPermission` コントラクトはダイアログを出さずに付与済みとして即座に返り、シナリオの `dismissAlerts` ガードはここでは発火しません。よってこのレーンでもフローは決定的なまま(LLM も固定の待ち時間もなし)に保たれます(通知を事前付与できない iOS では、このガードが代わりに「Allow」をタップします)。デバイス制御のフロー(`device`)もこのレーンに加えました(BE-0208 ユニット 5)。GPS の位置を上書きし(`emu geo fix`)、クリップボードを書き込んで読み戻し、落ち着いた画面を再度確認します。これは idb レーンが走らせるのと同じ `device.yaml` です。`setLocation` もクリップボードも両プラットフォームで宣言されるので、一つのファイルが iOS でも Android でも動きます(iOS 専用の `push` は `push.yaml` に分けました)。Stable の起動タブ上で、デバイス制御ファミリのうち `setLocation` と `clipboard` の両方を動かしています。`cmd clipboard` は実機では黙って何もせず、Android 10 以降はフォアグラウンドのアプリしかクリップボードを触れないため、クリップボードは showcase が `BajutsuAndroid` から組み込むアプリ内レシーバを経由します(BE-0233)。この書き込みと読み戻しは、強い assertion です。`visual (adb)` ジョブは Compose の Stable カタログのピクセル視覚回帰チェックを実行します(`make -C demos/showcase/android e2e-visual`、BE-0208 ユニット 4)。要素ツリーの golden とは異なり、ピクセルのベースラインはホスト依存です。x86_64 のソフトウェアレンダラ(swiftshader)とローカルの arm64 エミュレータはピクセル単位で食い違うため、このベースラインは arm64 ではなくこの x86_64 レーンで採取してコミットします(`demos/showcase/scenarios/visual/baselines_android/`)。上部のステータスバーはマスクするので、時計が比較を揺らすことはありません。決定論的で実行環境に依存しないジョブ、すなわち `smoke (adb)` と `conformance (adb)` を、常に結果を報告する集約ジョブ `E2E (android)`(必須チェック、BE-0279)に集約します。`golden (adb)` と `visual (adb)` はその `needs:` から意図的に外し、参考シグナルにとどめます(要素ツリーの golden は上流依存の変化で赤くなりえ、ピクセルのベースラインはホスト依存だからです)。これは iOS の `E2E` が引くのと同じ判断基準です | | [`devicefarm.yml`](../../.github/workflows/devicefarm.yml) | Linux | **手動のみ**(`workflow_dispatch`) | **AWS Device Farm へのバッチ投入**(BE-0235)。showcase の Compose APK をビルドし、Bajutsu と config とシナリオをパッケージ化して [`scripts/devicefarm_submit.py`](../../scripts/devicefarm_submit.py) に渡します。このスクリプトが、`bajutsu run --backend adb` を Device Farm のホストで実行するカスタム環境のテスト仕様をアップロードし、実行をポーリングし、成果物をダウンロードして、**Bajutsu 自身の manifest 判定**(Device Farm の分類ではありません)を表示します。決定的なコアの外側の CI 側のグルーなので、判定に LLM は触れません。起動は `workflow_dispatch` のみで(push / PR では動かず、必須チェックにもなりません)、認証は GitHub OIDC から発行する短命の AWS 認証情報(`AWS_DEVICEFARM_ROLE_ARN`)を `devicefarm` の Environment にスコープし、プロジェクトとデバイスプールの ARN はリポジトリ変数に置きます。いずれかが未設定ならジョブは緑の no-op になり、運用者がアカウントを接続するまで休止します。実アカウントでのシリアル解決の実証は文書化した手動手順([AWS Device Farm](devicefarm.md) を参照)とし、決定的ゲートからは意図的に外しています | diff --git a/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md b/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md index 8e4b38d1d..6c694636c 100644 --- a/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md +++ b/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md @@ -7,8 +7,9 @@ |---|---| | 提案 | [BE-0281](BE-0281-ios-on-device-actuation-coverage-ja.md) | | 提案者 | [@0x0c](https://github.com/0x0c) | -| 状態 | **提案** | +| 状態 | **実装済み** | | トラッキング Issue | [検索](https://github.com/bajutsu-e2e/bajutsu/issues?q=is%3Aissue+label%3Aroadmap-tracking+in%3Atitle+"BE-0281") | +| 実装 PR | [#1181](https://github.com/bajutsu-e2e/bajutsu/pull/1181) | | トピック | プラットフォーム対応 | | 関連 | [BE-0210](../BE-0210-android-actuation-fidelity/BE-0210-android-actuation-fidelity-ja.md), [BE-0221](../BE-0221-android-scenario-portability-guarantee/BE-0221-android-scenario-portability-guarantee-ja.md), [BE-0218](../BE-0218-e2e-simulator-flaky-readiness-actuation/BE-0218-e2e-simulator-flaky-readiness-actuation-ja.md), [BE-0240](../BE-0240-ios-capability-aware-actuator-selection/BE-0240-ios-capability-aware-actuator-selection-ja.md) | @@ -82,10 +83,28 @@ macOS ランナーは Linux の 10 倍で課金されるため、どの新しい > 作業分解(作業の単位ごとに 1 つ)に対応し、ログには変更内容と時期(古い順)を PR へのリンクと > ともに記録します。 -- [ ] iOS idb インタラクションジョブ(tap / type / swipe / scroll / back / doubleTap / longPress)。 -- [ ] XCUITest アクチュエーションシナリオ(ランナーチャネルの `/type`、`/swipe`、`/back`)。 -- [ ] iOS デバイスコントロールジョブ(`device.yaml` / `push.yaml`)、ゲート対象外。 -- [ ] macOS 課金のジョブについてゲートとシグナルの割り当てを適切な大きさに調整し、`ios-e2e.yml` を再利用する。 +- [x] iOS idb インタラクションジョブ(`back`)。`type`/`swipe`/`scroll`/`doubleTap`/ + `longPress` は XCUITest 側に残ります。idb はネイティブのタブバーを 1 つの不透明なグループに + 畳んでしまい、どのタブにも届きません。提案の想定にはなかった制約で、実装前に実機で確認済み + です。詳細はログを参照してください。 +- [x] XCUITest アクチュエーションシナリオ(`/type` は `search.yaml`、`/swipe` + `/back` は `notices.yaml` のランナーチャネルで実行)。 + 新規ファイルではなく既存の共有シナリオを再利用します。 +- [x] iOS デバイスコントロールジョブ(`device.yaml` / `push.yaml`)、ゲート対象外。 +- [x] macOS 課金のジョブについてゲートとシグナルの割り当てを適切な大きさに調整し、`ios-e2e.yml` を再利用する。 + +### ログ + +- [#1181](https://github.com/bajutsu-e2e/bajutsu/pull/1181) — ゲート対象外の新規ジョブ `actuation (idb)` を 1 つ着地させました。 + `back` は `navigation.yaml`、デバイスコントロールは `device.yaml` と `push.yaml` で実行します。 + それに加えて、既存の `xcuitest (multi-touch)` ジョブに実行を 2 つ追加で配線しました。 + `/type` は `search.yaml`、`/swipe` + `/back` は `notices.yaml` で実行します。 + idb と XCUITest のカバレッジは、共有できる箇所でビルドを共有しました。 + そのため、課金対象の新規ジョブは 2 つではなく 1 つで済んでいます。 + もとの設計からは範囲を狭めました。idb はネイティブのタブバーをまったくタップできないためです(SPEC.md §3、BE-0107)。 + 提案が想定していたようにタブをまたぐ `gestures.yaml` / `controls.yaml` のシナリオを、idb 上のインタラクションジョブで走らせることはできませんでした。 + この点は実装に先立つ実機での実行で確かめています(`Log` タブのタップで `一致なし`)。 + `type` / `swipe` / `scroll` / `longPress` / `doubleTap` は、idb 上ではモックしたユニットテストでしか実証されないままです。 + このギャップを塞ぐには新しいタブなしの showcase 画面が要ります。後続の項目に残します。 ## 参考 @@ -93,4 +112,4 @@ macOS ランナーは Linux の 10 倍で課金されるため、どの新しい - [BE-0221 — 共有ショーケースシナリオが Android で無改変に走ることの保証](../BE-0221-android-scenario-portability-guarantee/BE-0221-android-scenario-portability-guarantee-ja.md) - [BE-0218 — E2E Simulator ゲートの安定化](../BE-0218-e2e-simulator-flaky-readiness-actuation/BE-0218-e2e-simulator-flaky-readiness-actuation-ja.md) - [BE-0240 — iOS の capability を踏まえた自動アクチュエータ選択](../BE-0240-ios-capability-aware-actuator-selection/BE-0240-ios-capability-aware-actuator-selection-ja.md) -- `.github/workflows/ios-e2e.yml`、`demos/showcase/scenarios/gestures.yaml`、`demos/showcase/scenarios/device.yaml`、`demos/showcase/scenarios/push.yaml` +- `.github/workflows/ios-e2e.yml`、`demos/showcase/scenarios/navigation.yaml`、`demos/showcase/scenarios/device.yaml`、`demos/showcase/scenarios/push.yaml`、`demos/showcase/scenarios/search.yaml`、`demos/showcase/scenarios/notices.yaml` diff --git a/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md b/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md index a5b82a571..bbcbdf34a 100644 --- a/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md +++ b/roadmaps/BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md @@ -7,8 +7,9 @@ |---|---| | Proposal | [BE-0281](BE-0281-ios-on-device-actuation-coverage.md) | | Author | [@0x0c](https://github.com/0x0c) | -| Status | **Proposal** | +| Status | **Implemented** | | Tracking issue | [Search](https://github.com/bajutsu-e2e/bajutsu/issues?q=is%3Aissue+label%3Aroadmap-tracking+in%3Atitle+"BE-0281") | +| Implementing PR | [#1181](https://github.com/bajutsu-e2e/bajutsu/pull/1181) | | Topic | Platform support | | Related | [BE-0210](../BE-0210-android-actuation-fidelity/BE-0210-android-actuation-fidelity.md), [BE-0221](../BE-0221-android-scenario-portability-guarantee/BE-0221-android-scenario-portability-guarantee.md), [BE-0218](../BE-0218-e2e-simulator-flaky-readiness-actuation/BE-0218-e2e-simulator-flaky-readiness-actuation.md), [BE-0240](../BE-0240-ios-capability-aware-actuator-selection/BE-0240-ios-capability-aware-actuator-selection.md) | @@ -81,10 +82,26 @@ Proposal altitude. The work is MECE along the units below. > *Detailed design* (one box per unit of work); the log records what changed and when > (oldest first), linking the PRs. -- [ ] iOS idb interaction job (tap / type / swipe / scroll / back / doubleTap / longPress). -- [ ] XCUITest actuation scenario (`/type`, `/swipe`, `/back` on the runner channel). -- [ ] iOS device-control job (`device.yaml` / `push.yaml`), non-gating. -- [ ] Right-size gating versus signal for the macOS-metered jobs; reuse `ios-e2e.yml`. +- [x] iOS idb interaction job (`back`; `type`/`swipe`/`scroll`/`doubleTap`/`longPress` stay on + XCUITest — idb collapses the native tab bar into one opaque group and cannot reach any tab, a + constraint the proposal did not anticipate; verified on-device before landing, see the log). +- [x] XCUITest actuation scenario (`/type` via `search.yaml`, `/swipe` + `/back` via `notices.yaml` + on the runner channel — reusing existing shared scenarios rather than a new file). +- [x] iOS device-control job (`device.yaml` / `push.yaml`), non-gating. +- [x] Right-size gating versus signal for the macOS-metered jobs; reuse `ios-e2e.yml`. + +### Log + +- [#1181](https://github.com/bajutsu-e2e/bajutsu/pull/1181) — Landed as one new non-gating `actuation (idb)` job + (`navigation.yaml` for `back`, `device.yaml` + `push.yaml` for device control) plus two extra runs + wired into the existing `xcuitest (multi-touch)` job (`search.yaml` for `/type`, `notices.yaml` for + `/swipe` + `/back`) — one metered job added, not two, since idb and XCUITest coverage share a build + where possible. Scope narrowed from the original design: idb cannot tap the native tab bar at all + (SPEC.md §3, BE-0107), so the interaction job could not run the tab-crossing `gestures.yaml` / + `controls.yaml` scenarios over idb as the proposal assumed — confirmed by an on-device run before + implementation (`一致なし` on a `Log` tab tap). `type` / `swipe` / `scroll` / `longPress` / + `doubleTap` therefore stay proven over idb only by mocked unit tests; closing that gap needs a new + tab-free showcase screen, left for a follow-up item. ## References @@ -92,4 +109,4 @@ Proposal altitude. The work is MECE along the units below. - [BE-0221 — Guarantee shared showcase scenarios run unchanged on Android](../BE-0221-android-scenario-portability-guarantee/BE-0221-android-scenario-portability-guarantee.md) - [BE-0218 — Stabilize the E2E Simulator gate](../BE-0218-e2e-simulator-flaky-readiness-actuation/BE-0218-e2e-simulator-flaky-readiness-actuation.md) - [BE-0240 — Capability-aware automatic actuator selection for iOS](../BE-0240-ios-capability-aware-actuator-selection/BE-0240-ios-capability-aware-actuator-selection.md) -- `.github/workflows/ios-e2e.yml`, `demos/showcase/scenarios/gestures.yaml`, `demos/showcase/scenarios/device.yaml`, `demos/showcase/scenarios/push.yaml` +- `.github/workflows/ios-e2e.yml`, `demos/showcase/scenarios/navigation.yaml`, `demos/showcase/scenarios/device.yaml`, `demos/showcase/scenarios/push.yaml`, `demos/showcase/scenarios/search.yaml`, `demos/showcase/scenarios/notices.yaml` diff --git a/roadmaps/README-ja.md b/roadmaps/README-ja.md index 4c73a0237..d0e88cc67 100644 --- a/roadmaps/README-ja.md +++ b/roadmaps/README-ja.md @@ -105,7 +105,6 @@ | ID | 項目 | 状態 | |---|---|---| | [BE-0008](BE-0008-flutter-support/BE-0008-flutter-support-ja.md) | Flutter 対応 | 提案 | -| [BE-0281](BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage-ja.md) | iOS CI に実機で操作するテストを加える | 提案 | ### ドライバとバックエンドのアーキテクチャ diff --git a/roadmaps/README.md b/roadmaps/README.md index e5f01f6ea..4a508377b 100644 --- a/roadmaps/README.md +++ b/roadmaps/README.md @@ -105,7 +105,6 @@ Under consideration — not yet decided. Promote an item to *In progress* once w | ID | Item | Status | |---|---|---| | [BE-0008](BE-0008-flutter-support/BE-0008-flutter-support.md) | Flutter support | Proposal | -| [BE-0281](BE-0281-ios-on-device-actuation-coverage/BE-0281-ios-on-device-actuation-coverage.md) | Add real on-device actuation coverage to the iOS CI | Proposal | ### Driver & backend architecture