react-native-ai-debugger 2.4.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.js +0 -17
- package/build/__tests__/unit/overlayGuard.test.js.map +1 -1
- 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 +0 -16
- 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 +0 -38
- 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 +0 -36
- package/build/core/inspector.js.map +1 -1
- package/build/core/ios.d.ts +0 -127
- package/build/core/ios.d.ts.map +1 -1
- package/build/core/ios.js +2 -313
- 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 +1 -60
- package/build/core/screenLayout.js.map +1 -1
- package/build/core/screenSpace.d.ts +0 -107
- package/build/core/screenSpace.d.ts.map +1 -1
- package/build/core/screenSpace.js +0 -103
- 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 +0 -23
- package/build/core/screenSpaceDevice.js.map +1 -1
- package/build/core/screenState.d.ts +0 -73
- package/build/core/screenState.d.ts.map +1 -1
- package/build/core/screenState.js +6 -115
- 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 +2 -40
- package/build/pro/overlayGuard.d.ts.map +1 -1
- package/build/pro/overlayGuard.js +0 -63
- package/build/pro/overlayGuard.js.map +1 -1
- 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 +7 -330
- 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 +17 -45
- 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,7 +14,6 @@ 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
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
|
}
|
|
@@ -31,7 +30,6 @@ 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
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
|
}
|
|
@@ -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,24 +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
|
-
* Ceiling for the pre-dispatch overlay check. It shares the tap's deadline with the
|
|
962
|
-
* strategies that follow, so it must not be able to spend the whole budget on a fiber
|
|
963
|
-
* read — a screen big enough to make this slow is exactly when the tap still needs time.
|
|
964
|
-
*/
|
|
965
845
|
const OVERLAY_GUARD_BUDGET_MS = 3000;
|
|
966
|
-
// Per-strategy budget. OCR cap on Android is bumped via maxStrategyMs() because
|
|
967
|
-
// the ADB screencap+pull leg has ~2s variance on real devices; iOS stays at 5s
|
|
968
|
-
// where xcrun simctl screenshot is consistent.
|
|
969
|
-
// Heavy strategies (fiber on deep trees with multi-depth retries, axe accessibility
|
|
970
|
-
// dumps on dense iOS screens) need more headroom — previous caps produced spurious
|
|
971
|
-
// timeouts that the agent read as "element missing" when the strategy simply didn't
|
|
972
|
-
// get to finish. The overall TAP_TIMEOUT_MS budget still bounds the worst case.
|
|
973
|
-
// Coordinate looks "light" (one tap subprocess) but on iOS it pairs an axe/idb
|
|
974
|
-
// invocation with a CDP getDevicePixelRatio + best-effort fiber inspection;
|
|
975
|
-
// the 3000ms cap surfaced as spurious "coordinate timed out" failures while
|
|
976
|
-
// 20+ seconds of the overall budget were still unused. Align with the other
|
|
977
|
-
// strategies — the global TAP_TIMEOUT_MS still bounds the worst case.
|
|
978
846
|
const MAX_STRATEGY_MS = {
|
|
979
847
|
fiber: 8000,
|
|
980
848
|
accessibility: 6000,
|
|
@@ -986,9 +854,6 @@ function maxStrategyMs(strategy, platform) {
|
|
|
986
854
|
return 9000;
|
|
987
855
|
return MAX_STRATEGY_MS[strategy] ?? 5000;
|
|
988
856
|
}
|
|
989
|
-
// Matches only the outer withTimeout wrapper message for a tap strategy.
|
|
990
|
-
// Nested sub-operation errors inside a strategy (e.g. "CDP getProperties timed out after 150ms")
|
|
991
|
-
// must NOT be classified as a tap-level timeout.
|
|
992
857
|
const STRATEGY_TIMEOUT_RE = /^(fiber|accessibility|ocr|coordinate) timed out after \d+ms$/;
|
|
993
858
|
export function isTapTimeout(attempted) {
|
|
994
859
|
return attempted.some((a) => a.outcome === "timeout" || a.outcome === "skipped" || STRATEGY_TIMEOUT_RE.test(a.reason) || a.reason.startsWith("Skipped —"));
|
|
@@ -1005,12 +870,6 @@ function withTimeout(promise, ms, label) {
|
|
|
1005
870
|
});
|
|
1006
871
|
});
|
|
1007
872
|
}
|
|
1008
|
-
/**
|
|
1009
|
-
* Like withTimeout, but creates an AbortController whose signal is handed to
|
|
1010
|
-
* the inner factory. On timeout the signal aborts so subprocess work
|
|
1011
|
-
* (uiautomator dump, OCR fetch, screencap) can be killed instead of running
|
|
1012
|
-
* past the strategy cap and bleeding into total tap duration.
|
|
1013
|
-
*/
|
|
1014
873
|
function withCancelableTimeout(make, ms, label) {
|
|
1015
874
|
const ctrl = new AbortController();
|
|
1016
875
|
const timer = setTimeout(() => ctrl.abort(), ms);
|
|
@@ -1021,15 +880,6 @@ function withCancelableTimeout(make, ms, label) {
|
|
|
1021
880
|
throw err;
|
|
1022
881
|
});
|
|
1023
882
|
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Compute marker coordinates in screenshot-pixel space (what the returned PNG uses).
|
|
1026
|
-
* Unit rules by strategy, assuming screenshotScale = downscale factor:
|
|
1027
|
-
* coordinate : input is already screenshot pixels → pass through
|
|
1028
|
-
* ocr : match.tapCenter is image-pixel (= screenshot-pixel) → pass through
|
|
1029
|
-
* iOS points : point * DPR / screenshotScale
|
|
1030
|
-
* android dp : dp * densityScale / screenshotScale
|
|
1031
|
-
* android devicePx : devicePx / screenshotScale
|
|
1032
|
-
*/
|
|
1033
883
|
function computeMarkerPx(args) {
|
|
1034
884
|
const { strategy, input, convertedTo, platform, screenshotScale, devicePixelRatio, androidDensityScale } = args;
|
|
1035
885
|
const scale = screenshotScale || 1;
|
|
@@ -1042,36 +892,27 @@ function computeMarkerPx(args) {
|
|
|
1042
892
|
return { x: Math.round(convertedTo.x), y: Math.round(convertedTo.y) };
|
|
1043
893
|
}
|
|
1044
894
|
if (platform === "ios") {
|
|
1045
|
-
// fiber/accessibility on iOS return points
|
|
1046
895
|
const dpr = devicePixelRatio || 3;
|
|
1047
896
|
return {
|
|
1048
897
|
x: Math.round((convertedTo.x * dpr) / scale),
|
|
1049
898
|
y: Math.round((convertedTo.y * dpr) / scale)
|
|
1050
899
|
};
|
|
1051
900
|
}
|
|
1052
|
-
// Android
|
|
1053
901
|
if (strategy === "fiber+native" && androidDensityScale) {
|
|
1054
|
-
// Fabric fiber returns dp; fiber+native path scales to device pixels before tap
|
|
1055
902
|
return {
|
|
1056
903
|
x: Math.round((convertedTo.x * androidDensityScale) / scale),
|
|
1057
904
|
y: Math.round((convertedTo.y * androidDensityScale) / scale)
|
|
1058
905
|
};
|
|
1059
906
|
}
|
|
1060
|
-
// accessibility android: convertedTo is device pixels
|
|
1061
907
|
return {
|
|
1062
908
|
x: Math.round(convertedTo.x / scale),
|
|
1063
909
|
y: Math.round(convertedTo.y / scale)
|
|
1064
910
|
};
|
|
1065
911
|
}
|
|
1066
|
-
// D2 (Step 6): in-memory ring buffer of recent coord-strategy artifact keys
|
|
1067
|
-
// `${round(x/20)}:${round(y/20)}`. Entries expire after 60s. The first
|
|
1068
|
-
// failed coord tap at a grid cell uploads its artifact; retries within the
|
|
1069
|
-
// window get suppressed.
|
|
1070
912
|
const recentCoordArtifacts = new Map();
|
|
1071
913
|
const COORD_DEDUP_WINDOW_MS = 60_000;
|
|
1072
914
|
const COORD_DEDUP_BUCKET = 20;
|
|
1073
915
|
function shouldDedupArtifact(ctx) {
|
|
1074
|
-
// Only applies to coordinate-strategy artifacts where we have x,y coords.
|
|
1075
916
|
const winningStrat = ctx.attempted.find(a => a.reason === "success")?.strategy
|
|
1076
917
|
?? ctx.attempted[ctx.attempted.length - 1]?.strategy;
|
|
1077
918
|
if (winningStrat !== "coordinate")
|
|
@@ -1082,7 +923,6 @@ function shouldDedupArtifact(ctx) {
|
|
|
1082
923
|
return false;
|
|
1083
924
|
const key = `${Math.round(x / COORD_DEDUP_BUCKET)}:${Math.round(y / COORD_DEDUP_BUCKET)}`;
|
|
1084
925
|
const now = Date.now();
|
|
1085
|
-
// Sweep stale entries opportunistically — keeps the map from growing.
|
|
1086
926
|
for (const [k, ts] of recentCoordArtifacts) {
|
|
1087
927
|
if (now - ts > COORD_DEDUP_WINDOW_MS)
|
|
1088
928
|
recentCoordArtifacts.delete(k);
|
|
@@ -1093,26 +933,9 @@ function shouldDedupArtifact(ctx) {
|
|
|
1093
933
|
recentCoordArtifacts.set(key, now);
|
|
1094
934
|
return false;
|
|
1095
935
|
}
|
|
1096
|
-
/** Test-only: drop the dedup state so a fresh tap always uploads. */
|
|
1097
936
|
export function resetCoordArtifactDedup() {
|
|
1098
937
|
recentCoordArtifacts.clear();
|
|
1099
938
|
}
|
|
1100
|
-
/**
|
|
1101
|
-
* Turn an unmeaningful coordinate tap into an explanation.
|
|
1102
|
-
*
|
|
1103
|
-
* Coordinate spaces used to be the hard part here, and the old approach — recovering a
|
|
1104
|
-
* scale by dividing the caller's input by the executed tap point, then multiplying the
|
|
1105
|
-
* raw screen state by it — was wrong in a way that mattered. That ratio carries no inset
|
|
1106
|
-
* term, but the caller's coordinate comes from a space where `topInset` has been applied,
|
|
1107
|
-
* so on Android and inside iOS modal presentations the probe landed roughly
|
|
1108
|
-
* `topInset x factor` off. An offset probe can hit-test a neighbouring element, and
|
|
1109
|
-
* because `explainCoordinateMiss` checks reachable pressables before blocked ones, the
|
|
1110
|
-
* usual result was a confident "the coordinates were correct, check your onPress handler"
|
|
1111
|
-
* about an element that was in fact covered.
|
|
1112
|
-
*
|
|
1113
|
-
* Now that every tool shares one space, there is nothing to recover: normalise the state
|
|
1114
|
-
* the same way the layout tools do and hit-test the caller's own coordinate against it.
|
|
1115
|
-
*/
|
|
1116
939
|
async function diagnoseCoordinateMiss(args) {
|
|
1117
940
|
try {
|
|
1118
941
|
const { getScreenState } = await import("../core/screenState.js");
|
|
@@ -1130,12 +953,9 @@ async function diagnoseCoordinateMiss(args) {
|
|
|
1130
953
|
deviceId: app?.adbSerial
|
|
1131
954
|
});
|
|
1132
955
|
const normalised = screenStateToDeliveredPx(screenStateToScreenSpace(ss.screenState, metrics), metrics);
|
|
1133
|
-
// State and probe are both in delivered pixels now, so the reported coordinates
|
|
1134
|
-
// are already the caller's space — no mapping back.
|
|
1135
956
|
return explainCoordinateMiss(args.inputPoint, normalised) ?? undefined;
|
|
1136
957
|
}
|
|
1137
958
|
catch {
|
|
1138
|
-
// Diagnosis is best-effort — never let it turn a successful tap into an error.
|
|
1139
959
|
return undefined;
|
|
1140
960
|
}
|
|
1141
961
|
}
|
|
@@ -1143,26 +963,15 @@ async function captureTapArtifact(ctx) {
|
|
|
1143
963
|
try {
|
|
1144
964
|
const { getServerVersion, categorizeError } = await import("../core/telemetry.js");
|
|
1145
965
|
const strategyChain = ctx.attempted.map(a => `${a.strategy}:${a.reason.slice(0, 40)}`).join("|");
|
|
1146
|
-
// Resolve error category up-front so we can short-circuit driver-missing failures.
|
|
1147
|
-
// These aren't tap-tool bugs — they're host setup problems (no idb/axe/adb on PATH).
|
|
1148
|
-
// Skipping the artifact upload saves R2 storage and stops these from polluting the
|
|
1149
|
-
// failure dashboard, which is the signal we use to triage genuine tap regressions.
|
|
1150
966
|
const errorCategory = ctx.errorCategory
|
|
1151
967
|
?? categorizeError(ctx.errorMessage ?? "", strategyChain);
|
|
1152
968
|
if (errorCategory === "driver_missing") {
|
|
1153
969
|
return undefined;
|
|
1154
970
|
}
|
|
1155
|
-
// D1 (Step 6): no-metro unmeaningful taps have no diagnostic signal
|
|
1156
|
-
// beyond a screenshot diff that's already below threshold by definition.
|
|
1157
|
-
// ~50% of recent failure-artifact rows were these — they crowd out real
|
|
1158
|
-
// failures in the dashboard. Telemetry row still flows; just no R2 bundle.
|
|
1159
971
|
const noMetro = ctx.evidence ? !ctx.evidence.fiber.metroConnected : connectedApps.size === 0;
|
|
1160
972
|
if (ctx.outcome === "unmeaningful" && noMetro) {
|
|
1161
973
|
return undefined;
|
|
1162
974
|
}
|
|
1163
|
-
// D2 (Step 6): retry deduplication — coord-strategy taps at the same
|
|
1164
|
-
// grid cell within 60s upload the same near-identical artifact. The
|
|
1165
|
-
// first capture is enough; subsequent ones are noise.
|
|
1166
975
|
if (shouldDedupArtifact(ctx)) {
|
|
1167
976
|
return undefined;
|
|
1168
977
|
}
|
|
@@ -1258,7 +1067,6 @@ function attachArtifactSignals(result, signals) {
|
|
|
1258
1067
|
result.accessibilityMatchCount = signals.accessibilityMatchCount;
|
|
1259
1068
|
if (signals.appRoute)
|
|
1260
1069
|
result.appRoute = signals.appRoute;
|
|
1261
|
-
// Append agent-facing hints to error message
|
|
1262
1070
|
if (result.error) {
|
|
1263
1071
|
if (signals.ocrClosestMatch) {
|
|
1264
1072
|
result.error = `${result.error}\nClosest OCR match: ${signals.ocrClosestMatch}`;
|
|
@@ -1275,7 +1083,6 @@ function attachArtifactSignals(result, signals) {
|
|
|
1275
1083
|
}
|
|
1276
1084
|
return result;
|
|
1277
1085
|
}
|
|
1278
|
-
// --- Orchestrator ---
|
|
1279
1086
|
export async function tap(options) {
|
|
1280
1087
|
const query = buildQuery(options);
|
|
1281
1088
|
const strategy = options.strategy || "auto";
|
|
@@ -1283,7 +1090,6 @@ export async function tap(options) {
|
|
|
1283
1090
|
const maxTraversalDepth = options.maxTraversalDepth;
|
|
1284
1091
|
const deadline = Date.now() + TAP_TIMEOUT_MS;
|
|
1285
1092
|
const remainingMs = () => Math.max(0, deadline - Date.now());
|
|
1286
|
-
// Validate inputs
|
|
1287
1093
|
const hasSearchParam = query.text || query.testID || query.component;
|
|
1288
1094
|
const hasCoordinates = query.x !== undefined || query.y !== undefined;
|
|
1289
1095
|
if (!hasSearchParam && !hasCoordinates) {
|
|
@@ -1300,7 +1106,6 @@ export async function tap(options) {
|
|
|
1300
1106
|
error: "Both x and y coordinates must be provided"
|
|
1301
1107
|
};
|
|
1302
1108
|
}
|
|
1303
|
-
// Native mode: bypass React Native connection, tap directly via ADB/simctl
|
|
1304
1109
|
if (options.native && hasCoordinates) {
|
|
1305
1110
|
const nativeResolved = await resolveDeviceTarget(options.device);
|
|
1306
1111
|
if (!nativeResolved.ok) {
|
|
@@ -1313,7 +1118,6 @@ export async function tap(options) {
|
|
|
1313
1118
|
const platform = nativeResolved.target.platform;
|
|
1314
1119
|
const nativeUdid = nativeResolved.target.iosUdid;
|
|
1315
1120
|
const nativeShouldScreenshot = options.screenshot !== false;
|
|
1316
|
-
// Decoupled (I5, 2026-05-16): verify runs even when image bytes aren't returned.
|
|
1317
1121
|
const nativeShouldVerify = options.verify !== false;
|
|
1318
1122
|
let nativeBeforeBuffer = null;
|
|
1319
1123
|
let nativeScreenshotMeta;
|
|
@@ -1328,7 +1132,6 @@ export async function tap(options) {
|
|
|
1328
1132
|
};
|
|
1329
1133
|
}
|
|
1330
1134
|
}
|
|
1331
|
-
// If no screenshot was taken for verification, take one just for scaleFactor
|
|
1332
1135
|
if (!nativeScreenshotMeta) {
|
|
1333
1136
|
const ref = await captureScreenshot(platform, nativeUdid);
|
|
1334
1137
|
if (ref) {
|
|
@@ -1337,7 +1140,6 @@ export async function tap(options) {
|
|
|
1337
1140
|
originalHeight: ref.height,
|
|
1338
1141
|
scaleFactor: ref.scaleFactor
|
|
1339
1142
|
};
|
|
1340
|
-
// Also use it for verification if buffer is needed
|
|
1341
1143
|
if (!nativeBeforeBuffer) {
|
|
1342
1144
|
nativeBeforeBuffer = ref.buffer;
|
|
1343
1145
|
}
|
|
@@ -1406,10 +1208,6 @@ export async function tap(options) {
|
|
|
1406
1208
|
suggestion: `Take a screenshot (${platform === "ios" ? "ios_screenshot" : "android_screenshot"}) to verify coordinates.`
|
|
1407
1209
|
});
|
|
1408
1210
|
}
|
|
1409
|
-
// Resolve device target via the unified resolver. Replaces the prior chain
|
|
1410
|
-
// of (udid → connectedApp → findSimulatorByName) lookups and the standalone
|
|
1411
|
-
// "no platform → probe OS for devices" branch. Returns a structured error
|
|
1412
|
-
// when ambiguous; surface that to the caller so they can disambiguate.
|
|
1413
1211
|
const resolved = await resolveDeviceTarget(options.device);
|
|
1414
1212
|
if (!resolved.ok) {
|
|
1415
1213
|
return {
|
|
@@ -1421,10 +1219,6 @@ export async function tap(options) {
|
|
|
1421
1219
|
const deviceNote = resolved.note;
|
|
1422
1220
|
const platform = resolved.target.platform;
|
|
1423
1221
|
let targetUdid = resolved.target.iosUdid;
|
|
1424
|
-
// Pick the connected app to bias strategy selection. Prefer the registry
|
|
1425
|
-
// entry whose identifier matches the resolved target; fall back to a
|
|
1426
|
-
// device-name match (handy when registry entries lack identifiers yet);
|
|
1427
|
-
// last resort is the first connected app on the matching platform.
|
|
1428
1222
|
const allApps = Array.from(connectedApps.values());
|
|
1429
1223
|
let hasMetro = allApps.length > 0;
|
|
1430
1224
|
let app;
|
|
@@ -1439,16 +1233,11 @@ export async function tap(options) {
|
|
|
1439
1233
|
app = getConnectedAppByDevice(options.device) ?? undefined;
|
|
1440
1234
|
}
|
|
1441
1235
|
catch {
|
|
1442
|
-
// getConnectedAppByDevice throws on ambiguous matches; the resolver
|
|
1443
|
-
// would have already returned that error, so reaching here means
|
|
1444
|
-
// the resolved target's identifier just doesn't appear in the
|
|
1445
|
-
// registry yet — fall through to the platform-first pick below.
|
|
1446
1236
|
}
|
|
1447
1237
|
}
|
|
1448
1238
|
if (!app) {
|
|
1449
1239
|
app = allApps.find((a) => a.platform === platform) ?? allApps[0];
|
|
1450
1240
|
}
|
|
1451
|
-
// Try to auto-connect to Metro (for fiber strategy), but don't fail if it doesn't work.
|
|
1452
1241
|
if (!hasMetro) {
|
|
1453
1242
|
try {
|
|
1454
1243
|
await withTimeout((async () => {
|
|
@@ -1471,26 +1260,17 @@ export async function tap(options) {
|
|
|
1471
1260
|
}
|
|
1472
1261
|
}
|
|
1473
1262
|
catch {
|
|
1474
|
-
// Auto-connect failed — Metro-dependent strategies will be skipped
|
|
1475
1263
|
}
|
|
1476
1264
|
}
|
|
1477
|
-
// The response's `device` label must reflect the RESOLVED target, not the
|
|
1478
|
-
// registry-app fallback. When the registry lookup couldn't find a matching
|
|
1479
|
-
// app (e.g. simulatorUdid hadn't been backfilled yet), `app` lands on the
|
|
1480
|
-
// first iOS entry — which on a multi-sim setup is the WRONG device. The
|
|
1481
|
-
// resolver already knows the right deviceName; trust it. Bug #5 (2026-05-20).
|
|
1482
1265
|
const deviceName = resolved.target.deviceName || app?.deviceInfo?.deviceName;
|
|
1483
|
-
// Determine strategies
|
|
1484
1266
|
const strategies = getAvailableStrategies(query, strategy);
|
|
1485
1267
|
const attempted = [];
|
|
1486
1268
|
const evidence = makeEmptyEvidenceSink();
|
|
1487
|
-
// Early UI driver check for iOS — fail fast instead of falling through every strategy
|
|
1488
1269
|
const UI_DRIVER_REQUIRED_STRATEGIES = ["accessibility", "ocr", "coordinate"];
|
|
1489
1270
|
let uiDriverMissing = false;
|
|
1490
1271
|
if (platform === "ios") {
|
|
1491
1272
|
uiDriverMissing = !(await isUiDriverAvailable());
|
|
1492
1273
|
}
|
|
1493
|
-
// Filter strategies by available capabilities
|
|
1494
1274
|
const filteredStrategies = strategies.filter((strat) => {
|
|
1495
1275
|
if (strat === "fiber" && !hasMetro) {
|
|
1496
1276
|
attempted.push({
|
|
@@ -1524,18 +1304,6 @@ export async function tap(options) {
|
|
|
1524
1304
|
error: errorMessage
|
|
1525
1305
|
};
|
|
1526
1306
|
}
|
|
1527
|
-
// Refuse before dispatch when the target is under an overlay. Every strategy resolves
|
|
1528
|
-
// to a coordinate tap, so the OS would deliver the touch to the overlay — firing it
|
|
1529
|
-
// would press the wrong element and mutate state the caller never asked to change.
|
|
1530
|
-
//
|
|
1531
|
-
// Bounded, and failure-open by construction: the guard costs one fiber read, and if
|
|
1532
|
-
// that read is slow, errors, or cannot reach a verdict, the tap proceeds exactly as it
|
|
1533
|
-
// did before. Blocking a legitimate tap because a diagnostic timed out would be a far
|
|
1534
|
-
// worse regression than the mis-delivery this prevents — tap is the tool everything
|
|
1535
|
-
// else is built on.
|
|
1536
|
-
// Set when the tap is dispatched onto an overlay control that shadows a covered
|
|
1537
|
-
// element; attached to the successful result so the caller learns what actually
|
|
1538
|
-
// received the touch.
|
|
1539
1307
|
let shadowWarning;
|
|
1540
1308
|
if (hasMetro) {
|
|
1541
1309
|
const guardBudget = Math.min(remainingMs(), OVERLAY_GUARD_BUDGET_MS);
|
|
@@ -1555,9 +1323,6 @@ export async function tap(options) {
|
|
|
1555
1323
|
blockedBy = null;
|
|
1556
1324
|
}
|
|
1557
1325
|
if (blockedBy?.kind === "blocked") {
|
|
1558
|
-
// Recorded as an attempt so the refusal is distinguishable in telemetry
|
|
1559
|
-
// (_errorContext) from a tap that tried and failed. Without it every
|
|
1560
|
-
// declined tap looks like a tap-tool regression on the failure dashboard.
|
|
1561
1326
|
attempted.push({
|
|
1562
1327
|
strategy: "overlay-guard",
|
|
1563
1328
|
reason: `refused — target covered by ${blockedBy.overlay}`,
|
|
@@ -1578,8 +1343,6 @@ export async function tap(options) {
|
|
|
1578
1343
|
};
|
|
1579
1344
|
}
|
|
1580
1345
|
if (blockedBy?.kind === "shadowed") {
|
|
1581
|
-
// Dispatch, but name the element that will actually receive the touch.
|
|
1582
|
-
// Staying silent here is the reported bug in miniature.
|
|
1583
1346
|
shadowWarning =
|
|
1584
1347
|
`This coordinate is occupied by ${blockedBy.hit}, part of ${blockedBy.overlay} — ` +
|
|
1585
1348
|
`that is what received the tap. ${blockedBy.covered} sits underneath it and ` +
|
|
@@ -1587,18 +1350,10 @@ export async function tap(options) {
|
|
|
1587
1350
|
}
|
|
1588
1351
|
}
|
|
1589
1352
|
}
|
|
1590
|
-
// Determine screenshot and verification behavior.
|
|
1591
|
-
// Decoupled (I5, 2026-05-16): verify can run without returning image bytes —
|
|
1592
|
-
// capture cost is paid either way; bandwidth cost is what `screenshot` toggles.
|
|
1593
1353
|
const shouldScreenshot = options.screenshot !== false;
|
|
1594
1354
|
const canVerify = options.verify !== false;
|
|
1595
|
-
// Capture "before" screenshot. Always attempted (independent of canVerify)
|
|
1596
|
-
// so failure artifacts always carry a before.png — the diagnostic value of
|
|
1597
|
-
// the before frame doesn't depend on whether we run the post-tap diff.
|
|
1598
1355
|
let beforeBuffer = null;
|
|
1599
1356
|
let beforeScaleFactor;
|
|
1600
|
-
// Pixel dimensions of the frame we just captured. getDevicePixelRatio would
|
|
1601
|
-
// otherwise shell out for a screenshot of its own to learn exactly this.
|
|
1602
1357
|
let beforeDims;
|
|
1603
1358
|
{
|
|
1604
1359
|
const before = await captureScreenshot(platform, targetUdid);
|
|
@@ -1608,16 +1363,6 @@ export async function tap(options) {
|
|
|
1608
1363
|
beforeDims = { width: before.width, height: before.height };
|
|
1609
1364
|
}
|
|
1610
1365
|
}
|
|
1611
|
-
// OCR runs lazily when the loop reaches it (see the `case "ocr"` branch).
|
|
1612
|
-
// A concurrent pre-warm probe used to fire here to shave ~5s off OCR-win
|
|
1613
|
-
// rows, but it dispatched a paid Google Vision request at t=0 on every
|
|
1614
|
-
// text-predicate tap — and since cloud OCR (~200ms) finishes well before
|
|
1615
|
-
// the higher-priority strategy that usually wins, the post-win abort came
|
|
1616
|
-
// too late to cancel the billed request. OCR wins only ~1.9% of taps, so
|
|
1617
|
-
// the pre-warm paid for cloud Vision on ~42% of eligible taps to help a
|
|
1618
|
-
// tiny minority. Removed 2026-06-02; the timeout-recovery path below still
|
|
1619
|
-
// salvages the perfect-match-past-cap case.
|
|
1620
|
-
// Execute strategies in order with per-strategy caps and overall budget
|
|
1621
1366
|
for (const strat of filteredStrategies) {
|
|
1622
1367
|
const remaining = remainingMs();
|
|
1623
1368
|
if (remaining < MIN_STRATEGY_BUDGET_MS) {
|
|
@@ -1634,8 +1379,6 @@ export async function tap(options) {
|
|
|
1634
1379
|
try {
|
|
1635
1380
|
switch (strat) {
|
|
1636
1381
|
case "fiber":
|
|
1637
|
-
// Fiber is JS-only against a CDP target — no subprocess to cancel,
|
|
1638
|
-
// so the cheaper non-cancellable wrapper is fine.
|
|
1639
1382
|
result = await withTimeout(tryFiberStrategy(query, index, maxTraversalDepth, evidence, deviceName), budget, `fiber`);
|
|
1640
1383
|
break;
|
|
1641
1384
|
case "accessibility":
|
|
@@ -1645,9 +1388,6 @@ export async function tap(options) {
|
|
|
1645
1388
|
result = await withCancelableTimeout((signal) => tryOcrStrategy(query, platform, targetUdid, evidence, signal), budget, `ocr`);
|
|
1646
1389
|
break;
|
|
1647
1390
|
case "coordinate":
|
|
1648
|
-
// Prefer `beforeScaleFactor` (captured against `targetUdid` this turn)
|
|
1649
|
-
// over `app.lastScreenshot.scaleFactor` (stale and may belong to a
|
|
1650
|
-
// different device on a multi-sim setup). Bug #5 (2026-05-20).
|
|
1651
1391
|
result = await withTimeout(tryCoordinateStrategy(query.x, query.y, platform, beforeScaleFactor != null
|
|
1652
1392
|
? {
|
|
1653
1393
|
originalWidth: beforeDims?.width ?? 0,
|
|
@@ -1662,11 +1402,6 @@ export async function tap(options) {
|
|
|
1662
1402
|
}
|
|
1663
1403
|
catch (err) {
|
|
1664
1404
|
const reason = err instanceof Error ? err.message : String(err);
|
|
1665
|
-
// Classify the throw: a strategy-level timeout means "result UNKNOWN"
|
|
1666
|
-
// (the strategy didn't finish), distinct from "ran clean and found
|
|
1667
|
-
// nothing". Agents must not infer absence from timeouts. The
|
|
1668
|
-
// `outcome` field carries that distinction; `reason` stays the raw
|
|
1669
|
-
// message so existing log-matchers / classifiers keep working.
|
|
1670
1405
|
const outcome = STRATEGY_TIMEOUT_RE.test(reason) ? "timeout" : "error";
|
|
1671
1406
|
attempted.push({ strategy: strat, reason, outcome });
|
|
1672
1407
|
continue;
|
|
@@ -1701,11 +1436,6 @@ export async function tap(options) {
|
|
|
1701
1436
|
beforeScaleFactor,
|
|
1702
1437
|
markerPx: strategyMarker
|
|
1703
1438
|
}));
|
|
1704
|
-
// Burst always produces a screenshot for diff visualization. Drop the
|
|
1705
|
-
// bytes when the caller didn't ask for them AND the tap was meaningful
|
|
1706
|
-
// — keeping the screenshot on unmeaningful taps saves the agent a
|
|
1707
|
-
// round-trip when it needs to diagnose. Frames remain in imageBuffer
|
|
1708
|
-
// either way (accessible via get_images(groupId=verification.burstGroupId)).
|
|
1709
1439
|
if (!shouldScreenshot && verification?.meaningful !== false)
|
|
1710
1440
|
screenshot = undefined;
|
|
1711
1441
|
}
|
|
@@ -1719,8 +1449,6 @@ export async function tap(options) {
|
|
|
1719
1449
|
beforeScaleFactor,
|
|
1720
1450
|
markerPx: strategyMarker
|
|
1721
1451
|
}));
|
|
1722
|
-
// Mirror the burst-path gate: drop the screenshot only when the
|
|
1723
|
-
// caller didn't ask for it AND the tap was meaningful.
|
|
1724
1452
|
if (!shouldScreenshot && verification?.meaningful !== false)
|
|
1725
1453
|
screenshot = undefined;
|
|
1726
1454
|
}
|
|
@@ -1745,26 +1473,12 @@ export async function tap(options) {
|
|
|
1745
1473
|
screenshot,
|
|
1746
1474
|
verification
|
|
1747
1475
|
});
|
|
1748
|
-
// The guard already resolved what occupies this coordinate. Reporting it turns
|
|
1749
|
-
// "something responded" into "this element responded" for the one case where
|
|
1750
|
-
// the caller most likely meant something else.
|
|
1751
1476
|
if (shadowWarning) {
|
|
1752
1477
|
successResult.warning = successResult.warning
|
|
1753
1478
|
? `${successResult.warning}\n${shadowWarning}`
|
|
1754
1479
|
: shadowWarning;
|
|
1755
1480
|
}
|
|
1756
|
-
// Capture an artifact for "successful but unmeaningful" taps so we can
|
|
1757
|
-
// diagnose taps that landed wrong or hit non-responsive elements.
|
|
1758
|
-
// Uses the same `meaningful` flag the agent sees so dashboard outcomes
|
|
1759
|
-
// stay consistent with what the caller observed.
|
|
1760
1481
|
if (verification && !verification.skipped && verification.meaningful === false) {
|
|
1761
|
-
// A coordinate tap that changed nothing is the one case where the
|
|
1762
|
-
// agent has no idea what went wrong — it estimated x/y from a
|
|
1763
|
-
// screenshot and got back "nothing happened". Screen state knows
|
|
1764
|
-
// what sits at that point and what an overlay blocks; spend the
|
|
1765
|
-
// ~200ms here (already the slow path) to turn a dead end into a
|
|
1766
|
-
// next step. Text/testID/component predicates don't need this:
|
|
1767
|
-
// their failure modes are already named in `attempted`.
|
|
1768
1482
|
if (strat === "coordinate" && result.convertedTo) {
|
|
1769
1483
|
const diagnosis = await diagnoseCoordinateMiss({
|
|
1770
1484
|
point: { x: result.convertedTo.x, y: result.convertedTo.y },
|
|
@@ -1800,10 +1514,6 @@ export async function tap(options) {
|
|
|
1800
1514
|
}
|
|
1801
1515
|
return successResult;
|
|
1802
1516
|
}
|
|
1803
|
-
// Classify the strategy's non-success result. The reason string carries
|
|
1804
|
-
// the diagnostic from the strategy; outcome lets agents branch on category
|
|
1805
|
-
// without parsing prose. "invisible" / "ambiguous" are pinned by substring
|
|
1806
|
-
// matches the fiber strategy emits via pressables.ts.
|
|
1807
1517
|
const reasonStr = result.reason || "";
|
|
1808
1518
|
let attemptOutcome = "not-found";
|
|
1809
1519
|
if (result.ambiguous)
|
|
@@ -1813,36 +1523,26 @@ export async function tap(options) {
|
|
|
1813
1523
|
else if (STRATEGY_TIMEOUT_RE.test(reasonStr))
|
|
1814
1524
|
attemptOutcome = "timeout";
|
|
1815
1525
|
attempted.push({ strategy: strat, reason: reasonStr, outcome: attemptOutcome });
|
|
1816
|
-
// If fiber found an element with measured coordinates, do a native tap directly
|
|
1817
1526
|
if (strat === "fiber" && result.convertedTo && result.pressed) {
|
|
1818
1527
|
try {
|
|
1819
1528
|
const coords = result.convertedTo;
|
|
1820
1529
|
if (platform === "ios") {
|
|
1821
|
-
// react-native-screens modal/sheet presentations cause measureInWindow to
|
|
1822
|
-
// return y relative to the screen's content origin, not the window. If the
|
|
1823
|
-
// measured y falls inside the safe-area band, shift it down by the inset.
|
|
1824
1530
|
const safeAreaTop = await getIOSSafeAreaTop(targetUdid);
|
|
1825
1531
|
const tapY = (safeAreaTop > 0 && coords.y < safeAreaTop) ? coords.y + safeAreaTop : coords.y;
|
|
1826
1532
|
await iosTap(coords.x, tapY, { udid: targetUdid });
|
|
1827
1533
|
coords.y = tapY;
|
|
1828
1534
|
}
|
|
1829
1535
|
else {
|
|
1830
|
-
// Fabric returns dp — androidTap expects pixels
|
|
1831
|
-
// Convert dp to pixels using device density
|
|
1832
1536
|
const { androidGetDensity } = await import("../core/android.js");
|
|
1833
1537
|
const densityResult = await androidGetDensity();
|
|
1834
1538
|
const densityScale = (densityResult.density || 420) / 160;
|
|
1835
1539
|
const pxX = Math.round(coords.x * densityScale);
|
|
1836
1540
|
const pxY = Math.round(coords.y * densityScale);
|
|
1837
1541
|
await androidTap(pxX, pxY);
|
|
1838
|
-
// Report the actual tap coords (pixels) the caller can pass straight
|
|
1839
|
-
// to coordinate tools / verification — not the raw dp from fiber.
|
|
1840
|
-
// Matches every other Android coord report in the tool (OB1, 2026-05-20).
|
|
1841
1542
|
coords.x = pxX;
|
|
1842
1543
|
coords.y = pxY;
|
|
1843
1544
|
coords.unit = "pixels";
|
|
1844
1545
|
}
|
|
1845
|
-
// fiber+native uses native tap — always verify
|
|
1846
1546
|
let screenshot;
|
|
1847
1547
|
let verification;
|
|
1848
1548
|
let fnDpr;
|
|
@@ -1919,12 +1619,8 @@ export async function tap(options) {
|
|
|
1919
1619
|
});
|
|
1920
1620
|
}
|
|
1921
1621
|
catch {
|
|
1922
|
-
// Native tap at fiber coordinates failed — continue to next strategy
|
|
1923
1622
|
}
|
|
1924
1623
|
}
|
|
1925
|
-
// Ambiguous fiber result — multiple elements matched, no index given.
|
|
1926
|
-
// Return immediately with the full list so the agent can decide.
|
|
1927
|
-
// Do NOT fall through to other strategies (they can't resolve ambiguity).
|
|
1928
1624
|
if (result.matches && result.ambiguous) {
|
|
1929
1625
|
const { screenshot: matchScreenshot } = shouldScreenshot
|
|
1930
1626
|
? await verifyAndCapture({ platform, shouldVerify: false, shouldScreenshot: true, beforeBuffer: null, udid: targetUdid })
|
|
@@ -1948,15 +1644,8 @@ export async function tap(options) {
|
|
|
1948
1644
|
});
|
|
1949
1645
|
}
|
|
1950
1646
|
}
|
|
1951
|
-
// All strategies failed — check if the tap budget was the cause.
|
|
1952
|
-
// Only outer withTimeout wrapper messages or Skipped entries count; nested
|
|
1953
|
-
// sub-op errors that happen to contain "timed out" do not imply the tap
|
|
1954
|
-
// itself ran out of time.
|
|
1955
1647
|
const hitTimeout = isTapTimeout(attempted);
|
|
1956
1648
|
const elapsed = TAP_TIMEOUT_MS - remainingMs();
|
|
1957
|
-
// If fiber located the element but visibility filtered it out, prepend a
|
|
1958
|
-
// scroll-or-dismiss hint so the agent doesn't waste a turn assuming the
|
|
1959
|
-
// testID/component is missing.
|
|
1960
1649
|
const fiberSawInvisible = attempted.some(a => a.strategy === "fiber" && a.outcome === "invisible");
|
|
1961
1650
|
const allTimedOut = attempted.length > 0 && attempted.every(a => a.outcome === "timeout" || a.outcome === "skipped");
|
|
1962
1651
|
let suggestion = buildSuggestion(query, strategies, platform);
|
|
@@ -1964,8 +1653,6 @@ export async function tap(options) {
|
|
|
1964
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;
|
|
1965
1654
|
}
|
|
1966
1655
|
else if (allTimedOut) {
|
|
1967
|
-
// No strategy got to a definitive answer — don't let the agent conclude
|
|
1968
|
-
// the element is missing. Steer toward retry or a different strategy.
|
|
1969
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;
|
|
1970
1657
|
}
|
|
1971
1658
|
const { screenshot: failScreenshot } = shouldScreenshot
|
|
@@ -1978,8 +1665,6 @@ export async function tap(options) {
|
|
|
1978
1665
|
scaleFactor: failScreenshot.scaleFactor
|
|
1979
1666
|
};
|
|
1980
1667
|
}
|
|
1981
|
-
// Pick the right error framing: a definitive "not found" is different from
|
|
1982
|
-
// "all strategies timed out — we don't know whether it exists".
|
|
1983
1668
|
let errorOverride;
|
|
1984
1669
|
if (hitTimeout) {
|
|
1985
1670
|
errorOverride = `Tap timed out after ${elapsed}ms (budget ${TAP_TIMEOUT_MS}ms)`;
|
|
@@ -2014,10 +1699,6 @@ export async function tap(options) {
|
|
|
2014
1699
|
});
|
|
2015
1700
|
return attachArtifactSignals(failureResult, failSignals);
|
|
2016
1701
|
}
|
|
2017
|
-
// Matches testIDs typical of virtualized list items: trailing `-<digits>` (e.g. `store-item-0`,
|
|
2018
|
-
// `route-item-6`) or containing a UUID v4 fragment (e.g. `group-card-e4566bc6-9164-4fd0-...`).
|
|
2019
|
-
// Used by buildSuggestion() to tell the agent to scroll first when a list-item testID
|
|
2020
|
-
// can't be found in the visible fiber tree.
|
|
2021
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;
|
|
2022
1703
|
function looksLikeVirtualizedListItem(testID) {
|
|
2023
1704
|
return VIRTUALIZED_TESTID_RE.test(testID);
|
|
@@ -2039,10 +1720,6 @@ function buildSuggestion(query, triedStrategies, platform) {
|
|
|
2039
1720
|
if (query.testID && !triedStrategies.includes("ocr")) {
|
|
2040
1721
|
suggestions.push("testID not found in fiber/accessibility tree — verify the element is on the current screen with a screenshot");
|
|
2041
1722
|
}
|
|
2042
|
-
// List-item testIDs (suffix `-N` or containing a UUID) are typically rendered inside a
|
|
2043
|
-
// virtualized list — items beyond the viewport are unmounted, so the fiber tree genuinely
|
|
2044
|
-
// doesn't contain them. Adding the hint as a separate suggestion (rather than rewriting
|
|
2045
|
-
// the error) keeps the existing message stable for tooling that parses it.
|
|
2046
1723
|
if (query.testID && looksLikeVirtualizedListItem(query.testID)) {
|
|
2047
1724
|
suggestions.push("testID looks like a virtualized list item — scroll it on-screen first " +
|
|
2048
1725
|
`(swipe()${platform === "ios" ? " or ios_button arrow keys" : ""}) before tapping`);
|