execbro 0.0.1-reserve → 1.10.13
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/LICENSE +32 -0
- package/README.md +496 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts +56 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -0
- package/build/__tests__/helpers/fake-cdp-server.js +120 -0
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts +36 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.js +130 -0
- package/build/__tests__/helpers/tap-test-helpers.js.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts +2 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.js +118 -0
- package/build/__tests__/integration/auto-reconnect.test.js.map +1 -0
- package/build/__tests__/integration/connection-health.test.d.ts +2 -0
- package/build/__tests__/integration/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/integration/connection-health.test.js +157 -0
- package/build/__tests__/integration/connection-health.test.js.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts +2 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.js +124 -0
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts +2 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.js +103 -0
- package/build/__tests__/integration/per-call-timeout.test.js.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts +2 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.js +330 -0
- package/build/__tests__/integration/tap-strategies.test.js.map +1 -0
- package/build/__tests__/integration/tools.test.d.ts +2 -0
- package/build/__tests__/integration/tools.test.d.ts.map +1 -0
- package/build/__tests__/integration/tools.test.js +227 -0
- package/build/__tests__/integration/tools.test.js.map +1 -0
- package/build/__tests__/setup.d.ts +2 -0
- package/build/__tests__/setup.d.ts.map +1 -0
- package/build/__tests__/setup.js +15 -0
- package/build/__tests__/setup.js.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts +2 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js +98 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -0
- package/build/__tests__/unit/buildToken.test.d.ts +2 -0
- package/build/__tests__/unit/buildToken.test.d.ts.map +1 -0
- package/build/__tests__/unit/buildToken.test.js +22 -0
- package/build/__tests__/unit/buildToken.test.js.map +1 -0
- package/build/__tests__/unit/bundle.test.d.ts +2 -0
- package/build/__tests__/unit/bundle.test.d.ts.map +1 -0
- package/build/__tests__/unit/bundle.test.js +53 -0
- package/build/__tests__/unit/bundle.test.js.map +1 -0
- package/build/__tests__/unit/burst.test.d.ts +2 -0
- package/build/__tests__/unit/burst.test.d.ts.map +1 -0
- package/build/__tests__/unit/burst.test.js +54 -0
- package/build/__tests__/unit/burst.test.js.map +1 -0
- package/build/__tests__/unit/connection-health.test.d.ts +2 -0
- package/build/__tests__/unit/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/unit/connection-health.test.js +38 -0
- package/build/__tests__/unit/connection-health.test.js.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts +2 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.js +14 -0
- package/build/__tests__/unit/debugGlobals.test.js.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts +2 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.js +119 -0
- package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts +2 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.js +261 -0
- package/build/__tests__/unit/deviceResolver.test.js.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts +2 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.js +76 -0
- package/build/__tests__/unit/escapeNonAscii.test.js.map +1 -0
- package/build/__tests__/unit/exec.test.d.ts +2 -0
- package/build/__tests__/unit/exec.test.d.ts.map +1 -0
- package/build/__tests__/unit/exec.test.js +54 -0
- package/build/__tests__/unit/exec.test.js.map +1 -0
- package/build/__tests__/unit/executor.test.d.ts +2 -0
- package/build/__tests__/unit/executor.test.d.ts.map +1 -0
- package/build/__tests__/unit/executor.test.js +220 -0
- package/build/__tests__/unit/executor.test.js.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts +2 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.js +207 -0
- package/build/__tests__/unit/failureArtifact.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js +68 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.js +112 -0
- package/build/__tests__/unit/fastRefreshTools.test.js.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts +2 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.js +114 -0
- package/build/__tests__/unit/fiberMatch.test.js.map +1 -0
- package/build/__tests__/unit/fingerprint.test.d.ts +2 -0
- package/build/__tests__/unit/fingerprint.test.d.ts.map +1 -0
- package/build/__tests__/unit/fingerprint.test.js +99 -0
- package/build/__tests__/unit/fingerprint.test.js.map +1 -0
- package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInput.test.js +58 -0
- package/build/__tests__/unit/focusedInput.test.js.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.js +79 -0
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
- package/build/__tests__/unit/iconSemantics.test.d.ts +2 -0
- package/build/__tests__/unit/iconSemantics.test.d.ts.map +1 -0
- package/build/__tests__/unit/iconSemantics.test.js +80 -0
- package/build/__tests__/unit/iconSemantics.test.js.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts +2 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.js +128 -0
- package/build/__tests__/unit/imageBuffer.test.js.map +1 -0
- package/build/__tests__/unit/logs.test.d.ts +2 -0
- package/build/__tests__/unit/logs.test.d.ts.map +1 -0
- package/build/__tests__/unit/logs.test.js +90 -0
- package/build/__tests__/unit/logs.test.js.map +1 -0
- package/build/__tests__/unit/measureComponent.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponent.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponent.test.js +66 -0
- package/build/__tests__/unit/measureComponent.test.js.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.js +94 -0
- package/build/__tests__/unit/measureComponentTools.test.js.map +1 -0
- package/build/__tests__/unit/metro.test.d.ts +2 -0
- package/build/__tests__/unit/metro.test.d.ts.map +1 -0
- package/build/__tests__/unit/metro.test.js +61 -0
- package/build/__tests__/unit/metro.test.js.map +1 -0
- package/build/__tests__/unit/network.test.d.ts +2 -0
- package/build/__tests__/unit/network.test.d.ts.map +1 -0
- package/build/__tests__/unit/network.test.js +102 -0
- package/build/__tests__/unit/network.test.js.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts +2 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.js +104 -0
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -0
- package/build/__tests__/unit/probe.test.d.ts +2 -0
- package/build/__tests__/unit/probe.test.d.ts.map +1 -0
- package/build/__tests__/unit/probe.test.js +62 -0
- package/build/__tests__/unit/probe.test.js.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts +2 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js +29 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -0
- package/build/__tests__/unit/screenState.test.d.ts +2 -0
- package/build/__tests__/unit/screenState.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenState.test.js +384 -0
- package/build/__tests__/unit/screenState.test.js.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts +2 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.js +97 -0
- package/build/__tests__/unit/screenshot-diff.test.js.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts +2 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.js +89 -0
- package/build/__tests__/unit/tap-evidence.test.js.map +1 -0
- package/build/__tests__/unit/tap.test.d.ts +2 -0
- package/build/__tests__/unit/tap.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap.test.js +767 -0
- package/build/__tests__/unit/tap.test.js.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts +2 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.js +17 -0
- package/build/__tests__/unit/timeoutClamp.test.js.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts +2 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.js +145 -0
- package/build/__tests__/unit/toolDescriptions.test.js.map +1 -0
- package/build/__tests__/unit/transportClassify.test.d.ts +2 -0
- package/build/__tests__/unit/transportClassify.test.d.ts.map +1 -0
- package/build/__tests__/unit/transportClassify.test.js +60 -0
- package/build/__tests__/unit/transportClassify.test.js.map +1 -0
- package/build/__tests__/unit/verifyAction.test.d.ts +2 -0
- package/build/__tests__/unit/verifyAction.test.d.ts.map +1 -0
- package/build/__tests__/unit/verifyAction.test.js +26 -0
- package/build/__tests__/unit/verifyAction.test.js.map +1 -0
- package/build/core/android.d.ts +299 -0
- package/build/core/android.d.ts.map +1 -0
- package/build/core/android.js +1582 -0
- package/build/core/android.js.map +1 -0
- package/build/core/appDetection.d.ts +20 -0
- package/build/core/appDetection.d.ts.map +1 -0
- package/build/core/appDetection.js +167 -0
- package/build/core/appDetection.js.map +1 -0
- package/build/core/bundle.d.ts +49 -0
- package/build/core/bundle.d.ts.map +1 -0
- package/build/core/bundle.js +356 -0
- package/build/core/bundle.js.map +1 -0
- package/build/core/componentSearch.d.ts +41 -0
- package/build/core/componentSearch.d.ts.map +1 -0
- package/build/core/componentSearch.js +588 -0
- package/build/core/componentSearch.js.map +1 -0
- package/build/core/componentTree.d.ts +13 -0
- package/build/core/componentTree.d.ts.map +1 -0
- package/build/core/componentTree.js +275 -0
- package/build/core/componentTree.js.map +1 -0
- package/build/core/config.d.ts +9 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +28 -0
- package/build/core/config.js.map +1 -0
- package/build/core/connection.d.ts +102 -0
- package/build/core/connection.d.ts.map +1 -0
- package/build/core/connection.js +1710 -0
- package/build/core/connection.js.map +1 -0
- package/build/core/connectionState.d.ts +122 -0
- package/build/core/connectionState.d.ts.map +1 -0
- package/build/core/connectionState.js +313 -0
- package/build/core/connectionState.js.map +1 -0
- package/build/core/debugGlobals.d.ts +6 -0
- package/build/core/debugGlobals.d.ts.map +1 -0
- package/build/core/debugGlobals.js +370 -0
- package/build/core/debugGlobals.js.map +1 -0
- package/build/core/deviceDiscovery.d.ts +52 -0
- package/build/core/deviceDiscovery.d.ts.map +1 -0
- package/build/core/deviceDiscovery.js +74 -0
- package/build/core/deviceDiscovery.js.map +1 -0
- package/build/core/deviceResolver.d.ts +44 -0
- package/build/core/deviceResolver.d.ts.map +1 -0
- package/build/core/deviceResolver.js +225 -0
- package/build/core/deviceResolver.js.map +1 -0
- package/build/core/errorScreenParser.d.ts +30 -0
- package/build/core/errorScreenParser.d.ts.map +1 -0
- package/build/core/errorScreenParser.js +198 -0
- package/build/core/errorScreenParser.js.map +1 -0
- package/build/core/errors.d.ts +14 -0
- package/build/core/errors.d.ts.map +1 -0
- package/build/core/errors.js +17 -0
- package/build/core/errors.js.map +1 -0
- package/build/core/escapeNonAscii.d.ts +18 -0
- package/build/core/escapeNonAscii.d.ts.map +1 -0
- package/build/core/escapeNonAscii.js +352 -0
- package/build/core/escapeNonAscii.js.map +1 -0
- package/build/core/exec.d.ts +22 -0
- package/build/core/exec.d.ts.map +1 -0
- package/build/core/exec.js +54 -0
- package/build/core/exec.js.map +1 -0
- package/build/core/executor.d.ts +9 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +12 -0
- package/build/core/executor.js.map +1 -0
- package/build/core/failureArtifact.d.ts +120 -0
- package/build/core/failureArtifact.d.ts.map +1 -0
- package/build/core/failureArtifact.js +113 -0
- package/build/core/failureArtifact.js.map +1 -0
- package/build/core/fastRefreshRecorder.d.ts +23 -0
- package/build/core/fastRefreshRecorder.d.ts.map +1 -0
- package/build/core/fastRefreshRecorder.js +83 -0
- package/build/core/fastRefreshRecorder.js.map +1 -0
- package/build/core/fastRefreshTools.d.ts +19 -0
- package/build/core/fastRefreshTools.d.ts.map +1 -0
- package/build/core/fastRefreshTools.js +71 -0
- package/build/core/fastRefreshTools.js.map +1 -0
- package/build/core/feedback.d.ts +18 -0
- package/build/core/feedback.d.ts.map +1 -0
- package/build/core/feedback.js +73 -0
- package/build/core/feedback.js.map +1 -0
- package/build/core/fiberMatch.d.ts +25 -0
- package/build/core/fiberMatch.d.ts.map +1 -0
- package/build/core/fiberMatch.js +35 -0
- package/build/core/fiberMatch.js.map +1 -0
- package/build/core/fingerprint.d.ts +4 -0
- package/build/core/fingerprint.d.ts.map +1 -0
- package/build/core/fingerprint.js +47 -0
- package/build/core/fingerprint.js.map +1 -0
- package/build/core/focusedInput.d.ts +27 -0
- package/build/core/focusedInput.d.ts.map +1 -0
- package/build/core/focusedInput.js +110 -0
- package/build/core/focusedInput.js.map +1 -0
- package/build/core/focusedInputTools.d.ts +21 -0
- package/build/core/focusedInputTools.d.ts.map +1 -0
- package/build/core/focusedInputTools.js +51 -0
- package/build/core/focusedInputTools.js.map +1 -0
- package/build/core/format.d.ts +8 -0
- package/build/core/format.d.ts.map +1 -0
- package/build/core/format.js +34 -0
- package/build/core/format.js.map +1 -0
- package/build/core/guides.d.ts +21 -0
- package/build/core/guides.d.ts.map +1 -0
- package/build/core/guides.js +397 -0
- package/build/core/guides.js.map +1 -0
- package/build/core/iconSemantics.d.ts +18 -0
- package/build/core/iconSemantics.d.ts.map +1 -0
- package/build/core/iconSemantics.js +114 -0
- package/build/core/iconSemantics.js.map +1 -0
- package/build/core/imageBuffer.d.ts +37 -0
- package/build/core/imageBuffer.d.ts.map +1 -0
- package/build/core/imageBuffer.js +58 -0
- package/build/core/imageBuffer.js.map +1 -0
- package/build/core/index.d.ts +37 -0
- package/build/core/index.d.ts.map +1 -0
- package/build/core/index.js +66 -0
- package/build/core/index.js.map +1 -0
- package/build/core/inspector.d.ts +55 -0
- package/build/core/inspector.d.ts.map +1 -0
- package/build/core/inspector.js +603 -0
- package/build/core/inspector.js.map +1 -0
- package/build/core/ios.d.ts +263 -0
- package/build/core/ios.d.ts.map +1 -0
- package/build/core/ios.js +1705 -0
- package/build/core/ios.js.map +1 -0
- package/build/core/jsExecute.d.ts +62 -0
- package/build/core/jsExecute.d.ts.map +1 -0
- package/build/core/jsExecute.js +696 -0
- package/build/core/jsExecute.js.map +1 -0
- package/build/core/license.d.ts +48 -0
- package/build/core/license.d.ts.map +1 -0
- package/build/core/license.js +267 -0
- package/build/core/license.js.map +1 -0
- package/build/core/logbox.d.ts +58 -0
- package/build/core/logbox.d.ts.map +1 -0
- package/build/core/logbox.js +330 -0
- package/build/core/logbox.js.map +1 -0
- package/build/core/logs.d.ts +44 -0
- package/build/core/logs.d.ts.map +1 -0
- package/build/core/logs.js +149 -0
- package/build/core/logs.js.map +1 -0
- package/build/core/measureComponent.d.ts +19 -0
- package/build/core/measureComponent.d.ts.map +1 -0
- package/build/core/measureComponent.js +94 -0
- package/build/core/measureComponent.js.map +1 -0
- package/build/core/measureComponentTools.d.ts +6 -0
- package/build/core/measureComponentTools.d.ts.map +1 -0
- package/build/core/measureComponentTools.js +43 -0
- package/build/core/measureComponentTools.js.map +1 -0
- package/build/core/metro.d.ts +31 -0
- package/build/core/metro.d.ts.map +1 -0
- package/build/core/metro.js +124 -0
- package/build/core/metro.js.map +1 -0
- package/build/core/nativeOnlyHints.d.ts +14 -0
- package/build/core/nativeOnlyHints.d.ts.map +1 -0
- package/build/core/nativeOnlyHints.js +58 -0
- package/build/core/nativeOnlyHints.js.map +1 -0
- package/build/core/network.d.ts +43 -0
- package/build/core/network.d.ts.map +1 -0
- package/build/core/network.js +217 -0
- package/build/core/network.js.map +1 -0
- package/build/core/networkInterceptor.d.ts +30 -0
- package/build/core/networkInterceptor.d.ts.map +1 -0
- package/build/core/networkInterceptor.js +211 -0
- package/build/core/networkInterceptor.js.map +1 -0
- package/build/core/ocr.d.ts +87 -0
- package/build/core/ocr.d.ts.map +1 -0
- package/build/core/ocr.js +335 -0
- package/build/core/ocr.js.map +1 -0
- package/build/core/paths.d.ts +2 -0
- package/build/core/paths.d.ts.map +1 -0
- package/build/core/paths.js +28 -0
- package/build/core/paths.js.map +1 -0
- package/build/core/posthog.d.ts +5 -0
- package/build/core/posthog.d.ts.map +1 -0
- package/build/core/posthog.js +52 -0
- package/build/core/posthog.js.map +1 -0
- package/build/core/pressables.d.ts +46 -0
- package/build/core/pressables.d.ts.map +1 -0
- package/build/core/pressables.js +1719 -0
- package/build/core/pressables.js.map +1 -0
- package/build/core/probe.d.ts +12 -0
- package/build/core/probe.d.ts.map +1 -0
- package/build/core/probe.js +56 -0
- package/build/core/probe.js.map +1 -0
- package/build/core/redux.d.ts +22 -0
- package/build/core/redux.d.ts.map +1 -0
- package/build/core/redux.js +116 -0
- package/build/core/redux.js.map +1 -0
- package/build/core/register.d.ts +8 -0
- package/build/core/register.d.ts.map +1 -0
- package/build/core/register.js +202 -0
- package/build/core/register.js.map +1 -0
- package/build/core/rnGlobalsBootstrap.d.ts +16 -0
- package/build/core/rnGlobalsBootstrap.d.ts.map +1 -0
- package/build/core/rnGlobalsBootstrap.js +116 -0
- package/build/core/rnGlobalsBootstrap.js.map +1 -0
- package/build/core/screenLayout.d.ts +53 -0
- package/build/core/screenLayout.d.ts.map +1 -0
- package/build/core/screenLayout.js +878 -0
- package/build/core/screenLayout.js.map +1 -0
- package/build/core/screenState.d.ts +166 -0
- package/build/core/screenState.d.ts.map +1 -0
- package/build/core/screenState.js +1352 -0
- package/build/core/screenState.js.map +1 -0
- package/build/core/sdkBridge.d.ts +67 -0
- package/build/core/sdkBridge.d.ts.map +1 -0
- package/build/core/sdkBridge.js +148 -0
- package/build/core/sdkBridge.js.map +1 -0
- package/build/core/state.d.ts +37 -0
- package/build/core/state.d.ts.map +1 -0
- package/build/core/state.js +124 -0
- package/build/core/state.js.map +1 -0
- package/build/core/telemetry.d.ts +50 -0
- package/build/core/telemetry.d.ts.map +1 -0
- package/build/core/telemetry.js +653 -0
- package/build/core/telemetry.js.map +1 -0
- package/build/core/toolHelpers.d.ts +24 -0
- package/build/core/toolHelpers.d.ts.map +1 -0
- package/build/core/toolHelpers.js +118 -0
- package/build/core/toolHelpers.js.map +1 -0
- package/build/core/types.d.ts +162 -0
- package/build/core/types.d.ts.map +1 -0
- package/build/core/types.js +2 -0
- package/build/core/types.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +132 -0
- package/build/index.js.map +1 -0
- package/build/pro/screenshot-diff.d.ts +10 -0
- package/build/pro/screenshot-diff.d.ts.map +1 -0
- package/build/pro/screenshot-diff.js +38 -0
- package/build/pro/screenshot-diff.js.map +1 -0
- package/build/pro/tap.d.ts +291 -0
- package/build/pro/tap.d.ts.map +1 -0
- package/build/pro/tap.js +1839 -0
- package/build/pro/tap.js.map +1 -0
- package/build/pro/verifyAction.d.ts +50 -0
- package/build/pro/verifyAction.d.ts.map +1 -0
- package/build/pro/verifyAction.js +285 -0
- package/build/pro/verifyAction.js.map +1 -0
- package/build/tools/_deviceArg.d.ts +45 -0
- package/build/tools/_deviceArg.d.ts.map +1 -0
- package/build/tools/_deviceArg.js +58 -0
- package/build/tools/_deviceArg.js.map +1 -0
- package/build/tools/accountTools.d.ts +46 -0
- package/build/tools/accountTools.d.ts.map +1 -0
- package/build/tools/accountTools.js +223 -0
- package/build/tools/accountTools.js.map +1 -0
- package/build/tools/bundleTools.d.ts +3 -0
- package/build/tools/bundleTools.d.ts.map +1 -0
- package/build/tools/bundleTools.js +375 -0
- package/build/tools/bundleTools.js.map +1 -0
- package/build/tools/componentTools.d.ts +3 -0
- package/build/tools/componentTools.d.ts.map +1 -0
- package/build/tools/componentTools.js +746 -0
- package/build/tools/componentTools.js.map +1 -0
- package/build/tools/connectionTools.d.ts +3 -0
- package/build/tools/connectionTools.d.ts.map +1 -0
- package/build/tools/connectionTools.js +625 -0
- package/build/tools/connectionTools.js.map +1 -0
- package/build/tools/deviceTools.d.ts +3 -0
- package/build/tools/deviceTools.d.ts.map +1 -0
- package/build/tools/deviceTools.js +423 -0
- package/build/tools/deviceTools.js.map +1 -0
- package/build/tools/executionTools.d.ts +3 -0
- package/build/tools/executionTools.d.ts.map +1 -0
- package/build/tools/executionTools.js +243 -0
- package/build/tools/executionTools.js.map +1 -0
- package/build/tools/interactionTools.d.ts +3 -0
- package/build/tools/interactionTools.d.ts.map +1 -0
- package/build/tools/interactionTools.js +539 -0
- package/build/tools/interactionTools.js.map +1 -0
- package/build/tools/logTools.d.ts +3 -0
- package/build/tools/logTools.d.ts.map +1 -0
- package/build/tools/logTools.js +371 -0
- package/build/tools/logTools.js.map +1 -0
- package/build/tools/metaTools.d.ts +7 -0
- package/build/tools/metaTools.d.ts.map +1 -0
- package/build/tools/metaTools.js +186 -0
- package/build/tools/metaTools.js.map +1 -0
- package/build/tools/networkTools.d.ts +3 -0
- package/build/tools/networkTools.d.ts.map +1 -0
- package/build/tools/networkTools.js +478 -0
- package/build/tools/networkTools.js.map +1 -0
- package/build/tools/reduxTools.d.ts +3 -0
- package/build/tools/reduxTools.d.ts.map +1 -0
- package/build/tools/reduxTools.js +83 -0
- package/build/tools/reduxTools.js.map +1 -0
- package/build/tools/screenshotTools.d.ts +3 -0
- package/build/tools/screenshotTools.d.ts.map +1 -0
- package/build/tools/screenshotTools.js +703 -0
- package/build/tools/screenshotTools.js.map +1 -0
- package/package.json +63 -2
|
@@ -0,0 +1,1719 @@
|
|
|
1
|
+
import { executeInApp, delay } from "./jsExecute.js";
|
|
2
|
+
import { iconLabel } from "./iconSemantics.js";
|
|
3
|
+
import { getConnectedAppByDevice, getFirstConnectedApp, getConnectedAppBySimulatorUdid, getConnectedAppByAndroidDeviceId } from "./connection.js";
|
|
4
|
+
/**
|
|
5
|
+
* E1 / Step 5 (2026-05-15 plan): build a pressables list from the platform
|
|
6
|
+
* accessibility tree when fiber isn't reachable. Used when metro is offline
|
|
7
|
+
* (connectedApps.size === 0) so the screenshot tools can still hand the agent
|
|
8
|
+
* labelled coordinates instead of forcing it to guess pixels — the dominant
|
|
9
|
+
* cause of unmeaningful coordinate-strategy taps in the 7-day failure data.
|
|
10
|
+
*/
|
|
11
|
+
async function getAccessibilityPressables(platform, udid) {
|
|
12
|
+
try {
|
|
13
|
+
if (platform === "ios") {
|
|
14
|
+
const { iosGetUITree } = await import("./ios.js");
|
|
15
|
+
const tree = await iosGetUITree(udid);
|
|
16
|
+
if (!tree.success || !tree.elements)
|
|
17
|
+
return [];
|
|
18
|
+
const interactiveTraits = new Set(["button", "link", "searchfield", "tab", "image"]);
|
|
19
|
+
return tree.elements
|
|
20
|
+
.filter(el => {
|
|
21
|
+
// iOS: rely on a11y traits + element type. Buttons, links,
|
|
22
|
+
// tabs, and any element with a non-empty label/identifier
|
|
23
|
+
// that isn't a static text container.
|
|
24
|
+
const traitsLower = (el.traits || []).map(t => t.toLowerCase());
|
|
25
|
+
if (traitsLower.some(t => interactiveTraits.has(t)))
|
|
26
|
+
return true;
|
|
27
|
+
const type = (el.type || "").toLowerCase();
|
|
28
|
+
if (type.includes("button") || type.includes("textfield") || type.includes("searchfield"))
|
|
29
|
+
return true;
|
|
30
|
+
return false;
|
|
31
|
+
})
|
|
32
|
+
.map(el => {
|
|
33
|
+
const type = (el.type || "").toLowerCase();
|
|
34
|
+
return {
|
|
35
|
+
component: el.type || "Element",
|
|
36
|
+
path: "",
|
|
37
|
+
center: el.center,
|
|
38
|
+
frame: el.frame,
|
|
39
|
+
text: el.label || "",
|
|
40
|
+
testID: el.identifier || null,
|
|
41
|
+
accessibilityLabel: el.label || null,
|
|
42
|
+
hasLabel: !!el.label,
|
|
43
|
+
isInput: type.includes("textfield") || type.includes("searchfield")
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const { androidGetUITree } = await import("./android.js");
|
|
49
|
+
const tree = await androidGetUITree();
|
|
50
|
+
if (!tree.success || !tree.elements)
|
|
51
|
+
return [];
|
|
52
|
+
return tree.elements
|
|
53
|
+
.filter(el => el.clickable || el.focused || (el.className && el.className.includes("EditText")))
|
|
54
|
+
.map(el => ({
|
|
55
|
+
component: (el.className || "View").split(".").pop() || "View",
|
|
56
|
+
path: "",
|
|
57
|
+
center: el.center,
|
|
58
|
+
frame: { x: el.bounds.left, y: el.bounds.top, width: el.bounds.width, height: el.bounds.height },
|
|
59
|
+
text: el.text || "",
|
|
60
|
+
testID: el.resourceId || null,
|
|
61
|
+
accessibilityLabel: el.contentDesc || null,
|
|
62
|
+
hasLabel: !!(el.text || el.contentDesc),
|
|
63
|
+
isInput: !!(el.className && el.className.includes("EditText"))
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function getPressableElements(options = {}) {
|
|
72
|
+
const { device, platform, udid } = options;
|
|
73
|
+
// Resolve which connected RN app corresponds to the requested device/udid.
|
|
74
|
+
// The E1 fallback below must fire when no app matches THIS device, even if
|
|
75
|
+
// unrelated apps remain connected to Metro. Without this filter the fiber
|
|
76
|
+
// path would silently fall back to the first-connected app and return data
|
|
77
|
+
// for the wrong device (Bug 1, 2026-05-16).
|
|
78
|
+
let targetApp = null;
|
|
79
|
+
if (udid && platform === "ios") {
|
|
80
|
+
targetApp = getConnectedAppBySimulatorUdid(udid);
|
|
81
|
+
// scan_metro links simulatorUdid asynchronously after the CDP connect; in
|
|
82
|
+
// that window the udid lookup misses even though the right app is already
|
|
83
|
+
// connected. Fall back to the device-name match so callers (ios_screenshot)
|
|
84
|
+
// don't silently degrade to the accessibility-tree pressables list.
|
|
85
|
+
if (!targetApp && device) {
|
|
86
|
+
const matched = getConnectedAppByDevice(device);
|
|
87
|
+
if (matched && matched.platform === "ios")
|
|
88
|
+
targetApp = matched;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else if (device) {
|
|
92
|
+
const matched = getConnectedAppByDevice(device);
|
|
93
|
+
if (matched && (!platform || matched.platform === platform))
|
|
94
|
+
targetApp = matched;
|
|
95
|
+
}
|
|
96
|
+
else if (platform === "android") {
|
|
97
|
+
targetApp = getConnectedAppByAndroidDeviceId(undefined);
|
|
98
|
+
}
|
|
99
|
+
else if (platform === "ios") {
|
|
100
|
+
const first = getFirstConnectedApp();
|
|
101
|
+
targetApp = first && first.platform === "ios" ? first : null;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
targetApp = getFirstConnectedApp();
|
|
105
|
+
}
|
|
106
|
+
// When the caller didn't pin a platform, adopt the resolved app's platform so
|
|
107
|
+
// downstream branches (Android coordinate reconciliation below) run uniformly
|
|
108
|
+
// regardless of entry point. The standalone get_pressable_elements MCP tool
|
|
109
|
+
// omits `platform`; without this derivation it would skip reconciliation and
|
|
110
|
+
// return raw fiber DP that downstream consumers misinterpret as device pixels.
|
|
111
|
+
const effectivePlatform = platform ?? targetApp?.platform;
|
|
112
|
+
// E1 fallback: no app resolved for THIS device → use platform accessibility tree.
|
|
113
|
+
// Only kicks in when caller passed `platform` so we don't break older
|
|
114
|
+
// metro-required call paths that didn't opt into the fallback.
|
|
115
|
+
if (!targetApp && platform) {
|
|
116
|
+
const elements = await getAccessibilityPressables(platform, udid);
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
result: elements.length > 0
|
|
120
|
+
? `Pressable elements (from accessibility tree, ${elements.length} found):\n` +
|
|
121
|
+
elements.map((el, i) => {
|
|
122
|
+
const label = el.accessibilityLabel || el.text || el.testID || el.component;
|
|
123
|
+
const idStr = el.testID ? ` testID="${el.testID}"` : "";
|
|
124
|
+
const inputStr = el.isInput ? " [input]" : "";
|
|
125
|
+
return `${i + 1}. ${el.component} "${label}"${idStr}${inputStr} — center:(${el.center.x},${el.center.y}) frame:(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})`;
|
|
126
|
+
}).join("\n")
|
|
127
|
+
: "No interactive elements found via accessibility.",
|
|
128
|
+
parsedElements: elements
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// --- Step 1: walk fiber tree, find pressable/input elements, dispatch measureInWindow ---
|
|
132
|
+
const dispatchExpression = `
|
|
133
|
+
(function() {
|
|
134
|
+
var hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
135
|
+
if (!hook) return { error: 'React DevTools hook not found.' };
|
|
136
|
+
|
|
137
|
+
var roots = [];
|
|
138
|
+
if (hook.getFiberRoots) {
|
|
139
|
+
roots = Array.from(hook.getFiberRoots(1) || []);
|
|
140
|
+
}
|
|
141
|
+
if (roots.length === 0 && hook.renderers) {
|
|
142
|
+
for (var entry of hook.renderers) {
|
|
143
|
+
var r = Array.from(hook.getFiberRoots ? (hook.getFiberRoots(entry[0]) || []) : []);
|
|
144
|
+
if (r.length > 0) { roots = r; break; }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (roots.length === 0) return { error: 'No fiber roots found.' };
|
|
148
|
+
|
|
149
|
+
function getMeasurable(fiber) {
|
|
150
|
+
var sn = fiber.stateNode;
|
|
151
|
+
if (!sn) return null;
|
|
152
|
+
if (typeof sn.measureInWindow === 'function') return sn;
|
|
153
|
+
if (sn.canonical && sn.canonical.publicInstance &&
|
|
154
|
+
typeof sn.canonical.publicInstance.measureInWindow === 'function') {
|
|
155
|
+
return sn.canonical.publicInstance;
|
|
156
|
+
}
|
|
157
|
+
// Fabric leaf nodes like RCTText have no publicInstance. Measure via
|
|
158
|
+
// the native Fabric UIManager using the shadow node instead — needed
|
|
159
|
+
// for text bounds that are tight around the glyphs (not the scroll
|
|
160
|
+
// container the text happens to live inside).
|
|
161
|
+
if (sn.node && globalThis.nativeFabricUIManager &&
|
|
162
|
+
typeof globalThis.nativeFabricUIManager.measureInWindow === 'function') {
|
|
163
|
+
var node = sn.node;
|
|
164
|
+
return {
|
|
165
|
+
measureInWindow: function(cb) {
|
|
166
|
+
try { globalThis.nativeFabricUIManager.measureInWindow(node, cb); } catch(e) {}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function findFirstHost(fiber, depth) {
|
|
174
|
+
if (!fiber || depth > 20) return null;
|
|
175
|
+
if (typeof fiber.type === 'string' && getMeasurable(fiber)) return fiber;
|
|
176
|
+
var child = fiber.child;
|
|
177
|
+
while (child) {
|
|
178
|
+
var found = findFirstHost(child, depth + 1);
|
|
179
|
+
if (found) return found;
|
|
180
|
+
child = child.sibling;
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function findHostsInSubtree(fiber, depth, hosts, limit) {
|
|
186
|
+
if (!fiber || depth > 20 || hosts.length >= limit) return;
|
|
187
|
+
if (typeof fiber.type === 'string' && getMeasurable(fiber)) {
|
|
188
|
+
hosts.push(fiber);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
var child = fiber.child;
|
|
192
|
+
while (child && hosts.length < limit) {
|
|
193
|
+
findHostsInSubtree(child, depth + 1, hosts, limit);
|
|
194
|
+
child = child.sibling;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function collectText(fiber, d, isRoot) {
|
|
199
|
+
if (!fiber || d > 30) return '';
|
|
200
|
+
var props = fiber.memoizedProps;
|
|
201
|
+
// Stop descent at nested pressable/input boundaries — their text belongs to them, not to the outer wrapper.
|
|
202
|
+
if (!isRoot && props && (typeof props.onPress === 'function' ||
|
|
203
|
+
typeof props.onChangeText === 'function' ||
|
|
204
|
+
typeof props.onFocus === 'function')) {
|
|
205
|
+
return '';
|
|
206
|
+
}
|
|
207
|
+
// Host text fibers carry the raw string as memoizedProps — needed when a
|
|
208
|
+
// mixed-children Text falls through to the fiber-child walk below.
|
|
209
|
+
if (typeof props === 'string') return props;
|
|
210
|
+
if (typeof props === 'number') return String(props);
|
|
211
|
+
if (props) {
|
|
212
|
+
var ch = props.children;
|
|
213
|
+
if (typeof ch === 'string') return ch;
|
|
214
|
+
if (typeof ch === 'number') return String(ch);
|
|
215
|
+
if (Array.isArray(ch)) {
|
|
216
|
+
var inline = [];
|
|
217
|
+
var hasElement = false;
|
|
218
|
+
for (var ci = 0; ci < ch.length; ci++) {
|
|
219
|
+
if (typeof ch[ci] === 'string') inline.push(ch[ci]);
|
|
220
|
+
else if (typeof ch[ci] === 'number') inline.push(String(ch[ci]));
|
|
221
|
+
else if (ch[ci] != null && ch[ci] !== false) hasElement = true;
|
|
222
|
+
}
|
|
223
|
+
// Mixed content ("Create " + <Text>2 digital items</Text>): fall through
|
|
224
|
+
// to the fiber-child walk so nested Text elements are included; the host
|
|
225
|
+
// text fibers re-supply the inline strings, so nothing is lost or doubled.
|
|
226
|
+
if (inline.length > 0 && !hasElement) return inline.join('');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
var parts = [];
|
|
230
|
+
var child = fiber.child;
|
|
231
|
+
while (child) {
|
|
232
|
+
var t = collectText(child, d + 1, false);
|
|
233
|
+
if (t) parts.push(t);
|
|
234
|
+
child = child.sibling;
|
|
235
|
+
}
|
|
236
|
+
return parts.join(' ').replace(/\\s+/g, ' ').trim();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getComponentName(fiber) {
|
|
240
|
+
if (!fiber || !fiber.type) return null;
|
|
241
|
+
if (typeof fiber.type === 'string') return fiber.type;
|
|
242
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
var RN_PRIMITIVES = /^(Animated\\(.*|withAnimated.*|AnimatedComponent.*|ForwardRef.*|memo\\(.*|Context\\.Consumer|Context\\.Provider|ScrollViewContext(Base)?|VirtualizedListContext(Resetter)?|TextInputContext|KeyboardAvoidingViewContext|RCT.*|RNS.*|RNC.*|ViewManagerAdapter_.*|VirtualizedList.*|CellRenderer.*|FrameSizeProvider.*|MaybeScreenContainer|MaybeScreen|Navigation.*|Screen$|ScreenStack|ScreenContainer|ScreenContentWrapper|SceneView|DelayedFreeze|Freeze|Suspender|DebugContainer|StaticContainer|SafeAreaProvider.*|SafeAreaFrameContext|SafeAreaInsetsContext|ExpoRoot|ExpoRootComponent|GestureHandler.*|NativeViewGestureHandler|GestureDetector|PanGestureHandler|Reanimated.*|BottomTabNavigator|TabLayout|RouteNode|Route$|KeyboardProvider|PortalProviderComponent|BottomSheetModalProviderWrapper|ThemeContext|ThemeProvider|TextAncestorContext|PressabilityDebugView|TouchableHighlightImpl|StatusBarOverlay|BottomSheetHostingContainerComponent|BottomSheetGestureHandlersProvider|BottomSheetBackdropContainerComponent|BottomSheetContainerComponent|BottomSheetDraggableViewComponent|BottomSheetHandleContainerComponent|BottomSheetBackgroundContainerComponent|DebuggingOverlay|InspectorDeferred|Inspector|InspectorOverlay|InspectorPanel|StyleInspector|BoxInspector|BoxContainer|ElementBox|BorderBox|InspectorPanelButton)$/;
|
|
246
|
+
|
|
247
|
+
var hostFibers = [];
|
|
248
|
+
var fiberMeta = [];
|
|
249
|
+
var textFibers = [];
|
|
250
|
+
var textContents = [];
|
|
251
|
+
|
|
252
|
+
function findMeaningfulAncestorName(fiber) {
|
|
253
|
+
var cur = fiber.return;
|
|
254
|
+
var depth = 0;
|
|
255
|
+
var fallbackName = null;
|
|
256
|
+
while (cur && depth < 20) {
|
|
257
|
+
var name = getComponentName(cur);
|
|
258
|
+
if (name && typeof cur.type !== 'string') {
|
|
259
|
+
if (!fallbackName) fallbackName = name;
|
|
260
|
+
if (!RN_PRIMITIVES.test(name)) return name;
|
|
261
|
+
}
|
|
262
|
+
cur = cur.return;
|
|
263
|
+
depth++;
|
|
264
|
+
}
|
|
265
|
+
return fallbackName;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Layout-only and touch-wrapper components that should be skipped when scanning
|
|
269
|
+
// children for a meaningful icon/content component name.
|
|
270
|
+
var SKIP_IN_CHILD_SCAN = /^(View|Text|Image|ImageBackground|ScrollView|FlatList|SectionList|KeyboardAvoidingView|SafeAreaView|TouchableOpacity|TouchableHighlight|TouchableWithoutFeedback|TouchableNativeFeedback|Pressable|TextInput|ActivityIndicator|Switch|Modal|StatusBar|VirtualizedList|RefreshControl|Animated\\(.*|withAnimated.*|AnimatedComponent.*)$/;
|
|
271
|
+
|
|
272
|
+
// Scan children of a pressable for a meaningful component name (e.g. SvgChevronBack inside a View).
|
|
273
|
+
// Skips generic layout/touch wrappers to find the actual icon or content component.
|
|
274
|
+
function findMeaningfulChildName(fiber) {
|
|
275
|
+
function scan(f, d) {
|
|
276
|
+
if (!f || d > 12) return null;
|
|
277
|
+
var n = getComponentName(f);
|
|
278
|
+
if (n && typeof f.type !== 'string' && !RN_PRIMITIVES.test(n) && !SKIP_IN_CHILD_SCAN.test(n)) return n;
|
|
279
|
+
var c = f.child;
|
|
280
|
+
while (c) {
|
|
281
|
+
var r = scan(c, d + 1);
|
|
282
|
+
if (r) return r;
|
|
283
|
+
c = c.sibling;
|
|
284
|
+
}
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
return scan(fiber.child, 0);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
var GENERIC_COMPONENT = /^(View|TouchableOpacity|TouchableHighlight|TouchableWithoutFeedback|Pressable|TouchableNativeFeedback|Text|RCTView|RCTText|Unknown)$/;
|
|
291
|
+
|
|
292
|
+
// The components that render PressabilityDebugView in DEV builds. Used to walk
|
|
293
|
+
// up from a PDV's host view to the touchable that owns it — the composite chain
|
|
294
|
+
// in between is generic wrappers (View ForwardRef, Animated(View)) whose props
|
|
295
|
+
// carry no onPress, so they are useless as a collectText / identifier root.
|
|
296
|
+
var PDV_OWNER_COMPONENT = /^(Pressable|Touchable(Opacity|Highlight|WithoutFeedback|NativeFeedback|Bounce))$/;
|
|
297
|
+
|
|
298
|
+
function buildPath(fiber) {
|
|
299
|
+
var parts = [];
|
|
300
|
+
var cur = fiber;
|
|
301
|
+
var depth = 0;
|
|
302
|
+
while (cur && depth < 30) {
|
|
303
|
+
var name = getComponentName(cur);
|
|
304
|
+
if (name && typeof cur.type !== 'string' && !RN_PRIMITIVES.test(name)) {
|
|
305
|
+
parts.unshift(name);
|
|
306
|
+
}
|
|
307
|
+
cur = cur.return;
|
|
308
|
+
depth++;
|
|
309
|
+
}
|
|
310
|
+
// Keep last 3 segments
|
|
311
|
+
if (parts.length > 3) {
|
|
312
|
+
parts = parts.slice(-3);
|
|
313
|
+
return '... > ' + parts.join(' > ');
|
|
314
|
+
}
|
|
315
|
+
return parts.join(' > ');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function walkPressables(fiber, depth) {
|
|
319
|
+
if (!fiber || depth > 5000) return;
|
|
320
|
+
var name = getComponentName(fiber);
|
|
321
|
+
var props = fiber.memoizedProps;
|
|
322
|
+
|
|
323
|
+
// Skip inactive/unfocused screens
|
|
324
|
+
if (name === 'MaybeScreen' && props && props.active === 0) return;
|
|
325
|
+
if (name === 'SceneView' && props && props.focused === false) return;
|
|
326
|
+
if (name === 'RNSScreen' && props && props['aria-hidden'] === true) return;
|
|
327
|
+
|
|
328
|
+
var isPressable = props && typeof props.onPress === 'function';
|
|
329
|
+
var isInput = !isPressable && props && (typeof props.onChangeText === 'function' || typeof props.onFocus === 'function');
|
|
330
|
+
|
|
331
|
+
if (isPressable || isInput) {
|
|
332
|
+
var hostsForThis = [];
|
|
333
|
+
findHostsInSubtree(fiber, 0, hostsForThis, 16);
|
|
334
|
+
if (hostsForThis.length > 0) {
|
|
335
|
+
var text = collectText(fiber, 0, true);
|
|
336
|
+
var componentName = findMeaningfulAncestorName(fiber) || name || 'Unknown';
|
|
337
|
+
// Scan children for a meaningful name like SvgChevronBack: it replaces a
|
|
338
|
+
// generic name (View, TouchableOpacity) and is also kept as the icon hint
|
|
339
|
+
// for text-less buttons even when the ancestor name (FloatingHeader) wins.
|
|
340
|
+
var childName = (!text || GENERIC_COMPONENT.test(componentName)) ? findMeaningfulChildName(fiber) : null;
|
|
341
|
+
if (childName && GENERIC_COMPONENT.test(componentName)) componentName = childName;
|
|
342
|
+
var path = buildPath(fiber);
|
|
343
|
+
var testID = (props && (props.testID || props.nativeID)) || null;
|
|
344
|
+
var accessibilityLabel = (props && props.accessibilityLabel) || null;
|
|
345
|
+
|
|
346
|
+
var hostIndices = [];
|
|
347
|
+
for (var hi = 0; hi < hostsForThis.length; hi++) {
|
|
348
|
+
hostIndices.push(hostFibers.length);
|
|
349
|
+
hostFibers.push(hostsForThis[hi]);
|
|
350
|
+
}
|
|
351
|
+
fiberMeta.push({
|
|
352
|
+
component: componentName,
|
|
353
|
+
path: path,
|
|
354
|
+
text: text ? text.slice(0, 100) : '',
|
|
355
|
+
testID: testID,
|
|
356
|
+
accessibilityLabel: accessibilityLabel,
|
|
357
|
+
isInput: !!isInput,
|
|
358
|
+
icon: childName || null,
|
|
359
|
+
hostIndices: hostIndices
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
var child = fiber.child;
|
|
365
|
+
while (child) {
|
|
366
|
+
walkPressables(child, depth + 1);
|
|
367
|
+
child = child.sibling;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// PRIMARY detection: PressabilityDebugView-based walk — mirrors RN Inspector "Touchables" exactly.
|
|
372
|
+
// In DEV mode, every Pressable / TouchableOpacity / TouchableHighlight /
|
|
373
|
+
// TouchableWithoutFeedback / TouchableNativeFeedback renders a PressabilityDebugView
|
|
374
|
+
// as a child. Its immediate parent (fiber.return) is the HOST view that carries
|
|
375
|
+
// the event handlers — we measure that.
|
|
376
|
+
// FALLBACK: plain onPress detection for production builds (PDV not rendered).
|
|
377
|
+
|
|
378
|
+
var anyPDV = false;
|
|
379
|
+
(function quickCheckPDV(f, d) {
|
|
380
|
+
if (!f || d > 200 || anyPDV) return;
|
|
381
|
+
var n = getComponentName(f);
|
|
382
|
+
if (n === 'PressabilityDebugView') { anyPDV = true; return; }
|
|
383
|
+
quickCheckPDV(f.child, d + 1);
|
|
384
|
+
if (!anyPDV) quickCheckPDV(f.sibling, d);
|
|
385
|
+
})(roots[0].current, 0);
|
|
386
|
+
|
|
387
|
+
// Walk for TextInput elements (not covered by PressabilityDebugView in either mode).
|
|
388
|
+
function walkInputs(fiber, depth) {
|
|
389
|
+
if (!fiber || depth > 5000) return;
|
|
390
|
+
var name = getComponentName(fiber);
|
|
391
|
+
var props = fiber.memoizedProps;
|
|
392
|
+
if (name === 'MaybeScreen' && props && props.active === 0) return;
|
|
393
|
+
if (name === 'SceneView' && props && props.focused === false) return;
|
|
394
|
+
if (name === 'RNSScreen' && props && props['aria-hidden'] === true) return;
|
|
395
|
+
var isInput = props && !props.onPress &&
|
|
396
|
+
(typeof props.onChangeText === 'function' || typeof props.onFocus === 'function');
|
|
397
|
+
if (isInput) {
|
|
398
|
+
var hostsForThis = [];
|
|
399
|
+
findHostsInSubtree(fiber, 0, hostsForThis, 16);
|
|
400
|
+
if (hostsForThis.length > 0) {
|
|
401
|
+
var text = collectText(fiber, 0, true);
|
|
402
|
+
var componentName = findMeaningfulAncestorName(fiber) || name || 'Unknown';
|
|
403
|
+
if (GENERIC_COMPONENT.test(componentName)) {
|
|
404
|
+
var childName = findMeaningfulChildName(fiber);
|
|
405
|
+
if (childName) componentName = childName;
|
|
406
|
+
}
|
|
407
|
+
var props2 = fiber.memoizedProps || {};
|
|
408
|
+
var testID = (props2.testID || props2.nativeID) || null;
|
|
409
|
+
var accessibilityLabel = props2.accessibilityLabel || null;
|
|
410
|
+
var hostIndices = [];
|
|
411
|
+
for (var hi3 = 0; hi3 < hostsForThis.length; hi3++) {
|
|
412
|
+
hostIndices.push(hostFibers.length);
|
|
413
|
+
hostFibers.push(hostsForThis[hi3]);
|
|
414
|
+
}
|
|
415
|
+
fiberMeta.push({
|
|
416
|
+
component: componentName,
|
|
417
|
+
path: buildPath(fiber),
|
|
418
|
+
text: text ? text.slice(0, 100) : '',
|
|
419
|
+
testID: testID,
|
|
420
|
+
accessibilityLabel: accessibilityLabel,
|
|
421
|
+
isInput: true,
|
|
422
|
+
hostIndices: hostIndices
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
var child = fiber.child;
|
|
427
|
+
while (child) { walkInputs(child, depth + 1); child = child.sibling; }
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function walkPressabilityDebugViews(fiber, depth, hidden) {
|
|
431
|
+
if (!fiber || depth > 5000) return;
|
|
432
|
+
var name = getComponentName(fiber);
|
|
433
|
+
var props = fiber.memoizedProps;
|
|
434
|
+
var nextHidden = hidden;
|
|
435
|
+
if (name === 'MaybeScreen' && props && props.active === 0) nextHidden = true;
|
|
436
|
+
if (name === 'SceneView' && props && props.focused === false) nextHidden = true;
|
|
437
|
+
if (name === 'RNSScreen' && props && props['aria-hidden'] === true) nextHidden = true;
|
|
438
|
+
|
|
439
|
+
if (!nextHidden && name === 'PressabilityDebugView') {
|
|
440
|
+
// fiber.return = the HOST view that wraps the pressable's children
|
|
441
|
+
var hostFiber = fiber.return;
|
|
442
|
+
if (hostFiber && getMeasurable(hostFiber)) {
|
|
443
|
+
// Walk up from the host fiber to the touchable component that owns this
|
|
444
|
+
// PDV. Match by name (PDV_OWNER_COMPONENT) — stopping at the first
|
|
445
|
+
// composite ancestor is wrong because that's a generic wrapper
|
|
446
|
+
// (ForwardRef displayName 'View', then Animated(View) for Touchables).
|
|
447
|
+
var pressableFiber = hostFiber;
|
|
448
|
+
var firstComposite = null;
|
|
449
|
+
var cur = hostFiber.return;
|
|
450
|
+
var upDepth = 0;
|
|
451
|
+
while (cur && upDepth < 10) {
|
|
452
|
+
if (typeof cur.type !== 'string' && cur.type !== null) {
|
|
453
|
+
if (!firstComposite) firstComposite = cur;
|
|
454
|
+
var curName = getComponentName(cur);
|
|
455
|
+
if (curName && PDV_OWNER_COMPONENT.test(curName)) {
|
|
456
|
+
pressableFiber = cur;
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
cur = cur.return;
|
|
461
|
+
upDepth++;
|
|
462
|
+
}
|
|
463
|
+
if (pressableFiber === hostFiber && firstComposite) pressableFiber = firstComposite;
|
|
464
|
+
|
|
465
|
+
var pressableProps = pressableFiber.memoizedProps || {};
|
|
466
|
+
var text = collectText(pressableFiber, 0, true);
|
|
467
|
+
// Prefer the nearest semantic ancestor (FilterRow, RoleButton, ...) over
|
|
468
|
+
// generic Touchable/Pressable/View wrappers. The climb is capped at a few
|
|
469
|
+
// composite levels so a bare Pressable isn't named after its screen container.
|
|
470
|
+
var componentName = null;
|
|
471
|
+
var an = pressableFiber.return;
|
|
472
|
+
var anComposites = 0;
|
|
473
|
+
var anDepth = 0;
|
|
474
|
+
while (an && anDepth < 12 && anComposites < 4 && !componentName) {
|
|
475
|
+
if (typeof an.type !== 'string') {
|
|
476
|
+
var anName = getComponentName(an);
|
|
477
|
+
if (anName) {
|
|
478
|
+
anComposites++;
|
|
479
|
+
if (!RN_PRIMITIVES.test(anName) && !GENERIC_COMPONENT.test(anName)) {
|
|
480
|
+
componentName = anName;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
an = an.return;
|
|
485
|
+
anDepth++;
|
|
486
|
+
}
|
|
487
|
+
if (!componentName) {
|
|
488
|
+
componentName = findMeaningfulAncestorName(pressableFiber) || getComponentName(pressableFiber) || 'Unknown';
|
|
489
|
+
}
|
|
490
|
+
// Keep the icon child name even when the ancestor name wins — icon-only
|
|
491
|
+
// buttons get their semantics from it (SvgChevronBack → back button).
|
|
492
|
+
var childName = (!text || GENERIC_COMPONENT.test(componentName)) ? findMeaningfulChildName(pressableFiber) : null;
|
|
493
|
+
if (childName && GENERIC_COMPONENT.test(componentName)) componentName = childName;
|
|
494
|
+
var path = buildPath(pressableFiber);
|
|
495
|
+
|
|
496
|
+
// testID/accessibilityLabel are spread onto the host view by TouchableOpacity etc.
|
|
497
|
+
var hostProps = hostFiber.memoizedProps || {};
|
|
498
|
+
var testID = hostProps.testID || hostProps.nativeID || pressableProps.testID || pressableProps.nativeID || null;
|
|
499
|
+
var accessibilityLabel = hostProps.accessibilityLabel || pressableProps.accessibilityLabel || null;
|
|
500
|
+
|
|
501
|
+
var hostIdx = hostFibers.length;
|
|
502
|
+
hostFibers.push(hostFiber);
|
|
503
|
+
fiberMeta.push({
|
|
504
|
+
component: componentName,
|
|
505
|
+
path: path,
|
|
506
|
+
text: text ? text.slice(0, 100) : '',
|
|
507
|
+
testID: testID,
|
|
508
|
+
accessibilityLabel: accessibilityLabel,
|
|
509
|
+
isInput: false,
|
|
510
|
+
icon: childName || null,
|
|
511
|
+
hostIndices: [hostIdx]
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
// Do not recurse into PressabilityDebugView's own children (debug overlay)
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
var child = fiber.child;
|
|
519
|
+
while (child) {
|
|
520
|
+
walkPressabilityDebugViews(child, depth + 1, nextHidden);
|
|
521
|
+
child = child.sibling;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (anyPDV) {
|
|
526
|
+
walkPressabilityDebugViews(roots[0].current, 0, false);
|
|
527
|
+
walkInputs(roots[0].current, 0);
|
|
528
|
+
} else {
|
|
529
|
+
// Fallback: original onPress-based detection for production builds
|
|
530
|
+
walkPressables(roots[0].current, 0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Collect text fibers that are NOT inside a pressable — used later to attach
|
|
534
|
+
// spatial 'nearbyText' hints to icon-only pressables.
|
|
535
|
+
function extractTextString(fiber) {
|
|
536
|
+
var p = fiber.memoizedProps;
|
|
537
|
+
if (!p) return '';
|
|
538
|
+
var ch = p.children;
|
|
539
|
+
if (typeof ch === 'string') return ch;
|
|
540
|
+
if (typeof ch === 'number') return String(ch);
|
|
541
|
+
if (Array.isArray(ch)) {
|
|
542
|
+
var parts = [];
|
|
543
|
+
for (var k = 0; k < ch.length; k++) {
|
|
544
|
+
if (typeof ch[k] === 'string') parts.push(ch[k]);
|
|
545
|
+
else if (typeof ch[k] === 'number') parts.push(String(ch[k]));
|
|
546
|
+
}
|
|
547
|
+
if (parts.length > 0) return parts.join('');
|
|
548
|
+
}
|
|
549
|
+
return '';
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function walkTexts(fiber, depth, insidePressable, inHidden) {
|
|
553
|
+
if (!fiber || depth > 5000) return;
|
|
554
|
+
var name = getComponentName(fiber);
|
|
555
|
+
var props = fiber.memoizedProps;
|
|
556
|
+
|
|
557
|
+
// Track hidden-screen ancestry without early return — a MaybeScreen wrapper
|
|
558
|
+
// can enclose BOTH the active and inactive siblings, so we must keep walking
|
|
559
|
+
// to find the active one while still skipping the inactive subtree's texts.
|
|
560
|
+
var nextHidden = inHidden;
|
|
561
|
+
if (name === 'MaybeScreen' && props && props.active === 0) nextHidden = true;
|
|
562
|
+
if (name === 'SceneView' && props && props.focused === false) nextHidden = true;
|
|
563
|
+
if (name === 'RNSScreen' && props && props['aria-hidden'] === true) nextHidden = true;
|
|
564
|
+
|
|
565
|
+
var hasOnPress = props && typeof props.onPress === 'function';
|
|
566
|
+
var isInputHere = props && (typeof props.onChangeText === 'function' || typeof props.onFocus === 'function');
|
|
567
|
+
var nextInside = insidePressable || !!hasOnPress || !!isInputHere;
|
|
568
|
+
|
|
569
|
+
// Record standalone text when outside any pressable. Detects both stock
|
|
570
|
+
// Text and custom wrappers (CustomText, ThemedText, etc.) by checking for
|
|
571
|
+
// direct string/number children. Fabric RCTText has no publicInstance, so
|
|
572
|
+
// we walk up the fiber chain to the nearest measurable host (the View
|
|
573
|
+
// enclosing the text) and use its frame as the text's proxy bounds.
|
|
574
|
+
if (!insidePressable && !nextHidden && name !== 'RCTText' && typeof fiber.type !== 'string') {
|
|
575
|
+
var str = extractTextString(fiber);
|
|
576
|
+
if (str && str.length > 0 && str.length <= 120) {
|
|
577
|
+
var up = fiber;
|
|
578
|
+
var upDepth = 0;
|
|
579
|
+
var measurable = null;
|
|
580
|
+
while (up && upDepth < 20) {
|
|
581
|
+
if (typeof up.type === 'string' && getMeasurable(up)) {
|
|
582
|
+
measurable = up;
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
585
|
+
up = up.return;
|
|
586
|
+
upDepth++;
|
|
587
|
+
}
|
|
588
|
+
if (measurable) {
|
|
589
|
+
textFibers.push(measurable);
|
|
590
|
+
textContents.push(str);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
var child = fiber.child;
|
|
596
|
+
while (child) {
|
|
597
|
+
walkTexts(child, depth + 1, nextInside, nextHidden);
|
|
598
|
+
child = child.sibling;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
walkTexts(roots[0].current, 0, false, false);
|
|
602
|
+
|
|
603
|
+
if (hostFibers.length === 0) return { error: 'No pressable elements found on screen.' };
|
|
604
|
+
|
|
605
|
+
// Also measure the root view for viewport detection (appended; tracked by explicit index to preserve hostIndices)
|
|
606
|
+
var rootHost = findFirstHost(roots[0].current, 0);
|
|
607
|
+
var rootIdx = -1;
|
|
608
|
+
if (rootHost) {
|
|
609
|
+
rootIdx = hostFibers.length;
|
|
610
|
+
hostFibers.push(rootHost);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
globalThis.__pressableFibers = hostFibers;
|
|
614
|
+
globalThis.__pressableMeta = fiberMeta;
|
|
615
|
+
globalThis.__pressableMeasurements = new Array(hostFibers.length).fill(null);
|
|
616
|
+
globalThis.__pressableRootIdx = rootIdx;
|
|
617
|
+
|
|
618
|
+
for (var i = 0; i < hostFibers.length; i++) {
|
|
619
|
+
try {
|
|
620
|
+
(function(idx) {
|
|
621
|
+
getMeasurable(hostFibers[idx]).measureInWindow(function(fx, fy, fw, fh) {
|
|
622
|
+
globalThis.__pressableMeasurements[idx] = { x: fx, y: fy, width: fw, height: fh };
|
|
623
|
+
});
|
|
624
|
+
})(i);
|
|
625
|
+
} catch(e) {}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
globalThis.__pressableTextContents = textContents;
|
|
629
|
+
globalThis.__pressableTextMeasurements = new Array(textFibers.length).fill(null);
|
|
630
|
+
for (var ti = 0; ti < textFibers.length; ti++) {
|
|
631
|
+
try {
|
|
632
|
+
(function(idx) {
|
|
633
|
+
getMeasurable(textFibers[idx]).measureInWindow(function(fx, fy, fw, fh) {
|
|
634
|
+
globalThis.__pressableTextMeasurements[idx] = { x: fx, y: fy, width: fw, height: fh };
|
|
635
|
+
});
|
|
636
|
+
})(ti);
|
|
637
|
+
} catch(e) {}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return { count: hostFibers.length, textCount: textFibers.length };
|
|
641
|
+
})()
|
|
642
|
+
`;
|
|
643
|
+
const dispatchResult = await executeInApp(dispatchExpression, false, { timeoutMs: 30000, originatingToolName: "get_pressable_elements" }, device);
|
|
644
|
+
if (!dispatchResult.success)
|
|
645
|
+
return dispatchResult;
|
|
646
|
+
try {
|
|
647
|
+
const parsed = JSON.parse(dispatchResult.result || "{}");
|
|
648
|
+
if (parsed.error)
|
|
649
|
+
return { success: false, error: parsed.error };
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
/* ignore */
|
|
653
|
+
}
|
|
654
|
+
// Wait for measureInWindow callbacks
|
|
655
|
+
await delay(300);
|
|
656
|
+
// --- Step 2: read measurements, filter visible, build results ---
|
|
657
|
+
const resolveExpression = `
|
|
658
|
+
(function() {
|
|
659
|
+
var fibers = globalThis.__pressableFibers;
|
|
660
|
+
var meta = globalThis.__pressableMeta;
|
|
661
|
+
var measurements = globalThis.__pressableMeasurements;
|
|
662
|
+
var rootIdx = globalThis.__pressableRootIdx;
|
|
663
|
+
var textContents = globalThis.__pressableTextContents || [];
|
|
664
|
+
var textMeasurements = globalThis.__pressableTextMeasurements || [];
|
|
665
|
+
globalThis.__pressableFibers = null;
|
|
666
|
+
globalThis.__pressableMeta = null;
|
|
667
|
+
globalThis.__pressableMeasurements = null;
|
|
668
|
+
globalThis.__pressableRootIdx = null;
|
|
669
|
+
globalThis.__pressableTextContents = null;
|
|
670
|
+
globalThis.__pressableTextMeasurements = null;
|
|
671
|
+
|
|
672
|
+
if (!fibers || !measurements || !meta) {
|
|
673
|
+
return { error: 'No measurement data. Run get_pressable_elements again.' };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Get viewport dimensions from the explicit root measurement (fallback to scanning)
|
|
677
|
+
var viewportW = 9999, viewportH = 9999;
|
|
678
|
+
var rootM = (rootIdx != null && rootIdx >= 0) ? measurements[rootIdx] : null;
|
|
679
|
+
if (rootM && rootM.width > 0 && rootM.height > 0) {
|
|
680
|
+
viewportW = rootM.width;
|
|
681
|
+
viewportH = rootM.height + (rootM.y > 0 ? rootM.y : 0);
|
|
682
|
+
} else {
|
|
683
|
+
for (var v = 0; v < measurements.length; v++) {
|
|
684
|
+
if (measurements[v] && measurements[v].x === 0 && measurements[v].y <= 0 &&
|
|
685
|
+
measurements[v].width > 0 && measurements[v].height > 0) {
|
|
686
|
+
viewportW = measurements[v].width;
|
|
687
|
+
viewportH = measurements[v].height + measurements[v].y;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
var elements = [];
|
|
694
|
+
|
|
695
|
+
for (var i = 0; i < meta.length; i++) {
|
|
696
|
+
var info = meta[i];
|
|
697
|
+
|
|
698
|
+
// Union all host measurements for this pressable to get its true bounds
|
|
699
|
+
var uMinX = Infinity, uMinY = Infinity, uMaxX = -Infinity, uMaxY = -Infinity;
|
|
700
|
+
var hasValid = false;
|
|
701
|
+
var indices = info.hostIndices || [];
|
|
702
|
+
for (var hi2 = 0; hi2 < indices.length; hi2++) {
|
|
703
|
+
var mm = measurements[indices[hi2]];
|
|
704
|
+
if (!mm || mm.width <= 0 || mm.height <= 0) continue;
|
|
705
|
+
hasValid = true;
|
|
706
|
+
if (mm.x < uMinX) uMinX = mm.x;
|
|
707
|
+
if (mm.y < uMinY) uMinY = mm.y;
|
|
708
|
+
if (mm.x + mm.width > uMaxX) uMaxX = mm.x + mm.width;
|
|
709
|
+
if (mm.y + mm.height > uMaxY) uMaxY = mm.y + mm.height;
|
|
710
|
+
}
|
|
711
|
+
if (!hasValid) continue;
|
|
712
|
+
var m = { x: uMinX, y: uMinY, width: uMaxX - uMinX, height: uMaxY - uMinY };
|
|
713
|
+
|
|
714
|
+
// Filter: only visible within viewport
|
|
715
|
+
if (m.width <= 0 || m.height <= 0) continue;
|
|
716
|
+
if (m.x + m.width < 0 || m.y + m.height < 0) continue;
|
|
717
|
+
if (m.x > viewportW || m.y > viewportH) continue;
|
|
718
|
+
|
|
719
|
+
var text = info.text || '';
|
|
720
|
+
|
|
721
|
+
elements.push({
|
|
722
|
+
component: info.component,
|
|
723
|
+
path: info.path,
|
|
724
|
+
center: {
|
|
725
|
+
x: Math.round(m.x + m.width / 2),
|
|
726
|
+
y: Math.round(m.y + m.height / 2)
|
|
727
|
+
},
|
|
728
|
+
frame: {
|
|
729
|
+
x: Math.round(m.x),
|
|
730
|
+
y: Math.round(m.y),
|
|
731
|
+
width: Math.round(m.width),
|
|
732
|
+
height: Math.round(m.height)
|
|
733
|
+
},
|
|
734
|
+
text: text,
|
|
735
|
+
testID: info.testID,
|
|
736
|
+
accessibilityLabel: info.accessibilityLabel,
|
|
737
|
+
hasLabel: text.length > 0,
|
|
738
|
+
isInput: info.isInput,
|
|
739
|
+
icon: info.icon || null
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Deduplicate: multiple nested pressables (View > TouchableOpacity > TouchableOpacity)
|
|
744
|
+
// often share the same frame. Keep the one with the most meaningful component name,
|
|
745
|
+
// but merge text/testID/accessibilityLabel from the loser so stopping collectText
|
|
746
|
+
// at nested-pressable boundaries does not drop labels across the merge.
|
|
747
|
+
var HOST_NAMES = /^(View|TouchableOpacity|TouchableHighlight|TouchableWithoutFeedback|Pressable|TouchableNativeFeedback|Text|RCTView|RCTText)$/;
|
|
748
|
+
function mergeFields(winner, loser) {
|
|
749
|
+
if (!winner.text && loser.text) {
|
|
750
|
+
winner.text = loser.text;
|
|
751
|
+
winner.hasLabel = winner.text.length > 0;
|
|
752
|
+
}
|
|
753
|
+
if (!winner.testID && loser.testID) winner.testID = loser.testID;
|
|
754
|
+
if (!winner.accessibilityLabel && loser.accessibilityLabel) winner.accessibilityLabel = loser.accessibilityLabel;
|
|
755
|
+
if (!winner.icon && loser.icon) winner.icon = loser.icon;
|
|
756
|
+
return winner;
|
|
757
|
+
}
|
|
758
|
+
var deduped = {};
|
|
759
|
+
for (var di = 0; di < elements.length; di++) {
|
|
760
|
+
var el = elements[di];
|
|
761
|
+
var key = el.frame.x + ',' + el.frame.y + ',' + el.frame.width + ',' + el.frame.height;
|
|
762
|
+
var existing = deduped[key];
|
|
763
|
+
if (!existing) {
|
|
764
|
+
deduped[key] = el;
|
|
765
|
+
} else {
|
|
766
|
+
var existingIsGeneric = HOST_NAMES.test(existing.component);
|
|
767
|
+
var newIsGeneric = HOST_NAMES.test(el.component);
|
|
768
|
+
var winner, loser;
|
|
769
|
+
if (existingIsGeneric && !newIsGeneric) {
|
|
770
|
+
winner = el; loser = existing;
|
|
771
|
+
} else if (!existingIsGeneric && newIsGeneric) {
|
|
772
|
+
winner = existing; loser = el;
|
|
773
|
+
} else {
|
|
774
|
+
// Both generic or both meaningful — prefer the one with more identifiers.
|
|
775
|
+
if (!existing.testID && el.testID) { winner = el; loser = existing; }
|
|
776
|
+
else if (!existing.accessibilityLabel && el.accessibilityLabel) { winner = el; loser = existing; }
|
|
777
|
+
else { winner = existing; loser = el; }
|
|
778
|
+
}
|
|
779
|
+
deduped[key] = mergeFields(winner, loser);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
elements = [];
|
|
783
|
+
for (var dk in deduped) {
|
|
784
|
+
elements.push(deduped[dk]);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Tag wrappers: pressables that cover >=50% of viewport AND geometrically contain another pressable.
|
|
788
|
+
// These are typically keyboard-dismiss/full-screen Touchable wrappers — agents should skip them.
|
|
789
|
+
var viewportArea = (viewportW > 0 && viewportH > 0 && viewportW < 9999 && viewportH < 9999)
|
|
790
|
+
? viewportW * viewportH : 0;
|
|
791
|
+
if (viewportArea > 0) {
|
|
792
|
+
for (var wi = 0; wi < elements.length; wi++) {
|
|
793
|
+
var we = elements[wi];
|
|
794
|
+
var weArea = we.frame.width * we.frame.height;
|
|
795
|
+
if (weArea < viewportArea * 0.5) continue;
|
|
796
|
+
for (var wj = 0; wj < elements.length; wj++) {
|
|
797
|
+
if (wj === wi) continue;
|
|
798
|
+
var other = elements[wj];
|
|
799
|
+
if (other.frame.x >= we.frame.x &&
|
|
800
|
+
other.frame.y >= we.frame.y &&
|
|
801
|
+
other.frame.x + other.frame.width <= we.frame.x + we.frame.width &&
|
|
802
|
+
other.frame.y + other.frame.height <= we.frame.y + we.frame.height &&
|
|
803
|
+
other.frame.width * other.frame.height < weArea) {
|
|
804
|
+
we.isWrapper = true;
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Build a normalized list of visible standalone texts (content + frame)
|
|
812
|
+
var textBoxes = [];
|
|
813
|
+
for (var tmi = 0; tmi < textMeasurements.length; tmi++) {
|
|
814
|
+
var tm = textMeasurements[tmi];
|
|
815
|
+
var tc = textContents[tmi];
|
|
816
|
+
if (!tm || !tc) continue;
|
|
817
|
+
if (tm.width <= 0 || tm.height <= 0) continue;
|
|
818
|
+
if (tm.x + tm.width < 0 || tm.y + tm.height < 0) continue;
|
|
819
|
+
if (tm.x > viewportW || tm.y > viewportH) continue;
|
|
820
|
+
textBoxes.push({
|
|
821
|
+
text: tc,
|
|
822
|
+
x: tm.x, y: tm.y, width: tm.width, height: tm.height,
|
|
823
|
+
cx: tm.x + tm.width / 2, cy: tm.y + tm.height / 2
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Humanize component name → intent (strip Svg/Icon prefixes, split camelCase)
|
|
828
|
+
function humanize(name) {
|
|
829
|
+
if (!name) return '';
|
|
830
|
+
var n = String(name);
|
|
831
|
+
n = n.replace(/^(Svg|Icon)/, '').replace(/(Svg|Icon)$/, '');
|
|
832
|
+
n = n.replace(/([a-z0-9])([A-Z])/g, '$1 $2');
|
|
833
|
+
n = n.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2');
|
|
834
|
+
return n.trim().toLowerCase();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Attach nearbyText for pressables that lack their own text/label.
|
|
838
|
+
// Nearest text by center-to-center distance, within a reasonable radius.
|
|
839
|
+
var NEARBY_RADIUS = 120; // points
|
|
840
|
+
for (var ei = 0; ei < elements.length; ei++) {
|
|
841
|
+
var pe = elements[ei];
|
|
842
|
+
pe.intent = humanize(pe.icon || pe.component);
|
|
843
|
+
// Skip when the pressable already has its own human-readable text or a11y label.
|
|
844
|
+
// testID alone doesn't suppress nearbyText — the testID may be cryptic and the
|
|
845
|
+
// surrounding visible text is still a useful handle for an agent.
|
|
846
|
+
if (pe.hasLabel || pe.accessibilityLabel) continue;
|
|
847
|
+
if (textBoxes.length === 0) continue;
|
|
848
|
+
|
|
849
|
+
var pcx = pe.frame.x + pe.frame.width / 2;
|
|
850
|
+
var pcy = pe.frame.y + pe.frame.height / 2;
|
|
851
|
+
var best = null;
|
|
852
|
+
var bestDist = Infinity;
|
|
853
|
+
for (var tbi = 0; tbi < textBoxes.length; tbi++) {
|
|
854
|
+
var tb = textBoxes[tbi];
|
|
855
|
+
// Skip texts contained inside this pressable (shouldn't happen — walkTexts excludes them — but defensive)
|
|
856
|
+
if (tb.x >= pe.frame.x && tb.y >= pe.frame.y &&
|
|
857
|
+
tb.x + tb.width <= pe.frame.x + pe.frame.width &&
|
|
858
|
+
tb.y + tb.height <= pe.frame.y + pe.frame.height) continue;
|
|
859
|
+
// Row siblings first: a checkbox's label sits in the same row but its
|
|
860
|
+
// long text's CENTER is far away, so center distance alone misses it.
|
|
861
|
+
// Row-aligned + small horizontal edge gap = strongest signal.
|
|
862
|
+
var rowAligned = Math.abs(tb.cy - pcy) <= Math.max(24, pe.frame.height / 2);
|
|
863
|
+
var gapL = pe.frame.x - (tb.x + tb.width);
|
|
864
|
+
var gapR = tb.x - (pe.frame.x + pe.frame.width);
|
|
865
|
+
var hGap = Math.max(gapL, gapR);
|
|
866
|
+
if (hGap < 0) hGap = 0;
|
|
867
|
+
var d;
|
|
868
|
+
if (rowAligned && hGap <= 80) {
|
|
869
|
+
d = hGap;
|
|
870
|
+
} else {
|
|
871
|
+
var dx = tb.cx - pcx;
|
|
872
|
+
var dy = tb.cy - pcy;
|
|
873
|
+
d = Math.sqrt(dx * dx + dy * dy);
|
|
874
|
+
if (d > NEARBY_RADIUS) continue;
|
|
875
|
+
}
|
|
876
|
+
if (d < bestDist) {
|
|
877
|
+
bestDist = d;
|
|
878
|
+
best = tb;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (best) pe.nearbyText = best.text;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Sort top-to-bottom, left-to-right
|
|
885
|
+
elements.sort(function(a, b) {
|
|
886
|
+
if (a.center.y !== b.center.y) return a.center.y - b.center.y;
|
|
887
|
+
return a.center.x - b.center.x;
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
var iconCount = 0;
|
|
891
|
+
var labeledCount = 0;
|
|
892
|
+
for (var j = 0; j < elements.length; j++) {
|
|
893
|
+
if (elements[j].hasLabel) labeledCount++;
|
|
894
|
+
else iconCount++;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
pressableElements: elements,
|
|
899
|
+
summary: 'Found ' + elements.length + ' pressable elements (' + iconCount + ' icon-only, ' + labeledCount + ' with text labels)'
|
|
900
|
+
};
|
|
901
|
+
})()
|
|
902
|
+
`;
|
|
903
|
+
const result = await executeInApp(resolveExpression, false, { timeoutMs: 30000, originatingToolName: "get_pressable_elements" }, device);
|
|
904
|
+
if (result.success && result.result) {
|
|
905
|
+
try {
|
|
906
|
+
const parsed = JSON.parse(result.result);
|
|
907
|
+
if (parsed.error)
|
|
908
|
+
return { success: false, error: parsed.error };
|
|
909
|
+
const pressableElements = parsed.pressableElements || [];
|
|
910
|
+
// Android coordinate reconciliation (2026-05-17).
|
|
911
|
+
//
|
|
912
|
+
// The fiber walker produces rich metadata (component names, testID, intent,
|
|
913
|
+
// wrapper flags, nearbyText) but its measureInWindow output on Bridgeless/Fabric
|
|
914
|
+
// is in app-window DP space — off from the actual screen-pixel hit area by the
|
|
915
|
+
// status bar height (varies per device) AND an additional unaccounted offset
|
|
916
|
+
// we couldn't characterize across devices.
|
|
917
|
+
//
|
|
918
|
+
// uiautomator's bounds, on the other hand, are in absolute display pixels and
|
|
919
|
+
// match what tap(x, y) → adb input dispatches — they are touch-accurate.
|
|
920
|
+
//
|
|
921
|
+
// Strategy: keep fiber's metadata, but replace coords with uiautomator's bounds
|
|
922
|
+
// when we can match a fiber element to a uiautomator node by text / contentDesc /
|
|
923
|
+
// resourceId. For icon-only pressables that have no text-match candidate,
|
|
924
|
+
// fall back to fiber's DP coords scaled by density — they're imperfect but
|
|
925
|
+
// they keep the same gross layout that an agent could use as a "near this
|
|
926
|
+
// region" hint, and they're better than nothing.
|
|
927
|
+
//
|
|
928
|
+
// iOS is unaffected — fiber returns points and AXe returns points; uniform.
|
|
929
|
+
if (effectivePlatform === "android" && pressableElements.length > 0) {
|
|
930
|
+
let densityScale = 2.625;
|
|
931
|
+
let statusBarPixels = 0;
|
|
932
|
+
try {
|
|
933
|
+
const { androidGetDensity, androidGetStatusBarHeight } = await import("./android.js");
|
|
934
|
+
const [densityResult, statusBarResult] = await Promise.all([
|
|
935
|
+
androidGetDensity(),
|
|
936
|
+
androidGetStatusBarHeight().catch(() => ({ success: false, heightPixels: 0 })),
|
|
937
|
+
]);
|
|
938
|
+
densityScale = (densityResult.density || 420) / 160;
|
|
939
|
+
if (statusBarResult.success && statusBarResult.heightPixels) {
|
|
940
|
+
statusBarPixels = statusBarResult.heightPixels;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
catch { /* default */ }
|
|
944
|
+
let uiNodes;
|
|
945
|
+
try {
|
|
946
|
+
const { androidGetUITree } = await import("./android.js");
|
|
947
|
+
const tree = await androidGetUITree();
|
|
948
|
+
if (tree.success && tree.elements)
|
|
949
|
+
uiNodes = tree.elements;
|
|
950
|
+
}
|
|
951
|
+
catch { /* uiautomator unavailable */ }
|
|
952
|
+
const norm = (s) => (s ?? "").toLowerCase().replace(/\s+/g, " ").trim();
|
|
953
|
+
const findUiMatch = (el) => {
|
|
954
|
+
if (!uiNodes || uiNodes.length === 0)
|
|
955
|
+
return null;
|
|
956
|
+
const targetTestID = norm(el.testID);
|
|
957
|
+
const targetText = norm(el.text);
|
|
958
|
+
const targetA11y = norm(el.accessibilityLabel);
|
|
959
|
+
if (!targetTestID && !targetText && !targetA11y)
|
|
960
|
+
return null;
|
|
961
|
+
// Prefer testID → resourceId exact match (most stable).
|
|
962
|
+
if (targetTestID) {
|
|
963
|
+
const m = uiNodes.find(u => norm(u.resourceId).endsWith("/" + targetTestID) || norm(u.resourceId) === targetTestID);
|
|
964
|
+
if (m)
|
|
965
|
+
return m;
|
|
966
|
+
}
|
|
967
|
+
// Then exact text/contentDesc match.
|
|
968
|
+
for (const query of [targetText, targetA11y]) {
|
|
969
|
+
if (!query)
|
|
970
|
+
continue;
|
|
971
|
+
const m = uiNodes.find(u => norm(u.text) === query || norm(u.contentDesc) === query);
|
|
972
|
+
if (m)
|
|
973
|
+
return m;
|
|
974
|
+
}
|
|
975
|
+
// Then substring — handles fiber's collected text being a superset of one node's label.
|
|
976
|
+
for (const query of [targetText, targetA11y]) {
|
|
977
|
+
if (!query)
|
|
978
|
+
continue;
|
|
979
|
+
const m = uiNodes.find(u => {
|
|
980
|
+
const ut = norm(u.text);
|
|
981
|
+
const ud = norm(u.contentDesc);
|
|
982
|
+
return (ut && (ut.includes(query) || query.includes(ut))) ||
|
|
983
|
+
(ud && (ud.includes(query) || query.includes(ud)));
|
|
984
|
+
});
|
|
985
|
+
if (m)
|
|
986
|
+
return m;
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
};
|
|
990
|
+
for (const el of pressableElements) {
|
|
991
|
+
const match = findUiMatch(el);
|
|
992
|
+
if (match) {
|
|
993
|
+
el.center = { x: match.center.x, y: match.center.y };
|
|
994
|
+
el.frame = {
|
|
995
|
+
x: match.bounds.left,
|
|
996
|
+
y: match.bounds.top,
|
|
997
|
+
width: match.bounds.width,
|
|
998
|
+
height: match.bounds.height,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
// Fall back to fiber's DP → device-pixel scaling, plus the dynamic
|
|
1003
|
+
// status-bar pixel offset so the y origin lines up with display
|
|
1004
|
+
// coordinates (uiautomator's space). measureInWindow on Bridgeless
|
|
1005
|
+
// returns coordinates relative to the React window which sits
|
|
1006
|
+
// below the system status bar on non-edge-to-edge apps; without
|
|
1007
|
+
// this shift, fallback elements report y-values that are
|
|
1008
|
+
// ~status-bar-height too small.
|
|
1009
|
+
//
|
|
1010
|
+
// Note: this is a best-effort correction. Edge-to-edge apps where
|
|
1011
|
+
// the React window starts at the display origin will see a
|
|
1012
|
+
// small over-correction here. The proper fix would query
|
|
1013
|
+
// WindowInsets, but the data isn't exposed via adb in a way
|
|
1014
|
+
// we can reach today, and the icon-only-no-text fallback is
|
|
1015
|
+
// already a "region hint" path — agents should prefer
|
|
1016
|
+
// tap(component=...) / tap(testID=...) for precision.
|
|
1017
|
+
el.center = {
|
|
1018
|
+
x: el.center.x * densityScale,
|
|
1019
|
+
y: el.center.y * densityScale + statusBarPixels,
|
|
1020
|
+
};
|
|
1021
|
+
el.frame = {
|
|
1022
|
+
x: el.frame.x * densityScale,
|
|
1023
|
+
y: el.frame.y * densityScale + statusBarPixels,
|
|
1024
|
+
width: el.frame.width * densityScale,
|
|
1025
|
+
height: el.frame.height * densityScale,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
// Format as readable text
|
|
1031
|
+
const lines = [parsed.summary, ""];
|
|
1032
|
+
for (let i = 0; i < pressableElements.length; i++) {
|
|
1033
|
+
const el = pressableElements[i];
|
|
1034
|
+
const num = i + 1;
|
|
1035
|
+
const iconHint = !el.hasLabel ? iconLabel(el.component, el.icon) : null;
|
|
1036
|
+
const label = el.hasLabel
|
|
1037
|
+
? `"${el.text}"`
|
|
1038
|
+
: iconHint
|
|
1039
|
+
? `(${iconHint})`
|
|
1040
|
+
: el.intent
|
|
1041
|
+
? `(${el.intent} icon)`
|
|
1042
|
+
: "(icon/image)";
|
|
1043
|
+
const ids = [];
|
|
1044
|
+
if (el.testID)
|
|
1045
|
+
ids.push(`testID="${el.testID}"`);
|
|
1046
|
+
if (el.accessibilityLabel)
|
|
1047
|
+
ids.push(`a11y="${el.accessibilityLabel}"`);
|
|
1048
|
+
const idStr = ids.length > 0 ? ` [${ids.join(", ")}]` : "";
|
|
1049
|
+
const inputStr = el.isInput ? " (input)" : "";
|
|
1050
|
+
const wrapperStr = el.isWrapper ? " [wrapper — skip unless dismissing keyboard]" : "";
|
|
1051
|
+
const nearPart = el.nearbyText ? ` near "${el.nearbyText}"` : "";
|
|
1052
|
+
lines.push(`${num}. ${el.component} ${label}${nearPart} — center:(${el.center.x},${el.center.y}) frame:(${el.frame.x},${el.frame.y} ${el.frame.width}x${el.frame.height})${idStr}${inputStr}${wrapperStr}`);
|
|
1053
|
+
if (el.path)
|
|
1054
|
+
lines.push(` path: ${el.path}`);
|
|
1055
|
+
}
|
|
1056
|
+
return {
|
|
1057
|
+
success: true,
|
|
1058
|
+
result: lines.join("\n"),
|
|
1059
|
+
parsedElements: pressableElements
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
catch {
|
|
1063
|
+
// If parsing fails, return original result
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return result;
|
|
1067
|
+
}
|
|
1068
|
+
// ============================================================================
|
|
1069
|
+
// Press Element (invoke onPress via React Fiber Tree)
|
|
1070
|
+
// ============================================================================
|
|
1071
|
+
/**
|
|
1072
|
+
* Find a pressable element in the React fiber tree and invoke its onPress handler.
|
|
1073
|
+
* Matches by text content, testID, or component name.
|
|
1074
|
+
*/
|
|
1075
|
+
export async function pressElement(options) {
|
|
1076
|
+
const { text, testID, component, index = 0, maxTraversalDepth = 15 } = options;
|
|
1077
|
+
if (!text && !testID && !component) {
|
|
1078
|
+
return { success: false, error: "At least one of text, testID, or component must be provided." };
|
|
1079
|
+
}
|
|
1080
|
+
const esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1081
|
+
const textParam = text ? `'${esc(text)}'` : "null";
|
|
1082
|
+
const testIDParam = testID ? `'${esc(testID)}'` : "null";
|
|
1083
|
+
const componentParam = component ? `'${esc(component)}'` : "null";
|
|
1084
|
+
// --- Step 1: Walk fiber tree, collect pressable/input elements, dispatch measureInWindow ---
|
|
1085
|
+
const dispatchExpression = `
|
|
1086
|
+
(function() {
|
|
1087
|
+
var hook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
|
|
1088
|
+
if (!hook) return { error: 'React DevTools hook not found. Ensure app is running in __DEV__ mode.' };
|
|
1089
|
+
|
|
1090
|
+
var roots = [];
|
|
1091
|
+
if (hook.getFiberRoots) {
|
|
1092
|
+
roots = Array.from(hook.getFiberRoots(1) || []);
|
|
1093
|
+
}
|
|
1094
|
+
if (roots.length === 0 && hook.renderers) {
|
|
1095
|
+
for (var entry of hook.renderers) {
|
|
1096
|
+
var r = Array.from(hook.getFiberRoots ? (hook.getFiberRoots(entry[0]) || []) : []);
|
|
1097
|
+
if (r.length > 0) { roots = r; break; }
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
if (roots.length === 0) return { error: 'No fiber roots found. Is a React Native app mounted?' };
|
|
1101
|
+
|
|
1102
|
+
var searchText = ${textParam};
|
|
1103
|
+
var searchTestID = ${testIDParam};
|
|
1104
|
+
var searchComponent = ${componentParam};
|
|
1105
|
+
var targetIndex = ${index};
|
|
1106
|
+
var maxTraversalUp = ${maxTraversalDepth};
|
|
1107
|
+
|
|
1108
|
+
function getComponentName(fiber) {
|
|
1109
|
+
if (!fiber || !fiber.type) return null;
|
|
1110
|
+
if (typeof fiber.type === 'string') return fiber.type;
|
|
1111
|
+
return fiber.type.displayName || fiber.type.name || null;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// When a fiber holds a string/number child via memoizedProps.children, return it
|
|
1115
|
+
// without recursing — Text > RCTText > NativeText all carry the same string,
|
|
1116
|
+
// and walking through every layer duplicates it (e.g. "CircularsCircularsCirculars").
|
|
1117
|
+
function extractText(fiber, depth) {
|
|
1118
|
+
if (!fiber || depth > 5000) return '';
|
|
1119
|
+
var props = fiber.memoizedProps;
|
|
1120
|
+
if (props) {
|
|
1121
|
+
var ch = props.children;
|
|
1122
|
+
if (typeof ch === 'string') return ch;
|
|
1123
|
+
if (typeof ch === 'number') return String(ch);
|
|
1124
|
+
if (Array.isArray(ch)) {
|
|
1125
|
+
var allPrimitive = ch.length > 0;
|
|
1126
|
+
var inline = [];
|
|
1127
|
+
for (var i = 0; i < ch.length; i++) {
|
|
1128
|
+
if (typeof ch[i] === 'string') inline.push(ch[i]);
|
|
1129
|
+
else if (typeof ch[i] === 'number') inline.push(String(ch[i]));
|
|
1130
|
+
else { allPrimitive = false; }
|
|
1131
|
+
}
|
|
1132
|
+
if (allPrimitive && inline.length > 0) return inline.join('');
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
var parts = [];
|
|
1136
|
+
var child = fiber.child;
|
|
1137
|
+
while (child) {
|
|
1138
|
+
var t = extractText(child, depth + 1);
|
|
1139
|
+
if (t) parts.push(t);
|
|
1140
|
+
child = child.sibling;
|
|
1141
|
+
}
|
|
1142
|
+
return parts.join(' ');
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
function getMeasurable(fiber) {
|
|
1146
|
+
var sn = fiber.stateNode;
|
|
1147
|
+
if (!sn) return null;
|
|
1148
|
+
if (typeof sn.measureInWindow === 'function') return sn;
|
|
1149
|
+
if (sn.canonical && sn.canonical.publicInstance &&
|
|
1150
|
+
typeof sn.canonical.publicInstance.measureInWindow === 'function') {
|
|
1151
|
+
return sn.canonical.publicInstance;
|
|
1152
|
+
}
|
|
1153
|
+
// Fabric leaf nodes like RCTText have no publicInstance. Measure via
|
|
1154
|
+
// the native Fabric UIManager using the shadow node instead — needed
|
|
1155
|
+
// for text bounds that are tight around the glyphs (not the scroll
|
|
1156
|
+
// container the text happens to live inside).
|
|
1157
|
+
if (sn.node && globalThis.nativeFabricUIManager &&
|
|
1158
|
+
typeof globalThis.nativeFabricUIManager.measureInWindow === 'function') {
|
|
1159
|
+
var node = sn.node;
|
|
1160
|
+
return {
|
|
1161
|
+
measureInWindow: function(cb) {
|
|
1162
|
+
try { globalThis.nativeFabricUIManager.measureInWindow(node, cb); } catch(e) {}
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
return null;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
var RN_PRIMITIVES = /^(Animated\\(.*|withAnimated.*|AnimatedComponent.*|ForwardRef.*|memo\\(.*|Context\\.Consumer|Context\\.Provider|ScrollViewContext(Base)?|VirtualizedListContext(Resetter)?|TextInputContext|KeyboardAvoidingViewContext|RCT.*|RNS.*|RNC.*|ViewManagerAdapter_.*|VirtualizedList.*|CellRenderer.*|FrameSizeProvider.*|MaybeScreenContainer|MaybeScreen|Navigation.*|Screen$|ScreenStack|ScreenContainer|ScreenContentWrapper|SceneView|DelayedFreeze|Freeze|Suspender|DebugContainer|StaticContainer|SafeAreaProvider.*|SafeAreaFrameContext|SafeAreaInsetsContext|ExpoRoot|ExpoRootComponent|GestureHandler.*|NativeViewGestureHandler|GestureDetector|PanGestureHandler|Reanimated.*|BottomTabNavigator|TabLayout|RouteNode|Route$|KeyboardProvider|PortalProviderComponent|BottomSheetModalProviderWrapper|ThemeContext|ThemeProvider|TextAncestorContext|PressabilityDebugView|TouchableHighlightImpl|StatusBarOverlay|BottomSheetHostingContainerComponent|BottomSheetGestureHandlersProvider|BottomSheetBackdropContainerComponent|BottomSheetContainerComponent|BottomSheetDraggableViewComponent|BottomSheetHandleContainerComponent|BottomSheetBackgroundContainerComponent|DebuggingOverlay|InspectorDeferred|Inspector|InspectorOverlay|InspectorPanel|StyleInspector|BoxInspector|BoxContainer|ElementBox|BorderBox|InspectorPanelButton)$/;
|
|
1170
|
+
|
|
1171
|
+
function isScreenHidden(name, props) {
|
|
1172
|
+
if (!props) return false;
|
|
1173
|
+
if (name === 'RNSScreen' && props['aria-hidden'] === true) return true;
|
|
1174
|
+
if (name === 'MaybeScreen' && props.active === 0) return true;
|
|
1175
|
+
if (name === 'SceneView' && props.focused === false) return true;
|
|
1176
|
+
return false;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function findMeaningfulAncestorName(fiber) {
|
|
1180
|
+
var cur = fiber.return;
|
|
1181
|
+
var depth = 0;
|
|
1182
|
+
var fallbackName = null;
|
|
1183
|
+
while (cur && depth < 20) {
|
|
1184
|
+
var aname = getComponentName(cur);
|
|
1185
|
+
if (aname && typeof cur.type !== 'string') {
|
|
1186
|
+
if (!fallbackName) fallbackName = aname;
|
|
1187
|
+
if (!RN_PRIMITIVES.test(aname)) return aname;
|
|
1188
|
+
}
|
|
1189
|
+
cur = cur.return;
|
|
1190
|
+
depth++;
|
|
1191
|
+
}
|
|
1192
|
+
return fallbackName;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// Walk UP collecting testID/nativeID from ancestors. Stop at screen boundaries.
|
|
1196
|
+
function collectAncestorTestIDs(fiber, maxUp) {
|
|
1197
|
+
var ids = [];
|
|
1198
|
+
var cur = fiber.return;
|
|
1199
|
+
var d = 0;
|
|
1200
|
+
while (cur && d < maxUp) {
|
|
1201
|
+
var cname = getComponentName(cur);
|
|
1202
|
+
if (cname === 'RNSScreen' || cname === 'MaybeScreen' || cname === 'SceneView') break;
|
|
1203
|
+
var cp = cur.memoizedProps;
|
|
1204
|
+
if (cp) {
|
|
1205
|
+
if (typeof cp.testID === 'string' && cp.testID) ids.push(cp.testID);
|
|
1206
|
+
if (typeof cp.nativeID === 'string' && cp.nativeID) ids.push(cp.nativeID);
|
|
1207
|
+
}
|
|
1208
|
+
cur = cur.return;
|
|
1209
|
+
d++;
|
|
1210
|
+
}
|
|
1211
|
+
return ids;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
// Find the first measurable host descendant of a fiber.
|
|
1215
|
+
// For inputs, prefer TextInput-specific hosts over generic RCTView.
|
|
1216
|
+
function findFirstHost(fiber, depth, isInput) {
|
|
1217
|
+
if (!fiber || depth > 20) return null;
|
|
1218
|
+
if (typeof fiber.type === 'string' && getMeasurable(fiber)) {
|
|
1219
|
+
if (isInput) {
|
|
1220
|
+
var hostType = typeof fiber.type === 'string' ? fiber.type : '';
|
|
1221
|
+
if (hostType.indexOf('TextInput') !== -1 || hostType.indexOf('textinput') !== -1) {
|
|
1222
|
+
return fiber;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return fiber;
|
|
1226
|
+
}
|
|
1227
|
+
var child = fiber.child;
|
|
1228
|
+
var fallback = null;
|
|
1229
|
+
while (child) {
|
|
1230
|
+
var found = findFirstHost(child, depth + 1, isInput);
|
|
1231
|
+
if (found) {
|
|
1232
|
+
if (isInput) {
|
|
1233
|
+
var ft = typeof found.type === 'string' ? found.type : '';
|
|
1234
|
+
if (ft.indexOf('TextInput') !== -1 || ft.indexOf('textinput') !== -1) {
|
|
1235
|
+
return found;
|
|
1236
|
+
}
|
|
1237
|
+
if (!fallback) fallback = found;
|
|
1238
|
+
} else {
|
|
1239
|
+
return found;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
child = child.sibling;
|
|
1243
|
+
}
|
|
1244
|
+
return fallback;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
var hostFibers = [];
|
|
1248
|
+
var tapMeta = [];
|
|
1249
|
+
|
|
1250
|
+
// Phase 1: Walk the entire tree, collect all pressable/input elements
|
|
1251
|
+
function walkFiber(fiber, depth, path) {
|
|
1252
|
+
if (!fiber || depth > 5000) return;
|
|
1253
|
+
var name = getComponentName(fiber);
|
|
1254
|
+
var props = fiber.memoizedProps;
|
|
1255
|
+
|
|
1256
|
+
if (isScreenHidden(name, props)) return;
|
|
1257
|
+
|
|
1258
|
+
var isPressable = props && typeof props.onPress === 'function';
|
|
1259
|
+
var isInput = !isPressable && props && (typeof props.onChangeText === 'function' || typeof props.onFocus === 'function');
|
|
1260
|
+
|
|
1261
|
+
if (isPressable || isInput) {
|
|
1262
|
+
var text = '';
|
|
1263
|
+
if (isPressable) {
|
|
1264
|
+
text = extractText(fiber, 0);
|
|
1265
|
+
} else {
|
|
1266
|
+
var val = typeof props.value === 'string' ? props.value : '';
|
|
1267
|
+
var defVal = typeof props.defaultValue === 'string' ? props.defaultValue : '';
|
|
1268
|
+
var ph = typeof props.placeholder === 'string' ? props.placeholder : '';
|
|
1269
|
+
text = extractText(fiber, 0) || val || defVal || ph;
|
|
1270
|
+
}
|
|
1271
|
+
var tid = props.testID || props.nativeID || null;
|
|
1272
|
+
var meaningful = findMeaningfulAncestorName(fiber);
|
|
1273
|
+
var ancestorIDs = collectAncestorTestIDs(fiber, maxTraversalUp);
|
|
1274
|
+
|
|
1275
|
+
var host = findFirstHost(fiber, 0, isInput);
|
|
1276
|
+
if (host) {
|
|
1277
|
+
hostFibers.push(host);
|
|
1278
|
+
tapMeta.push({
|
|
1279
|
+
name: name || '(anonymous)',
|
|
1280
|
+
meaningfulComponentName: meaningful || null,
|
|
1281
|
+
text: text.substring(0, 100),
|
|
1282
|
+
testID: tid,
|
|
1283
|
+
ancestorTestIDs: ancestorIDs,
|
|
1284
|
+
path: path.join(' > '),
|
|
1285
|
+
isInput: isInput,
|
|
1286
|
+
isPressable: isPressable,
|
|
1287
|
+
source: 'direct'
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
var child = fiber.child;
|
|
1293
|
+
while (child) {
|
|
1294
|
+
var childName = getComponentName(child);
|
|
1295
|
+
walkFiber(child, depth + 1, childName ? path.concat([childName]) : path);
|
|
1296
|
+
child = child.sibling;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
for (var ri = 0; ri < roots.length; ri++) {
|
|
1301
|
+
walkFiber(roots[ri].current, 0, []);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Phase 2a: testID on non-pressable wrapper — walk UP or DOWN to pressable/input.
|
|
1305
|
+
// Skipped if Phase 1 already matched via own testID or ancestor testID.
|
|
1306
|
+
if (searchTestID !== null) {
|
|
1307
|
+
var hasEnrichedTestIDMatch = false;
|
|
1308
|
+
for (var di = 0; di < tapMeta.length; di++) {
|
|
1309
|
+
if (tapMeta[di].testID === searchTestID) { hasEnrichedTestIDMatch = true; break; }
|
|
1310
|
+
var aids = tapMeta[di].ancestorTestIDs || [];
|
|
1311
|
+
for (var ai = 0; ai < aids.length; ai++) {
|
|
1312
|
+
if (aids[ai] === searchTestID) { hasEnrichedTestIDMatch = true; break; }
|
|
1313
|
+
}
|
|
1314
|
+
if (hasEnrichedTestIDMatch) break;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (!hasEnrichedTestIDMatch) {
|
|
1318
|
+
function findDescendantPressable(fiber, d) {
|
|
1319
|
+
if (!fiber || d > 10) return null;
|
|
1320
|
+
var fp = fiber.memoizedProps;
|
|
1321
|
+
var dIsPressable = fp && typeof fp.onPress === 'function';
|
|
1322
|
+
var dIsInput = !dIsPressable && fp && (typeof fp.onChangeText === 'function' || typeof fp.onFocus === 'function');
|
|
1323
|
+
if (dIsPressable || dIsInput) return { fiber: fiber, isPressable: dIsPressable, isInput: dIsInput };
|
|
1324
|
+
var c = fiber.child;
|
|
1325
|
+
while (c) {
|
|
1326
|
+
var r = findDescendantPressable(c, d + 1);
|
|
1327
|
+
if (r) return r;
|
|
1328
|
+
c = c.sibling;
|
|
1329
|
+
}
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function findByTestID2a(fiber, path) {
|
|
1334
|
+
if (!fiber) return;
|
|
1335
|
+
var name = getComponentName(fiber);
|
|
1336
|
+
var props = fiber.memoizedProps;
|
|
1337
|
+
if (isScreenHidden(name, props)) return;
|
|
1338
|
+
|
|
1339
|
+
var tid = props && (props.testID || props.nativeID || null);
|
|
1340
|
+
if (tid === searchTestID) {
|
|
1341
|
+
var nIsPressable = props && typeof props.onPress === 'function';
|
|
1342
|
+
var nIsInput = !nIsPressable && props && (typeof props.onChangeText === 'function' || typeof props.onFocus === 'function');
|
|
1343
|
+
|
|
1344
|
+
if (nIsPressable || nIsInput) {
|
|
1345
|
+
var text = nIsPressable ? extractText(fiber, 0) : (extractText(fiber, 0) || (typeof props.value === 'string' ? props.value : '') || (typeof props.defaultValue === 'string' ? props.defaultValue : '') || (typeof props.placeholder === 'string' ? props.placeholder : ''));
|
|
1346
|
+
var host = findFirstHost(fiber, 0, nIsInput);
|
|
1347
|
+
if (host) {
|
|
1348
|
+
hostFibers.push(host);
|
|
1349
|
+
tapMeta.push({
|
|
1350
|
+
name: name || '(anonymous)',
|
|
1351
|
+
meaningfulComponentName: findMeaningfulAncestorName(fiber) || null,
|
|
1352
|
+
text: text.substring(0, 100),
|
|
1353
|
+
testID: searchTestID,
|
|
1354
|
+
ancestorTestIDs: [],
|
|
1355
|
+
path: path.join(' > '),
|
|
1356
|
+
isInput: nIsInput,
|
|
1357
|
+
isPressable: nIsPressable,
|
|
1358
|
+
source: 'testID-direct'
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
} else {
|
|
1362
|
+
var foundAncestor = false;
|
|
1363
|
+
var parent = fiber.return;
|
|
1364
|
+
var d = 0;
|
|
1365
|
+
while (parent && d < maxTraversalUp) {
|
|
1366
|
+
var pp = parent.memoizedProps;
|
|
1367
|
+
var pIsPressable = pp && typeof pp.onPress === 'function';
|
|
1368
|
+
var pIsInput = !pIsPressable && pp && (typeof pp.onChangeText === 'function' || typeof pp.onFocus === 'function');
|
|
1369
|
+
if (pIsPressable || pIsInput) {
|
|
1370
|
+
var pText = pIsPressable ? extractText(parent, 0) : (extractText(parent, 0) || (typeof pp.value === 'string' ? pp.value : '') || (typeof pp.defaultValue === 'string' ? pp.defaultValue : '') || (typeof pp.placeholder === 'string' ? pp.placeholder : ''));
|
|
1371
|
+
var host = findFirstHost(parent, 0, pIsInput);
|
|
1372
|
+
if (host) {
|
|
1373
|
+
hostFibers.push(host);
|
|
1374
|
+
tapMeta.push({
|
|
1375
|
+
name: name || '(anonymous)',
|
|
1376
|
+
meaningfulComponentName: findMeaningfulAncestorName(parent) || null,
|
|
1377
|
+
text: pText.substring(0, 100),
|
|
1378
|
+
testID: pp.testID || pp.nativeID || searchTestID,
|
|
1379
|
+
ancestorTestIDs: [],
|
|
1380
|
+
path: path.join(' > '),
|
|
1381
|
+
isInput: pIsInput,
|
|
1382
|
+
isPressable: pIsPressable,
|
|
1383
|
+
source: 'testID-ancestor'
|
|
1384
|
+
});
|
|
1385
|
+
foundAncestor = true;
|
|
1386
|
+
}
|
|
1387
|
+
break;
|
|
1388
|
+
}
|
|
1389
|
+
parent = parent.return;
|
|
1390
|
+
d++;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (!foundAncestor) {
|
|
1394
|
+
var desc = findDescendantPressable(fiber, 0);
|
|
1395
|
+
if (desc) {
|
|
1396
|
+
var dp = desc.fiber.memoizedProps;
|
|
1397
|
+
var dText = desc.isPressable ? extractText(desc.fiber, 0) : (extractText(desc.fiber, 0) || (typeof dp.value === 'string' ? dp.value : '') || (typeof dp.defaultValue === 'string' ? dp.defaultValue : '') || (typeof dp.placeholder === 'string' ? dp.placeholder : ''));
|
|
1398
|
+
var dhost = findFirstHost(desc.fiber, 0, desc.isInput);
|
|
1399
|
+
if (dhost) {
|
|
1400
|
+
hostFibers.push(dhost);
|
|
1401
|
+
tapMeta.push({
|
|
1402
|
+
name: getComponentName(desc.fiber) || '(anonymous)',
|
|
1403
|
+
meaningfulComponentName: findMeaningfulAncestorName(desc.fiber) || null,
|
|
1404
|
+
text: dText.substring(0, 100),
|
|
1405
|
+
testID: dp.testID || dp.nativeID || searchTestID,
|
|
1406
|
+
ancestorTestIDs: [searchTestID],
|
|
1407
|
+
path: path.join(' > '),
|
|
1408
|
+
isInput: desc.isInput,
|
|
1409
|
+
isPressable: desc.isPressable,
|
|
1410
|
+
source: 'testID-descendant'
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
var child = fiber.child;
|
|
1418
|
+
while (child) {
|
|
1419
|
+
var childName = getComponentName(child);
|
|
1420
|
+
findByTestID2a(child, childName ? path.concat([childName]) : path);
|
|
1421
|
+
child = child.sibling;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
for (var ri2a = 0; ri2a < roots.length; ri2a++) {
|
|
1425
|
+
findByTestID2a(roots[ri2a].current, []);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// Phase 2b: component name on non-pressable node — walk UP or DOWN to pressable parent.
|
|
1431
|
+
// Skipped if Phase 1 already matched via own name or meaningfulComponentName.
|
|
1432
|
+
if (searchComponent !== null) {
|
|
1433
|
+
var scLower = searchComponent.toLowerCase();
|
|
1434
|
+
var hasEnrichedComponentMatch = false;
|
|
1435
|
+
for (var ci = 0; ci < tapMeta.length; ci++) {
|
|
1436
|
+
var cn = (tapMeta[ci].name || '').toLowerCase();
|
|
1437
|
+
var cm = (tapMeta[ci].meaningfulComponentName || '').toLowerCase();
|
|
1438
|
+
if (cn.indexOf(scLower) !== -1 || cm.indexOf(scLower) !== -1) {
|
|
1439
|
+
hasEnrichedComponentMatch = true; break;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
if (!hasEnrichedComponentMatch) {
|
|
1444
|
+
function findDescendantPressableOnly(fiber, d) {
|
|
1445
|
+
if (!fiber || d > 10) return null;
|
|
1446
|
+
var fp = fiber.memoizedProps;
|
|
1447
|
+
if (fp && typeof fp.onPress === 'function') return fiber;
|
|
1448
|
+
var c = fiber.child;
|
|
1449
|
+
while (c) {
|
|
1450
|
+
var r = findDescendantPressableOnly(c, d + 1);
|
|
1451
|
+
if (r) return r;
|
|
1452
|
+
c = c.sibling;
|
|
1453
|
+
}
|
|
1454
|
+
return null;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
function findByName2b(fiber, path) {
|
|
1458
|
+
if (!fiber) return;
|
|
1459
|
+
var name = getComponentName(fiber);
|
|
1460
|
+
var props = fiber.memoizedProps;
|
|
1461
|
+
if (isScreenHidden(name, props)) return;
|
|
1462
|
+
|
|
1463
|
+
if (name && name.toLowerCase().indexOf(scLower) !== -1) {
|
|
1464
|
+
var foundAncestor = false;
|
|
1465
|
+
var parent = fiber.return;
|
|
1466
|
+
var d = 0;
|
|
1467
|
+
while (parent && d < maxTraversalUp) {
|
|
1468
|
+
var pp = parent.memoizedProps;
|
|
1469
|
+
if (pp && typeof pp.onPress === 'function') {
|
|
1470
|
+
var text = extractText(parent, 0);
|
|
1471
|
+
var host = findFirstHost(parent, 0, false);
|
|
1472
|
+
if (host) {
|
|
1473
|
+
hostFibers.push(host);
|
|
1474
|
+
tapMeta.push({
|
|
1475
|
+
name: name,
|
|
1476
|
+
meaningfulComponentName: findMeaningfulAncestorName(parent) || null,
|
|
1477
|
+
text: text.substring(0, 100),
|
|
1478
|
+
testID: pp.testID || pp.nativeID || null,
|
|
1479
|
+
ancestorTestIDs: [],
|
|
1480
|
+
path: path.join(' > '),
|
|
1481
|
+
isInput: false,
|
|
1482
|
+
isPressable: true,
|
|
1483
|
+
source: 'component-ancestor'
|
|
1484
|
+
});
|
|
1485
|
+
foundAncestor = true;
|
|
1486
|
+
}
|
|
1487
|
+
break;
|
|
1488
|
+
}
|
|
1489
|
+
parent = parent.return;
|
|
1490
|
+
d++;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
if (!foundAncestor) {
|
|
1494
|
+
var descFiber = findDescendantPressableOnly(fiber, 0);
|
|
1495
|
+
if (descFiber) {
|
|
1496
|
+
var dp = descFiber.memoizedProps;
|
|
1497
|
+
var dText = extractText(descFiber, 0);
|
|
1498
|
+
var dhost = findFirstHost(descFiber, 0, false);
|
|
1499
|
+
if (dhost) {
|
|
1500
|
+
hostFibers.push(dhost);
|
|
1501
|
+
tapMeta.push({
|
|
1502
|
+
name: getComponentName(descFiber) || '(anonymous)',
|
|
1503
|
+
meaningfulComponentName: name,
|
|
1504
|
+
text: dText.substring(0, 100),
|
|
1505
|
+
testID: dp.testID || dp.nativeID || null,
|
|
1506
|
+
ancestorTestIDs: [],
|
|
1507
|
+
path: path.join(' > '),
|
|
1508
|
+
isInput: false,
|
|
1509
|
+
isPressable: true,
|
|
1510
|
+
source: 'component-descendant'
|
|
1511
|
+
});
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
var child = fiber.child;
|
|
1517
|
+
while (child) {
|
|
1518
|
+
var childName = getComponentName(child);
|
|
1519
|
+
findByName2b(child, childName ? path.concat([childName]) : path);
|
|
1520
|
+
child = child.sibling;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
for (var ri2b = 0; ri2b < roots.length; ri2b++) {
|
|
1524
|
+
findByName2b(roots[ri2b].current, []);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (hostFibers.length === 0) {
|
|
1530
|
+
var criteria = [];
|
|
1531
|
+
if (searchText !== null) criteria.push('text="' + searchText + '"');
|
|
1532
|
+
if (searchTestID !== null) criteria.push('testID="' + searchTestID + '"');
|
|
1533
|
+
if (searchComponent !== null) criteria.push('component="' + searchComponent + '"');
|
|
1534
|
+
return { error: 'No pressable or focusable elements found. Searched for: ' + criteria.join(', ') };
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// Store host fibers and metadata globally for step 2, dispatch measureInWindow
|
|
1538
|
+
globalThis.__tapHostFibers = hostFibers;
|
|
1539
|
+
globalThis.__tapMeta = tapMeta;
|
|
1540
|
+
globalThis.__tapMeasurements = new Array(hostFibers.length).fill(null);
|
|
1541
|
+
|
|
1542
|
+
for (var mi = 0; mi < hostFibers.length; mi++) {
|
|
1543
|
+
try {
|
|
1544
|
+
(function(idx) {
|
|
1545
|
+
getMeasurable(hostFibers[idx]).measureInWindow(function(fx, fy, fw, fh) {
|
|
1546
|
+
globalThis.__tapMeasurements[idx] = { x: fx, y: fy, width: fw, height: fh };
|
|
1547
|
+
});
|
|
1548
|
+
})(mi);
|
|
1549
|
+
} catch(e) {}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
return { count: hostFibers.length };
|
|
1553
|
+
})()
|
|
1554
|
+
`;
|
|
1555
|
+
const dispatchResult = await executeInApp(dispatchExpression, false, { timeoutMs: 30000, originatingToolName: "tap" }, options.device);
|
|
1556
|
+
if (!dispatchResult.success)
|
|
1557
|
+
return dispatchResult;
|
|
1558
|
+
try {
|
|
1559
|
+
const parsed = JSON.parse(dispatchResult.result || "{}");
|
|
1560
|
+
if (parsed.error)
|
|
1561
|
+
return { success: false, error: parsed.error };
|
|
1562
|
+
}
|
|
1563
|
+
catch {
|
|
1564
|
+
/* ignore */
|
|
1565
|
+
}
|
|
1566
|
+
// Wait for measureInWindow callbacks
|
|
1567
|
+
await delay(300);
|
|
1568
|
+
// --- Step 2: Read measurements, filter visible, match by query ---
|
|
1569
|
+
const resolveExpression = `
|
|
1570
|
+
(function() {
|
|
1571
|
+
var hostFibers = globalThis.__tapHostFibers;
|
|
1572
|
+
var meta = globalThis.__tapMeta;
|
|
1573
|
+
var measurements = globalThis.__tapMeasurements;
|
|
1574
|
+
globalThis.__tapHostFibers = null;
|
|
1575
|
+
globalThis.__tapMeta = null;
|
|
1576
|
+
globalThis.__tapMeasurements = null;
|
|
1577
|
+
|
|
1578
|
+
if (!hostFibers || !measurements || !meta) {
|
|
1579
|
+
return { error: 'No measurement data. Dispatch step may have failed.' };
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
var searchText = ${textParam};
|
|
1583
|
+
var searchTestID = ${testIDParam};
|
|
1584
|
+
var searchComponent = ${componentParam};
|
|
1585
|
+
var targetIndex = ${index};
|
|
1586
|
+
|
|
1587
|
+
// Determine viewport bounds
|
|
1588
|
+
var viewportW = 9999, viewportH = 9999;
|
|
1589
|
+
for (var v = 0; v < measurements.length; v++) {
|
|
1590
|
+
if (measurements[v] && measurements[v].x === 0 && measurements[v].y <= 0 &&
|
|
1591
|
+
measurements[v].width > 0 && measurements[v].height > 0) {
|
|
1592
|
+
viewportW = measurements[v].width;
|
|
1593
|
+
viewportH = measurements[v].height + measurements[v].y;
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// Filter visible and match. Track query-matching elements that fail
|
|
1599
|
+
// the visibility filter so we can distinguish "exists but off-screen /
|
|
1600
|
+
// not laid out" from "doesn't exist in the fiber tree at all".
|
|
1601
|
+
var matches = [];
|
|
1602
|
+
var invisibleMatches = [];
|
|
1603
|
+
for (var i = 0; i < measurements.length; i++) {
|
|
1604
|
+
var m = measurements[i];
|
|
1605
|
+
if (!m) continue;
|
|
1606
|
+
|
|
1607
|
+
var info = meta[i];
|
|
1608
|
+
|
|
1609
|
+
// Match by query — OR across own and enriched identifiers
|
|
1610
|
+
var matched = true;
|
|
1611
|
+
if (searchText !== null) {
|
|
1612
|
+
matched = matched && info.text.toLowerCase().indexOf(searchText.toLowerCase()) !== -1;
|
|
1613
|
+
}
|
|
1614
|
+
if (searchTestID !== null) {
|
|
1615
|
+
var ownTidMatch = info.testID === searchTestID;
|
|
1616
|
+
var aTids = info.ancestorTestIDs || [];
|
|
1617
|
+
var ancestorTidMatch = false;
|
|
1618
|
+
for (var ti = 0; ti < aTids.length; ti++) {
|
|
1619
|
+
if (aTids[ti] === searchTestID) { ancestorTidMatch = true; break; }
|
|
1620
|
+
}
|
|
1621
|
+
matched = matched && (ownTidMatch || ancestorTidMatch);
|
|
1622
|
+
}
|
|
1623
|
+
if (searchComponent !== null) {
|
|
1624
|
+
var scq = searchComponent.toLowerCase();
|
|
1625
|
+
var ownNameMatch = (info.name || '').toLowerCase().indexOf(scq) !== -1;
|
|
1626
|
+
var meaningfulMatch = (info.meaningfulComponentName || '').toLowerCase().indexOf(scq) !== -1;
|
|
1627
|
+
matched = matched && (ownNameMatch || meaningfulMatch);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
if (!matched) continue;
|
|
1631
|
+
|
|
1632
|
+
// Visibility filter: positive dimensions, within viewport
|
|
1633
|
+
var visible = m.width > 0 && m.height > 0 &&
|
|
1634
|
+
(m.x + m.width >= 0) && (m.y + m.height >= 0) &&
|
|
1635
|
+
m.x <= viewportW && m.y <= viewportH;
|
|
1636
|
+
|
|
1637
|
+
if (visible) {
|
|
1638
|
+
matches.push({
|
|
1639
|
+
name: info.name,
|
|
1640
|
+
text: info.text,
|
|
1641
|
+
testID: info.testID,
|
|
1642
|
+
path: info.path,
|
|
1643
|
+
isInput: info.isInput,
|
|
1644
|
+
x: Math.round(m.x + m.width / 2),
|
|
1645
|
+
y: Math.round(m.y + m.height / 2)
|
|
1646
|
+
});
|
|
1647
|
+
} else {
|
|
1648
|
+
var reason;
|
|
1649
|
+
if (m.width <= 0 || m.height <= 0) reason = 'zero-size';
|
|
1650
|
+
else if (m.y >= viewportH) reason = 'below-viewport';
|
|
1651
|
+
else if (m.y + m.height <= 0) reason = 'above-viewport';
|
|
1652
|
+
else if (m.x >= viewportW) reason = 'right-of-viewport';
|
|
1653
|
+
else if (m.x + m.width <= 0) reason = 'left-of-viewport';
|
|
1654
|
+
else reason = 'off-screen';
|
|
1655
|
+
invisibleMatches.push({
|
|
1656
|
+
name: info.name,
|
|
1657
|
+
text: info.text,
|
|
1658
|
+
testID: info.testID,
|
|
1659
|
+
reason: reason,
|
|
1660
|
+
x: Math.round(m.x),
|
|
1661
|
+
y: Math.round(m.y),
|
|
1662
|
+
width: Math.round(m.width),
|
|
1663
|
+
height: Math.round(m.height)
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
if (matches.length === 0) {
|
|
1669
|
+
var criteria = [];
|
|
1670
|
+
if (searchText !== null) criteria.push('text="' + searchText + '"');
|
|
1671
|
+
if (searchTestID !== null) criteria.push('testID="' + searchTestID + '"');
|
|
1672
|
+
if (searchComponent !== null) criteria.push('component="' + searchComponent + '"');
|
|
1673
|
+
if (invisibleMatches.length > 0) {
|
|
1674
|
+
// Element exists in the fiber tree but isn't visible — scroll,
|
|
1675
|
+
// dismiss an overlay, or wait for layout before retrying.
|
|
1676
|
+
return {
|
|
1677
|
+
error: 'Found ' + invisibleMatches.length + ' fiber match(es) for ' + criteria.join(', ') + ' but none are visible (reasons: ' + invisibleMatches.slice(0, 3).map(function(x) { return x.reason; }).join(', ') + '). The element exists in the React tree but is off-screen or has zero dimensions.',
|
|
1678
|
+
invisibleMatches: invisibleMatches.slice(0, 10),
|
|
1679
|
+
existsInTree: true
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
return {
|
|
1683
|
+
error: 'No pressable or focusable elements found matching: ' + criteria.join(', ') + '. The element is not present in the React tree on the current screen.',
|
|
1684
|
+
existsInTree: false
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
if (targetIndex >= matches.length) {
|
|
1689
|
+
return {
|
|
1690
|
+
error: 'Found ' + matches.length + ' visible match(es) but index ' + targetIndex + ' requested (0-based). Use index 0-' + (matches.length - 1) + '.',
|
|
1691
|
+
matches: matches.map(function(m, i) {
|
|
1692
|
+
return { index: i, component: m.name, text: m.text, testID: m.testID };
|
|
1693
|
+
})
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
var target = matches[targetIndex];
|
|
1698
|
+
var result = {
|
|
1699
|
+
needsNativeTap: true,
|
|
1700
|
+
nativeTapTarget: { x: target.x, y: target.y, unit: 'points' },
|
|
1701
|
+
pressed: target.name,
|
|
1702
|
+
matchIndex: targetIndex,
|
|
1703
|
+
totalMatches: matches.length,
|
|
1704
|
+
text: target.text,
|
|
1705
|
+
testID: target.testID,
|
|
1706
|
+
path: target.path,
|
|
1707
|
+
isInput: target.isInput
|
|
1708
|
+
};
|
|
1709
|
+
if (matches.length > 1) {
|
|
1710
|
+
result.allMatches = matches.map(function(m, i) {
|
|
1711
|
+
return { index: i, component: m.name, text: m.text, testID: m.testID, x: m.x, y: m.y };
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
return result;
|
|
1715
|
+
})()
|
|
1716
|
+
`;
|
|
1717
|
+
return executeInApp(resolveExpression, false, { timeoutMs: 10000, originatingToolName: "tap" }, options.device);
|
|
1718
|
+
}
|
|
1719
|
+
//# sourceMappingURL=pressables.js.map
|