Skip to content

Commit cc76ed5

Browse files
committed
feat(limrun): serve iOS alert get/wait/accept/dismiss from tree capture
Limrun iOS direct sessions refused all four alert legs with unsupported-provider-mode even though the session's element-tree capture already shows the system alert and its touch seam can press its buttons. Issue #3349. Detect the alert from the capture: a system alert arrives as its own blank-label AXApplication root hosting text and pressable buttons, while the app's own UIAlertController rides the app's named root, so app-owned dialogs stay app UI. Accept and dismiss press exactly the button whose label states the asked-for choice and confirm the alert is gone; an ambiguous choice is reported, never guessed, and dismiss never authorizes tracking. The four legs bind through the same catalog and facts the Android tree-based legs already use.
1 parent ca016ec commit cc76ed5

8 files changed

Lines changed: 997 additions & 16 deletions
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
import type { IosTreeNode } from './snapshot.ts';
2+
3+
/**
4+
* Trees shaped from live Limrun iOS instances (issue #3349 evidence). The shapes are what the
5+
* detection rules are actually tested against, so each keeps the provider's real fields:
6+
* `role`/`type` pairs, `AXLabel`, per-node `pid`, and a `frame` dictionary.
7+
*
8+
* The captured discriminator: a system alert arrives as its own `AXApplication` root whose
9+
* `AXLabel` is blank and whose `pid` (and every child's `pid`) is the hosting process, not the
10+
* app's. The app's own `UIAlertController` arrives inside the app's root with the app's `pid`.
11+
*/
12+
13+
/** The app under test, as the app's own root reports it. */
14+
export const LIMRUN_APP_PID = 59_012;
15+
/** The process hosting system alerts (SpringBoard's alert agent on the instance). */
16+
export const LIMRUN_ALERT_HOST_PID = 24_261;
17+
/** SpringBoard, which contributes the status-bar nodes that ride along on app screens. */
18+
export const LIMRUN_SPRINGBOARD_PID = 33_887;
19+
20+
export const LIMRUN_ALERT_SCREEN: Readonly<{ width: number; height: number }> = Object.freeze({
21+
width: 402,
22+
height: 874,
23+
});
24+
25+
function alertRoot(children: IosTreeNode[]): IosTreeNode {
26+
return {
27+
type: 'Application',
28+
role: 'AXApplication',
29+
AXLabel: ' ',
30+
pid: LIMRUN_ALERT_HOST_PID,
31+
frame: { x: 0, y: 0, ...LIMRUN_ALERT_SCREEN },
32+
traits: ['None'],
33+
children,
34+
};
35+
}
36+
37+
/** Positive control: the live ATT prompt tree — its own root, host pid, two texts, two buttons. */
38+
export function limrunAttAlertTree(): IosTreeNode {
39+
return alertRoot([
40+
{
41+
type: 'StaticText',
42+
role: 'AXStaticText',
43+
AXLabel: 'Allow “ATTProbe” to track your activity across other companies’ apps and websites?',
44+
pid: LIMRUN_ALERT_HOST_PID,
45+
frame: { x: 71, y: 381.33, width: 260, height: 64.33 },
46+
traits: ['Scrollable', 'StaticText'],
47+
},
48+
{
49+
type: 'StaticText',
50+
role: 'AXStaticText',
51+
AXLabel: 'This build asks for tracking authorization once, for automation testing.',
52+
pid: LIMRUN_ALERT_HOST_PID,
53+
frame: { x: 71, y: 453.33, width: 260, height: 38 },
54+
traits: ['Scrollable', 'StaticText'],
55+
},
56+
{
57+
type: 'Button',
58+
role: 'AXButton',
59+
AXLabel: 'Ask App Not to Track',
60+
pid: LIMRUN_ALERT_HOST_PID,
61+
enabled: true,
62+
frame: { x: 57, y: 511.67, width: 288, height: 48 },
63+
traits: ['Button', 'Scrollable'],
64+
},
65+
{
66+
type: 'Button',
67+
role: 'AXButton',
68+
AXLabel: 'Allow',
69+
pid: LIMRUN_ALERT_HOST_PID,
70+
enabled: true,
71+
frame: { x: 57, y: 567.67, width: 288, height: 48 },
72+
traits: ['Button', 'Scrollable'],
73+
},
74+
]);
75+
}
76+
77+
/** The same alert with its buttons removed: a text sheet is not an actionable alert. */
78+
export function limrunAlertTreeWithoutButtons(): IosTreeNode {
79+
const root = limrunAttAlertTree();
80+
return { ...root, children: (root.children ?? []).filter((n) => n.role !== 'AXButton') };
81+
}
82+
83+
/** An alert whose only choices are ambiguous: no label classifies as accept or dismiss. */
84+
export function limrunAlertAmbiguousButtonsTree(): IosTreeNode {
85+
const root = limrunAttAlertTree();
86+
return {
87+
...root,
88+
children: [
89+
...(root.children ?? []).slice(0, 2),
90+
{
91+
type: 'Button',
92+
role: 'AXButton',
93+
AXLabel: 'Maybe Later',
94+
pid: LIMRUN_ALERT_HOST_PID,
95+
enabled: true,
96+
frame: { x: 57, y: 511.67, width: 288, height: 48 },
97+
traits: ['Button', 'Scrollable'],
98+
},
99+
{
100+
type: 'Button',
101+
role: 'AXButton',
102+
AXLabel: 'More Info',
103+
pid: LIMRUN_ALERT_HOST_PID,
104+
enabled: true,
105+
frame: { x: 57, y: 567.67, width: 288, height: 48 },
106+
traits: ['Button', 'Scrollable'],
107+
},
108+
],
109+
};
110+
}
111+
112+
/** The live app home screen: app root with the app pid, plus SpringBoard status-bar nodes. */
113+
export function limrunAppScreenTree(): IosTreeNode {
114+
return {
115+
type: 'Application',
116+
role: 'AXApplication',
117+
AXLabel: 'ATTProbe',
118+
pid: LIMRUN_APP_PID,
119+
frame: { x: 0, y: 0, ...LIMRUN_ALERT_SCREEN },
120+
traits: ['None'],
121+
children: [
122+
{
123+
type: 'StaticText',
124+
role: 'AXStaticText',
125+
AXLabel: 'ATT Probe',
126+
pid: LIMRUN_APP_PID,
127+
frame: { x: 161, y: 418.5, width: 80, height: 20.33 },
128+
},
129+
{
130+
type: 'Button',
131+
role: 'AXButton',
132+
AXLabel: 'Request tracking',
133+
AXUniqueId: 'request-tracking',
134+
pid: LIMRUN_APP_PID,
135+
enabled: true,
136+
frame: { x: 136.67, y: 441, width: 129, height: 20.33 },
137+
},
138+
{
139+
type: 'Button',
140+
role: 'AXButton',
141+
AXLabel: 'Present app alert',
142+
AXUniqueId: 'present-app-alert',
143+
pid: LIMRUN_APP_PID,
144+
enabled: true,
145+
frame: { x: 136, y: 463.33, width: 130.33, height: 20.33 },
146+
},
147+
{
148+
type: 'StaticText',
149+
role: 'AXStaticText',
150+
AXLabel: '21:15',
151+
pid: LIMRUN_SPRINGBOARD_PID,
152+
frame: { x: 53, y: 22.33, width: 42, height: 20.33 },
153+
},
154+
{
155+
type: 'GenericElement',
156+
role: 'AXGenericElement',
157+
AXLabel: 'Cellular',
158+
pid: LIMRUN_SPRINGBOARD_PID,
159+
frame: { x: 288.33, y: 26.33, width: 19.33, height: 12.33 },
160+
},
161+
{
162+
type: 'GenericElement',
163+
role: 'AXGenericElement',
164+
AXLabel: '100% battery power',
165+
pid: LIMRUN_SPRINGBOARD_PID,
166+
frame: { x: 339.33, y: 26, width: 27.33, height: 13 },
167+
},
168+
],
169+
};
170+
}
171+
172+
/**
173+
* Negative control: the live app-owned `UIAlertController` ("Unsaved changes" / Cancel /
174+
* Discard). It shares the flat text+button shape of a system alert but rides the app's root
175+
* with the app's pid, so the alert legs must refuse to treat it as one.
176+
*/
177+
export function limrunAppAlertDialogTree(): IosTreeNode {
178+
const home = limrunAppScreenTree();
179+
return {
180+
...home,
181+
children: [
182+
{
183+
type: 'StaticText',
184+
role: 'AXStaticText',
185+
AXLabel: 'Unsaved changes',
186+
pid: LIMRUN_APP_PID,
187+
frame: { x: 82, y: 408.67, width: 238, height: 20.33 },
188+
},
189+
{
190+
type: 'StaticText',
191+
role: 'AXStaticText',
192+
AXLabel: 'Leave without saving?',
193+
pid: LIMRUN_APP_PID,
194+
frame: { x: 82, y: 432.67, width: 238, height: 15.67 },
195+
},
196+
{
197+
type: 'Button',
198+
role: 'AXButton',
199+
AXLabel: 'Cancel',
200+
pid: LIMRUN_APP_PID,
201+
enabled: true,
202+
frame: { x: 66, y: 469.33, width: 135, height: 44 },
203+
traits: ['Button', 'Scrollable'],
204+
},
205+
{
206+
type: 'Button',
207+
role: 'AXButton',
208+
AXLabel: 'Discard',
209+
pid: LIMRUN_APP_PID,
210+
enabled: true,
211+
frame: { x: 201.33, y: 469.33, width: 134.67, height: 44 },
212+
traits: ['Button', 'Scrollable'],
213+
},
214+
...(home.children ?? []).slice(3),
215+
],
216+
};
217+
}
218+
219+
/** SpringBoard's home screen: a blank-label root, but full of launch icons rather than a dialog. */
220+
export function limrunHomeScreenTree(): IosTreeNode {
221+
return alertRoot([
222+
{
223+
type: 'Button',
224+
role: 'AXButton',
225+
AXLabel: 'Maps',
226+
pid: LIMRUN_SPRINGBOARD_PID,
227+
enabled: true,
228+
frame: { x: 24.33, y: 88, width: 168.33, height: 191 },
229+
traits: ['Scrollable', 'UpdatesFrequently', 'LaunchIcon'],
230+
},
231+
{
232+
type: 'Button',
233+
role: 'AXButton',
234+
AXLabel: 'Calendar',
235+
pid: LIMRUN_SPRINGBOARD_PID,
236+
enabled: true,
237+
frame: { x: 209.67, y: 88, width: 168.33, height: 191 },
238+
traits: ['Scrollable', 'UpdatesFrequently', 'LaunchIcon'],
239+
},
240+
]);
241+
}
242+
243+
/** A capture with no alert: the app screen with the ATT alert grafted on as a sibling root. */
244+
export function limrunAlertOverlayOnAppScreenTree(): IosTreeNode[] {
245+
return [limrunAppScreenTree(), limrunAttAlertTree()];
246+
}

0 commit comments

Comments
 (0)