Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
- Settled observation: opt-in (`--settle`) post-action payload on press/click/fill/longpress — the quiet-window stable loop re-captures until the UI settles, and the response carries the diff vs the pre-action tree (changed lines only, added lines with fresh refs, `refsGeneration` when the settled tree was stored). Best-effort: never fails the action; `settled: false` plus a hint on never-quiet content.
- Snapshot capture plan: per-strategy ordered chain of iOS snapshot capture backends (recursive tree, query sweep, private AX) run by one plan runner under a shared wall-clock budget; recovery ordering is declared data, never a per-call-site branch.
- Snapshot quality verdict: structured outcome (state, backend, reason code, effective depth, collapsed leaves) computed once by the plan runner and shipped with every planned snapshot payload; the daemon and CLI render it instead of re-deriving degradation from node shapes.
- iOS WebView semantic presentation: the interactive snapshot projection that recognizes XCTest's typed `WebView` root and WebKit's `Other -> StaticText` wrapper pairs. It keeps raw diagnostics unchanged, presents ordinary wrapper text as `StaticText`, and presents wrappers carrying WebKit's numeric HTML heading level as `Heading`.
- AX-unavailable target invalidation: iOS/macOS runner behavior where a root accessibility snapshot failure such as `kAXErrorIllegalArgument` marks the cached `XCUIApplication` target handle suspect. The runner fails closed for degraded interactive snapshots, clears the cached target, and lets the next command reacquire the app through normal activation.
- Resolution disclosure (ADR 0012 decision 2): additive `resolution` field on press/click/fill/longpress responses discloses how the acting path resolved its target — `runtime`/`unique` or `runtime`/`disambiguated` (with `matchCount`/`winnerDiagnostic`/`tiebreak`/up-to-5 `alternatives`) on the daemon tree, `ref`/`exact` for a resolved `@ref` (runtime-ref and native-ref), `ref`/`label-fallback` when runtime-ref recovered a stale `@ref` via its recorded trailing label, or `direct-ios`/`not-observed` on the XCTest fast path; absent entirely on the coordinate path and on dispatches whose runner actually executed the maestro non-hittable coordinate fallback (permission alone keeps the direct path's `not-observed`). Pre-action diagnostics only: `winnerDiagnostic`/`alternatives` entries carry an opaque, non-`@` `diagnosticRef` that is never ref-issued, never MCP-pinned, and cannot be reused as an `@ref` target — a fresh snapshot/find is required before acting on an alternative.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,23 @@ extension RunnerTests {
case .collectionView: return "CollectionView"
case .table: return "Table"
case .scrollView: return "ScrollView"
case .toolbar: return "Toolbar"
case .searchField: return "SearchField"
case .segmentedControl: return "SegmentedControl"
case .stepper: return "Stepper"
case .picker: return "Picker"
case .activityIndicator: return "ActivityIndicator"
case .progressIndicator: return "ProgressIndicator"
case .checkBox: return "CheckBox"
case .menuItem: return "MenuItem"
case .webView: return "WebView"
case .other: return "Other"
default:
switch type.rawValue {
case 19:
return "Keyboard"
case 20:
return "Key"
case 24:
return "SearchField"
default:
return "Element(\(type.rawValue))"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ final class RunnerTests: XCTestCase {
.tabBar,
.textField,
.secureTextField,
.textView
.textView,
.webView
]
// Keep blocker actions narrow to avoid false positives from generic hittable containers.
let actionableTypes: Set<XCUIElement.ElementType> = [
Expand Down
8 changes: 8 additions & 0 deletions examples/test-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ It is intentionally small, but each surface is dense with durable accessibility
- `Product detail`: back navigation, quantity stepper, multiline notes, save action
- `Checkout form`: required-field validation, fill vs type, checkbox state, choice groups, keyboard dismiss, success summary
- `Settings`: switch rows, accordion content, loading and error states, retry flow, destructive-confirm modal
- `WebView accessibility`: a deterministic semantic fixture plus live websites with varied HTML for native accessibility snapshot verification

Navigation uses Expo Router native bottom tabs, so the tab bar itself is also part of the test surface.

The deterministic WebView fixture is the stable accessibility oracle. On iOS, an interactive
snapshot should expose its root as `webview`, both titles as `heading`, paragraph and label content
as `text`, and the form controls as `text-field`, `switch`, and `button`. The live-site buttons are
exploratory smoke coverage for real-world WebKit trees, not stable assertion targets.
Use an unscoped snapshot for this oracle: XCTest can detach a scoped WebKit document subtree from
its `WebView` ancestor, leaving insufficient evidence for safe semantic projection.

## Coverage map

These are the main case families this app can support without adding more screens:
Expand Down
1 change: 1 addition & 0 deletions examples/test-app/app/(tabs)/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function SettingsRoute() {
notificationsEnabled={state.notificationsEnabled}
onOpenAccessorySetup={() => router.push('/accessory-setup')}
onOpenInertSurface={() => router.push('/inert')}
onOpenWebViewLab={() => router.push('/webview')}
onConfirmReset={state.resetLabState}
onLoadDiagnostics={state.loadDiagnostics}
onRetryDiagnostics={state.retryDiagnostics}
Expand Down
1 change: 1 addition & 0 deletions examples/test-app/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function RootLayoutContent() {
<Stack.Screen name="accessory-setup" />
<Stack.Screen name="inert" options={{ presentation: 'fullScreenModal' }} />
<Stack.Screen name="product/[productId]" />
<Stack.Screen name="webview" />
</Stack>
{toastMessage ? <ToastViewport message={toastMessage} /> : null}
</ThemeProvider>
Expand Down
9 changes: 9 additions & 0 deletions examples/test-app/app/webview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useRouter } from 'expo-router';

import { WebViewScreen } from '../src/screens/WebViewScreen';

export default function WebViewRoute() {
const router = useRouter();

return <WebViewScreen onClose={() => router.back()} />;
}
3 changes: 2 additions & 1 deletion examples/test-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"react-native-gesture-handler": "^2.31.2",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.25.2",
"react-native-web": "^0.21.2"
"react-native-web": "^0.21.2",
"react-native-webview": "13.16.1"
},
"devDependencies": {
"@expo/fingerprint": "^0.19.4",
Expand Down
Loading
Loading