mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
main
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
89c7536850 |
feat(ios): support explicit iOS simulator keychain reset (#2345)
* feat(ios): support explicit iOS simulator keychain reset `settings clear-app-state` never touched keychain-backed credentials (e.g. Firebase auth), so a customer's fresh-install reset via the CLI left an app signed in when their in-app reset button did not (#2282). simctl exposes no per-app keychain reset, only a whole-simulator one (`simctl keychain <device> reset`), so this ships as a separate, explicit `settings reset-keychain clear` command rather than folding it into `clear-app-state` — callers opt in knowing the scope is the whole simulator, not just the app under test. Split the pre-existing `apps.test.ts` and `snapshot-handler.test.ts` suites along the `app-settings.ts`/`snapshot-settings.ts` modules they actually mirror, since both were already over the test-file-size tripwire and could not grow further. * fix(ios): reject extra reset-keychain arguments and add live-tested keychain fixture settings reset-keychain clear <extra-arg> silently dropped the extra argument in both the CLI reader and the direct-daemon parser, so a caller expecting per-app scoping could get a whole-simulator wipe without any signal something was off. Reject it instead in both places, with tests proving no settings mutation happens. Also add a small keychain-backed "auth" fixture to the test-app's automation lab (expo-secure-store) so the settings reset-keychain guarantee has a real regression surface: authenticate, verify the credential survives clear-app-state and a plain relaunch, then verify reset-keychain actually clears it. Validated live against a disposable iOS simulator. * fix(ci): stop a bare gradle.properties append from corrupting the last line expo prebuild's generated android/gradle.properties has no trailing newline, so `echo "org.gradle.jvmargs=-Xmx4g" >> gradle.properties` appended directly onto its last line instead of a new one, producing expo.inlineModules.watchedDirectories=[]org.gradle.jvmargs=-Xmx4g. Gradle's JSON.parse of that property then fails at configure time, before any real compilation runs -- the exact "Process 'command 'node'' finished with non-zero exit value 1" failure this branch hit on Android Release and the Smoke Tests fixture-app fallback build. This was a dormant bug: the Android build-cache job only runs on a fingerprint miss, and no PR had changed the test-app's native dependencies in a while. Adding expo-secure-store (#2282's keychain fixture) was enough to trigger it. Reproduced locally against a clean install with the exact CI script, confirmed the corrupted property, and confirmed the printf-based fix builds cleanly (870/870 tasks). |
||
|
|
338aa2a0d5 |
refactor: route every native selector resolution through the policy interface (#1715)
* refactor: route every native selector resolution through the policy interface #1649 declared the per-caller ambiguity matrix; four native call sites still bypassed it, spreading `selectorResolutionKnobs(row)` into a raw `resolveSelectorChain` instead of naming the row. That left the "one interface" claim aspirational: a caller could restate its contract as engine knobs and nothing would notice. - `is` non-exists, `get text`/`get attrs`, find's read actions, and the covered-selector diagnosis probe now call `resolveSelectorChainWithPolicy` with their existing row. Semantics are byte-identical: the knob-backed branch of that interface forwards to the same engine call the call sites built by hand. - The façade drops `resolveSelectorChain` and `selectorResolutionKnobs`, so no knob-taking resolver is reachable from outside the package and a call site cannot re-acquire the knobs even by accident. `requireUnique`/`disambiguateAmbiguous` are now named in exactly one function, which `resolve-with-policy.ts` and the replay resolver both derive through. - `get` names the two rows it may consume as a type, so pointing it at any other ambiguity contract is a compile error. Tests: selector-read-policy.test.ts pins which row each read command consumes, end to end, on one ambiguous fixture — the only tree the rows disagree on. Each assertion was proven red by re-pointing its caller at a neighbouring row. The knob-consistency check moves into the package beside the now-private helper. Test call sites that used the raw resolver move to `resolveRecordedTarget`, the same knobs and the path that actually replays a recorded chain. Extracting the failure branch drops `resolveSelectorInteractionTarget` below the complexity threshold; its `fallow-ignore` waiver is removed (verified load-bearing before the extraction, unnecessary after). Closes #1630. Structural stages (occlusion, off-screen, promotion, poll budget) stay per-caller pipeline code, tracked in #1656. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuKzQWn6WQcMYaAZVvJzdD * test: observe which node find's row selected, not just that one existed #1715 review, P2: the find row assertion was only half a pin. `find exists` returns `found: true` for any resolved node, and the `list` call it leaned on goes through listFindMatches — a path that consumes no policy row at all. So repointing findFirstLocatorMatch at `readText` left both assertions green while selection silently moved from the document-order head to the tiebreak winner. Assert through `find get_attrs`, which returns the ref of the node the row actually selected. Both neighbouring rows are now red: `readText` fails '@e3' !== '@e2' (the move the old test missed), `readUnique` fails by refusing the ambiguous screen. `exists` stays as a second, weaker assertion on the same resolution. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuKzQWn6WQcMYaAZVvJzdD * refactor: route is exists through the matrix, collapse the double match pass Follow-up tightening on the same seam. `is exists` reached findSelectorChainMatch directly while the `readAny` row's own doc claimed to serve "`exists` and find's read-only actions" — true of the docs, not of the code, which is the unverifiable-claim shape #1656's review called out. It now names `readAny`, the row it always described. Equivalent by construction: both take the first alternative with any match under requireRect: false, and disclose that alternative's count. That leaves the root façade with no consumer for findSelectorChainMatch, so it goes the way of resolveSelectorChain — dropped from the string-only façade, kept on the published ./ast surface. Its façade-twin type SelectorChainMatch dies with it (fallow caught it). resolveSelectorChainWithPolicy matched twice on the uniqueness path: once via resolveSelectorChain, then again to fill matchedNodes. Hoisting the single list call above the row switch removes that second pass, collapses two duplicated ambiguous literals into one helper, and drops a `?? [resolution.node]` fallback that was unreachable — a resolution implies its alternative matched, so the list is never null there. While hoisting: the resolved arm's matchedNodes can describe a different alternative than resolution.selector, because uniqueness skips an ambiguous alternative to try the next one. Unreachable today (only first-match callers read it, where both come from one list), and left as-is rather than silently changed — but the doc claimed "the alternative it came from", so it now says what is actually true. Tests: is exists gets a caller-level pin on the shared ambiguous fixture — passes with matches: 2 where its fail-closed siblings refuse — proven red by pointing it at readUnique. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuKzQWn6WQcMYaAZVvJzdD * test: discriminate is exists's row by alternative, guard the façade structurally #1715 review, second regression-validity gap. The `is exists` pin observed only `pass: true` and `matches: 2` on a fixture whose first alternative was merely TIEBREAKABLE — so disambiguation succeeded there and reported the same count first-match would. `readAny`, `readText`, and the pre-migration raw lookup all produced that, and only the readUnique swap I had checked went red. One mutation proven is not the same as the row being pinned. `exists` exposes no node ref, so the row has to be read off WHICH alternative answered. New fixture: alternative one matches two nodes that are genuinely indistinguishable (same depth, same area, both on screen) so the tiebreak declines; alternative two matches exactly one. First-match answers from alternative one; every uniqueness row skips the undecidable alternative and answers from alternative two. Asserting the selector now separates them — readText and readUnique both fail with `id="save-unique"` where `label="Save"` is expected. Restoring the raw lookup stays behaviourally invisible, though: findSelectorChainMatch is equivalent to the readAny row it migrated to, which is precisely why that migration preserved semantics. No fixture assertion can catch that revert, so the guard is structural — the façade's export list must not carry resolveSelectorChain, findSelectorChainMatch, or selectorResolutionKnobs. Follows the packages/maestro index.test.ts absence-assertion precedent. Verified red by re-exporting the lookup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HuKzQWn6WQcMYaAZVvJzdD * fix: cover selector routes in device replays * test: simplify selector replay regression --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
32b9db2d7a |
test: add Android full emulator coverage (#1484)
* test: add Android full emulator coverage * ci: package Android helpers before nightly coverage * fix: harden Android nightly runtime evidence * fix: expose trace artifacts in MCP schema * style: format Android coverage manifest * fix: address Android coverage review findings * refactor: share live device coverage helpers * fix: restore fixture landmarks in device smokes * refactor: centralize live artifact assertions * fix: normalize fixture canary visibility |
||
|
|
4e4ecdea0d |
test(ios): expand simulator e2e coverage (#1408)
* test(ios): expand simulator e2e coverage * test(ios): make coverage checks host portable * test(ios): handle deep link confirmation * test(ios): fix deep link prompt selector * ci: stabilize full simulator nightly * test(ios): stabilize permission prompt lifecycle * test(e2e): wait for route-specific landmarks * test(e2e): reset permissions from inactive app * test(e2e): redeliver trusted cold deep links * test(ios): verify orientation native readback * test(ios): stabilize simulator permission coverage * test(ios): wait for tab target after deep link * test(ios): paginate full event timeline * test(ios): simplify event pagination coverage * test(ios): stabilize simulator e2e coverage * test(ios): model simulator recorder lifetime * ci(test-app): cache fixture dependencies * fix(ci): isolate test app cache by node * fix(ios): settle fixture route navigation * test(ci): waive unbenchmarked ios system UI help * fix(ios): tolerate delayed simulator scale lookup * 0.20.1 * test(ci): remove superseded system UI waiver * fix(ios): harden simulator e2e reliability * chore: clarify Apple runner CI steps * fix(ios): wait before fixture home snapshot * fix(ios): require exact catalog navigation * chore(ci): format rebased workflows * fix(ios): retry unobserved fixture navigation * refactor(test): remove iOS e2e workarounds * fix(ci): verify fixture artifact provenance * fix(ci): align fixture artifact fingerprints * fix(ci): use unified Android helper packager * perf(ci): scope fixture build concurrency * chore: format fixture artifact tests * test(ios): update split Apple coverage owner * fix(ios): accept deep-link confirmation alerts |
||
|
|
ef118b9d11 |
ci(test-app): fingerprint-keyed build cache — disk locally, Release artifacts in CI (#1321)
Splits the test app's build caching by context instead of running one remote cache for both. Locally, `expo run:*` caches the native build on disk via the expo-build-disk-cache provider, keyed by the Expo fingerprint. A second run with no native change reuses the first build; a screen edit never rebuilds, because Metro serves JS. This is the original ask — "next time we don't build unless native changes" — and needs no token, no network, and no custom provider. In CI, test-app-build-cache.yml builds a Release binary per platform when the fingerprint has no artifact yet, and publishes it as a GitHub Actions artifact named `fingerprint.<hash>.<platform>`. Release, not dev-client, so the JS bundle is embedded and a consuming job needs no Metro. setup-fixture-app installs it by downloading the artifact and refreshing the JS with @expo/repack-app, so keying on the native-only fingerprint stays correct — a JS-only change reuses the same native binary in seconds. It falls back to an inline build when no artifact exists yet, so a caller is never left without an app. Release removes the sharp edges the dev-client cache needed. Its simulator .app is universal (x86_64+arm64) rather than the active-arch-only slice a debug build emits, so no architecture tag. It links against the SDK but loading is gated by the deployment target, which the fingerprint already covers, so no toolchain tag. And the CLI only narrows *debug* builds to the device ABI, so a Release APK spans every ABI without the undocumented --all-arch flag. The artifact name collapses to fingerprint plus platform. This deletes build-cache-provider.js entirely — with it goes the custom Expo provider that had to reach GitHub from inside @expo/cli, and every workaround that forced: the fetch-nodeshim User-Agent shim, the arch/Xcode identity, the upload-intent handoff. CI now talks to the artifacts API with plain `gh api` outside the patched fetch, and locally the disk cache never hits the network. The fingerprint comes from @expo/fingerprint's own `fingerprint:generate` (no --platform, matching what @expo/cli hashes). Gitignoring /ios and /android is what makes it machine-independent: the library asks the VCS whether the platform markers are ignored and, concluding CNG, skips hashing them — so a developer's prebuild output and a fresh CI checkout agree. conformance-differential consumes setup-fixture-app, so it gains `permissions: actions: read` for the artifact lookup. The artifact lookup is non-fatal: a query outage leaves the id empty and falls through to an inline build like a miss does, rather than exiting the composite under set -e and turning a cache blip into a caller failure. test/scripts/setup-fixture-app-fallback-smoke.sh drives that step's real shell against a failing gh and asserts source=build; ci.yml runs it. |
||
|
|
236016ed8a |
fix(ios): support remote-hosted alerts on physical devices (#1232)
* fix(ios): probe remote-hosted system modals (AccessorySetupKit picker) when the springboard mirror yields no hittable actions * fix(ios): fail closed on host state, guard dismissal re-query, unit-test probe routing Addresses review on #1232: - Gate the remote-host probe to a foreground host (RemoteHostedSystemModalPolicy.isEligibleHostState); background/unknown hosts fail closed instead of substituting an unrelated action tree. - Wrap the alert-resolution fallback query in safeElementsQuery so a dismissed remote host raising kAXErrorServerNotFound is absorbed. - Extract routing/gating into RemoteHostedSystemModalPolicy and add simulator-free unit tests under AGENT_DEVICE_RUNNER_UNIT_TESTS. * refactor(ios): centralize blocking system modal resolution * fix(ios): bound alert dismissal rechecks * feat(ios): enable alerts on physical devices * fix(ios): bound alert system modal resolution * test(ios): add AccessorySetupKit picker fixture * fix(ios): validate remote-hosted system modal interactions * chore: keep pnpm checks non-interactive * fix(ios): share alert command deadline --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> |
||
|
|
f474f0784e |
feat: unify gesture planning and multi-touch execution (#1212)
* feat: unify gesture planning and multi-touch execution * fix: correct unified gesture helper behavior * refactor: tighten unified gesture architecture * fix: preserve gesture routing contracts * test: account for fresh gesture viewport * refactor: remove retired gesture series * fix: preserve example app navigation targets * test: reconcile unified gestures with helper ownership * docs: update Android helper gesture protocol * fix: refresh Maestro percentage swipe frames * refactor: remove stale Maestro frame cache * fix: harden unified gesture execution * fix: model gesture viewport in providers * refactor: remove legacy gesture paths * fix: remove unused swipe preset parser * refactor: tighten unified gesture boundaries * fix: close gesture review gaps * fix: preserve gesture compatibility contracts * fix: preserve multi-touch recording semantics * fix: refresh Apple runner state after app relaunch * test: lock Apple fling fallback route * fix: close Apple runner review gaps * refactor: tighten unified gesture seams * refactor: consolidate gesture planning policy * fix: preserve swipe response compatibility * fix: keep gesture lab aligned with replay coordinates |
||
|
|
4cd40aa621 |
feat: polish replay test progress reporter (#998)
* feat: polish replay test progress reporter * test: stabilize replay reporter cursor test in CI * refactor: dedupe replay reporter live progress checks * fix: make Expo build cache path configurable |