execbro 2.9.6 → 2.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/__tests__/unit/checkInjectedJs.test.d.ts +2 -0
- package/build/__tests__/unit/checkInjectedJs.test.d.ts.map +1 -0
- package/build/__tests__/unit/checkInjectedJs.test.js +42 -0
- package/build/__tests__/unit/checkInjectedJs.test.js.map +1 -0
- package/build/__tests__/unit/overlayAdoption.test.d.ts +2 -0
- package/build/__tests__/unit/overlayAdoption.test.d.ts.map +1 -0
- package/build/__tests__/unit/overlayAdoption.test.js +38 -0
- package/build/__tests__/unit/overlayAdoption.test.js.map +1 -0
- package/build/__tests__/unit/pressabilityAudit.test.d.ts +2 -0
- package/build/__tests__/unit/pressabilityAudit.test.d.ts.map +1 -0
- package/build/__tests__/unit/pressabilityAudit.test.js +96 -0
- package/build/__tests__/unit/pressabilityAudit.test.js.map +1 -0
- package/build/__tests__/unit/tapAndroidStatusBarInset.test.d.ts +2 -0
- package/build/__tests__/unit/tapAndroidStatusBarInset.test.d.ts.map +1 -0
- package/build/__tests__/unit/tapAndroidStatusBarInset.test.js +139 -0
- package/build/__tests__/unit/tapAndroidStatusBarInset.test.js.map +1 -0
- package/build/__tests__/unit/tapDuration.test.js +2 -2
- package/build/__tests__/unit/tapDuration.test.js.map +1 -1
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +86 -13
- package/build/core/guides.js.map +1 -1
- package/build/core/injected/overlayAdoption.d.ts +19 -0
- package/build/core/injected/overlayAdoption.d.ts.map +1 -0
- package/build/core/injected/overlayAdoption.js +17 -0
- package/build/core/injected/overlayAdoption.js.map +1 -0
- package/build/core/screenState.d.ts +18 -0
- package/build/core/screenState.d.ts.map +1 -1
- package/build/core/screenState.js +138 -7
- package/build/core/screenState.js.map +1 -1
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +7 -3
- package/build/pro/tap.js.map +1 -1
- package/package.json +3 -3
package/build/core/guides.js
CHANGED
|
@@ -21,8 +21,18 @@ const guides = [
|
|
|
21
21
|
- list_devices — iOS simulators, Android emulators, and physical devices in one call
|
|
22
22
|
- ios_boot_simulator — boot an iOS simulator if needed
|
|
23
23
|
- ios_launch_app / android_launch_app — launch the app
|
|
24
|
+
- ios_terminate_app — kill an app that is in a bad state before relaunching
|
|
25
|
+
- android_list_packages — find the package name when you only know the app by sight
|
|
24
26
|
- Wait 2-3 seconds, then scan_metro
|
|
25
27
|
|
|
28
|
+
## Stale Bundle After a Metro Restart
|
|
29
|
+
scan_metro detects that the process serving a port CHANGED (pid via lsof) and says so. It means
|
|
30
|
+
Fast Refresh history is discontinuous: edits made while Metro was down are NOT in the running
|
|
31
|
+
bundle, and reconnecting does not reconcile them. Devices attached on a restarted port are
|
|
32
|
+
flagged, and tap, swipe and get_screen_state carry a staleBundle warning until reload_app clears
|
|
33
|
+
it. Believe the warning — every other signal reads healthy, so stale behaviour otherwise looks
|
|
34
|
+
exactly like "my fix didn't work".
|
|
35
|
+
|
|
26
36
|
## Switch to Native Debugger
|
|
27
37
|
- disconnect_metro — closes all CDP connections and stops auto-reconnect
|
|
28
38
|
- The built-in React Native debugger can now connect
|
|
@@ -33,7 +43,8 @@ const guides = [
|
|
|
33
43
|
- connect_metro: connect to specific port (when you know it)
|
|
34
44
|
- disconnect_metro: close all connections (free CDP slot for native debugger)
|
|
35
45
|
- ensure_connection: health check with healthCheck=true
|
|
36
|
-
- get_connection_status: check uptime and gaps
|
|
46
|
+
- get_connection_status: check uptime and gaps
|
|
47
|
+
- get_license_status: installation id and license tier (activate_license / delete_account manage it)`
|
|
37
48
|
},
|
|
38
49
|
{
|
|
39
50
|
id: "inspect",
|
|
@@ -42,9 +53,9 @@ const guides = [
|
|
|
42
53
|
content: `# Component Inspection
|
|
43
54
|
|
|
44
55
|
## Recommended Workflow: Identify a Component on Screen
|
|
45
|
-
1.
|
|
46
|
-
2.
|
|
47
|
-
3. Pass
|
|
56
|
+
1. get_screen_state — screenshot-free, and every element comes back with a ready (x, y). Start here; only fall back to a screenshot (ios_screenshot / android_screenshot / ocr_screenshot) when you need to see the element to pick it out
|
|
57
|
+
2. Take the coordinate from that listing, or estimate it off the screenshot
|
|
58
|
+
3. Pass it straight through — every layout tool, tap() and the screenshots share one screen-space coordinate system, so no conversion is needed
|
|
48
59
|
4. Pick the right tool (see decision below) and call it with (x, y)
|
|
49
60
|
|
|
50
61
|
### inspect_at_point(x, y)
|
|
@@ -53,7 +64,7 @@ NO overlay, zero visual side effect, safe to call rapidly or across a transition
|
|
|
53
64
|
|
|
54
65
|
Returns:
|
|
55
66
|
- element name and full owner-tree path
|
|
56
|
-
- FRAME PER ANCESTOR (position/size
|
|
67
|
+
- FRAME PER ANCESTOR (position/size for every ancestor that hit-tested the point, in delivered-screenshot pixels — the same space as screenshots and tap)
|
|
57
68
|
- PROPS of the innermost component (handlers as [Function], refs, testID, custom props)
|
|
58
69
|
- the node's own style object
|
|
59
70
|
- source {file, line, column} plus the owner chain as "Source ancestors" (set source=false to skip in tight loops)
|
|
@@ -72,6 +83,7 @@ looks wrong and isn't on the node itself, walk the ancestors it returns.
|
|
|
72
83
|
- find_components(pattern) — regex search by component name across the fiber tree.
|
|
73
84
|
- get_component_tree — compact names-only structure by default; pass structureOnly=false only when you genuinely need props/styles for the whole tree.
|
|
74
85
|
- inspect_component(name) — deep dive into a specific component's props, state, and hooks.
|
|
86
|
+
- measure(componentName) — the reverse of inspect_at_point: geometry {x, y, width, height} for a component you can name, without hand-rolling a fiber walk in execute_in_app.
|
|
75
87
|
|
|
76
88
|
## Tips
|
|
77
89
|
- Works on Paper, Fabric, and Bridgeless / new arch.
|
|
@@ -96,6 +108,10 @@ looks wrong and isn't on the node itself, walk the ancestors it returns.
|
|
|
96
108
|
- A LogBox banner is excluded from the listing (it mounts above the app and its own buttons
|
|
97
109
|
would otherwise be the only pressables returned). A note says so when one is up — the
|
|
98
110
|
banner still covers screen, so use logbox({action:"dismiss"}) before trusting edge taps.
|
|
111
|
+
- Press targets the walk drops are counted and attributed to a rule (pruned as hidden,
|
|
112
|
+
not yet measurable, or dropped for zero size / off-viewport). An overlay listed with no
|
|
113
|
+
pressables inside it means treat that as a grouping fault in this tool, not an empty sheet
|
|
114
|
+
— screenshot and tap by coordinates instead of trusting the empty group.
|
|
99
115
|
|
|
100
116
|
## Coordinates
|
|
101
117
|
All layout tools, tap() and the screenshots share ONE screen-space coordinate system. A frame
|
|
@@ -125,16 +141,22 @@ size in a note when it applies.
|
|
|
125
141
|
- Style is the node's own style object, not a merged cascade — walk the returned ancestors when a value isn't on the node itself.
|
|
126
142
|
|
|
127
143
|
## Full Screen Layout
|
|
128
|
-
- get_screen_layout — full layout data for all components
|
|
129
|
-
|
|
144
|
+
- get_screen_layout — full layout data for all components. Host components (View, Text) are
|
|
145
|
+
already filtered out; the tree is custom components only, so there is nothing to switch off
|
|
146
|
+
- Use extended=true for layout styles (padding, margin, flex, backgroundColor)
|
|
130
147
|
- find_components with includeLayout=true for targeted layout info
|
|
148
|
+
- measure(componentName) — geometry for one named component, when you know the name and only
|
|
149
|
+
want its frame
|
|
131
150
|
|
|
132
151
|
## Key Tools
|
|
133
152
|
- get_screen_state: route + overlays + every element, screenshot-free (start here)
|
|
134
153
|
- ios_screenshot / android_screenshot: visual capture
|
|
135
154
|
- tap: also returns a post-tap screenshot by default (no separate screenshot call needed after tapping)
|
|
136
155
|
- ocr_screenshot: screenshot with text recognition and tap coordinates
|
|
137
|
-
- inspect_at_point: frames per ancestor + props + source file:line (no overlay, fast)
|
|
156
|
+
- inspect_at_point: frames per ancestor + props + source file:line (no overlay, fast)
|
|
157
|
+
- measure: geometry for one named component
|
|
158
|
+
- get_images: re-read any screenshot already captured (including tap burst frames) instead of taking another
|
|
159
|
+
- logbox: dismiss / ignore / push / detect the LogBox overlay — dismiss returns the full error content, so nothing is lost by clearing it`
|
|
138
160
|
},
|
|
139
161
|
{
|
|
140
162
|
id: "interact",
|
|
@@ -159,6 +181,13 @@ Use tap — it tries multiple strategies automatically and returns a post-tap sc
|
|
|
159
181
|
tap returns a screenshot after every action (screenshot=true by default) — no need to call ios_screenshot/android_screenshot after tapping.
|
|
160
182
|
For coordinate/accessibility/OCR taps, it also verifies if the tap caused a visual change (verify=true by default). Set screenshot=false for fastest execution.
|
|
161
183
|
|
|
184
|
+
### When verification says meaningful:false but you think the tap landed
|
|
185
|
+
Retry with burst=true. It captures 4 rapid screenshots after the tap, catching press animations
|
|
186
|
+
and highlights that settle before the ordinary after-screenshot. Then read
|
|
187
|
+
verification.transientChangeDetected, and pull the individual frames with
|
|
188
|
+
get_images({groupId: verification.burstGroupId}) — the frames are already in the shared image
|
|
189
|
+
buffer, so inspecting them costs no extra capture.
|
|
190
|
+
|
|
162
191
|
## Long Press
|
|
163
192
|
Pass duration (milliseconds) to hold the touch instead of releasing it: tap(testID="row-3", duration=800). Use it for context menus, drag starts and multi-select. React Native fires onLongPress at 500ms, so anything under 500 will not trigger it; 800 is a safe default. Works on both platforms and with every targeting strategy (testID, text, component, coordinates).
|
|
164
193
|
|
|
@@ -243,6 +272,17 @@ tap(text=...) skips fiber for non-ASCII (Hermes limitation) and uses accessibili
|
|
|
243
272
|
- dismiss_keyboard: blur the focused input, closing the keyboard.
|
|
244
273
|
- ios_button / android_key_event: hardware buttons (HOME, BACK, etc.)
|
|
245
274
|
- ios_open_url: deep links and universal links
|
|
275
|
+
- get_images: retrieve screenshots already captured by any tool, including tap burst frames
|
|
276
|
+
|
|
277
|
+
## Jumping Straight to a Screen
|
|
278
|
+
navigate drives the app's router instead of tapping through the UI, and checks the route
|
|
279
|
+
actually moved: navigate({to:"/settings"}) -> read changed -> get_screen_state.
|
|
280
|
+
Destinations are NOT interchangeable — Expo Router takes paths ("/event-details?id=1"),
|
|
281
|
+
React Navigation takes route names ("TarotNav"); the response says which router resolved, and
|
|
282
|
+
unknown React Navigation names are rejected before dispatch with nearest-match suggestions.
|
|
283
|
+
navigate({action:"back"}) and {action:"reset"} are available too. changed=false means it settled
|
|
284
|
+
without moving; indeterminate=true means no settled reading. Prefer this over a hand-written
|
|
285
|
+
router call through execute_in_app, which reports success whenever nothing throws.
|
|
246
286
|
|
|
247
287
|
## After Interactions
|
|
248
288
|
- Take a screenshot to verify the result`
|
|
@@ -262,10 +302,18 @@ tap(text=...) skips fiber for non-ASCII (Hermes limitation) and uses accessibili
|
|
|
262
302
|
3. clear_logs — reset buffer, then re-capture after a specific action
|
|
263
303
|
|
|
264
304
|
## Key Tools
|
|
265
|
-
- get_logs: retrieve logs with filtering (level, maxLogs, summary, verbose, startFromText)
|
|
305
|
+
- get_logs: retrieve logs with filtering (level, maxLogs, summary, verbose, startFromText, epoch)
|
|
266
306
|
- search_logs: text search across all captured logs
|
|
267
307
|
- clear_logs: reset the log buffer
|
|
268
308
|
|
|
309
|
+
## App Restarts (epochs)
|
|
310
|
+
Every log and network entry carries a per-device epoch that increments on each new app run, and
|
|
311
|
+
the output draws a "── app restarted (epoch N) ──" divider at each boundary. The default is
|
|
312
|
+
epoch="all", so pre-restart data is never hidden from you — pass epoch="current" to read only the
|
|
313
|
+
run in front of you, or a number for one specific run. get_network_requests takes the same
|
|
314
|
+
parameter. When a crash and its aftermath sit in one read, the divider is what tells you which
|
|
315
|
+
side of the restart a message came from.
|
|
316
|
+
|
|
269
317
|
## NATIVE LOGS (crashes the JS console cannot see)
|
|
270
318
|
|
|
271
319
|
get_logs(source="native") reads Android logcat / iOS os_log, filtered to your
|
|
@@ -356,6 +404,12 @@ are trying to fix.
|
|
|
356
404
|
network_replay({requestId}) re-issues a captured request, optionally with a
|
|
357
405
|
changed body or header, without driving the UI back to the screen that made it.
|
|
358
406
|
|
|
407
|
+
app_request issues a NEW request from inside the app, as the logged-in user — through the app's
|
|
408
|
+
real network stack, TLS trust and proxy config. Use it to reproduce a 4xx, check what an endpoint
|
|
409
|
+
returns for an edge case, or clean up test records the UI cannot reach. auth="auto" resolves the
|
|
410
|
+
token in-app, so the credential never enters the transcript; a hand-written fetch in
|
|
411
|
+
execute_in_app either digs the token out of redux or embeds a JWT literal, which does.
|
|
412
|
+
|
|
359
413
|
## Key Tools
|
|
360
414
|
- get_network_requests: list requests with filters (urlPattern, method, status, summary)
|
|
361
415
|
- search_network: search by URL pattern
|
|
@@ -364,6 +418,7 @@ changed body or header, without driving the UI back to the screen that made it.
|
|
|
364
418
|
- network_mock: replace or tamper with responses (add / list / remove / clear)
|
|
365
419
|
- network_condition: offline / slow / normal
|
|
366
420
|
- network_replay: re-issue a captured request, with optional overrides
|
|
421
|
+
- app_request: issue a new request from inside the app, authenticated, without putting the token in the transcript
|
|
367
422
|
|
|
368
423
|
## Tips
|
|
369
424
|
- Start with summary=true to see the request landscape
|
|
@@ -402,9 +457,12 @@ If the app called \`init({ stores, navigation, custom })\`, prefer the SDK paths
|
|
|
402
457
|
## Common Patterns (no SDK)
|
|
403
458
|
- Read Redux: execute_in_app("globalThis.__REDUX_STORE__.getState().sliceName")
|
|
404
459
|
- Dispatch action: execute_in_app("globalThis.__dispatch__(globalThis.__REDUX_ACTIONS__.slice.action(args))")
|
|
405
|
-
- Navigate: execute_in_app("globalThis.__navigate__('ScreenName')")
|
|
406
460
|
- Current route: execute_in_app("globalThis.__getCurrentRoute__()")
|
|
407
461
|
|
|
462
|
+
Note: do NOT navigate this way. A router call through execute_in_app reports success whenever
|
|
463
|
+
nothing throws — a path sent to a React Navigation ref changes nothing and warns only in LogBox.
|
|
464
|
+
Use the navigate tool, which verifies the route actually moved (see the "interact" guide).
|
|
465
|
+
|
|
408
466
|
## Long-Running Expressions
|
|
409
467
|
- Pass timeoutMs to raise the 10s default (capped at 120000) — the promise poll ladder is derived from it, so a big budget is actually used
|
|
410
468
|
- A promise that outlives the budget is kept in the app and its handle returned: execute_in_app({collect:"<handle>", waitMs:30000}) blocks server-side until it settles instead of making you poll
|
|
@@ -433,20 +491,35 @@ If the app called \`init({ stores, navigation, custom })\`, prefer the SDK paths
|
|
|
433
491
|
4. get_bundle_errors({ clear: true }) — read, then reset the error buffer
|
|
434
492
|
5. reload_app — trigger full JS bundle reload (only if needed)
|
|
435
493
|
|
|
494
|
+
## Did My Edit Land? (ask before reloading)
|
|
495
|
+
get_refresh_status answers whether the JS runtime ACCEPTED a Fast Refresh update, which is a
|
|
496
|
+
different question from whether Metro compiled (get_bundle_status). Capture a Date.now() BEFORE
|
|
497
|
+
editing, wait ~2s after saving, then get_refresh_status({since: <that timestamp>}) — updateCount
|
|
498
|
+
> 0 means the edit is in the running app. Use it instead of reloading on a hunch, and instead of
|
|
499
|
+
polling logs or screenshots. A full reload resets its ring buffer, so the next call reports
|
|
500
|
+
"recorder just installed".
|
|
501
|
+
|
|
436
502
|
## When to Reload
|
|
437
503
|
React Native has Fast Refresh by default. Only reload_app when:
|
|
438
|
-
-
|
|
504
|
+
- get_refresh_status shows no update accepted after a few seconds
|
|
439
505
|
- App is in a broken/error state
|
|
440
506
|
- Need to reset full app state (navigation, context)
|
|
441
507
|
- Made changes to native code or config files
|
|
442
508
|
|
|
509
|
+
reload_app reconnects itself — do NOT follow it with scan_metro. The fresh runtime often answers
|
|
510
|
+
no CDP probe within the tool's own wait, so the reply says reconnect is still in progress and a
|
|
511
|
+
backoff loop finishes it a few seconds later; get_apps immediately after such a reply can
|
|
512
|
+
legitimately come back empty. A reflexive scan_metro there throws away the navigation stack, auth
|
|
513
|
+
and in-memory caches for nothing.
|
|
514
|
+
|
|
443
515
|
## Red Screen Errors
|
|
444
516
|
If no errors captured via CDP, use get_bundle_errors with platform="ios" or "android" — this triggers screenshot+OCR fallback to read errors from the device screen.
|
|
445
517
|
|
|
446
518
|
## Key Tools
|
|
447
|
-
- get_bundle_status: Metro health check
|
|
519
|
+
- get_bundle_status: Metro health check (did it compile?)
|
|
520
|
+
- get_refresh_status: did the running runtime accept the Fast Refresh update?
|
|
448
521
|
- get_bundle_errors: compilation errors
|
|
449
|
-
- reload_app: full JS bundle reload
|
|
522
|
+
- reload_app: full JS bundle reload — reconnects itself, no scan_metro afterwards
|
|
450
523
|
- ensure_connection: verify connection with healthCheck=true`
|
|
451
524
|
},
|
|
452
525
|
{
|
package/build/core/guides.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guides.js","sourceRoot":"","sources":["../../src/core/guides.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,GAAY;IACpB;QACI,EAAE,EAAE,OAAO;QACX,KAAK,EAAE,eAAe;QACtB,OAAO,EAAE,yDAAyD;QAClE,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"guides.js","sourceRoot":"","sources":["../../src/core/guides.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface AdoptionRect {
|
|
2
|
+
x: number;
|
|
3
|
+
y: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
}
|
|
7
|
+
export interface AdoptionPressable {
|
|
8
|
+
overlayIdx: number | null;
|
|
9
|
+
paintIdx: number;
|
|
10
|
+
bounds: AdoptionRect;
|
|
11
|
+
}
|
|
12
|
+
export interface AdoptionOverlay {
|
|
13
|
+
hasContent: boolean;
|
|
14
|
+
enterIdx: number;
|
|
15
|
+
contentBounds: AdoptionRect;
|
|
16
|
+
}
|
|
17
|
+
export declare function adoptsByContainment(p: AdoptionPressable, o: AdoptionOverlay): boolean;
|
|
18
|
+
export declare const OVERLAY_ADOPTION_JS: string;
|
|
19
|
+
//# sourceMappingURL=overlayAdoption.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlayAdoption.d.ts","sourceRoot":"","sources":["../../../src/core/injected/overlayAdoption.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAE9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAE1B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,OAAO,CAAC;IAEpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,YAAY,CAAC;CAC/B;AAoBD,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,eAAe,GAAG,OAAO,CAUrF;AAGD,eAAO,MAAM,mBAAmB,QAAiE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function adoptsByContainment(p, o) {
|
|
2
|
+
if (p.overlayIdx != null)
|
|
3
|
+
return false;
|
|
4
|
+
if (!o.hasContent)
|
|
5
|
+
return false;
|
|
6
|
+
if (!(o.enterIdx >= 0))
|
|
7
|
+
return false;
|
|
8
|
+
if (!(p.paintIdx > o.enterIdx))
|
|
9
|
+
return false;
|
|
10
|
+
var b = p.bounds;
|
|
11
|
+
var ob = o.contentBounds;
|
|
12
|
+
return b.x >= ob.x && b.y >= ob.y &&
|
|
13
|
+
b.x + b.width <= ob.x + ob.width &&
|
|
14
|
+
b.y + b.height <= ob.y + ob.height;
|
|
15
|
+
}
|
|
16
|
+
export const OVERLAY_ADOPTION_JS = `var adoptsByContainment = ${adoptsByContainment.toString()};`;
|
|
17
|
+
//# sourceMappingURL=overlayAdoption.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlayAdoption.js","sourceRoot":"","sources":["../../../src/core/injected/overlayAdoption.ts"],"names":[],"mappings":"AA4CA,MAAM,UAAU,mBAAmB,CAAC,CAAoB,EAAE,CAAkB;IACxE,IAAI,CAAC,CAAC,UAAU,IAAI,IAAI;QAAE,OAAO,KAAK,CAAC;IACvC,IAAI,CAAC,CAAC,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC;IACjB,IAAI,EAAE,GAAG,CAAC,CAAC,aAAa,CAAC;IACzB,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7B,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK;QAChC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC;AAC3C,CAAC;AAGD,MAAM,CAAC,MAAM,mBAAmB,GAAG,6BAA6B,mBAAmB,CAAC,QAAQ,EAAE,GAAG,CAAC"}
|
|
@@ -143,6 +143,24 @@ export declare function formatScreenStateSummary(ss: ScreenState, convert?: Item
|
|
|
143
143
|
export declare function describePressHandler(raw: unknown): string | null;
|
|
144
144
|
export declare function describePropHandlers(raw: unknown): string | null;
|
|
145
145
|
export declare function parseScreenStateResponse(raw: unknown): ScreenState | null;
|
|
146
|
+
export interface PressabilityAuditCounts {
|
|
147
|
+
markerTotal?: number;
|
|
148
|
+
markerHiddenCount?: number;
|
|
149
|
+
markerUnmeasurableCount?: number;
|
|
150
|
+
markerSkipped?: Array<{
|
|
151
|
+
id: string;
|
|
152
|
+
why: string;
|
|
153
|
+
}>;
|
|
154
|
+
collectedCount?: number;
|
|
155
|
+
emittedCount?: number;
|
|
156
|
+
emptyOverlayGroups?: string[];
|
|
157
|
+
droppedByGeometry?: number;
|
|
158
|
+
droppedIds?: Array<{
|
|
159
|
+
id: string;
|
|
160
|
+
why: string;
|
|
161
|
+
}>;
|
|
162
|
+
}
|
|
163
|
+
export declare function formatPressabilityAudit(counts: PressabilityAuditCounts): string | null;
|
|
146
164
|
export declare function getScreenState(options?: {
|
|
147
165
|
device?: string;
|
|
148
166
|
}): Promise<ExecutionResult & {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenState.d.ts","sourceRoot":"","sources":["../../src/core/screenState.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"screenState.d.ts","sourceRoot":"","sources":["../../src/core/screenState.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { executeInApp, delay } from "./jsExecute.js";
|
|
|
2
2
|
import { iconSemanticHint } from "./iconSemantics.js";
|
|
3
3
|
import { VISIBILITY_HELPERS_JS, detectNativeSheet, NATIVE_SHEET_MARKER_RE_SRC } from "./injected/visibility.js";
|
|
4
4
|
import { RN_PRIMITIVES_SRC, GENERIC_COMPONENT_SRC } from "./injectedFilters.js";
|
|
5
|
+
import { OVERLAY_ADOPTION_JS } from "./injected/overlayAdoption.js";
|
|
5
6
|
import { SHEET_HELPERS_JS } from "./injected/sheetOffset.js";
|
|
6
7
|
export function markPressablesCoveredByOverlay(pressables, overlayBounds) {
|
|
7
8
|
for (const p of pressables) {
|
|
@@ -266,6 +267,35 @@ export function parseScreenStateResponse(raw) {
|
|
|
266
267
|
notes: r.notes ?? [],
|
|
267
268
|
};
|
|
268
269
|
}
|
|
270
|
+
export function formatPressabilityAudit(counts) {
|
|
271
|
+
const total = counts.markerTotal ?? 0;
|
|
272
|
+
const hidden = counts.markerHiddenCount ?? 0;
|
|
273
|
+
const unmeasurable = counts.markerUnmeasurableCount ?? 0;
|
|
274
|
+
const emptyGroups = counts.emptyOverlayGroups ?? [];
|
|
275
|
+
if (total === 0 || (hidden === 0 && unmeasurable === 0 && emptyGroups.length === 0))
|
|
276
|
+
return null;
|
|
277
|
+
const samples = (counts.markerSkipped ?? []).slice(0, 6).map((s) => `${s.id} (${s.why})`);
|
|
278
|
+
const detail = samples.length > 0 ? ` Skipped: ${samples.join("; ")}.` : "";
|
|
279
|
+
const droppedGeom = counts.droppedByGeometry ?? 0;
|
|
280
|
+
const droppedDetail = (counts.droppedIds ?? []).slice(0, 6).map((d) => `${d.id} (${d.why})`).join("; ");
|
|
281
|
+
if (emptyGroups.length > 0 && droppedGeom > 0) {
|
|
282
|
+
return (`${emptyGroups.join(", ")} is reported below with no pressables, and ${droppedGeom} press target(s) were dropped before grouping. ` +
|
|
283
|
+
`The two are not necessarily the same elements — an overlay listed with nothing in it is a grouping fault in this tool either way, ` +
|
|
284
|
+
`not evidence that the sheet is empty. Dropped: ${droppedDetail}. Screenshot the screen and tap by coordinates if you can see a control inside it.`);
|
|
285
|
+
}
|
|
286
|
+
if (emptyGroups.length > 0) {
|
|
287
|
+
return (`${emptyGroups.join(", ")} is reported below with no pressables, but React Native has ${total} press target(s) mounted on this screen. ` +
|
|
288
|
+
`An overlay detected confidently enough to be listed, holding nothing, is far more likely to be a grouping fault in this tool than a genuinely ` +
|
|
289
|
+
`empty sheet — screenshot the screen, and tap by coordinates if you can see a control inside it.`);
|
|
290
|
+
}
|
|
291
|
+
if (unmeasurable > 0) {
|
|
292
|
+
return (`${unmeasurable} of ${total} press target(s) React Native reports on this screen have no measurable host view and are NOT listed ` +
|
|
293
|
+
`below${hidden > 0 ? `, and ${hidden} more were pruned as hidden` : ""}. An unmeasurable press target is usually one that is mounted but ` +
|
|
294
|
+
`not laid out yet (sheet content mid-animation), so it may appear on a second call — re-read before concluding it is absent.${detail}`);
|
|
295
|
+
}
|
|
296
|
+
return (`${hidden} of ${total} press target(s) React Native reports were pruned as hidden and are not listed below. This is normally correct — ` +
|
|
297
|
+
`inactive navigator routes stay mounted — but if something you expected is missing, this names the rule that dropped it.${detail}`);
|
|
298
|
+
}
|
|
269
299
|
export async function getScreenState(options = {}) {
|
|
270
300
|
const { device } = options;
|
|
271
301
|
const dispatchExpression = `
|
|
@@ -844,6 +874,11 @@ export async function getScreenState(options = {}) {
|
|
|
844
874
|
|
|
845
875
|
var hostFibers = [];
|
|
846
876
|
var fiberMeta = [];
|
|
877
|
+
// Audit accumulators — see the PressabilityDebugView branch in the walk below.
|
|
878
|
+
var markerTotal = 0;
|
|
879
|
+
var markerHiddenCount = 0;
|
|
880
|
+
var markerUnmeasurableCount = 0;
|
|
881
|
+
var markerSkipped = [];
|
|
847
882
|
// overlayFiberMeta index -> fiberMeta index at which that overlay's subtree was entered.
|
|
848
883
|
var overlayEnterIdx = [];
|
|
849
884
|
// A count badge ("1", "99+") is the only text on icon buttons like a cart — treat it
|
|
@@ -1044,11 +1079,16 @@ export async function getScreenState(options = {}) {
|
|
|
1044
1079
|
return isScreenHidden(getComponentName(hostFiber), hostFiber.memoizedProps, hostFiber);
|
|
1045
1080
|
}
|
|
1046
1081
|
|
|
1047
|
-
function walkPressabilityDebugViews(fiber, depth, hidden, ovIdx) {
|
|
1082
|
+
function walkPressabilityDebugViews(fiber, depth, hidden, ovIdx, hiddenBy) {
|
|
1048
1083
|
if (!fiber || depth > 5000) return;
|
|
1049
1084
|
var name = getComponentName(fiber);
|
|
1050
1085
|
var props = fiber.memoizedProps;
|
|
1051
|
-
var
|
|
1086
|
+
var becameHidden = isScreenHidden(name, props, fiber);
|
|
1087
|
+
var nextHidden = hidden || becameHidden;
|
|
1088
|
+
// Which rule pruned this subtree. Every marker dropped below here is
|
|
1089
|
+
// attributed to it, so an unexpected pruner is legible in the audit instead
|
|
1090
|
+
// of presenting as a silently shorter list.
|
|
1091
|
+
var nextHiddenBy = hiddenBy || (becameHidden ? (name || 'unnamed') : null);
|
|
1052
1092
|
|
|
1053
1093
|
// Track which overlay subtree we're inside — membership by ancestry, not
|
|
1054
1094
|
// geometry. A bottom sheet's subtree includes a full-screen backdrop, so
|
|
@@ -1069,6 +1109,33 @@ export async function getScreenState(options = {}) {
|
|
|
1069
1109
|
}
|
|
1070
1110
|
}
|
|
1071
1111
|
|
|
1112
|
+
// Audit: RN renders a PressabilityDebugView inside every press target it owns
|
|
1113
|
+
// (Pressable, every Touchable*, and gesture-handler's Pressable), whether or not
|
|
1114
|
+
// "Show Touchables" is on — the component returns null when disabled but the
|
|
1115
|
+
// fiber is still mounted. So the marker count is RN's own answer to "how many
|
|
1116
|
+
// press targets exist", and anything this walk reports is a subset of it.
|
|
1117
|
+
// Counting the drops, with the rule that caused each one, turns a silently short
|
|
1118
|
+
// list into a statement about what was skipped and why.
|
|
1119
|
+
if (name === 'PressabilityDebugView') {
|
|
1120
|
+
markerTotal++;
|
|
1121
|
+
var auditHost = fiber.return;
|
|
1122
|
+
var auditWhy = null;
|
|
1123
|
+
if (nextHidden) auditWhy = 'pruned as hidden by ' + nextHiddenBy;
|
|
1124
|
+
else if (!auditHost) auditWhy = 'marker had no host view';
|
|
1125
|
+
else if (!getMeasurable(auditHost)) auditWhy = 'host view is not measurable';
|
|
1126
|
+
if (auditWhy) {
|
|
1127
|
+
if (auditWhy.indexOf('pruned as hidden') === 0) markerHiddenCount++;
|
|
1128
|
+
else markerUnmeasurableCount++;
|
|
1129
|
+
if (markerSkipped.length < 12) {
|
|
1130
|
+
var ap = (auditHost && auditHost.memoizedProps) || {};
|
|
1131
|
+
markerSkipped.push({
|
|
1132
|
+
id: ap.testID || ap.nativeID || ap.accessibilityLabel || getComponentName(auditHost) || 'unnamed',
|
|
1133
|
+
why: auditWhy
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1072
1139
|
if (!nextHidden && name === 'PressabilityDebugView') {
|
|
1073
1140
|
var hostFiber = fiber.return;
|
|
1074
1141
|
if (hostFiber && getMeasurable(hostFiber)) {
|
|
@@ -1188,11 +1255,11 @@ export async function getScreenState(options = {}) {
|
|
|
1188
1255
|
|
|
1189
1256
|
var child = fiber.child;
|
|
1190
1257
|
while (child) {
|
|
1191
|
-
walkPressabilityDebugViews(child, depth + 1, nextHidden, ovIdx);
|
|
1258
|
+
walkPressabilityDebugViews(child, depth + 1, nextHidden, ovIdx, nextHiddenBy);
|
|
1192
1259
|
child = child.sibling;
|
|
1193
1260
|
}
|
|
1194
1261
|
}
|
|
1195
|
-
walkPressabilityDebugViews(roots[0].current, 0, false, null);
|
|
1262
|
+
walkPressabilityDebugViews(roots[0].current, 0, false, null, null);
|
|
1196
1263
|
|
|
1197
1264
|
// ------------------------------------------------------------------
|
|
1198
1265
|
// 3b. Standalone texts — proximity labels for icon-only pressables
|
|
@@ -1484,6 +1551,16 @@ export async function getScreenState(options = {}) {
|
|
|
1484
1551
|
} catch(e) {}
|
|
1485
1552
|
}
|
|
1486
1553
|
|
|
1554
|
+
// Handed to the resolve pass the same way the measurements are: this script and the
|
|
1555
|
+
// one that builds the response are separate evaluations with no shared scope.
|
|
1556
|
+
globalThis.__screenStateAudit = {
|
|
1557
|
+
markerTotal: markerTotal,
|
|
1558
|
+
markerHiddenCount: markerHiddenCount,
|
|
1559
|
+
markerUnmeasurableCount: markerUnmeasurableCount,
|
|
1560
|
+
markerSkipped: markerSkipped,
|
|
1561
|
+
collectedCount: hostFibers.length
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1487
1564
|
return { count: hostFibers.length + textFibers.length + imageFibers.length, overlayCount: overlayFiberMeta.length };
|
|
1488
1565
|
})()
|
|
1489
1566
|
`;
|
|
@@ -1502,6 +1579,7 @@ export async function getScreenState(options = {}) {
|
|
|
1502
1579
|
const measureBudgetMs = Math.min(3000, 400 + dispatchedNodes * 2);
|
|
1503
1580
|
await delay(measureBudgetMs);
|
|
1504
1581
|
const resolveExpression = `
|
|
1582
|
+
${OVERLAY_ADOPTION_JS}
|
|
1505
1583
|
(function() {
|
|
1506
1584
|
${SHEET_HELPERS_JS}
|
|
1507
1585
|
var hostFibers = globalThis.__screenStateFibers;
|
|
@@ -1693,6 +1771,8 @@ export async function getScreenState(options = {}) {
|
|
|
1693
1771
|
}
|
|
1694
1772
|
|
|
1695
1773
|
// Build pressable list
|
|
1774
|
+
var droppedByGeometry = 0;
|
|
1775
|
+
var droppedIds = [];
|
|
1696
1776
|
var allPressables = [];
|
|
1697
1777
|
for (var i = 0; i < meta.length; i++) {
|
|
1698
1778
|
if (i === rootIdx) continue;
|
|
@@ -1700,7 +1780,26 @@ export async function getScreenState(options = {}) {
|
|
|
1700
1780
|
if (!m0) { unmeasuredCount++; continue; }
|
|
1701
1781
|
var tr = withTransform(shiftRect(m0, shiftFor(sheetIdx, i)), meta[i].transform);
|
|
1702
1782
|
var m = tr.m;
|
|
1703
|
-
if (!keepElement(m, tr.unreliable))
|
|
1783
|
+
if (!keepElement(m, tr.unreliable)) {
|
|
1784
|
+
// Dropped for geometry: zero-sized, or measured outside the viewport. Both are
|
|
1785
|
+
// usually right, and both were silent — which is why a sheet whose content
|
|
1786
|
+
// measures at zero size looked exactly like a sheet with no buttons in it.
|
|
1787
|
+
droppedByGeometry++;
|
|
1788
|
+
// Deduped by identity: a parked drawer contributes dozens of identical rows
|
|
1789
|
+
// and would otherwise fill the sample, hiding the one element being hunted.
|
|
1790
|
+
var dropId = meta[i].testID || meta[i].label || meta[i].component || 'unnamed';
|
|
1791
|
+
var seenDrop = false;
|
|
1792
|
+
for (var dq = 0; dq < droppedIds.length; dq++) if (droppedIds[dq].id === dropId) { seenDrop = true; break; }
|
|
1793
|
+
if (!seenDrop && droppedIds.length < 12) {
|
|
1794
|
+
droppedIds.push({
|
|
1795
|
+
id: dropId,
|
|
1796
|
+
why: (m.width <= 0 || m.height <= 0)
|
|
1797
|
+
? 'measured ' + Math.round(m.width) + 'x' + Math.round(m.height)
|
|
1798
|
+
: 'measured off-viewport at ' + Math.round(m.x) + ',' + Math.round(m.y)
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
continue;
|
|
1802
|
+
}
|
|
1704
1803
|
allPressables.push({
|
|
1705
1804
|
transformNote: tr.note,
|
|
1706
1805
|
// Position in the DFS collection order == paint order. Kept so occlusion can
|
|
@@ -1810,8 +1909,15 @@ export async function getScreenState(options = {}) {
|
|
|
1810
1909
|
var p = allPressables[pi];
|
|
1811
1910
|
var assignedToOverlay = false;
|
|
1812
1911
|
for (var ov = 0; ov < overlays.length; ov++) {
|
|
1813
|
-
|
|
1814
|
-
|
|
1912
|
+
// Containment is a fallback for content the overlay portals out of its own
|
|
1913
|
+
// subtree, so it must only ever adopt something painted ON TOP of the overlay.
|
|
1914
|
+
// Without the paint-order test it also adopts the screen behind: an RN <Modal>
|
|
1915
|
+
// measures a content rect large enough to contain the buttons underneath it,
|
|
1916
|
+
// and those were reported as the modal's own contents — i.e. as reachable,
|
|
1917
|
+
// when they are exactly what the modal blocks (verified on the test app,
|
|
1918
|
+
// 2026-09-04). The occludes() helper already encodes the same ordering for the
|
|
1919
|
+
// inverse question; this is that rule applied to adoption.
|
|
1920
|
+
if (p.overlayIdx === overlays[ov].origIdx || adoptsByContainment(p, overlays[ov])) {
|
|
1815
1921
|
overlays[ov].pressables.push(p);
|
|
1816
1922
|
assignedToOverlay = true;
|
|
1817
1923
|
break;
|
|
@@ -1898,6 +2004,23 @@ export async function getScreenState(options = {}) {
|
|
|
1898
2004
|
overlays[so].images.sort(byPosition);
|
|
1899
2005
|
}
|
|
1900
2006
|
|
|
2007
|
+
var audit = globalThis.__screenStateAudit || {};
|
|
2008
|
+
// A press target can survive the walk and still never reach the caller: overlay
|
|
2009
|
+
// membership, coverage and measurement each drop elements in this pass. Counting
|
|
2010
|
+
// what is actually emitted is the only way that loss is visible at all — the
|
|
2011
|
+
// Gorhom sheet case (portaled outside the navigator, collected, then emitted
|
|
2012
|
+
// nowhere) produced an empty sheet group and no other trace (2026-09-04).
|
|
2013
|
+
var emittedPressables = rootPressables.length;
|
|
2014
|
+
var emptyOverlayGroups = [];
|
|
2015
|
+
for (var ec = 0; ec < overlays.length; ec++) {
|
|
2016
|
+
var opl = (overlays[ec].pressables || []).length;
|
|
2017
|
+
emittedPressables += opl;
|
|
2018
|
+
// An overlay confident enough to be reported, holding nothing, is the shape of a
|
|
2019
|
+
// real defect rather than of routine filtering: the sheet is on screen with a
|
|
2020
|
+
// visible button and the caller is told it is empty.
|
|
2021
|
+
if (opl === 0) emptyOverlayGroups.push(overlays[ec].type || 'overlay');
|
|
2022
|
+
}
|
|
2023
|
+
|
|
1901
2024
|
// Strip bounds from overlay objects (not in public interface)
|
|
1902
2025
|
var cleanOverlays = overlays.map(function(o) {
|
|
1903
2026
|
return { type: o.type, title: o.title, pressables: o.pressables, texts: o.texts, images: o.images };
|
|
@@ -1920,6 +2043,11 @@ export async function getScreenState(options = {}) {
|
|
|
1920
2043
|
return { route: route, overlays: cleanOverlays, pressables: rootPressables, texts: rootTexts, images: rootImages, nativeMarkers: nativeMarkers,
|
|
1921
2044
|
logBoxSkipped: logBoxSkipped,
|
|
1922
2045
|
unmeasuredCount: unmeasuredCount, transformedCount: transformedCount, sheetShift: Math.round(sheetShiftApplied),
|
|
2046
|
+
markerTotal: audit.markerTotal, markerHiddenCount: audit.markerHiddenCount,
|
|
2047
|
+
markerUnmeasurableCount: audit.markerUnmeasurableCount, markerSkipped: audit.markerSkipped,
|
|
2048
|
+
collectedCount: audit.collectedCount, emittedCount: emittedPressables,
|
|
2049
|
+
emptyOverlayGroups: emptyOverlayGroups,
|
|
2050
|
+
droppedByGeometry: droppedByGeometry, droppedIds: droppedIds,
|
|
1923
2051
|
dispatchedCount: meta.length + textContents.length + imageMeta.length };
|
|
1924
2052
|
})()
|
|
1925
2053
|
`;
|
|
@@ -1945,6 +2073,9 @@ export async function getScreenState(options = {}) {
|
|
|
1945
2073
|
completenessNotes.push(`${counts.unmeasuredCount} element(s) did not return a measurement within ${measureBudgetMs}ms and are NOT listed below. ` +
|
|
1946
2074
|
`This is a timing miss, not an empty screen — call get_screen_state again, or take a screenshot, before concluding anything is absent.`);
|
|
1947
2075
|
}
|
|
2076
|
+
const markerNote = formatPressabilityAudit(counts);
|
|
2077
|
+
if (markerNote)
|
|
2078
|
+
completenessNotes.push(markerNote);
|
|
1948
2079
|
if ((counts.transformedCount ?? 0) > 0) {
|
|
1949
2080
|
completenessNotes.push(`${counts.transformedCount} element(s) are marked ⚠transformed. Their frames come from the layout tree, which does not include ` +
|
|
1950
2081
|
`native-driven transforms (sticky headers, collapsing toolbars, animating sheets), so the coordinates may not be where the element ` +
|