Files
callstack__agent-device/test/integration/ios-simulator-e2e/behavior-coverage.ts
Michał Pierzchała 47b1cae548 fix(ios): refuse Simulator bridge trees that end at a web view's remote content (#2484) (#2486)
* 0.21.1

* fix(ios): refuse Simulator bridge trees that end at a web view's remote content (#2484)

Since 0.21.0 the host AX bridge is the snapshot source for local iOS
Simulators. It reads one process, and a WebKit page lives in another:
Safari and WKWebView screens were published as chrome plus empty webview
nodes, with no ref reaching the page.

The decoder now counts AXRemoteElement leaves that sit under a WebView
ancestor and reach the viewport, and the source refuses such a tree as
remote-content-boundary. The existing route fallback serves XCTest, which
resolves remote elements, for the rest of the app generation and discloses
the switch in the snapshot warning. Frameless leaves are refused; zero-area
and off-screen ones are published.

Adds a fixture-backed smoke scenario that drives the WebView lab through the
default route, amends ADR 0004 and the bridge README, and shares the e2e
snapshotNodes helper.
2026-09-11 13:12:57 +02:00

91 lines
3.1 KiB
TypeScript

export type IosSimulatorBehaviorId =
| 'background-foreground-resume'
| 'cold-start-deep-link-navigation'
| 'host-focus-preservation'
| 'interrupted-system-ui-flow'
| 'long-list-scroll-recovery'
| 'modal-open-close'
| 'permission-state-recovery'
| 'regular-visible-depth-frontier'
| 'text-entry-keyboard-lifecycle'
| 'webview-remote-content';
type BehaviorCoverageEntry =
| {
assertion: string;
level: 'live';
owner: string;
}
| {
assertion: string;
level: 'workflow-live';
owner: { path: string; test: string };
};
/**
* Cross-command mobile usage patterns requested by #320. Command ownership
* remains exhaustive in the command coverage declarations; this table prevents
* that command-level view from hiding missing end-to-end journeys.
*/
export const IOS_SIMULATOR_BEHAVIOR_COVERAGE = {
'cold-start-deep-link-navigation': {
assertion:
'launchApp(clearState) removes seeded app data, opens the stored deep route, renders its payload, and navigates onward',
level: 'live',
owner: 'smoke:automation-input',
},
'text-entry-keyboard-lifecycle': {
assertion:
'fill opens the keyboard, before/after pixels prove dismissal, and type appends text',
level: 'live',
owner: 'smoke:form-input',
},
'background-foreground-resume': {
assertion: 'Home backgrounds the fixture and its persisted transition survives foregrounding',
level: 'live',
owner: 'full:lifecycle-system',
},
'modal-open-close': {
assertion: 'a native page-sheet modal opens structurally and closes through its control',
level: 'live',
owner: 'smoke:automation-input',
},
'permission-state-recovery': {
assertion:
'microphone reset, grant, denial, and second reset produce exact app-observed states',
level: 'live',
owner: 'full:lifecycle-system',
},
'regular-visible-depth-frontier': {
assertion:
'regular depth 1 retains an independently projected child after its clipped structural parent is removed, while raw depth remains traversal-bounded',
level: 'live',
owner: 'smoke:regular-visible-depth-frontier',
},
'webview-remote-content': {
assertion:
'a WKWebView page keeps its link and field label in snapshots: the route refuses the bridge tree that ends at the remote element and discloses the XCTest fallback',
level: 'live',
owner: 'smoke:webview-remote-content',
},
'interrupted-system-ui-flow': {
assertion: 'Home and app switcher expose distinct system pixels before fixture restoration',
level: 'live',
owner: 'full:lifecycle-system',
},
'long-list-scroll-recovery': {
assertion: 'direction, bottom footer, reverse movement, and top rediscovery are all observed',
level: 'live',
owner: 'full:fixture-replays',
},
'host-focus-preservation': {
assertion:
'when the hosted runner can establish a Finder canary, it remains frontmost across simulator automation',
level: 'workflow-live',
owner: {
path: '.github/workflows/ios.yml',
test: 'Assert simulator automation preserved host focus',
},
},
} as const satisfies Record<IosSimulatorBehaviorId, BehaviorCoverageEntry>;