|
| 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