execbro 2.3.0 → 2.6.0
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/README.md +2 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts +0 -12
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -1
- package/build/__tests__/helpers/fake-cdp-server.js +0 -22
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -1
- package/build/__tests__/helpers/tap-test-helpers.d.ts +0 -23
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -1
- package/build/__tests__/helpers/tap-test-helpers.js +2 -29
- package/build/__tests__/helpers/tap-test-helpers.js.map +1 -1
- package/build/__tests__/integration/auto-reconnect.test.js +4 -14
- package/build/__tests__/integration/auto-reconnect.test.js.map +1 -1
- package/build/__tests__/integration/connection-health.test.js +1 -10
- package/build/__tests__/integration/connection-health.test.js.map +1 -1
- package/build/__tests__/integration/execute-in-app.test.js +1 -11
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -1
- package/build/__tests__/integration/native-logs.test.js +0 -5
- package/build/__tests__/integration/native-logs.test.js.map +1 -1
- package/build/__tests__/integration/per-call-timeout.test.js +1 -5
- package/build/__tests__/integration/per-call-timeout.test.js.map +1 -1
- package/build/__tests__/integration/socket-close-midflight.test.js +2 -12
- package/build/__tests__/integration/socket-close-midflight.test.js.map +1 -1
- package/build/__tests__/integration/stale-target-probe.test.js +2 -17
- package/build/__tests__/integration/stale-target-probe.test.js.map +1 -1
- package/build/__tests__/integration/tap-strategies.test.js +2 -36
- package/build/__tests__/integration/tap-strategies.test.js.map +1 -1
- package/build/__tests__/integration/text-input.test.js +0 -39
- package/build/__tests__/integration/text-input.test.js.map +1 -1
- package/build/__tests__/integration/tools.test.js +1 -7
- package/build/__tests__/integration/tools.test.js.map +1 -1
- package/build/__tests__/setup.js +0 -2
- package/build/__tests__/setup.js.map +1 -1
- package/build/__tests__/unit/androidEmulatorAvds.test.js +0 -10
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -1
- package/build/__tests__/unit/androidPinch.test.d.ts +2 -0
- package/build/__tests__/unit/androidPinch.test.d.ts.map +1 -0
- package/build/__tests__/unit/androidPinch.test.js +119 -0
- package/build/__tests__/unit/androidPinch.test.js.map +1 -0
- package/build/__tests__/unit/appContext.test.js +0 -2
- package/build/__tests__/unit/appContext.test.js.map +1 -1
- package/build/__tests__/unit/appRequest.test.js +0 -3
- package/build/__tests__/unit/appRequest.test.js.map +1 -1
- package/build/__tests__/unit/buildToken.test.js +0 -6
- package/build/__tests__/unit/buildToken.test.js.map +1 -1
- package/build/__tests__/unit/burst.test.js +0 -1
- package/build/__tests__/unit/burst.test.js.map +1 -1
- package/build/__tests__/unit/clearLogsNative.test.js +0 -8
- package/build/__tests__/unit/clearLogsNative.test.js.map +1 -1
- package/build/__tests__/unit/componentFormatNaming.test.js +0 -13
- package/build/__tests__/unit/componentFormatNaming.test.js.map +1 -1
- package/build/__tests__/unit/componentSource.test.js +0 -4
- package/build/__tests__/unit/componentSource.test.js.map +1 -1
- package/build/__tests__/unit/componentSourceResolve.test.js +0 -1
- package/build/__tests__/unit/componentSourceResolve.test.js.map +1 -1
- package/build/__tests__/unit/contextWiring.test.js +0 -3
- package/build/__tests__/unit/contextWiring.test.js.map +1 -1
- package/build/__tests__/unit/coordinateMiss.test.js +0 -16
- package/build/__tests__/unit/coordinateMiss.test.js.map +1 -1
- package/build/__tests__/unit/deviceDiscovery.test.js +0 -4
- package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolution.test.js +0 -2
- package/build/__tests__/unit/deviceResolution.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolver.test.js +0 -1
- package/build/__tests__/unit/deviceResolver.test.js.map +1 -1
- package/build/__tests__/unit/deviceResolverStaleRetry.test.js +0 -8
- package/build/__tests__/unit/deviceResolverStaleRetry.test.js.map +1 -1
- package/build/__tests__/unit/emulatorBridge.test.d.ts +2 -0
- package/build/__tests__/unit/emulatorBridge.test.d.ts.map +1 -0
- package/build/__tests__/unit/emulatorBridge.test.js +76 -0
- package/build/__tests__/unit/emulatorBridge.test.js.map +1 -0
- package/build/__tests__/unit/emulatorGrpc.test.d.ts +2 -0
- package/build/__tests__/unit/emulatorGrpc.test.d.ts.map +1 -0
- package/build/__tests__/unit/emulatorGrpc.test.js +33 -0
- package/build/__tests__/unit/emulatorGrpc.test.js.map +1 -0
- package/build/__tests__/unit/epochRender.test.js +0 -3
- package/build/__tests__/unit/epochRender.test.js.map +1 -1
- package/build/__tests__/unit/errorCategorization.test.js +0 -18
- package/build/__tests__/unit/errorCategorization.test.js.map +1 -1
- package/build/__tests__/unit/exec.test.js +0 -1
- package/build/__tests__/unit/exec.test.js.map +1 -1
- package/build/__tests__/unit/executor.test.js +0 -15
- package/build/__tests__/unit/executor.test.js.map +1 -1
- package/build/__tests__/unit/findElementTreeReuse.test.js +0 -12
- package/build/__tests__/unit/findElementTreeReuse.test.js.map +1 -1
- package/build/__tests__/unit/fingerprint.test.js +0 -4
- package/build/__tests__/unit/fingerprint.test.js.map +1 -1
- package/build/__tests__/unit/focusedInputTools.test.js +0 -4
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -1
- package/build/__tests__/unit/getLogsEmptyContract.test.js +0 -30
- package/build/__tests__/unit/getLogsEmptyContract.test.js.map +1 -1
- package/build/__tests__/unit/getLogsNativeSource.test.js +0 -22
- package/build/__tests__/unit/getLogsNativeSource.test.js.map +1 -1
- package/build/__tests__/unit/graphqlOperation.test.d.ts +2 -0
- package/build/__tests__/unit/graphqlOperation.test.d.ts.map +1 -0
- package/build/__tests__/unit/graphqlOperation.test.js +107 -0
- package/build/__tests__/unit/graphqlOperation.test.js.map +1 -0
- package/build/__tests__/unit/inputTarget.test.js +0 -16
- package/build/__tests__/unit/inputTarget.test.js.map +1 -1
- package/build/__tests__/unit/inputTextTool.test.js +0 -3
- package/build/__tests__/unit/inputTextTool.test.js.map +1 -1
- package/build/__tests__/unit/jsLogEvents.test.js +0 -3
- package/build/__tests__/unit/jsLogEvents.test.js.map +1 -1
- package/build/__tests__/unit/keyboardMetrics.test.js +0 -2
- package/build/__tests__/unit/keyboardMetrics.test.js.map +1 -1
- package/build/__tests__/unit/keyboardRaise.test.js +0 -2
- package/build/__tests__/unit/keyboardRaise.test.js.map +1 -1
- package/build/__tests__/unit/licenseGrace.test.js +1 -4
- package/build/__tests__/unit/licenseGrace.test.js.map +1 -1
- package/build/__tests__/unit/logEventFormat.test.js +0 -27
- package/build/__tests__/unit/logEventFormat.test.js.map +1 -1
- package/build/__tests__/unit/logEvents.test.js +1 -5
- package/build/__tests__/unit/logEvents.test.js.map +1 -1
- package/build/__tests__/unit/logGrouping.test.js +0 -2
- package/build/__tests__/unit/logGrouping.test.js.map +1 -1
- package/build/__tests__/unit/logOwnership.test.js +0 -11
- package/build/__tests__/unit/logOwnership.test.js.map +1 -1
- package/build/__tests__/unit/logRelevance.test.js +0 -6
- package/build/__tests__/unit/logRelevance.test.js.map +1 -1
- package/build/__tests__/unit/logSourceAndroid.test.js +0 -4
- package/build/__tests__/unit/logSourceAndroid.test.js.map +1 -1
- package/build/__tests__/unit/logSourceIos.test.js +0 -5
- package/build/__tests__/unit/logSourceIos.test.js.map +1 -1
- package/build/__tests__/unit/logs.test.js +0 -1
- package/build/__tests__/unit/logs.test.js.map +1 -1
- package/build/__tests__/unit/measureComponentTools.test.js +0 -1
- package/build/__tests__/unit/measureComponentTools.test.js.map +1 -1
- package/build/__tests__/unit/metro.test.js +0 -2
- package/build/__tests__/unit/metro.test.js.map +1 -1
- package/build/__tests__/unit/nativeInputValue.test.js +0 -4
- package/build/__tests__/unit/nativeInputValue.test.js.map +1 -1
- package/build/__tests__/unit/nativeLogs.test.js +1 -23
- package/build/__tests__/unit/nativeLogs.test.js.map +1 -1
- package/build/__tests__/unit/navigateVerify.test.js +0 -6
- package/build/__tests__/unit/navigateVerify.test.js.map +1 -1
- package/build/__tests__/unit/navigation.test.js +0 -5
- package/build/__tests__/unit/navigation.test.js.map +1 -1
- package/build/__tests__/unit/networkInterceptor.test.js +0 -2
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -1
- package/build/__tests__/unit/overlayGuard.test.d.ts +2 -0
- package/build/__tests__/unit/overlayGuard.test.d.ts.map +1 -0
- package/build/__tests__/unit/overlayGuard.test.js +123 -0
- package/build/__tests__/unit/overlayGuard.test.js.map +1 -0
- package/build/__tests__/unit/pendingExecutions.test.js +0 -6
- package/build/__tests__/unit/pendingExecutions.test.js.map +1 -1
- package/build/__tests__/unit/pinchGeometry.test.d.ts +2 -0
- package/build/__tests__/unit/pinchGeometry.test.d.ts.map +1 -0
- package/build/__tests__/unit/pinchGeometry.test.js +139 -0
- package/build/__tests__/unit/pinchGeometry.test.js.map +1 -0
- package/build/__tests__/unit/pinchTool.test.d.ts +2 -0
- package/build/__tests__/unit/pinchTool.test.d.ts.map +1 -0
- package/build/__tests__/unit/pinchTool.test.js +39 -0
- package/build/__tests__/unit/pinchTool.test.js.map +1 -0
- package/build/__tests__/unit/probe.test.js +0 -1
- package/build/__tests__/unit/probe.test.js.map +1 -1
- package/build/__tests__/unit/projectMemory.test.js +6 -7
- package/build/__tests__/unit/projectMemory.test.js.map +1 -1
- package/build/__tests__/unit/projectStore.test.js +2 -2
- package/build/__tests__/unit/projectStore.test.js.map +1 -1
- package/build/__tests__/unit/reloadAppAutoConnect.test.js +0 -9
- package/build/__tests__/unit/reloadAppAutoConnect.test.js.map +1 -1
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js +3 -3
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -1
- package/build/__tests__/unit/routeHistory.test.d.ts +2 -0
- package/build/__tests__/unit/routeHistory.test.d.ts.map +1 -0
- package/build/__tests__/unit/routeHistory.test.js +136 -0
- package/build/__tests__/unit/routeHistory.test.js.map +1 -0
- package/build/__tests__/unit/screenSpace.test.js +30 -13
- package/build/__tests__/unit/screenSpace.test.js.map +1 -1
- package/build/__tests__/unit/screenState.test.js +3 -6
- package/build/__tests__/unit/screenState.test.js.map +1 -1
- package/build/__tests__/unit/screenStateInputValue.test.js +0 -7
- package/build/__tests__/unit/screenStateInputValue.test.js.map +1 -1
- package/build/__tests__/unit/screenStateKeyboard.test.js +0 -1
- package/build/__tests__/unit/screenStateKeyboard.test.js.map +1 -1
- package/build/__tests__/unit/screenshot-diff.test.js +0 -6
- package/build/__tests__/unit/screenshot-diff.test.js.map +1 -1
- package/build/__tests__/unit/sdkMirrorPoller.test.js +0 -8
- package/build/__tests__/unit/sdkMirrorPoller.test.js.map +1 -1
- package/build/__tests__/unit/settleAndDiff.test.js +0 -11
- package/build/__tests__/unit/settleAndDiff.test.js.map +1 -1
- package/build/__tests__/unit/signedVerdict.test.js +0 -5
- package/build/__tests__/unit/signedVerdict.test.js.map +1 -1
- package/build/__tests__/unit/swipeDirection.test.js +6 -9
- package/build/__tests__/unit/swipeDirection.test.js.map +1 -1
- package/build/__tests__/unit/symbolicate.test.js +0 -1
- package/build/__tests__/unit/symbolicate.test.js.map +1 -1
- package/build/__tests__/unit/tap.test.js +2 -40
- package/build/__tests__/unit/tap.test.js.map +1 -1
- package/build/__tests__/unit/textEntry.test.js +0 -27
- package/build/__tests__/unit/textEntry.test.js.map +1 -1
- package/build/__tests__/unit/toolDescriptions.test.js +0 -40
- package/build/__tests__/unit/toolDescriptions.test.js.map +1 -1
- package/build/__tests__/unit/toolsJson.test.js +0 -10
- package/build/__tests__/unit/toolsJson.test.js.map +1 -1
- package/build/__tests__/unit/transportClassify.test.js +0 -20
- package/build/__tests__/unit/transportClassify.test.js.map +1 -1
- package/build/__tests__/unit/truncate.test.js +0 -5
- package/build/__tests__/unit/truncate.test.js.map +1 -1
- package/build/__tests__/unit/usageCache.test.js +0 -3
- package/build/__tests__/unit/usageCache.test.js.map +1 -1
- package/build/__tests__/unit/usageGate.test.js +1 -1
- package/build/__tests__/unit/usageGate.test.js.map +1 -1
- package/build/__tests__/unit/visibility.test.js +0 -8
- package/build/__tests__/unit/visibility.test.js.map +1 -1
- package/build/__tests__/unit/waitForMeasurements.test.js +2 -7
- package/build/__tests__/unit/waitForMeasurements.test.js.map +1 -1
- package/build/bin.js +0 -18
- package/build/bin.js.map +1 -1
- package/build/core/android.d.ts +0 -120
- package/build/core/android.d.ts.map +1 -1
- package/build/core/android.js +4 -259
- package/build/core/android.js.map +1 -1
- package/build/core/androidPinch.d.ts +21 -0
- package/build/core/androidPinch.d.ts.map +1 -0
- package/build/core/androidPinch.js +72 -0
- package/build/core/androidPinch.js.map +1 -0
- package/build/core/androidSystemBars.d.ts +7 -0
- package/build/core/androidSystemBars.d.ts.map +1 -0
- package/build/core/androidSystemBars.js +21 -0
- package/build/core/androidSystemBars.js.map +1 -0
- package/build/core/appContext.d.ts +0 -50
- package/build/core/appContext.d.ts.map +1 -1
- package/build/core/appContext.js +0 -50
- package/build/core/appContext.js.map +1 -1
- package/build/core/appDetection.d.ts +0 -16
- package/build/core/appDetection.d.ts.map +1 -1
- package/build/core/appDetection.js +0 -36
- package/build/core/appDetection.js.map +1 -1
- package/build/core/appRequest.d.ts +0 -12
- package/build/core/appRequest.d.ts.map +1 -1
- package/build/core/appRequest.js +0 -15
- package/build/core/appRequest.js.map +1 -1
- package/build/core/bufferConfig.d.ts +0 -2
- package/build/core/bufferConfig.d.ts.map +1 -1
- package/build/core/bufferConfig.js +0 -2
- package/build/core/bufferConfig.js.map +1 -1
- package/build/core/bundle.js +0 -40
- package/build/core/bundle.js.map +1 -1
- package/build/core/componentSearch.d.ts +0 -19
- package/build/core/componentSearch.d.ts.map +1 -1
- package/build/core/componentSearch.js +1 -22
- package/build/core/componentSearch.js.map +1 -1
- package/build/core/componentSource.d.ts +0 -21
- package/build/core/componentSource.d.ts.map +1 -1
- package/build/core/componentSource.js +0 -27
- package/build/core/componentSource.js.map +1 -1
- package/build/core/componentTree.js +0 -12
- package/build/core/componentTree.js.map +1 -1
- package/build/core/config.d.ts +0 -8
- package/build/core/config.d.ts.map +1 -1
- package/build/core/config.js +0 -10
- package/build/core/config.js.map +1 -1
- package/build/core/connection.d.ts +0 -76
- package/build/core/connection.d.ts.map +1 -1
- package/build/core/connection.js +15 -340
- package/build/core/connection.js.map +1 -1
- package/build/core/connectionState.d.ts +0 -78
- package/build/core/connectionState.d.ts.map +1 -1
- package/build/core/connectionState.js +4 -92
- package/build/core/connectionState.js.map +1 -1
- package/build/core/debugGlobals.js +0 -68
- package/build/core/debugGlobals.js.map +1 -1
- package/build/core/deviceDiscovery.d.ts +0 -5
- package/build/core/deviceDiscovery.d.ts.map +1 -1
- package/build/core/deviceDiscovery.js +0 -12
- package/build/core/deviceDiscovery.js.map +1 -1
- package/build/core/deviceResolver.d.ts +0 -10
- package/build/core/deviceResolver.d.ts.map +1 -1
- package/build/core/deviceResolver.js +0 -54
- package/build/core/deviceResolver.js.map +1 -1
- package/build/core/emulatorBridge.d.ts +17 -0
- package/build/core/emulatorBridge.d.ts.map +1 -0
- package/build/core/emulatorBridge.js +78 -0
- package/build/core/emulatorBridge.js.map +1 -0
- package/build/core/emulatorGrpc.d.ts +14 -0
- package/build/core/emulatorGrpc.d.ts.map +1 -0
- package/build/core/emulatorGrpc.js +93 -0
- package/build/core/emulatorGrpc.js.map +1 -0
- package/build/core/epochRender.d.ts +0 -12
- package/build/core/epochRender.d.ts.map +1 -1
- package/build/core/epochRender.js +0 -12
- package/build/core/epochRender.js.map +1 -1
- package/build/core/errorScreenParser.d.ts +0 -21
- package/build/core/errorScreenParser.d.ts.map +1 -1
- package/build/core/errorScreenParser.js +0 -50
- package/build/core/errorScreenParser.js.map +1 -1
- package/build/core/errors.d.ts +0 -15
- package/build/core/errors.d.ts.map +1 -1
- package/build/core/errors.js +0 -15
- package/build/core/errors.js.map +1 -1
- package/build/core/escapeNonAscii.d.ts +0 -9
- package/build/core/escapeNonAscii.d.ts.map +1 -1
- package/build/core/escapeNonAscii.js +1 -43
- package/build/core/escapeNonAscii.js.map +1 -1
- package/build/core/exec.d.ts +0 -12
- package/build/core/exec.d.ts.map +1 -1
- package/build/core/exec.js +2 -14
- package/build/core/exec.js.map +1 -1
- package/build/core/executor.js +0 -3
- package/build/core/executor.js.map +1 -1
- package/build/core/fastRefreshRecorder.js +0 -4
- package/build/core/fastRefreshRecorder.js.map +1 -1
- package/build/core/fastRefreshTools.js +0 -3
- package/build/core/fastRefreshTools.js.map +1 -1
- package/build/core/feedback.js +0 -4
- package/build/core/feedback.js.map +1 -1
- package/build/core/graphqlOperation.d.ts +13 -0
- package/build/core/graphqlOperation.d.ts.map +1 -0
- package/build/core/graphqlOperation.js +80 -0
- package/build/core/graphqlOperation.js.map +1 -0
- package/build/core/guides.d.ts +0 -10
- package/build/core/guides.d.ts.map +1 -1
- package/build/core/guides.js +18 -12
- package/build/core/guides.js.map +1 -1
- package/build/core/iconSemantics.d.ts +0 -15
- package/build/core/iconSemantics.d.ts.map +1 -1
- package/build/core/iconSemantics.js +0 -31
- package/build/core/iconSemantics.js.map +1 -1
- package/build/core/index.d.ts +2 -0
- package/build/core/index.d.ts.map +1 -1
- package/build/core/index.js +5 -47
- package/build/core/index.js.map +1 -1
- package/build/core/injected/visibility.d.ts +0 -30
- package/build/core/injected/visibility.d.ts.map +1 -1
- package/build/core/injected/visibility.js +0 -33
- package/build/core/injected/visibility.js.map +1 -1
- package/build/core/injectedFilters.d.ts +0 -28
- package/build/core/injectedFilters.d.ts.map +1 -1
- package/build/core/injectedFilters.js +0 -28
- package/build/core/injectedFilters.js.map +1 -1
- package/build/core/inputTarget.d.ts +1 -34
- package/build/core/inputTarget.d.ts.map +1 -1
- package/build/core/inputTarget.js +0 -26
- package/build/core/inputTarget.js.map +1 -1
- package/build/core/inputTargetTools.d.ts +0 -7
- package/build/core/inputTargetTools.d.ts.map +1 -1
- package/build/core/inputTargetTools.js +0 -7
- package/build/core/inputTargetTools.js.map +1 -1
- package/build/core/inspector.d.ts +0 -33
- package/build/core/inspector.d.ts.map +1 -1
- package/build/core/inspector.js +27 -40
- package/build/core/inspector.js.map +1 -1
- package/build/core/ios.d.ts +11 -122
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +32 -299
- package/build/core/ios.js.map +1 -1
- package/build/core/jsExecute.d.ts +0 -100
- package/build/core/jsExecute.d.ts.map +1 -1
- package/build/core/jsExecute.js +0 -261
- package/build/core/jsExecute.js.map +1 -1
- package/build/core/jsLogEvents.d.ts +0 -8
- package/build/core/jsLogEvents.d.ts.map +1 -1
- package/build/core/jsLogEvents.js +0 -8
- package/build/core/jsLogEvents.js.map +1 -1
- package/build/core/keyboardMetrics.js +0 -8
- package/build/core/keyboardMetrics.js.map +1 -1
- package/build/core/keyboardRaise.d.ts +0 -8
- package/build/core/keyboardRaise.d.ts.map +1 -1
- package/build/core/keyboardRaise.js +0 -23
- package/build/core/keyboardRaise.js.map +1 -1
- package/build/core/license.d.ts +0 -4
- package/build/core/license.d.ts.map +1 -1
- package/build/core/license.js +2 -55
- package/build/core/license.js.map +1 -1
- package/build/core/logDiagnosis.d.ts +0 -34
- package/build/core/logDiagnosis.d.ts.map +1 -1
- package/build/core/logDiagnosis.js +0 -15
- package/build/core/logDiagnosis.js.map +1 -1
- package/build/core/logEventFormat.d.ts +0 -1
- package/build/core/logEventFormat.d.ts.map +1 -1
- package/build/core/logEventFormat.js +0 -30
- package/build/core/logEventFormat.js.map +1 -1
- package/build/core/logEvents.d.ts +0 -54
- package/build/core/logEvents.d.ts.map +1 -1
- package/build/core/logEvents.js +0 -37
- package/build/core/logEvents.js.map +1 -1
- package/build/core/logGrouping.d.ts +0 -9
- package/build/core/logGrouping.d.ts.map +1 -1
- package/build/core/logGrouping.js +0 -17
- package/build/core/logGrouping.js.map +1 -1
- package/build/core/logOwnership.d.ts +0 -17
- package/build/core/logOwnership.d.ts.map +1 -1
- package/build/core/logOwnership.js +0 -34
- package/build/core/logOwnership.js.map +1 -1
- package/build/core/logRelevance.d.ts +0 -8
- package/build/core/logRelevance.d.ts.map +1 -1
- package/build/core/logRelevance.js +0 -15
- package/build/core/logRelevance.js.map +1 -1
- package/build/core/logSourceAndroid.d.ts +0 -8
- package/build/core/logSourceAndroid.d.ts.map +1 -1
- package/build/core/logSourceAndroid.js +0 -11
- package/build/core/logSourceAndroid.js.map +1 -1
- package/build/core/logSourceIos.d.ts +0 -8
- package/build/core/logSourceIos.d.ts.map +1 -1
- package/build/core/logSourceIos.js +1 -24
- package/build/core/logSourceIos.js.map +1 -1
- package/build/core/logbox.d.ts +0 -30
- package/build/core/logbox.d.ts.map +1 -1
- package/build/core/logbox.js +0 -43
- package/build/core/logbox.js.map +1 -1
- package/build/core/logs.d.ts +0 -6
- package/build/core/logs.d.ts.map +1 -1
- package/build/core/logs.js +0 -19
- package/build/core/logs.js.map +1 -1
- package/build/core/metro.d.ts +0 -14
- package/build/core/metro.d.ts.map +1 -1
- package/build/core/metro.js +1 -32
- package/build/core/metro.js.map +1 -1
- package/build/core/moduleRegistry.d.ts +0 -17
- package/build/core/moduleRegistry.d.ts.map +1 -1
- package/build/core/moduleRegistry.js +0 -17
- package/build/core/moduleRegistry.js.map +1 -1
- package/build/core/nativeInputValue.d.ts +0 -29
- package/build/core/nativeInputValue.d.ts.map +1 -1
- package/build/core/nativeInputValue.js +0 -18
- package/build/core/nativeInputValue.js.map +1 -1
- package/build/core/nativeLogs.d.ts +0 -47
- package/build/core/nativeLogs.d.ts.map +1 -1
- package/build/core/nativeLogs.js +0 -82
- package/build/core/nativeLogs.js.map +1 -1
- package/build/core/nativeOnlyHints.js +0 -4
- package/build/core/nativeOnlyHints.js.map +1 -1
- package/build/core/navigate.d.ts +0 -15
- package/build/core/navigate.d.ts.map +1 -1
- package/build/core/navigate.js +0 -22
- package/build/core/navigate.js.map +1 -1
- package/build/core/navigation.d.ts +0 -60
- package/build/core/navigation.d.ts.map +1 -1
- package/build/core/navigation.js +0 -60
- package/build/core/navigation.js.map +1 -1
- package/build/core/network.d.ts +0 -5
- package/build/core/network.d.ts.map +1 -1
- package/build/core/network.js +10 -31
- package/build/core/network.js.map +1 -1
- package/build/core/networkInterceptor.d.ts +0 -19
- package/build/core/networkInterceptor.d.ts.map +1 -1
- package/build/core/networkInterceptor.js +3 -27
- package/build/core/networkInterceptor.js.map +1 -1
- package/build/core/ocr.d.ts +0 -31
- package/build/core/ocr.d.ts.map +1 -1
- package/build/core/ocr.js +1 -71
- package/build/core/ocr.js.map +1 -1
- package/build/core/paths.js +0 -1
- package/build/core/paths.js.map +1 -1
- package/build/core/pinchGeometry.d.ts +28 -0
- package/build/core/pinchGeometry.d.ts.map +1 -0
- package/build/core/pinchGeometry.js +97 -0
- package/build/core/pinchGeometry.js.map +1 -0
- package/build/core/pinchThresholds.d.ts +14 -0
- package/build/core/pinchThresholds.d.ts.map +1 -0
- package/build/core/pinchThresholds.js +9 -0
- package/build/core/pinchThresholds.js.map +1 -0
- package/build/core/posthog.js +0 -7
- package/build/core/posthog.js.map +1 -1
- package/build/core/pressables.d.ts +0 -25
- package/build/core/pressables.d.ts.map +1 -1
- package/build/core/pressables.js +0 -37
- package/build/core/pressables.js.map +1 -1
- package/build/core/probe.d.ts +0 -9
- package/build/core/probe.d.ts.map +1 -1
- package/build/core/probe.js +0 -9
- package/build/core/probe.js.map +1 -1
- package/build/core/projectMemory.d.ts +0 -2
- package/build/core/projectMemory.d.ts.map +1 -1
- package/build/core/projectMemory.js +2 -7
- package/build/core/projectMemory.js.map +1 -1
- package/build/core/projectStore.d.ts +0 -18
- package/build/core/projectStore.d.ts.map +1 -1
- package/build/core/projectStore.js +0 -19
- package/build/core/projectStore.js.map +1 -1
- package/build/core/promiseHandles.d.ts +0 -13
- package/build/core/promiseHandles.d.ts.map +1 -1
- package/build/core/promiseHandles.js +0 -25
- package/build/core/promiseHandles.js.map +1 -1
- package/build/core/register.js +0 -44
- package/build/core/register.js.map +1 -1
- package/build/core/rnGlobalsBootstrap.d.ts +0 -13
- package/build/core/rnGlobalsBootstrap.d.ts.map +1 -1
- package/build/core/rnGlobalsBootstrap.js +0 -22
- package/build/core/rnGlobalsBootstrap.js.map +1 -1
- package/build/core/routeHistory.d.ts +24 -0
- package/build/core/routeHistory.d.ts.map +1 -0
- package/build/core/routeHistory.js +186 -0
- package/build/core/routeHistory.js.map +1 -0
- package/build/core/screenLayout.d.ts +0 -11
- package/build/core/screenLayout.d.ts.map +1 -1
- package/build/core/screenLayout.js +24 -61
- package/build/core/screenLayout.js.map +1 -1
- package/build/core/screenSpace.d.ts +32 -44
- package/build/core/screenSpace.d.ts.map +1 -1
- package/build/core/screenSpace.js +80 -46
- package/build/core/screenSpace.js.map +1 -1
- package/build/core/screenSpaceDevice.d.ts +0 -8
- package/build/core/screenSpaceDevice.d.ts.map +1 -1
- package/build/core/screenSpaceDevice.js +31 -14
- package/build/core/screenSpaceDevice.js.map +1 -1
- package/build/core/screenState.d.ts +2 -74
- package/build/core/screenState.d.ts.map +1 -1
- package/build/core/screenState.js +204 -145
- package/build/core/screenState.js.map +1 -1
- package/build/core/sdkBridge.js +0 -8
- package/build/core/sdkBridge.js.map +1 -1
- package/build/core/sdkMirrorPoller.d.ts +0 -18
- package/build/core/sdkMirrorPoller.d.ts.map +1 -1
- package/build/core/sdkMirrorPoller.js +0 -47
- package/build/core/sdkMirrorPoller.js.map +1 -1
- package/build/core/selectionBuffer.d.ts +0 -6
- package/build/core/selectionBuffer.d.ts.map +1 -1
- package/build/core/selectionBuffer.js +0 -5
- package/build/core/selectionBuffer.js.map +1 -1
- package/build/core/signedVerdict.js +0 -6
- package/build/core/signedVerdict.js.map +1 -1
- package/build/core/state.d.ts +0 -31
- package/build/core/state.d.ts.map +1 -1
- package/build/core/state.js +0 -57
- package/build/core/state.js.map +1 -1
- package/build/core/symbolicate.js +0 -11
- package/build/core/symbolicate.js.map +1 -1
- package/build/core/telemetry.d.ts +0 -47
- package/build/core/telemetry.d.ts.map +1 -1
- package/build/core/telemetry.js +1 -173
- package/build/core/telemetry.js.map +1 -1
- package/build/core/textEntry.d.ts +0 -34
- package/build/core/textEntry.d.ts.map +1 -1
- package/build/core/textEntry.js +0 -73
- package/build/core/textEntry.js.map +1 -1
- package/build/core/toolHelpers.d.ts +0 -12
- package/build/core/toolHelpers.d.ts.map +1 -1
- package/build/core/toolHelpers.js +2 -47
- package/build/core/toolHelpers.js.map +1 -1
- package/build/core/truncate.d.ts +0 -17
- package/build/core/truncate.d.ts.map +1 -1
- package/build/core/truncate.js +0 -32
- package/build/core/truncate.js.map +1 -1
- package/build/core/types.d.ts +0 -6
- package/build/core/types.d.ts.map +1 -1
- package/build/core/usageCache.js +0 -12
- package/build/core/usageCache.js.map +1 -1
- package/build/index.js +0 -18
- package/build/index.js.map +1 -1
- package/build/pro/coordinateMiss.d.ts +0 -30
- package/build/pro/coordinateMiss.d.ts.map +1 -1
- package/build/pro/coordinateMiss.js +0 -17
- package/build/pro/coordinateMiss.js.map +1 -1
- package/build/pro/overlayGuard.d.ts +27 -0
- package/build/pro/overlayGuard.d.ts.map +1 -0
- package/build/pro/overlayGuard.js +103 -0
- package/build/pro/overlayGuard.js.map +1 -0
- package/build/pro/screenshot-diff.js +4 -6
- package/build/pro/screenshot-diff.js.map +1 -1
- package/build/pro/tap.d.ts +0 -55
- package/build/pro/tap.d.ts.map +1 -1
- package/build/pro/tap.js +72 -316
- package/build/pro/tap.js.map +1 -1
- package/build/pro/usageGate.js +0 -9
- package/build/pro/usageGate.js.map +1 -1
- package/build/pro/usageNotifications.js +1 -13
- package/build/pro/usageNotifications.js.map +1 -1
- package/build/pro/verifyAction.d.ts +0 -13
- package/build/pro/verifyAction.d.ts.map +1 -1
- package/build/pro/verifyAction.js +0 -45
- package/build/pro/verifyAction.js.map +1 -1
- package/build/tools/_deviceArg.d.ts +0 -35
- package/build/tools/_deviceArg.d.ts.map +1 -1
- package/build/tools/_deviceArg.js +0 -35
- package/build/tools/_deviceArg.js.map +1 -1
- package/build/tools/accountTools.js +2 -2
- package/build/tools/accountTools.js.map +1 -1
- package/build/tools/bundleTools.js +0 -16
- package/build/tools/bundleTools.js.map +1 -1
- package/build/tools/componentTools.d.ts.map +1 -1
- package/build/tools/componentTools.js +47 -53
- package/build/tools/componentTools.js.map +1 -1
- package/build/tools/connectionTools.js +1 -59
- package/build/tools/connectionTools.js.map +1 -1
- package/build/tools/deviceTools.js +0 -11
- package/build/tools/deviceTools.js.map +1 -1
- package/build/tools/executionTools.js +1 -18
- package/build/tools/executionTools.js.map +1 -1
- package/build/tools/interactionTools.d.ts +0 -10
- package/build/tools/interactionTools.d.ts.map +1 -1
- package/build/tools/interactionTools.js +190 -59
- package/build/tools/interactionTools.js.map +1 -1
- package/build/tools/logTools.d.ts +0 -13
- package/build/tools/logTools.d.ts.map +1 -1
- package/build/tools/logTools.js +2 -126
- package/build/tools/logTools.js.map +1 -1
- package/build/tools/metaTools.js +0 -10
- package/build/tools/metaTools.js.map +1 -1
- package/build/tools/networkTools.js +4 -43
- package/build/tools/networkTools.js.map +1 -1
- package/build/tools/requestTools.js +0 -5
- package/build/tools/requestTools.js.map +1 -1
- package/build/tools/screenshotTools.js +5 -111
- package/build/tools/screenshotTools.js.map +1 -1
- package/package.json +2 -2
- package/tools.json +16 -1
package/build/pro/tap.js
CHANGED
|
@@ -14,9 +14,8 @@ export function buildVerificationExplanation(v) {
|
|
|
14
14
|
const action = v.action ?? "tap";
|
|
15
15
|
const Action = action[0].toUpperCase() + action.slice(1);
|
|
16
16
|
const target = action === "swipe" ? "scroll surface" : "element";
|
|
17
|
-
// Burst path with typed verdict
|
|
18
17
|
if (v.kind === "settled_elsewhere") {
|
|
19
|
-
return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff).
|
|
18
|
+
return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). Something on screen responded; a pixel diff cannot identify which element, so this is not confirmation that the intended target handled it.`;
|
|
20
19
|
}
|
|
21
20
|
if (v.kind === "snap_back") {
|
|
22
21
|
if (action === "swipe") {
|
|
@@ -31,9 +30,8 @@ export function buildVerificationExplanation(v) {
|
|
|
31
30
|
return (`No visual change detected — neither persistent nor transient across burst frames. ` +
|
|
32
31
|
`The ${target} may not respond visually or the ${action} may have missed its target.`);
|
|
33
32
|
}
|
|
34
|
-
// Legacy non-burst path
|
|
35
33
|
if (v.meaningful) {
|
|
36
|
-
return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff).
|
|
34
|
+
return `${Action} caused a visible UI change (${pct(v.changeRate)} pixel diff). Something on screen responded; a pixel diff cannot identify which element, so this is not confirmation that the intended target handled it.`;
|
|
37
35
|
}
|
|
38
36
|
return (`No visual change detected between before and after screenshots. ` +
|
|
39
37
|
`The ${target} may not respond visually or the ${action} may have missed.`);
|
|
@@ -67,9 +65,6 @@ export async function analyzeBurstFrames(frames, options) {
|
|
|
67
65
|
const persistentDiff = await compareScreenshots(frames[0], frames[frames.length - 1], options);
|
|
68
66
|
const persistentChangeRate = persistentDiff.changeRate;
|
|
69
67
|
const transientChangeDetected = !persistentDiff.changed && framesWithChange.length > 0;
|
|
70
|
-
// Snap-back (transient motion that reverts) is NOT a successful gesture —
|
|
71
|
-
// the content moved during the drag but returned to its starting position.
|
|
72
|
-
// Callers diagnosing scroll failures need this to read as "didn't work".
|
|
73
68
|
const meaningful = persistentDiff.changed;
|
|
74
69
|
let kind;
|
|
75
70
|
if (persistentDiff.changed) {
|
|
@@ -91,7 +86,6 @@ export async function analyzeBurstFrames(frames, options) {
|
|
|
91
86
|
kind
|
|
92
87
|
};
|
|
93
88
|
}
|
|
94
|
-
// --- Helpers ---
|
|
95
89
|
export function buildQuery(options) {
|
|
96
90
|
const query = {};
|
|
97
91
|
if (options.text !== undefined)
|
|
@@ -106,11 +100,6 @@ export function buildQuery(options) {
|
|
|
106
100
|
query.y = options.y;
|
|
107
101
|
return query;
|
|
108
102
|
}
|
|
109
|
-
/**
|
|
110
|
-
* Check if text contains characters that break Hermes Runtime.evaluate.
|
|
111
|
-
* Standard accented Latin characters (Polish, Vietnamese, French, German, etc.)
|
|
112
|
-
* and Cyrillic work fine in Hermes. Only emoji and special Unicode ranges cause issues.
|
|
113
|
-
*/
|
|
114
103
|
export function hasProblematicUnicode(text) {
|
|
115
104
|
const emojiPattern = /[\u{1F600}-\u{1F64F}]|[\u{1F300}-\u{1F5FF}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{FE00}-\u{FE0F}]|[\u{1F900}-\u{1F9FF}]|[\u{200D}]|[\u{20E3}]|[\u{E0020}-\u{E007F}]/u;
|
|
116
105
|
return emojiPattern.test(text);
|
|
@@ -157,13 +146,6 @@ export function reconstructLinesFromWords(words) {
|
|
|
157
146
|
return { text, bbox: { x0, y0, x1, y1 }, tapCenter: { x: tapX, y: tapY }, words: sortedWords };
|
|
158
147
|
});
|
|
159
148
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Narrow the tap point to just the words that produced the substring match.
|
|
162
|
-
* Without this, a query for "Remotely" against a reconstructed line "Yes Remotely No"
|
|
163
|
-
* would tap at the line's centroid (middle of "Remotely" in this case, but easily wrong
|
|
164
|
-
* for asymmetric layouts). Tracks character offsets through the joined text so we know
|
|
165
|
-
* which constituent words contributed to the matched range.
|
|
166
|
-
*/
|
|
167
149
|
function refineTapCenterToMatchingWords(line, normalizedNeedle) {
|
|
168
150
|
const normalizedFull = normalizeForMatch(line.text);
|
|
169
151
|
const startChar = normalizedFull.indexOf(normalizedNeedle);
|
|
@@ -176,11 +158,10 @@ function refineTapCenterToMatchingWords(line, normalizedNeedle) {
|
|
|
176
158
|
const wNorm = normalizeForMatch(w.text);
|
|
177
159
|
const wordStart = offset;
|
|
178
160
|
const wordEnd = offset + wNorm.length;
|
|
179
|
-
// Word overlaps the matched range
|
|
180
161
|
if (wordEnd > startChar && wordStart < endChar) {
|
|
181
162
|
covered.push(w);
|
|
182
163
|
}
|
|
183
|
-
offset = wordEnd + 1;
|
|
164
|
+
offset = wordEnd + 1;
|
|
184
165
|
}
|
|
185
166
|
if (covered.length === 0)
|
|
186
167
|
return line.tapCenter;
|
|
@@ -206,10 +187,6 @@ export function findOcrMatch(ocrResult, query) {
|
|
|
206
187
|
const substringWord = words.find((w) => normalizeForMatch(w.text).includes(needle));
|
|
207
188
|
if (substringWord)
|
|
208
189
|
return { text: substringWord.text, tapCenter: substringWord.tapCenter };
|
|
209
|
-
// Fall through to phrases reconstructed from word detections. Tap point is
|
|
210
|
-
// refined to only the words covering the matched substring, so a query for
|
|
211
|
-
// "Continue with Google" inside "[icon] Continue with Google" lands on the
|
|
212
|
-
// text run instead of the line centroid (and won't drift onto the icon).
|
|
213
190
|
const reconstructed = reconstructLinesFromWords(words);
|
|
214
191
|
const exactRecon = reconstructed.find((l) => normalizeForMatch(l.text) === needle);
|
|
215
192
|
if (exactRecon) {
|
|
@@ -226,7 +203,6 @@ export function getAvailableStrategies(query, strategy) {
|
|
|
226
203
|
return ["coordinate"];
|
|
227
204
|
}
|
|
228
205
|
if (strategy !== "auto") {
|
|
229
|
-
// Always fallback to OCR for text queries — explicit strategy may miss visible text
|
|
230
206
|
if (query.text && strategy !== "ocr" && strategy !== "coordinate") {
|
|
231
207
|
return [strategy, "ocr"];
|
|
232
208
|
}
|
|
@@ -249,16 +225,6 @@ export function getAvailableStrategies(query, strategy) {
|
|
|
249
225
|
}
|
|
250
226
|
return ["fiber", "accessibility", "ocr"];
|
|
251
227
|
}
|
|
252
|
-
/**
|
|
253
|
-
* Convert screenshot image coordinates to platform-native tap coordinates.
|
|
254
|
-
*
|
|
255
|
-
* For iOS: screenshot pixels → device pixels (undo downscale) → points (÷ DPR)
|
|
256
|
-
* For Android: screenshot pixels → device pixels (undo downscale)
|
|
257
|
-
*
|
|
258
|
-
* IMPORTANT: Only use this for EXTERNAL coordinates from screenshots.
|
|
259
|
-
* Internal strategies (OCR, accessibility, fiber) produce tap-ready coordinates
|
|
260
|
-
* and call iosTap/androidTap directly — they must NOT go through this function.
|
|
261
|
-
*/
|
|
262
228
|
export function convertScreenshotToTapCoords(pixelX, pixelY, platform, devicePixelRatio, scaleFactor = 1) {
|
|
263
229
|
const deviceX = pixelX * scaleFactor;
|
|
264
230
|
const deviceY = pixelY * scaleFactor;
|
|
@@ -270,16 +236,7 @@ export function convertScreenshotToTapCoords(pixelX, pixelY, platform, devicePix
|
|
|
270
236
|
y: Math.round(deviceY / devicePixelRatio)
|
|
271
237
|
};
|
|
272
238
|
}
|
|
273
|
-
/** @deprecated Use convertScreenshotToTapCoords instead */
|
|
274
239
|
export const convertPixelsToPoints = convertScreenshotToTapCoords;
|
|
275
|
-
/**
|
|
276
|
-
* Turn a swipe direction (+ optional pixel distance) into screenshot-pixel
|
|
277
|
-
* start/end coordinates, centered on the screen. Content-scroll semantics:
|
|
278
|
-
* "up" = finger travels bottom→top, revealing content below.
|
|
279
|
-
* Distance defaults to 33% of the relevant axis and endpoints clamp to the
|
|
280
|
-
* 10%–90% margin so the gesture never runs off-screen. Exact travel length is
|
|
281
|
-
* preserved; for odd distances the band may sit ≤1px off the axis midpoint.
|
|
282
|
-
*/
|
|
283
240
|
export function computeSwipeFromDirection(direction, distance, width, height) {
|
|
284
241
|
const vertical = direction === "up" || direction === "down";
|
|
285
242
|
const axis = vertical ? height : width;
|
|
@@ -288,11 +245,10 @@ export function computeSwipeFromDirection(direction, distance, width, height) {
|
|
|
288
245
|
const cy = Math.round(height / 2);
|
|
289
246
|
const lo = Math.round(0.1 * axis);
|
|
290
247
|
const hi = Math.round(0.9 * axis);
|
|
291
|
-
// Center a band of length d on the axis midpoint, then clamp to [lo, hi].
|
|
292
248
|
const mid = Math.round(axis / 2);
|
|
293
249
|
const half = Math.floor(d / 2);
|
|
294
|
-
let far = mid - half;
|
|
295
|
-
let near = far + d;
|
|
250
|
+
let far = mid - half;
|
|
251
|
+
let near = far + d;
|
|
296
252
|
if (near > hi) {
|
|
297
253
|
near = hi;
|
|
298
254
|
far = near - d;
|
|
@@ -301,29 +257,23 @@ export function computeSwipeFromDirection(direction, distance, width, height) {
|
|
|
301
257
|
far = lo;
|
|
302
258
|
near = far + d;
|
|
303
259
|
}
|
|
304
|
-
// distance exceeds the available span — collapse to the full margin
|
|
305
260
|
if (near > hi)
|
|
306
261
|
near = hi;
|
|
307
262
|
if (far < lo)
|
|
308
263
|
far = lo;
|
|
309
264
|
switch (direction) {
|
|
310
|
-
case "up":
|
|
265
|
+
case "up":
|
|
311
266
|
return { startX: cx, startY: near, endX: cx, endY: far };
|
|
312
|
-
case "down":
|
|
267
|
+
case "down":
|
|
313
268
|
return { startX: cx, startY: far, endX: cx, endY: near };
|
|
314
|
-
case "left":
|
|
269
|
+
case "left":
|
|
315
270
|
return { startX: near, startY: cy, endX: far, endY: cy };
|
|
316
|
-
case "right":
|
|
271
|
+
case "right":
|
|
317
272
|
return { startX: far, startY: cy, endX: near, endY: cy };
|
|
318
273
|
}
|
|
319
274
|
}
|
|
320
275
|
export function formatTapSuccess(data) {
|
|
321
276
|
const { screenshot, verification, ...rest } = data;
|
|
322
|
-
// I3 (2026-05-16): when the tap landed on a TextInput-like element and the diff
|
|
323
|
-
// reports no visual change, that does NOT mean the focus failed. iOS simulators with
|
|
324
|
-
// the hardware keyboard connected (Cmd+K) suppress the on-screen keyboard, so the
|
|
325
|
-
// input is focused but the screen looks identical. Surface this so the agent doesn't
|
|
326
|
-
// assume the tap missed and retry the wrong thing.
|
|
327
277
|
const isTextInputComponent = (() => {
|
|
328
278
|
const name = data.component ?? data.pressed ?? "";
|
|
329
279
|
return /textfield|textinput|edittext|searchfield/i.test(name);
|
|
@@ -370,16 +320,6 @@ function buildErrorMessage(query) {
|
|
|
370
320
|
parts.push(`component="${query.component}"`);
|
|
371
321
|
return `No element found matching ${parts.join(", ")}`;
|
|
372
322
|
}
|
|
373
|
-
// I2 (2026-05-16): RN component composition routinely produces N matches at the same
|
|
374
|
-
// coordinates for the same testID/text (e.g. ThemedButton → TouchableOpacity → TouchableOpacity).
|
|
375
|
-
// The C1 ambiguity guard refuses all such taps and demands `index=`. That's correct in the
|
|
376
|
-
// spatial-ambiguity case (two distinct buttons share a label) but wrong in the
|
|
377
|
-
// wrapper-ambiguity case — there's only one logical button, just stacked components.
|
|
378
|
-
//
|
|
379
|
-
// Collapse matches that share the same (testID, text) and overlap geometrically
|
|
380
|
-
// (centers within TOLERANCE_PX). Keeps the first occurrence — fiber/a11y walks parent-first,
|
|
381
|
-
// so the outermost wrapper survives (its onPress almost always proxies to inner handlers).
|
|
382
|
-
// Spatial ambiguity (different x/y) is preserved and continues to surface the C1 refusal.
|
|
383
323
|
function collapseGeometricallyEquivalentMatches(matches, getCenter, getKey) {
|
|
384
324
|
if (matches.length <= 1)
|
|
385
325
|
return matches;
|
|
@@ -412,7 +352,6 @@ function ocrSimilarity(a, b) {
|
|
|
412
352
|
return 0;
|
|
413
353
|
if (a === b)
|
|
414
354
|
return 1;
|
|
415
|
-
// Dice coefficient over character bigrams (case/space-insensitive)
|
|
416
355
|
const bigrams = (s) => {
|
|
417
356
|
const m = new Map();
|
|
418
357
|
if (s.length < 2) {
|
|
@@ -451,9 +390,6 @@ export function findClosestOcrText(ocrResult, query) {
|
|
|
451
390
|
candidates.push(...ocrResult.words.map(w => ({ text: w.text })));
|
|
452
391
|
if (ocrResult?.lines?.length)
|
|
453
392
|
candidates.push(...ocrResult.lines.map(l => ({ text: l.text })));
|
|
454
|
-
// Include reconstructed phrases so the diagnostic surfaces "Continue with Google@1.00"
|
|
455
|
-
// instead of "Continue@0.54" when the engine fragmented a multi-word phrase into
|
|
456
|
-
// separate word detections.
|
|
457
393
|
if (ocrResult?.words?.length) {
|
|
458
394
|
candidates.push(...reconstructLinesFromWords(ocrResult.words).map(l => ({ text: l.text })));
|
|
459
395
|
}
|
|
@@ -470,7 +406,6 @@ export function findClosestOcrText(ocrResult, query) {
|
|
|
470
406
|
}
|
|
471
407
|
return best;
|
|
472
408
|
}
|
|
473
|
-
// --- Strategy Functions ---
|
|
474
409
|
async function tryFiberStrategy(query, index, maxTraversalDepth, sink, device) {
|
|
475
410
|
if (sink) {
|
|
476
411
|
sink.fiber.ran = true;
|
|
@@ -478,10 +413,8 @@ async function tryFiberStrategy(query, index, maxTraversalDepth, sink, device) {
|
|
|
478
413
|
}
|
|
479
414
|
const startedAt = Date.now();
|
|
480
415
|
try {
|
|
481
|
-
// Retry with increasing depth if the initial traversal finds nothing
|
|
482
416
|
const baseDepth = maxTraversalDepth ?? 15;
|
|
483
417
|
const depthAttempts = [baseDepth];
|
|
484
|
-
// Only add deeper retries if user didn't explicitly set a high depth
|
|
485
418
|
if (baseDepth <= 15) {
|
|
486
419
|
depthAttempts.push(30, 45);
|
|
487
420
|
}
|
|
@@ -540,16 +473,8 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
|
|
|
540
473
|
}
|
|
541
474
|
return strategyResult;
|
|
542
475
|
}
|
|
543
|
-
// Fiber finds the element by text/testID/component, then measures its
|
|
544
|
-
// host component's screen position for a native tap. This ensures the tap
|
|
545
|
-
// goes through React's event pipeline, executing any onPress wrappers
|
|
546
|
-
// (analytics, debouncing, state tracking) inside the component.
|
|
547
476
|
if (parsed.needsNativeTap) {
|
|
548
|
-
// Ambiguity guard: if multiple elements match and the caller didn't
|
|
549
|
-
// specify an explicit index, refuse to tap and surface the full list
|
|
550
|
-
// so the agent can pick the right one.
|
|
551
477
|
if ((parsed.totalMatches ?? 1) > 1 && index === undefined) {
|
|
552
|
-
// I2: collapse geometrically-identical wrapper matches before refusing.
|
|
553
478
|
const collapsed = collapseGeometricallyEquivalentMatches(parsed.allMatches ?? [], (m) => ({ x: m.x, y: m.y }), (m) => `${m.testID ?? ""}::${m.text ?? ""}`);
|
|
554
479
|
if (collapsed.length > 1) {
|
|
555
480
|
return {
|
|
@@ -559,9 +484,6 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
|
|
|
559
484
|
ambiguous: true
|
|
560
485
|
};
|
|
561
486
|
}
|
|
562
|
-
// Collapsed to one logical element — fall through and tap. The JS-side
|
|
563
|
-
// search already picked allMatches[0] (the outermost wrapper) for
|
|
564
|
-
// parsed.pressed/parsed.nativeTapTarget.
|
|
565
487
|
}
|
|
566
488
|
const elementType = parsed.isInput ? "input element" : "pressable element";
|
|
567
489
|
if (parsed.nativeTapTarget && parsed.nativeTapTarget.x && parsed.nativeTapTarget.y) {
|
|
@@ -584,7 +506,6 @@ async function tryFiberAtDepth(query, index, maxTraversalDepth, device) {
|
|
|
584
506
|
reason: `Found ${parsed.pressed} (${elementType}) but could not measure coordinates — falling through to next strategy`
|
|
585
507
|
};
|
|
586
508
|
}
|
|
587
|
-
// All elements now use needsNativeTap — this shouldn't be reached
|
|
588
509
|
return {
|
|
589
510
|
success: false,
|
|
590
511
|
reason: "Unexpected: element did not request native tap"
|
|
@@ -611,28 +532,20 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
611
532
|
};
|
|
612
533
|
}
|
|
613
534
|
if (platform === "ios") {
|
|
614
|
-
// One accessibility dump serves every pass below: `axe describe-ui`
|
|
615
|
-
// costs ~210ms and the screen cannot change between predicates, so
|
|
616
|
-
// re-fetching per pass was pure overhead (2-3 dumps per tap).
|
|
617
535
|
const { iosGetUITree } = await import("../core/ios.js");
|
|
618
536
|
const tree = await iosGetUITree(udid);
|
|
619
|
-
// iOS: testID maps to accessibilityIdentifier — search by identifier first,
|
|
620
|
-
// then fall back to labelContains for text-based searches
|
|
621
537
|
let result;
|
|
622
538
|
if (hasTestID && !hasText) {
|
|
623
|
-
// Try exact identifier match first (testID → accessibilityIdentifier)
|
|
624
539
|
result = await iosFindElement({
|
|
625
540
|
identifier: query.testID,
|
|
626
541
|
index
|
|
627
542
|
}, udid, tree);
|
|
628
|
-
// Fall back to identifierContains if exact match fails
|
|
629
543
|
if (!result.success || !result.allMatches || result.allMatches.length === 0) {
|
|
630
544
|
result = await iosFindElement({
|
|
631
545
|
identifierContains: query.testID,
|
|
632
546
|
index
|
|
633
547
|
}, udid, tree);
|
|
634
548
|
}
|
|
635
|
-
// Last resort: try labelContains in case testID is reflected in label
|
|
636
549
|
if (!result.success || !result.allMatches || result.allMatches.length === 0) {
|
|
637
550
|
result = await iosFindElement({
|
|
638
551
|
labelContains: query.testID,
|
|
@@ -642,8 +555,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
642
555
|
}
|
|
643
556
|
else {
|
|
644
557
|
const searchText = query.text;
|
|
645
|
-
// Exact label match first to avoid matching dialog titles like "Clear All"
|
|
646
|
-
// when the query is a single word that happens to be a substring ("Clear").
|
|
647
558
|
result = await iosFindElement({
|
|
648
559
|
label: searchText,
|
|
649
560
|
index
|
|
@@ -670,12 +581,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
670
581
|
reason: result.error ?? "No iOS accessibility match"
|
|
671
582
|
};
|
|
672
583
|
}
|
|
673
|
-
// Ambiguity guard: when the predicate matches multiple a11y elements
|
|
674
|
-
// (e.g. text="My Events" hits both the header title AND the bottom tab),
|
|
675
|
-
// refuse to tap without an explicit index — picking allMatches[0] silently
|
|
676
|
-
// landed on non-interactive header elements in production telemetry.
|
|
677
|
-
// Mirrors the fiber-strategy guard further up the file.
|
|
678
|
-
// I2: collapse geometrically-identical wrapper matches before refusing.
|
|
679
584
|
const iosEffectiveMatches = result.allMatches.length > 1
|
|
680
585
|
? collapseGeometricallyEquivalentMatches(result.allMatches, (m) => ({ x: m.center?.x, y: m.center?.y }), (m) => `${m.identifier ?? ""}::${m.label ?? ""}`)
|
|
681
586
|
: result.allMatches;
|
|
@@ -712,7 +617,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
712
617
|
};
|
|
713
618
|
}
|
|
714
619
|
else {
|
|
715
|
-
// Android: testID maps to resource-id, text maps to text content
|
|
716
620
|
const searchOptions = { index };
|
|
717
621
|
if (hasTestID && !hasText) {
|
|
718
622
|
searchOptions.resourceId = query.testID;
|
|
@@ -720,13 +624,9 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
720
624
|
else if (hasText) {
|
|
721
625
|
searchOptions.textContains = query.text;
|
|
722
626
|
}
|
|
723
|
-
// Same single-dump reuse as iOS above — a uiautomator hierarchy dump
|
|
724
|
-
// is the most expensive step of an Android accessibility tap.
|
|
725
627
|
const { androidGetUITree } = await import("../core/android.js");
|
|
726
628
|
const androidTree = await androidGetUITree(undefined, signal);
|
|
727
629
|
let result = await androidFindElement(searchOptions, undefined, signal, androidTree);
|
|
728
|
-
// If testID search via resourceId failed, try contentDescContains
|
|
729
|
-
// (older RN versions map testID to content-description)
|
|
730
630
|
if (hasTestID && !hasText && (!result.success || !result.allMatches || result.allMatches.length === 0)) {
|
|
731
631
|
result = await androidFindElement({
|
|
732
632
|
contentDescContains: query.testID,
|
|
@@ -748,8 +648,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
748
648
|
reason: result.error ?? "No Android accessibility match"
|
|
749
649
|
};
|
|
750
650
|
}
|
|
751
|
-
// Ambiguity guard — see iOS branch above for rationale.
|
|
752
|
-
// I2: collapse geometrically-identical wrapper matches before refusing.
|
|
753
651
|
const androidEffectiveMatches = result.allMatches.length > 1
|
|
754
652
|
? collapseGeometricallyEquivalentMatches(result.allMatches, (m) => ({ x: m.center?.x, y: m.center?.y }), (m) => `${m.resourceId ?? ""}::${m.text ?? m.contentDesc ?? ""}`)
|
|
755
653
|
: result.allMatches;
|
|
@@ -797,11 +695,6 @@ async function tryAccessibilityStrategy(query, index, platform, udid, sink, sign
|
|
|
797
695
|
sink.accessibility.durationMs = Date.now() - startedAt;
|
|
798
696
|
}
|
|
799
697
|
}
|
|
800
|
-
/**
|
|
801
|
-
* Run OCR sense (capture + recognize + match) and tap the match. Also records
|
|
802
|
-
* `sink.ocr.bestCandidate` (matched text + tap coords) into the evidence sink,
|
|
803
|
-
* which is serialized into the R2 failure bundle for diagnostics.
|
|
804
|
-
*/
|
|
805
698
|
async function tryOcrStrategy(query, platform, udid, sink, signal) {
|
|
806
699
|
if (sink)
|
|
807
700
|
sink.ocr.ran = true;
|
|
@@ -855,10 +748,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
|
|
|
855
748
|
sink.ocr.closestMatch = findClosestOcrText(ocrResult, searchText);
|
|
856
749
|
}
|
|
857
750
|
const match = findOcrMatch(ocrResult, searchText);
|
|
858
|
-
// Record the best candidate (matched text + tap coords) into the
|
|
859
|
-
// evidence sink. This is serialized into the R2 failure bundle and is
|
|
860
|
-
// useful when diagnosing OCR taps (e.g. OCR found the match at these
|
|
861
|
-
// coords but the tap didn't register a visual change).
|
|
862
751
|
if (sink && match) {
|
|
863
752
|
const closest = sink.ocr.closestMatch;
|
|
864
753
|
sink.ocr.bestCandidate = {
|
|
@@ -875,7 +764,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
|
|
|
875
764
|
};
|
|
876
765
|
}
|
|
877
766
|
if (platform === "ios") {
|
|
878
|
-
// tapCenter is in image-pixel space (downscaled) — convert to points
|
|
879
767
|
const { getDevicePixelRatio } = await import("../core/ios.js");
|
|
880
768
|
const dpr = await getDevicePixelRatio(udid);
|
|
881
769
|
const tapResult = await iosTap(Math.round((match.tapCenter.x * scaleFactor) / dpr), Math.round((match.tapCenter.y * scaleFactor) / dpr), { udid });
|
|
@@ -887,7 +775,6 @@ async function tryOcrStrategy(query, platform, udid, sink, signal) {
|
|
|
887
775
|
}
|
|
888
776
|
}
|
|
889
777
|
else {
|
|
890
|
-
// Android: image-pixel → device-pixel (undo downscale), ADB accepts pixels
|
|
891
778
|
await androidTap(Math.round(match.tapCenter.x * scaleFactor), Math.round(match.tapCenter.y * scaleFactor));
|
|
892
779
|
}
|
|
893
780
|
return {
|
|
@@ -917,8 +804,6 @@ async function tryCoordinateStrategy(pixelX, pixelY, platform, lastScreenshot, u
|
|
|
917
804
|
if (platform === "ios") {
|
|
918
805
|
const scaleFactor = lastScreenshot?.scaleFactor ?? 1;
|
|
919
806
|
const { getDevicePixelRatio } = await import("../core/ios.js");
|
|
920
|
-
// Reuse the caller's frame dimensions when it has them, so the first
|
|
921
|
-
// coordinate tap of a session doesn't pay for an extra screenshot.
|
|
922
807
|
const dprHint = lastScreenshot && lastScreenshot.originalWidth > 0 && lastScreenshot.originalHeight > 0
|
|
923
808
|
? { width: lastScreenshot.originalWidth, height: lastScreenshot.originalHeight }
|
|
924
809
|
: undefined;
|
|
@@ -957,18 +842,7 @@ async function tryCoordinateStrategy(pixelX, pixelY, platform, lastScreenshot, u
|
|
|
957
842
|
}
|
|
958
843
|
const TAP_TIMEOUT_MS = 25000;
|
|
959
844
|
const MIN_STRATEGY_BUDGET_MS = 500;
|
|
960
|
-
|
|
961
|
-
// the ADB screencap+pull leg has ~2s variance on real devices; iOS stays at 5s
|
|
962
|
-
// where xcrun simctl screenshot is consistent.
|
|
963
|
-
// Heavy strategies (fiber on deep trees with multi-depth retries, axe accessibility
|
|
964
|
-
// dumps on dense iOS screens) need more headroom — previous caps produced spurious
|
|
965
|
-
// timeouts that the agent read as "element missing" when the strategy simply didn't
|
|
966
|
-
// get to finish. The overall TAP_TIMEOUT_MS budget still bounds the worst case.
|
|
967
|
-
// Coordinate looks "light" (one tap subprocess) but on iOS it pairs an axe/idb
|
|
968
|
-
// invocation with a CDP getDevicePixelRatio + best-effort fiber inspection;
|
|
969
|
-
// the 3000ms cap surfaced as spurious "coordinate timed out" failures while
|
|
970
|
-
// 20+ seconds of the overall budget were still unused. Align with the other
|
|
971
|
-
// strategies — the global TAP_TIMEOUT_MS still bounds the worst case.
|
|
845
|
+
const OVERLAY_GUARD_BUDGET_MS = 3000;
|
|
972
846
|
const MAX_STRATEGY_MS = {
|
|
973
847
|
fiber: 8000,
|
|
974
848
|
accessibility: 6000,
|
|
@@ -980,9 +854,6 @@ function maxStrategyMs(strategy, platform) {
|
|
|
980
854
|
return 9000;
|
|
981
855
|
return MAX_STRATEGY_MS[strategy] ?? 5000;
|
|
982
856
|
}
|
|
983
|
-
// Matches only the outer withTimeout wrapper message for a tap strategy.
|
|
984
|
-
// Nested sub-operation errors inside a strategy (e.g. "CDP getProperties timed out after 150ms")
|
|
985
|
-
// must NOT be classified as a tap-level timeout.
|
|
986
857
|
const STRATEGY_TIMEOUT_RE = /^(fiber|accessibility|ocr|coordinate) timed out after \d+ms$/;
|
|
987
858
|
export function isTapTimeout(attempted) {
|
|
988
859
|
return attempted.some((a) => a.outcome === "timeout" || a.outcome === "skipped" || STRATEGY_TIMEOUT_RE.test(a.reason) || a.reason.startsWith("Skipped —"));
|
|
@@ -999,12 +870,6 @@ function withTimeout(promise, ms, label) {
|
|
|
999
870
|
});
|
|
1000
871
|
});
|
|
1001
872
|
}
|
|
1002
|
-
/**
|
|
1003
|
-
* Like withTimeout, but creates an AbortController whose signal is handed to
|
|
1004
|
-
* the inner factory. On timeout the signal aborts so subprocess work
|
|
1005
|
-
* (uiautomator dump, OCR fetch, screencap) can be killed instead of running
|
|
1006
|
-
* past the strategy cap and bleeding into total tap duration.
|
|
1007
|
-
*/
|
|
1008
873
|
function withCancelableTimeout(make, ms, label) {
|
|
1009
874
|
const ctrl = new AbortController();
|
|
1010
875
|
const timer = setTimeout(() => ctrl.abort(), ms);
|
|
@@ -1015,15 +880,6 @@ function withCancelableTimeout(make, ms, label) {
|
|
|
1015
880
|
throw err;
|
|
1016
881
|
});
|
|
1017
882
|
}
|
|
1018
|
-
/**
|
|
1019
|
-
* Compute marker coordinates in screenshot-pixel space (what the returned PNG uses).
|
|
1020
|
-
* Unit rules by strategy, assuming screenshotScale = downscale factor:
|
|
1021
|
-
* coordinate : input is already screenshot pixels → pass through
|
|
1022
|
-
* ocr : match.tapCenter is image-pixel (= screenshot-pixel) → pass through
|
|
1023
|
-
* iOS points : point * DPR / screenshotScale
|
|
1024
|
-
* android dp : dp * densityScale / screenshotScale
|
|
1025
|
-
* android devicePx : devicePx / screenshotScale
|
|
1026
|
-
*/
|
|
1027
883
|
function computeMarkerPx(args) {
|
|
1028
884
|
const { strategy, input, convertedTo, platform, screenshotScale, devicePixelRatio, androidDensityScale } = args;
|
|
1029
885
|
const scale = screenshotScale || 1;
|
|
@@ -1036,36 +892,27 @@ function computeMarkerPx(args) {
|
|
|
1036
892
|
return { x: Math.round(convertedTo.x), y: Math.round(convertedTo.y) };
|
|
1037
893
|
}
|
|
1038
894
|
if (platform === "ios") {
|
|
1039
|
-
// fiber/accessibility on iOS return points
|
|
1040
895
|
const dpr = devicePixelRatio || 3;
|
|
1041
896
|
return {
|
|
1042
897
|
x: Math.round((convertedTo.x * dpr) / scale),
|
|
1043
898
|
y: Math.round((convertedTo.y * dpr) / scale)
|
|
1044
899
|
};
|
|
1045
900
|
}
|
|
1046
|
-
// Android
|
|
1047
901
|
if (strategy === "fiber+native" && androidDensityScale) {
|
|
1048
|
-
// Fabric fiber returns dp; fiber+native path scales to device pixels before tap
|
|
1049
902
|
return {
|
|
1050
903
|
x: Math.round((convertedTo.x * androidDensityScale) / scale),
|
|
1051
904
|
y: Math.round((convertedTo.y * androidDensityScale) / scale)
|
|
1052
905
|
};
|
|
1053
906
|
}
|
|
1054
|
-
// accessibility android: convertedTo is device pixels
|
|
1055
907
|
return {
|
|
1056
908
|
x: Math.round(convertedTo.x / scale),
|
|
1057
909
|
y: Math.round(convertedTo.y / scale)
|
|
1058
910
|
};
|
|
1059
911
|
}
|
|
1060
|
-
// D2 (Step 6): in-memory ring buffer of recent coord-strategy artifact keys
|
|
1061
|
-
// `${round(x/20)}:${round(y/20)}`. Entries expire after 60s. The first
|
|
1062
|
-
// failed coord tap at a grid cell uploads its artifact; retries within the
|
|
1063
|
-
// window get suppressed.
|
|
1064
912
|
const recentCoordArtifacts = new Map();
|
|
1065
913
|
const COORD_DEDUP_WINDOW_MS = 60_000;
|
|
1066
914
|
const COORD_DEDUP_BUCKET = 20;
|
|
1067
915
|
function shouldDedupArtifact(ctx) {
|
|
1068
|
-
// Only applies to coordinate-strategy artifacts where we have x,y coords.
|
|
1069
916
|
const winningStrat = ctx.attempted.find(a => a.reason === "success")?.strategy
|
|
1070
917
|
?? ctx.attempted[ctx.attempted.length - 1]?.strategy;
|
|
1071
918
|
if (winningStrat !== "coordinate")
|
|
@@ -1076,7 +923,6 @@ function shouldDedupArtifact(ctx) {
|
|
|
1076
923
|
return false;
|
|
1077
924
|
const key = `${Math.round(x / COORD_DEDUP_BUCKET)}:${Math.round(y / COORD_DEDUP_BUCKET)}`;
|
|
1078
925
|
const now = Date.now();
|
|
1079
|
-
// Sweep stale entries opportunistically — keeps the map from growing.
|
|
1080
926
|
for (const [k, ts] of recentCoordArtifacts) {
|
|
1081
927
|
if (now - ts > COORD_DEDUP_WINDOW_MS)
|
|
1082
928
|
recentCoordArtifacts.delete(k);
|
|
@@ -1087,47 +933,29 @@ function shouldDedupArtifact(ctx) {
|
|
|
1087
933
|
recentCoordArtifacts.set(key, now);
|
|
1088
934
|
return false;
|
|
1089
935
|
}
|
|
1090
|
-
/** Test-only: drop the dedup state so a fresh tap always uploads. */
|
|
1091
936
|
export function resetCoordArtifactDedup() {
|
|
1092
937
|
recentCoordArtifacts.clear();
|
|
1093
938
|
}
|
|
1094
|
-
/**
|
|
1095
|
-
* Turn an unmeaningful coordinate tap into an explanation.
|
|
1096
|
-
*
|
|
1097
|
-
* The tricky part is coordinate spaces. The caller works in screenshot pixels;
|
|
1098
|
-
* the tap was executed in points (iOS) or device pixels (Android); the screen
|
|
1099
|
-
* state reports points/dp. We map the tapped point INTO screen-state space to
|
|
1100
|
-
* do the hit test, and map the answer BACK into the caller's screenshot pixels
|
|
1101
|
-
* so any suggested coordinates can be passed straight to another tap().
|
|
1102
|
-
*/
|
|
1103
939
|
async function diagnoseCoordinateMiss(args) {
|
|
1104
940
|
try {
|
|
1105
941
|
const { getScreenState } = await import("../core/screenState.js");
|
|
1106
942
|
const { explainCoordinateMiss } = await import("./coordinateMiss.js");
|
|
943
|
+
const { screenStateToScreenSpace, screenStateToDeliveredPx } = await import("../core/screenSpace.js");
|
|
944
|
+
const { resolveScreenSpaceMetrics } = await import("../core/screenSpaceDevice.js");
|
|
945
|
+
const { getConnectedAppByDevice, getFirstConnectedApp } = await import("../core/index.js");
|
|
1107
946
|
const ss = await getScreenState({ device: args.deviceName });
|
|
1108
947
|
if (!ss.success || !ss.screenState)
|
|
1109
948
|
return undefined;
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
// Ratio between screen-state space and the caller's screenshot pixels,
|
|
1119
|
-
// derived from the conversion this very tap already performed. Uses the
|
|
1120
|
-
// larger axis so a near-zero coordinate can't amplify rounding error.
|
|
1121
|
-
const useY = Math.abs(pointInState.y) > Math.abs(pointInState.x);
|
|
1122
|
-
const numerator = useY ? args.inputPoint.y : args.inputPoint.x;
|
|
1123
|
-
const denominator = useY ? pointInState.y : pointInState.x;
|
|
1124
|
-
const factor = denominator !== 0 && Number.isFinite(numerator / denominator)
|
|
1125
|
-
? numerator / denominator
|
|
1126
|
-
: 1;
|
|
1127
|
-
return explainCoordinateMiss(pointInState, ss.screenState, (v) => Math.round(v * factor)) ?? undefined;
|
|
949
|
+
const app = (args.deviceName ? getConnectedAppByDevice(args.deviceName) : null) ?? getFirstConnectedApp();
|
|
950
|
+
const metrics = await resolveScreenSpaceMetrics({
|
|
951
|
+
platform: app?.platform ?? "ios",
|
|
952
|
+
udid: app?.simulatorUdid,
|
|
953
|
+
deviceId: app?.adbSerial
|
|
954
|
+
});
|
|
955
|
+
const normalised = screenStateToDeliveredPx(screenStateToScreenSpace(ss.screenState, metrics), metrics);
|
|
956
|
+
return explainCoordinateMiss(args.inputPoint, normalised) ?? undefined;
|
|
1128
957
|
}
|
|
1129
958
|
catch {
|
|
1130
|
-
// Diagnosis is best-effort — never let it turn a successful tap into an error.
|
|
1131
959
|
return undefined;
|
|
1132
960
|
}
|
|
1133
961
|
}
|
|
@@ -1135,26 +963,15 @@ async function captureTapArtifact(ctx) {
|
|
|
1135
963
|
try {
|
|
1136
964
|
const { getServerVersion, categorizeError } = await import("../core/telemetry.js");
|
|
1137
965
|
const strategyChain = ctx.attempted.map(a => `${a.strategy}:${a.reason.slice(0, 40)}`).join("|");
|
|
1138
|
-
// Resolve error category up-front so we can short-circuit driver-missing failures.
|
|
1139
|
-
// These aren't tap-tool bugs — they're host setup problems (no idb/axe/adb on PATH).
|
|
1140
|
-
// Skipping the artifact upload saves R2 storage and stops these from polluting the
|
|
1141
|
-
// failure dashboard, which is the signal we use to triage genuine tap regressions.
|
|
1142
966
|
const errorCategory = ctx.errorCategory
|
|
1143
967
|
?? categorizeError(ctx.errorMessage ?? "", strategyChain);
|
|
1144
968
|
if (errorCategory === "driver_missing") {
|
|
1145
969
|
return undefined;
|
|
1146
970
|
}
|
|
1147
|
-
// D1 (Step 6): no-metro unmeaningful taps have no diagnostic signal
|
|
1148
|
-
// beyond a screenshot diff that's already below threshold by definition.
|
|
1149
|
-
// ~50% of recent failure-artifact rows were these — they crowd out real
|
|
1150
|
-
// failures in the dashboard. Telemetry row still flows; just no R2 bundle.
|
|
1151
971
|
const noMetro = ctx.evidence ? !ctx.evidence.fiber.metroConnected : connectedApps.size === 0;
|
|
1152
972
|
if (ctx.outcome === "unmeaningful" && noMetro) {
|
|
1153
973
|
return undefined;
|
|
1154
974
|
}
|
|
1155
|
-
// D2 (Step 6): retry deduplication — coord-strategy taps at the same
|
|
1156
|
-
// grid cell within 60s upload the same near-identical artifact. The
|
|
1157
|
-
// first capture is enough; subsequent ones are noise.
|
|
1158
975
|
if (shouldDedupArtifact(ctx)) {
|
|
1159
976
|
return undefined;
|
|
1160
977
|
}
|
|
@@ -1250,7 +1067,6 @@ function attachArtifactSignals(result, signals) {
|
|
|
1250
1067
|
result.accessibilityMatchCount = signals.accessibilityMatchCount;
|
|
1251
1068
|
if (signals.appRoute)
|
|
1252
1069
|
result.appRoute = signals.appRoute;
|
|
1253
|
-
// Append agent-facing hints to error message
|
|
1254
1070
|
if (result.error) {
|
|
1255
1071
|
if (signals.ocrClosestMatch) {
|
|
1256
1072
|
result.error = `${result.error}\nClosest OCR match: ${signals.ocrClosestMatch}`;
|
|
@@ -1267,7 +1083,6 @@ function attachArtifactSignals(result, signals) {
|
|
|
1267
1083
|
}
|
|
1268
1084
|
return result;
|
|
1269
1085
|
}
|
|
1270
|
-
// --- Orchestrator ---
|
|
1271
1086
|
export async function tap(options) {
|
|
1272
1087
|
const query = buildQuery(options);
|
|
1273
1088
|
const strategy = options.strategy || "auto";
|
|
@@ -1275,7 +1090,6 @@ export async function tap(options) {
|
|
|
1275
1090
|
const maxTraversalDepth = options.maxTraversalDepth;
|
|
1276
1091
|
const deadline = Date.now() + TAP_TIMEOUT_MS;
|
|
1277
1092
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
1278
|
-
// Validate inputs
|
|
1279
1093
|
const hasSearchParam = query.text || query.testID || query.component;
|
|
1280
1094
|
const hasCoordinates = query.x !== undefined || query.y !== undefined;
|
|
1281
1095
|
if (!hasSearchParam && !hasCoordinates) {
|
|
@@ -1292,7 +1106,6 @@ export async function tap(options) {
|
|
|
1292
1106
|
error: "Both x and y coordinates must be provided"
|
|
1293
1107
|
};
|
|
1294
1108
|
}
|
|
1295
|
-
// Native mode: bypass React Native connection, tap directly via ADB/simctl
|
|
1296
1109
|
if (options.native && hasCoordinates) {
|
|
1297
1110
|
const nativeResolved = await resolveDeviceTarget(options.device);
|
|
1298
1111
|
if (!nativeResolved.ok) {
|
|
@@ -1305,7 +1118,6 @@ export async function tap(options) {
|
|
|
1305
1118
|
const platform = nativeResolved.target.platform;
|
|
1306
1119
|
const nativeUdid = nativeResolved.target.iosUdid;
|
|
1307
1120
|
const nativeShouldScreenshot = options.screenshot !== false;
|
|
1308
|
-
// Decoupled (I5, 2026-05-16): verify runs even when image bytes aren't returned.
|
|
1309
1121
|
const nativeShouldVerify = options.verify !== false;
|
|
1310
1122
|
let nativeBeforeBuffer = null;
|
|
1311
1123
|
let nativeScreenshotMeta;
|
|
@@ -1320,7 +1132,6 @@ export async function tap(options) {
|
|
|
1320
1132
|
};
|
|
1321
1133
|
}
|
|
1322
1134
|
}
|
|
1323
|
-
// If no screenshot was taken for verification, take one just for scaleFactor
|
|
1324
1135
|
if (!nativeScreenshotMeta) {
|
|
1325
1136
|
const ref = await captureScreenshot(platform, nativeUdid);
|
|
1326
1137
|
if (ref) {
|
|
@@ -1329,7 +1140,6 @@ export async function tap(options) {
|
|
|
1329
1140
|
originalHeight: ref.height,
|
|
1330
1141
|
scaleFactor: ref.scaleFactor
|
|
1331
1142
|
};
|
|
1332
|
-
// Also use it for verification if buffer is needed
|
|
1333
1143
|
if (!nativeBeforeBuffer) {
|
|
1334
1144
|
nativeBeforeBuffer = ref.buffer;
|
|
1335
1145
|
}
|
|
@@ -1398,10 +1208,6 @@ export async function tap(options) {
|
|
|
1398
1208
|
suggestion: `Take a screenshot (${platform === "ios" ? "ios_screenshot" : "android_screenshot"}) to verify coordinates.`
|
|
1399
1209
|
});
|
|
1400
1210
|
}
|
|
1401
|
-
// Resolve device target via the unified resolver. Replaces the prior chain
|
|
1402
|
-
// of (udid → connectedApp → findSimulatorByName) lookups and the standalone
|
|
1403
|
-
// "no platform → probe OS for devices" branch. Returns a structured error
|
|
1404
|
-
// when ambiguous; surface that to the caller so they can disambiguate.
|
|
1405
1211
|
const resolved = await resolveDeviceTarget(options.device);
|
|
1406
1212
|
if (!resolved.ok) {
|
|
1407
1213
|
return {
|
|
@@ -1413,10 +1219,6 @@ export async function tap(options) {
|
|
|
1413
1219
|
const deviceNote = resolved.note;
|
|
1414
1220
|
const platform = resolved.target.platform;
|
|
1415
1221
|
let targetUdid = resolved.target.iosUdid;
|
|
1416
|
-
// Pick the connected app to bias strategy selection. Prefer the registry
|
|
1417
|
-
// entry whose identifier matches the resolved target; fall back to a
|
|
1418
|
-
// device-name match (handy when registry entries lack identifiers yet);
|
|
1419
|
-
// last resort is the first connected app on the matching platform.
|
|
1420
1222
|
const allApps = Array.from(connectedApps.values());
|
|
1421
1223
|
let hasMetro = allApps.length > 0;
|
|
1422
1224
|
let app;
|
|
@@ -1431,16 +1233,11 @@ export async function tap(options) {
|
|
|
1431
1233
|
app = getConnectedAppByDevice(options.device) ?? undefined;
|
|
1432
1234
|
}
|
|
1433
1235
|
catch {
|
|
1434
|
-
// getConnectedAppByDevice throws on ambiguous matches; the resolver
|
|
1435
|
-
// would have already returned that error, so reaching here means
|
|
1436
|
-
// the resolved target's identifier just doesn't appear in the
|
|
1437
|
-
// registry yet — fall through to the platform-first pick below.
|
|
1438
1236
|
}
|
|
1439
1237
|
}
|
|
1440
1238
|
if (!app) {
|
|
1441
1239
|
app = allApps.find((a) => a.platform === platform) ?? allApps[0];
|
|
1442
1240
|
}
|
|
1443
|
-
// Try to auto-connect to Metro (for fiber strategy), but don't fail if it doesn't work.
|
|
1444
1241
|
if (!hasMetro) {
|
|
1445
1242
|
try {
|
|
1446
1243
|
await withTimeout((async () => {
|
|
@@ -1463,26 +1260,17 @@ export async function tap(options) {
|
|
|
1463
1260
|
}
|
|
1464
1261
|
}
|
|
1465
1262
|
catch {
|
|
1466
|
-
// Auto-connect failed — Metro-dependent strategies will be skipped
|
|
1467
1263
|
}
|
|
1468
1264
|
}
|
|
1469
|
-
// The response's `device` label must reflect the RESOLVED target, not the
|
|
1470
|
-
// registry-app fallback. When the registry lookup couldn't find a matching
|
|
1471
|
-
// app (e.g. simulatorUdid hadn't been backfilled yet), `app` lands on the
|
|
1472
|
-
// first iOS entry — which on a multi-sim setup is the WRONG device. The
|
|
1473
|
-
// resolver already knows the right deviceName; trust it. Bug #5 (2026-05-20).
|
|
1474
1265
|
const deviceName = resolved.target.deviceName || app?.deviceInfo?.deviceName;
|
|
1475
|
-
// Determine strategies
|
|
1476
1266
|
const strategies = getAvailableStrategies(query, strategy);
|
|
1477
1267
|
const attempted = [];
|
|
1478
1268
|
const evidence = makeEmptyEvidenceSink();
|
|
1479
|
-
// Early UI driver check for iOS — fail fast instead of falling through every strategy
|
|
1480
1269
|
const UI_DRIVER_REQUIRED_STRATEGIES = ["accessibility", "ocr", "coordinate"];
|
|
1481
1270
|
let uiDriverMissing = false;
|
|
1482
1271
|
if (platform === "ios") {
|
|
1483
1272
|
uiDriverMissing = !(await isUiDriverAvailable());
|
|
1484
1273
|
}
|
|
1485
|
-
// Filter strategies by available capabilities
|
|
1486
1274
|
const filteredStrategies = strategies.filter((strat) => {
|
|
1487
1275
|
if (strat === "fiber" && !hasMetro) {
|
|
1488
1276
|
attempted.push({
|
|
@@ -1516,18 +1304,56 @@ export async function tap(options) {
|
|
|
1516
1304
|
error: errorMessage
|
|
1517
1305
|
};
|
|
1518
1306
|
}
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1307
|
+
let shadowWarning;
|
|
1308
|
+
if (hasMetro) {
|
|
1309
|
+
const guardBudget = Math.min(remainingMs(), OVERLAY_GUARD_BUDGET_MS);
|
|
1310
|
+
if (guardBudget >= MIN_STRATEGY_BUDGET_MS) {
|
|
1311
|
+
let blockedBy = null;
|
|
1312
|
+
try {
|
|
1313
|
+
const { checkOverlayBlocking } = await import("./overlayGuard.js");
|
|
1314
|
+
blockedBy = await withTimeout(checkOverlayBlocking({
|
|
1315
|
+
query,
|
|
1316
|
+
platform,
|
|
1317
|
+
udid: targetUdid,
|
|
1318
|
+
deviceId: resolved.target.androidSerial,
|
|
1319
|
+
deviceName
|
|
1320
|
+
}), guardBudget, "overlay-guard");
|
|
1321
|
+
}
|
|
1322
|
+
catch {
|
|
1323
|
+
blockedBy = null;
|
|
1324
|
+
}
|
|
1325
|
+
if (blockedBy?.kind === "blocked") {
|
|
1326
|
+
attempted.push({
|
|
1327
|
+
strategy: "overlay-guard",
|
|
1328
|
+
reason: `refused — target covered by ${blockedBy.overlay}`,
|
|
1329
|
+
outcome: "skipped"
|
|
1330
|
+
});
|
|
1331
|
+
return {
|
|
1332
|
+
success: false,
|
|
1333
|
+
query,
|
|
1334
|
+
attempted,
|
|
1335
|
+
device: deviceName,
|
|
1336
|
+
platform,
|
|
1337
|
+
error: `Refused: ${blockedBy.element} is covered by ${blockedBy.overlay}. ` +
|
|
1338
|
+
`A tap here would be delivered to the overlay, not to the target, ` +
|
|
1339
|
+
`so it was not dispatched.`,
|
|
1340
|
+
suggestion: `Dismiss or close the overlay first, then retry. ` +
|
|
1341
|
+
`get_screen_state lists the overlay's own controls, and everything behind it under "🚫 Blocked by overlay". ` +
|
|
1342
|
+
`To tap the overlay itself, use coordinates from its own group.`
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
if (blockedBy?.kind === "shadowed") {
|
|
1346
|
+
shadowWarning =
|
|
1347
|
+
`This coordinate is occupied by ${blockedBy.hit}, part of ${blockedBy.overlay} — ` +
|
|
1348
|
+
`that is what received the tap. ${blockedBy.covered} sits underneath it and ` +
|
|
1349
|
+
`cannot be tapped until the overlay closes.`;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1522
1353
|
const shouldScreenshot = options.screenshot !== false;
|
|
1523
1354
|
const canVerify = options.verify !== false;
|
|
1524
|
-
// Capture "before" screenshot. Always attempted (independent of canVerify)
|
|
1525
|
-
// so failure artifacts always carry a before.png — the diagnostic value of
|
|
1526
|
-
// the before frame doesn't depend on whether we run the post-tap diff.
|
|
1527
1355
|
let beforeBuffer = null;
|
|
1528
1356
|
let beforeScaleFactor;
|
|
1529
|
-
// Pixel dimensions of the frame we just captured. getDevicePixelRatio would
|
|
1530
|
-
// otherwise shell out for a screenshot of its own to learn exactly this.
|
|
1531
1357
|
let beforeDims;
|
|
1532
1358
|
{
|
|
1533
1359
|
const before = await captureScreenshot(platform, targetUdid);
|
|
@@ -1537,16 +1363,6 @@ export async function tap(options) {
|
|
|
1537
1363
|
beforeDims = { width: before.width, height: before.height };
|
|
1538
1364
|
}
|
|
1539
1365
|
}
|
|
1540
|
-
// OCR runs lazily when the loop reaches it (see the `case "ocr"` branch).
|
|
1541
|
-
// A concurrent pre-warm probe used to fire here to shave ~5s off OCR-win
|
|
1542
|
-
// rows, but it dispatched a paid Google Vision request at t=0 on every
|
|
1543
|
-
// text-predicate tap — and since cloud OCR (~200ms) finishes well before
|
|
1544
|
-
// the higher-priority strategy that usually wins, the post-win abort came
|
|
1545
|
-
// too late to cancel the billed request. OCR wins only ~1.9% of taps, so
|
|
1546
|
-
// the pre-warm paid for cloud Vision on ~42% of eligible taps to help a
|
|
1547
|
-
// tiny minority. Removed 2026-06-02; the timeout-recovery path below still
|
|
1548
|
-
// salvages the perfect-match-past-cap case.
|
|
1549
|
-
// Execute strategies in order with per-strategy caps and overall budget
|
|
1550
1366
|
for (const strat of filteredStrategies) {
|
|
1551
1367
|
const remaining = remainingMs();
|
|
1552
1368
|
if (remaining < MIN_STRATEGY_BUDGET_MS) {
|
|
@@ -1563,8 +1379,6 @@ export async function tap(options) {
|
|
|
1563
1379
|
try {
|
|
1564
1380
|
switch (strat) {
|
|
1565
1381
|
case "fiber":
|
|
1566
|
-
// Fiber is JS-only against a CDP target — no subprocess to cancel,
|
|
1567
|
-
// so the cheaper non-cancellable wrapper is fine.
|
|
1568
1382
|
result = await withTimeout(tryFiberStrategy(query, index, maxTraversalDepth, evidence, deviceName), budget, `fiber`);
|
|
1569
1383
|
break;
|
|
1570
1384
|
case "accessibility":
|
|
@@ -1574,9 +1388,6 @@ export async function tap(options) {
|
|
|
1574
1388
|
result = await withCancelableTimeout((signal) => tryOcrStrategy(query, platform, targetUdid, evidence, signal), budget, `ocr`);
|
|
1575
1389
|
break;
|
|
1576
1390
|
case "coordinate":
|
|
1577
|
-
// Prefer `beforeScaleFactor` (captured against `targetUdid` this turn)
|
|
1578
|
-
// over `app.lastScreenshot.scaleFactor` (stale and may belong to a
|
|
1579
|
-
// different device on a multi-sim setup). Bug #5 (2026-05-20).
|
|
1580
1391
|
result = await withTimeout(tryCoordinateStrategy(query.x, query.y, platform, beforeScaleFactor != null
|
|
1581
1392
|
? {
|
|
1582
1393
|
originalWidth: beforeDims?.width ?? 0,
|
|
@@ -1591,11 +1402,6 @@ export async function tap(options) {
|
|
|
1591
1402
|
}
|
|
1592
1403
|
catch (err) {
|
|
1593
1404
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1594
|
-
// Classify the throw: a strategy-level timeout means "result UNKNOWN"
|
|
1595
|
-
// (the strategy didn't finish), distinct from "ran clean and found
|
|
1596
|
-
// nothing". Agents must not infer absence from timeouts. The
|
|
1597
|
-
// `outcome` field carries that distinction; `reason` stays the raw
|
|
1598
|
-
// message so existing log-matchers / classifiers keep working.
|
|
1599
1405
|
const outcome = STRATEGY_TIMEOUT_RE.test(reason) ? "timeout" : "error";
|
|
1600
1406
|
attempted.push({ strategy: strat, reason, outcome });
|
|
1601
1407
|
continue;
|
|
@@ -1630,11 +1436,6 @@ export async function tap(options) {
|
|
|
1630
1436
|
beforeScaleFactor,
|
|
1631
1437
|
markerPx: strategyMarker
|
|
1632
1438
|
}));
|
|
1633
|
-
// Burst always produces a screenshot for diff visualization. Drop the
|
|
1634
|
-
// bytes when the caller didn't ask for them AND the tap was meaningful
|
|
1635
|
-
// — keeping the screenshot on unmeaningful taps saves the agent a
|
|
1636
|
-
// round-trip when it needs to diagnose. Frames remain in imageBuffer
|
|
1637
|
-
// either way (accessible via get_images(groupId=verification.burstGroupId)).
|
|
1638
1439
|
if (!shouldScreenshot && verification?.meaningful !== false)
|
|
1639
1440
|
screenshot = undefined;
|
|
1640
1441
|
}
|
|
@@ -1648,8 +1449,6 @@ export async function tap(options) {
|
|
|
1648
1449
|
beforeScaleFactor,
|
|
1649
1450
|
markerPx: strategyMarker
|
|
1650
1451
|
}));
|
|
1651
|
-
// Mirror the burst-path gate: drop the screenshot only when the
|
|
1652
|
-
// caller didn't ask for it AND the tap was meaningful.
|
|
1653
1452
|
if (!shouldScreenshot && verification?.meaningful !== false)
|
|
1654
1453
|
screenshot = undefined;
|
|
1655
1454
|
}
|
|
@@ -1674,18 +1473,12 @@ export async function tap(options) {
|
|
|
1674
1473
|
screenshot,
|
|
1675
1474
|
verification
|
|
1676
1475
|
});
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1476
|
+
if (shadowWarning) {
|
|
1477
|
+
successResult.warning = successResult.warning
|
|
1478
|
+
? `${successResult.warning}\n${shadowWarning}`
|
|
1479
|
+
: shadowWarning;
|
|
1480
|
+
}
|
|
1681
1481
|
if (verification && !verification.skipped && verification.meaningful === false) {
|
|
1682
|
-
// A coordinate tap that changed nothing is the one case where the
|
|
1683
|
-
// agent has no idea what went wrong — it estimated x/y from a
|
|
1684
|
-
// screenshot and got back "nothing happened". Screen state knows
|
|
1685
|
-
// what sits at that point and what an overlay blocks; spend the
|
|
1686
|
-
// ~200ms here (already the slow path) to turn a dead end into a
|
|
1687
|
-
// next step. Text/testID/component predicates don't need this:
|
|
1688
|
-
// their failure modes are already named in `attempted`.
|
|
1689
1482
|
if (strat === "coordinate" && result.convertedTo) {
|
|
1690
1483
|
const diagnosis = await diagnoseCoordinateMiss({
|
|
1691
1484
|
point: { x: result.convertedTo.x, y: result.convertedTo.y },
|
|
@@ -1721,10 +1514,6 @@ export async function tap(options) {
|
|
|
1721
1514
|
}
|
|
1722
1515
|
return successResult;
|
|
1723
1516
|
}
|
|
1724
|
-
// Classify the strategy's non-success result. The reason string carries
|
|
1725
|
-
// the diagnostic from the strategy; outcome lets agents branch on category
|
|
1726
|
-
// without parsing prose. "invisible" / "ambiguous" are pinned by substring
|
|
1727
|
-
// matches the fiber strategy emits via pressables.ts.
|
|
1728
1517
|
const reasonStr = result.reason || "";
|
|
1729
1518
|
let attemptOutcome = "not-found";
|
|
1730
1519
|
if (result.ambiguous)
|
|
@@ -1734,36 +1523,26 @@ export async function tap(options) {
|
|
|
1734
1523
|
else if (STRATEGY_TIMEOUT_RE.test(reasonStr))
|
|
1735
1524
|
attemptOutcome = "timeout";
|
|
1736
1525
|
attempted.push({ strategy: strat, reason: reasonStr, outcome: attemptOutcome });
|
|
1737
|
-
// If fiber found an element with measured coordinates, do a native tap directly
|
|
1738
1526
|
if (strat === "fiber" && result.convertedTo && result.pressed) {
|
|
1739
1527
|
try {
|
|
1740
1528
|
const coords = result.convertedTo;
|
|
1741
1529
|
if (platform === "ios") {
|
|
1742
|
-
// react-native-screens modal/sheet presentations cause measureInWindow to
|
|
1743
|
-
// return y relative to the screen's content origin, not the window. If the
|
|
1744
|
-
// measured y falls inside the safe-area band, shift it down by the inset.
|
|
1745
1530
|
const safeAreaTop = await getIOSSafeAreaTop(targetUdid);
|
|
1746
1531
|
const tapY = (safeAreaTop > 0 && coords.y < safeAreaTop) ? coords.y + safeAreaTop : coords.y;
|
|
1747
1532
|
await iosTap(coords.x, tapY, { udid: targetUdid });
|
|
1748
1533
|
coords.y = tapY;
|
|
1749
1534
|
}
|
|
1750
1535
|
else {
|
|
1751
|
-
// Fabric returns dp — androidTap expects pixels
|
|
1752
|
-
// Convert dp to pixels using device density
|
|
1753
1536
|
const { androidGetDensity } = await import("../core/android.js");
|
|
1754
1537
|
const densityResult = await androidGetDensity();
|
|
1755
1538
|
const densityScale = (densityResult.density || 420) / 160;
|
|
1756
1539
|
const pxX = Math.round(coords.x * densityScale);
|
|
1757
1540
|
const pxY = Math.round(coords.y * densityScale);
|
|
1758
1541
|
await androidTap(pxX, pxY);
|
|
1759
|
-
// Report the actual tap coords (pixels) the caller can pass straight
|
|
1760
|
-
// to coordinate tools / verification — not the raw dp from fiber.
|
|
1761
|
-
// Matches every other Android coord report in the tool (OB1, 2026-05-20).
|
|
1762
1542
|
coords.x = pxX;
|
|
1763
1543
|
coords.y = pxY;
|
|
1764
1544
|
coords.unit = "pixels";
|
|
1765
1545
|
}
|
|
1766
|
-
// fiber+native uses native tap — always verify
|
|
1767
1546
|
let screenshot;
|
|
1768
1547
|
let verification;
|
|
1769
1548
|
let fnDpr;
|
|
@@ -1840,12 +1619,8 @@ export async function tap(options) {
|
|
|
1840
1619
|
});
|
|
1841
1620
|
}
|
|
1842
1621
|
catch {
|
|
1843
|
-
// Native tap at fiber coordinates failed — continue to next strategy
|
|
1844
1622
|
}
|
|
1845
1623
|
}
|
|
1846
|
-
// Ambiguous fiber result — multiple elements matched, no index given.
|
|
1847
|
-
// Return immediately with the full list so the agent can decide.
|
|
1848
|
-
// Do NOT fall through to other strategies (they can't resolve ambiguity).
|
|
1849
1624
|
if (result.matches && result.ambiguous) {
|
|
1850
1625
|
const { screenshot: matchScreenshot } = shouldScreenshot
|
|
1851
1626
|
? await verifyAndCapture({ platform, shouldVerify: false, shouldScreenshot: true, beforeBuffer: null, udid: targetUdid })
|
|
@@ -1869,15 +1644,8 @@ export async function tap(options) {
|
|
|
1869
1644
|
});
|
|
1870
1645
|
}
|
|
1871
1646
|
}
|
|
1872
|
-
// All strategies failed — check if the tap budget was the cause.
|
|
1873
|
-
// Only outer withTimeout wrapper messages or Skipped entries count; nested
|
|
1874
|
-
// sub-op errors that happen to contain "timed out" do not imply the tap
|
|
1875
|
-
// itself ran out of time.
|
|
1876
1647
|
const hitTimeout = isTapTimeout(attempted);
|
|
1877
1648
|
const elapsed = TAP_TIMEOUT_MS - remainingMs();
|
|
1878
|
-
// If fiber located the element but visibility filtered it out, prepend a
|
|
1879
|
-
// scroll-or-dismiss hint so the agent doesn't waste a turn assuming the
|
|
1880
|
-
// testID/component is missing.
|
|
1881
1649
|
const fiberSawInvisible = attempted.some(a => a.strategy === "fiber" && a.outcome === "invisible");
|
|
1882
1650
|
const allTimedOut = attempted.length > 0 && attempted.every(a => a.outcome === "timeout" || a.outcome === "skipped");
|
|
1883
1651
|
let suggestion = buildSuggestion(query, strategies, platform);
|
|
@@ -1885,8 +1653,6 @@ export async function tap(options) {
|
|
|
1885
1653
|
suggestion = `Element exists in the React tree but is not on screen. Try scrolling it into view with swipe(), dismiss any overlay covering it, or wait for layout to settle. ` + suggestion;
|
|
1886
1654
|
}
|
|
1887
1655
|
else if (allTimedOut) {
|
|
1888
|
-
// No strategy got to a definitive answer — don't let the agent conclude
|
|
1889
|
-
// the element is missing. Steer toward retry or a different strategy.
|
|
1890
1656
|
suggestion = `All strategies timed out — the element's presence is UNKNOWN. Retry the tap (transient slowness is common on dense screens), or try a different strategy explicitly (e.g. strategy='fiber' if accessibility timed out). ` + suggestion;
|
|
1891
1657
|
}
|
|
1892
1658
|
const { screenshot: failScreenshot } = shouldScreenshot
|
|
@@ -1899,8 +1665,6 @@ export async function tap(options) {
|
|
|
1899
1665
|
scaleFactor: failScreenshot.scaleFactor
|
|
1900
1666
|
};
|
|
1901
1667
|
}
|
|
1902
|
-
// Pick the right error framing: a definitive "not found" is different from
|
|
1903
|
-
// "all strategies timed out — we don't know whether it exists".
|
|
1904
1668
|
let errorOverride;
|
|
1905
1669
|
if (hitTimeout) {
|
|
1906
1670
|
errorOverride = `Tap timed out after ${elapsed}ms (budget ${TAP_TIMEOUT_MS}ms)`;
|
|
@@ -1935,10 +1699,6 @@ export async function tap(options) {
|
|
|
1935
1699
|
});
|
|
1936
1700
|
return attachArtifactSignals(failureResult, failSignals);
|
|
1937
1701
|
}
|
|
1938
|
-
// Matches testIDs typical of virtualized list items: trailing `-<digits>` (e.g. `store-item-0`,
|
|
1939
|
-
// `route-item-6`) or containing a UUID v4 fragment (e.g. `group-card-e4566bc6-9164-4fd0-...`).
|
|
1940
|
-
// Used by buildSuggestion() to tell the agent to scroll first when a list-item testID
|
|
1941
|
-
// can't be found in the visible fiber tree.
|
|
1942
1702
|
const VIRTUALIZED_TESTID_RE = /(-\d+$)|([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i;
|
|
1943
1703
|
function looksLikeVirtualizedListItem(testID) {
|
|
1944
1704
|
return VIRTUALIZED_TESTID_RE.test(testID);
|
|
@@ -1960,10 +1720,6 @@ function buildSuggestion(query, triedStrategies, platform) {
|
|
|
1960
1720
|
if (query.testID && !triedStrategies.includes("ocr")) {
|
|
1961
1721
|
suggestions.push("testID not found in fiber/accessibility tree — verify the element is on the current screen with a screenshot");
|
|
1962
1722
|
}
|
|
1963
|
-
// List-item testIDs (suffix `-N` or containing a UUID) are typically rendered inside a
|
|
1964
|
-
// virtualized list — items beyond the viewport are unmounted, so the fiber tree genuinely
|
|
1965
|
-
// doesn't contain them. Adding the hint as a separate suggestion (rather than rewriting
|
|
1966
|
-
// the error) keeps the existing message stable for tooling that parses it.
|
|
1967
1723
|
if (query.testID && looksLikeVirtualizedListItem(query.testID)) {
|
|
1968
1724
|
suggestions.push("testID looks like a virtualized list item — scroll it on-screen first " +
|
|
1969
1725
|
`(swipe()${platform === "ios" ? " or ios_button arrow keys" : ""}) before tapping`);
|