execbro 0.0.1-reserve → 1.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +32 -0
- package/README.md +496 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts +56 -0
- package/build/__tests__/helpers/fake-cdp-server.d.ts.map +1 -0
- package/build/__tests__/helpers/fake-cdp-server.js +120 -0
- package/build/__tests__/helpers/fake-cdp-server.js.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts +36 -0
- package/build/__tests__/helpers/tap-test-helpers.d.ts.map +1 -0
- package/build/__tests__/helpers/tap-test-helpers.js +130 -0
- package/build/__tests__/helpers/tap-test-helpers.js.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts +2 -0
- package/build/__tests__/integration/auto-reconnect.test.d.ts.map +1 -0
- package/build/__tests__/integration/auto-reconnect.test.js +118 -0
- package/build/__tests__/integration/auto-reconnect.test.js.map +1 -0
- package/build/__tests__/integration/connection-health.test.d.ts +2 -0
- package/build/__tests__/integration/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/integration/connection-health.test.js +157 -0
- package/build/__tests__/integration/connection-health.test.js.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts +2 -0
- package/build/__tests__/integration/execute-in-app.test.d.ts.map +1 -0
- package/build/__tests__/integration/execute-in-app.test.js +124 -0
- package/build/__tests__/integration/execute-in-app.test.js.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts +2 -0
- package/build/__tests__/integration/per-call-timeout.test.d.ts.map +1 -0
- package/build/__tests__/integration/per-call-timeout.test.js +103 -0
- package/build/__tests__/integration/per-call-timeout.test.js.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts +2 -0
- package/build/__tests__/integration/tap-strategies.test.d.ts.map +1 -0
- package/build/__tests__/integration/tap-strategies.test.js +330 -0
- package/build/__tests__/integration/tap-strategies.test.js.map +1 -0
- package/build/__tests__/integration/tools.test.d.ts +2 -0
- package/build/__tests__/integration/tools.test.d.ts.map +1 -0
- package/build/__tests__/integration/tools.test.js +227 -0
- package/build/__tests__/integration/tools.test.js.map +1 -0
- package/build/__tests__/setup.d.ts +2 -0
- package/build/__tests__/setup.d.ts.map +1 -0
- package/build/__tests__/setup.js +15 -0
- package/build/__tests__/setup.js.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts +2 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.d.ts.map +1 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js +98 -0
- package/build/__tests__/unit/androidEmulatorAvds.test.js.map +1 -0
- package/build/__tests__/unit/buildToken.test.d.ts +2 -0
- package/build/__tests__/unit/buildToken.test.d.ts.map +1 -0
- package/build/__tests__/unit/buildToken.test.js +22 -0
- package/build/__tests__/unit/buildToken.test.js.map +1 -0
- package/build/__tests__/unit/bundle.test.d.ts +2 -0
- package/build/__tests__/unit/bundle.test.d.ts.map +1 -0
- package/build/__tests__/unit/bundle.test.js +53 -0
- package/build/__tests__/unit/bundle.test.js.map +1 -0
- package/build/__tests__/unit/burst.test.d.ts +2 -0
- package/build/__tests__/unit/burst.test.d.ts.map +1 -0
- package/build/__tests__/unit/burst.test.js +54 -0
- package/build/__tests__/unit/burst.test.js.map +1 -0
- package/build/__tests__/unit/connection-health.test.d.ts +2 -0
- package/build/__tests__/unit/connection-health.test.d.ts.map +1 -0
- package/build/__tests__/unit/connection-health.test.js +38 -0
- package/build/__tests__/unit/connection-health.test.js.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts +2 -0
- package/build/__tests__/unit/debugGlobals.test.d.ts.map +1 -0
- package/build/__tests__/unit/debugGlobals.test.js +14 -0
- package/build/__tests__/unit/debugGlobals.test.js.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts +2 -0
- package/build/__tests__/unit/deviceDiscovery.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceDiscovery.test.js +119 -0
- package/build/__tests__/unit/deviceDiscovery.test.js.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts +2 -0
- package/build/__tests__/unit/deviceResolver.test.d.ts.map +1 -0
- package/build/__tests__/unit/deviceResolver.test.js +261 -0
- package/build/__tests__/unit/deviceResolver.test.js.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts +2 -0
- package/build/__tests__/unit/escapeNonAscii.test.d.ts.map +1 -0
- package/build/__tests__/unit/escapeNonAscii.test.js +76 -0
- package/build/__tests__/unit/escapeNonAscii.test.js.map +1 -0
- package/build/__tests__/unit/exec.test.d.ts +2 -0
- package/build/__tests__/unit/exec.test.d.ts.map +1 -0
- package/build/__tests__/unit/exec.test.js +54 -0
- package/build/__tests__/unit/exec.test.js.map +1 -0
- package/build/__tests__/unit/executor.test.d.ts +2 -0
- package/build/__tests__/unit/executor.test.d.ts.map +1 -0
- package/build/__tests__/unit/executor.test.js +220 -0
- package/build/__tests__/unit/executor.test.js.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts +2 -0
- package/build/__tests__/unit/failureArtifact.test.d.ts.map +1 -0
- package/build/__tests__/unit/failureArtifact.test.js +207 -0
- package/build/__tests__/unit/failureArtifact.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js +68 -0
- package/build/__tests__/unit/fastRefreshRecorder.test.js.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts +2 -0
- package/build/__tests__/unit/fastRefreshTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/fastRefreshTools.test.js +112 -0
- package/build/__tests__/unit/fastRefreshTools.test.js.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts +2 -0
- package/build/__tests__/unit/fiberMatch.test.d.ts.map +1 -0
- package/build/__tests__/unit/fiberMatch.test.js +114 -0
- package/build/__tests__/unit/fiberMatch.test.js.map +1 -0
- package/build/__tests__/unit/fingerprint.test.d.ts +2 -0
- package/build/__tests__/unit/fingerprint.test.d.ts.map +1 -0
- package/build/__tests__/unit/fingerprint.test.js +99 -0
- package/build/__tests__/unit/fingerprint.test.js.map +1 -0
- package/build/__tests__/unit/focusedInput.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInput.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInput.test.js +58 -0
- package/build/__tests__/unit/focusedInput.test.js.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts +2 -0
- package/build/__tests__/unit/focusedInputTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/focusedInputTools.test.js +79 -0
- package/build/__tests__/unit/focusedInputTools.test.js.map +1 -0
- package/build/__tests__/unit/iconSemantics.test.d.ts +2 -0
- package/build/__tests__/unit/iconSemantics.test.d.ts.map +1 -0
- package/build/__tests__/unit/iconSemantics.test.js +80 -0
- package/build/__tests__/unit/iconSemantics.test.js.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts +2 -0
- package/build/__tests__/unit/imageBuffer.test.d.ts.map +1 -0
- package/build/__tests__/unit/imageBuffer.test.js +128 -0
- package/build/__tests__/unit/imageBuffer.test.js.map +1 -0
- package/build/__tests__/unit/logs.test.d.ts +2 -0
- package/build/__tests__/unit/logs.test.d.ts.map +1 -0
- package/build/__tests__/unit/logs.test.js +90 -0
- package/build/__tests__/unit/logs.test.js.map +1 -0
- package/build/__tests__/unit/measureComponent.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponent.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponent.test.js +66 -0
- package/build/__tests__/unit/measureComponent.test.js.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts +2 -0
- package/build/__tests__/unit/measureComponentTools.test.d.ts.map +1 -0
- package/build/__tests__/unit/measureComponentTools.test.js +94 -0
- package/build/__tests__/unit/measureComponentTools.test.js.map +1 -0
- package/build/__tests__/unit/metro.test.d.ts +2 -0
- package/build/__tests__/unit/metro.test.d.ts.map +1 -0
- package/build/__tests__/unit/metro.test.js +61 -0
- package/build/__tests__/unit/metro.test.js.map +1 -0
- package/build/__tests__/unit/network.test.d.ts +2 -0
- package/build/__tests__/unit/network.test.d.ts.map +1 -0
- package/build/__tests__/unit/network.test.js +102 -0
- package/build/__tests__/unit/network.test.js.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts +2 -0
- package/build/__tests__/unit/networkInterceptor.test.d.ts.map +1 -0
- package/build/__tests__/unit/networkInterceptor.test.js +104 -0
- package/build/__tests__/unit/networkInterceptor.test.js.map +1 -0
- package/build/__tests__/unit/probe.test.d.ts +2 -0
- package/build/__tests__/unit/probe.test.d.ts.map +1 -0
- package/build/__tests__/unit/probe.test.js +62 -0
- package/build/__tests__/unit/probe.test.js.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts +2 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.d.ts.map +1 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js +29 -0
- package/build/__tests__/unit/rnGlobalsBootstrap.test.js.map +1 -0
- package/build/__tests__/unit/screenState.test.d.ts +2 -0
- package/build/__tests__/unit/screenState.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenState.test.js +384 -0
- package/build/__tests__/unit/screenState.test.js.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts +2 -0
- package/build/__tests__/unit/screenshot-diff.test.d.ts.map +1 -0
- package/build/__tests__/unit/screenshot-diff.test.js +97 -0
- package/build/__tests__/unit/screenshot-diff.test.js.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts +2 -0
- package/build/__tests__/unit/tap-evidence.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap-evidence.test.js +89 -0
- package/build/__tests__/unit/tap-evidence.test.js.map +1 -0
- package/build/__tests__/unit/tap.test.d.ts +2 -0
- package/build/__tests__/unit/tap.test.d.ts.map +1 -0
- package/build/__tests__/unit/tap.test.js +767 -0
- package/build/__tests__/unit/tap.test.js.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts +2 -0
- package/build/__tests__/unit/timeoutClamp.test.d.ts.map +1 -0
- package/build/__tests__/unit/timeoutClamp.test.js +17 -0
- package/build/__tests__/unit/timeoutClamp.test.js.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts +2 -0
- package/build/__tests__/unit/toolDescriptions.test.d.ts.map +1 -0
- package/build/__tests__/unit/toolDescriptions.test.js +145 -0
- package/build/__tests__/unit/toolDescriptions.test.js.map +1 -0
- package/build/__tests__/unit/transportClassify.test.d.ts +2 -0
- package/build/__tests__/unit/transportClassify.test.d.ts.map +1 -0
- package/build/__tests__/unit/transportClassify.test.js +60 -0
- package/build/__tests__/unit/transportClassify.test.js.map +1 -0
- package/build/__tests__/unit/verifyAction.test.d.ts +2 -0
- package/build/__tests__/unit/verifyAction.test.d.ts.map +1 -0
- package/build/__tests__/unit/verifyAction.test.js +26 -0
- package/build/__tests__/unit/verifyAction.test.js.map +1 -0
- package/build/core/android.d.ts +299 -0
- package/build/core/android.d.ts.map +1 -0
- package/build/core/android.js +1582 -0
- package/build/core/android.js.map +1 -0
- package/build/core/appDetection.d.ts +20 -0
- package/build/core/appDetection.d.ts.map +1 -0
- package/build/core/appDetection.js +167 -0
- package/build/core/appDetection.js.map +1 -0
- package/build/core/bundle.d.ts +49 -0
- package/build/core/bundle.d.ts.map +1 -0
- package/build/core/bundle.js +356 -0
- package/build/core/bundle.js.map +1 -0
- package/build/core/componentSearch.d.ts +41 -0
- package/build/core/componentSearch.d.ts.map +1 -0
- package/build/core/componentSearch.js +588 -0
- package/build/core/componentSearch.js.map +1 -0
- package/build/core/componentTree.d.ts +13 -0
- package/build/core/componentTree.d.ts.map +1 -0
- package/build/core/componentTree.js +275 -0
- package/build/core/componentTree.js.map +1 -0
- package/build/core/config.d.ts +9 -0
- package/build/core/config.d.ts.map +1 -0
- package/build/core/config.js +28 -0
- package/build/core/config.js.map +1 -0
- package/build/core/connection.d.ts +102 -0
- package/build/core/connection.d.ts.map +1 -0
- package/build/core/connection.js +1710 -0
- package/build/core/connection.js.map +1 -0
- package/build/core/connectionState.d.ts +122 -0
- package/build/core/connectionState.d.ts.map +1 -0
- package/build/core/connectionState.js +313 -0
- package/build/core/connectionState.js.map +1 -0
- package/build/core/debugGlobals.d.ts +6 -0
- package/build/core/debugGlobals.d.ts.map +1 -0
- package/build/core/debugGlobals.js +370 -0
- package/build/core/debugGlobals.js.map +1 -0
- package/build/core/deviceDiscovery.d.ts +52 -0
- package/build/core/deviceDiscovery.d.ts.map +1 -0
- package/build/core/deviceDiscovery.js +74 -0
- package/build/core/deviceDiscovery.js.map +1 -0
- package/build/core/deviceResolver.d.ts +44 -0
- package/build/core/deviceResolver.d.ts.map +1 -0
- package/build/core/deviceResolver.js +225 -0
- package/build/core/deviceResolver.js.map +1 -0
- package/build/core/errorScreenParser.d.ts +30 -0
- package/build/core/errorScreenParser.d.ts.map +1 -0
- package/build/core/errorScreenParser.js +198 -0
- package/build/core/errorScreenParser.js.map +1 -0
- package/build/core/errors.d.ts +14 -0
- package/build/core/errors.d.ts.map +1 -0
- package/build/core/errors.js +17 -0
- package/build/core/errors.js.map +1 -0
- package/build/core/escapeNonAscii.d.ts +18 -0
- package/build/core/escapeNonAscii.d.ts.map +1 -0
- package/build/core/escapeNonAscii.js +352 -0
- package/build/core/escapeNonAscii.js.map +1 -0
- package/build/core/exec.d.ts +22 -0
- package/build/core/exec.d.ts.map +1 -0
- package/build/core/exec.js +54 -0
- package/build/core/exec.js.map +1 -0
- package/build/core/executor.d.ts +9 -0
- package/build/core/executor.d.ts.map +1 -0
- package/build/core/executor.js +12 -0
- package/build/core/executor.js.map +1 -0
- package/build/core/failureArtifact.d.ts +120 -0
- package/build/core/failureArtifact.d.ts.map +1 -0
- package/build/core/failureArtifact.js +113 -0
- package/build/core/failureArtifact.js.map +1 -0
- package/build/core/fastRefreshRecorder.d.ts +23 -0
- package/build/core/fastRefreshRecorder.d.ts.map +1 -0
- package/build/core/fastRefreshRecorder.js +83 -0
- package/build/core/fastRefreshRecorder.js.map +1 -0
- package/build/core/fastRefreshTools.d.ts +19 -0
- package/build/core/fastRefreshTools.d.ts.map +1 -0
- package/build/core/fastRefreshTools.js +71 -0
- package/build/core/fastRefreshTools.js.map +1 -0
- package/build/core/feedback.d.ts +18 -0
- package/build/core/feedback.d.ts.map +1 -0
- package/build/core/feedback.js +73 -0
- package/build/core/feedback.js.map +1 -0
- package/build/core/fiberMatch.d.ts +25 -0
- package/build/core/fiberMatch.d.ts.map +1 -0
- package/build/core/fiberMatch.js +35 -0
- package/build/core/fiberMatch.js.map +1 -0
- package/build/core/fingerprint.d.ts +4 -0
- package/build/core/fingerprint.d.ts.map +1 -0
- package/build/core/fingerprint.js +47 -0
- package/build/core/fingerprint.js.map +1 -0
- package/build/core/focusedInput.d.ts +27 -0
- package/build/core/focusedInput.d.ts.map +1 -0
- package/build/core/focusedInput.js +110 -0
- package/build/core/focusedInput.js.map +1 -0
- package/build/core/focusedInputTools.d.ts +21 -0
- package/build/core/focusedInputTools.d.ts.map +1 -0
- package/build/core/focusedInputTools.js +51 -0
- package/build/core/focusedInputTools.js.map +1 -0
- package/build/core/format.d.ts +8 -0
- package/build/core/format.d.ts.map +1 -0
- package/build/core/format.js +34 -0
- package/build/core/format.js.map +1 -0
- package/build/core/guides.d.ts +21 -0
- package/build/core/guides.d.ts.map +1 -0
- package/build/core/guides.js +397 -0
- package/build/core/guides.js.map +1 -0
- package/build/core/iconSemantics.d.ts +18 -0
- package/build/core/iconSemantics.d.ts.map +1 -0
- package/build/core/iconSemantics.js +114 -0
- package/build/core/iconSemantics.js.map +1 -0
- package/build/core/imageBuffer.d.ts +37 -0
- package/build/core/imageBuffer.d.ts.map +1 -0
- package/build/core/imageBuffer.js +58 -0
- package/build/core/imageBuffer.js.map +1 -0
- package/build/core/index.d.ts +37 -0
- package/build/core/index.d.ts.map +1 -0
- package/build/core/index.js +66 -0
- package/build/core/index.js.map +1 -0
- package/build/core/inspector.d.ts +55 -0
- package/build/core/inspector.d.ts.map +1 -0
- package/build/core/inspector.js +603 -0
- package/build/core/inspector.js.map +1 -0
- package/build/core/ios.d.ts +263 -0
- package/build/core/ios.d.ts.map +1 -0
- package/build/core/ios.js +1705 -0
- package/build/core/ios.js.map +1 -0
- package/build/core/jsExecute.d.ts +62 -0
- package/build/core/jsExecute.d.ts.map +1 -0
- package/build/core/jsExecute.js +696 -0
- package/build/core/jsExecute.js.map +1 -0
- package/build/core/license.d.ts +48 -0
- package/build/core/license.d.ts.map +1 -0
- package/build/core/license.js +267 -0
- package/build/core/license.js.map +1 -0
- package/build/core/logbox.d.ts +58 -0
- package/build/core/logbox.d.ts.map +1 -0
- package/build/core/logbox.js +330 -0
- package/build/core/logbox.js.map +1 -0
- package/build/core/logs.d.ts +44 -0
- package/build/core/logs.d.ts.map +1 -0
- package/build/core/logs.js +149 -0
- package/build/core/logs.js.map +1 -0
- package/build/core/measureComponent.d.ts +19 -0
- package/build/core/measureComponent.d.ts.map +1 -0
- package/build/core/measureComponent.js +94 -0
- package/build/core/measureComponent.js.map +1 -0
- package/build/core/measureComponentTools.d.ts +6 -0
- package/build/core/measureComponentTools.d.ts.map +1 -0
- package/build/core/measureComponentTools.js +43 -0
- package/build/core/measureComponentTools.js.map +1 -0
- package/build/core/metro.d.ts +31 -0
- package/build/core/metro.d.ts.map +1 -0
- package/build/core/metro.js +124 -0
- package/build/core/metro.js.map +1 -0
- package/build/core/nativeOnlyHints.d.ts +14 -0
- package/build/core/nativeOnlyHints.d.ts.map +1 -0
- package/build/core/nativeOnlyHints.js +58 -0
- package/build/core/nativeOnlyHints.js.map +1 -0
- package/build/core/network.d.ts +43 -0
- package/build/core/network.d.ts.map +1 -0
- package/build/core/network.js +217 -0
- package/build/core/network.js.map +1 -0
- package/build/core/networkInterceptor.d.ts +30 -0
- package/build/core/networkInterceptor.d.ts.map +1 -0
- package/build/core/networkInterceptor.js +211 -0
- package/build/core/networkInterceptor.js.map +1 -0
- package/build/core/ocr.d.ts +87 -0
- package/build/core/ocr.d.ts.map +1 -0
- package/build/core/ocr.js +335 -0
- package/build/core/ocr.js.map +1 -0
- package/build/core/paths.d.ts +2 -0
- package/build/core/paths.d.ts.map +1 -0
- package/build/core/paths.js +28 -0
- package/build/core/paths.js.map +1 -0
- package/build/core/posthog.d.ts +5 -0
- package/build/core/posthog.d.ts.map +1 -0
- package/build/core/posthog.js +52 -0
- package/build/core/posthog.js.map +1 -0
- package/build/core/pressables.d.ts +46 -0
- package/build/core/pressables.d.ts.map +1 -0
- package/build/core/pressables.js +1719 -0
- package/build/core/pressables.js.map +1 -0
- package/build/core/probe.d.ts +12 -0
- package/build/core/probe.d.ts.map +1 -0
- package/build/core/probe.js +56 -0
- package/build/core/probe.js.map +1 -0
- package/build/core/redux.d.ts +22 -0
- package/build/core/redux.d.ts.map +1 -0
- package/build/core/redux.js +116 -0
- package/build/core/redux.js.map +1 -0
- package/build/core/register.d.ts +8 -0
- package/build/core/register.d.ts.map +1 -0
- package/build/core/register.js +202 -0
- package/build/core/register.js.map +1 -0
- package/build/core/rnGlobalsBootstrap.d.ts +16 -0
- package/build/core/rnGlobalsBootstrap.d.ts.map +1 -0
- package/build/core/rnGlobalsBootstrap.js +116 -0
- package/build/core/rnGlobalsBootstrap.js.map +1 -0
- package/build/core/screenLayout.d.ts +53 -0
- package/build/core/screenLayout.d.ts.map +1 -0
- package/build/core/screenLayout.js +878 -0
- package/build/core/screenLayout.js.map +1 -0
- package/build/core/screenState.d.ts +166 -0
- package/build/core/screenState.d.ts.map +1 -0
- package/build/core/screenState.js +1352 -0
- package/build/core/screenState.js.map +1 -0
- package/build/core/sdkBridge.d.ts +67 -0
- package/build/core/sdkBridge.d.ts.map +1 -0
- package/build/core/sdkBridge.js +148 -0
- package/build/core/sdkBridge.js.map +1 -0
- package/build/core/state.d.ts +37 -0
- package/build/core/state.d.ts.map +1 -0
- package/build/core/state.js +124 -0
- package/build/core/state.js.map +1 -0
- package/build/core/telemetry.d.ts +50 -0
- package/build/core/telemetry.d.ts.map +1 -0
- package/build/core/telemetry.js +653 -0
- package/build/core/telemetry.js.map +1 -0
- package/build/core/toolHelpers.d.ts +24 -0
- package/build/core/toolHelpers.d.ts.map +1 -0
- package/build/core/toolHelpers.js +118 -0
- package/build/core/toolHelpers.js.map +1 -0
- package/build/core/types.d.ts +162 -0
- package/build/core/types.d.ts.map +1 -0
- package/build/core/types.js +2 -0
- package/build/core/types.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +132 -0
- package/build/index.js.map +1 -0
- package/build/pro/screenshot-diff.d.ts +10 -0
- package/build/pro/screenshot-diff.d.ts.map +1 -0
- package/build/pro/screenshot-diff.js +38 -0
- package/build/pro/screenshot-diff.js.map +1 -0
- package/build/pro/tap.d.ts +291 -0
- package/build/pro/tap.d.ts.map +1 -0
- package/build/pro/tap.js +1839 -0
- package/build/pro/tap.js.map +1 -0
- package/build/pro/verifyAction.d.ts +50 -0
- package/build/pro/verifyAction.d.ts.map +1 -0
- package/build/pro/verifyAction.js +285 -0
- package/build/pro/verifyAction.js.map +1 -0
- package/build/tools/_deviceArg.d.ts +45 -0
- package/build/tools/_deviceArg.d.ts.map +1 -0
- package/build/tools/_deviceArg.js +58 -0
- package/build/tools/_deviceArg.js.map +1 -0
- package/build/tools/accountTools.d.ts +46 -0
- package/build/tools/accountTools.d.ts.map +1 -0
- package/build/tools/accountTools.js +223 -0
- package/build/tools/accountTools.js.map +1 -0
- package/build/tools/bundleTools.d.ts +3 -0
- package/build/tools/bundleTools.d.ts.map +1 -0
- package/build/tools/bundleTools.js +375 -0
- package/build/tools/bundleTools.js.map +1 -0
- package/build/tools/componentTools.d.ts +3 -0
- package/build/tools/componentTools.d.ts.map +1 -0
- package/build/tools/componentTools.js +746 -0
- package/build/tools/componentTools.js.map +1 -0
- package/build/tools/connectionTools.d.ts +3 -0
- package/build/tools/connectionTools.d.ts.map +1 -0
- package/build/tools/connectionTools.js +625 -0
- package/build/tools/connectionTools.js.map +1 -0
- package/build/tools/deviceTools.d.ts +3 -0
- package/build/tools/deviceTools.d.ts.map +1 -0
- package/build/tools/deviceTools.js +423 -0
- package/build/tools/deviceTools.js.map +1 -0
- package/build/tools/executionTools.d.ts +3 -0
- package/build/tools/executionTools.d.ts.map +1 -0
- package/build/tools/executionTools.js +243 -0
- package/build/tools/executionTools.js.map +1 -0
- package/build/tools/interactionTools.d.ts +3 -0
- package/build/tools/interactionTools.d.ts.map +1 -0
- package/build/tools/interactionTools.js +539 -0
- package/build/tools/interactionTools.js.map +1 -0
- package/build/tools/logTools.d.ts +3 -0
- package/build/tools/logTools.d.ts.map +1 -0
- package/build/tools/logTools.js +371 -0
- package/build/tools/logTools.js.map +1 -0
- package/build/tools/metaTools.d.ts +7 -0
- package/build/tools/metaTools.d.ts.map +1 -0
- package/build/tools/metaTools.js +186 -0
- package/build/tools/metaTools.js.map +1 -0
- package/build/tools/networkTools.d.ts +3 -0
- package/build/tools/networkTools.d.ts.map +1 -0
- package/build/tools/networkTools.js +478 -0
- package/build/tools/networkTools.js.map +1 -0
- package/build/tools/reduxTools.d.ts +3 -0
- package/build/tools/reduxTools.d.ts.map +1 -0
- package/build/tools/reduxTools.js +83 -0
- package/build/tools/reduxTools.js.map +1 -0
- package/build/tools/screenshotTools.d.ts +3 -0
- package/build/tools/screenshotTools.d.ts.map +1 -0
- package/build/tools/screenshotTools.js +703 -0
- package/build/tools/screenshotTools.js.map +1 -0
- package/package.json +63 -2
|
@@ -0,0 +1,767 @@
|
|
|
1
|
+
// src/__tests__/unit/tap.test.ts
|
|
2
|
+
import { describe, it, expect } from "@jest/globals";
|
|
3
|
+
import { buildQuery, getAvailableStrategies, hasProblematicUnicode, convertScreenshotToTapCoords, formatTapSuccess, formatTapFailure, buildVerificationExplanation, isTapTimeout, findOcrMatch, } from "../../pro/tap.js";
|
|
4
|
+
describe("ConnectedApp type", () => {
|
|
5
|
+
it("accepts platform and lastScreenshot fields", () => {
|
|
6
|
+
const app = {
|
|
7
|
+
ws: {},
|
|
8
|
+
deviceInfo: {
|
|
9
|
+
id: "test",
|
|
10
|
+
title: "Hermes React Native",
|
|
11
|
+
description: "",
|
|
12
|
+
appId: "com.test",
|
|
13
|
+
type: "node",
|
|
14
|
+
webSocketDebuggerUrl: "ws://localhost:8081",
|
|
15
|
+
deviceName: "iPhone 16",
|
|
16
|
+
},
|
|
17
|
+
port: 8081,
|
|
18
|
+
platform: "ios",
|
|
19
|
+
lastScreenshot: {
|
|
20
|
+
originalWidth: 1179,
|
|
21
|
+
originalHeight: 2556,
|
|
22
|
+
scaleFactor: 1,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
expect(app.platform).toBe("ios");
|
|
26
|
+
expect(app.lastScreenshot?.originalWidth).toBe(1179);
|
|
27
|
+
});
|
|
28
|
+
it("allows lastScreenshot to be undefined", () => {
|
|
29
|
+
const app = {
|
|
30
|
+
ws: {},
|
|
31
|
+
deviceInfo: {
|
|
32
|
+
id: "test",
|
|
33
|
+
title: "Hermes React Native",
|
|
34
|
+
description: "",
|
|
35
|
+
appId: "com.test",
|
|
36
|
+
type: "node",
|
|
37
|
+
webSocketDebuggerUrl: "ws://localhost:8081",
|
|
38
|
+
deviceName: "iPhone 16",
|
|
39
|
+
},
|
|
40
|
+
port: 8081,
|
|
41
|
+
platform: "ios",
|
|
42
|
+
};
|
|
43
|
+
expect(app.lastScreenshot).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("buildQuery", () => {
|
|
47
|
+
it("builds query from text param", () => {
|
|
48
|
+
const q = buildQuery({ text: "Submit" });
|
|
49
|
+
expect(q).toEqual({ text: "Submit" });
|
|
50
|
+
});
|
|
51
|
+
it("builds query from coordinates", () => {
|
|
52
|
+
const q = buildQuery({ x: 300, y: 600 });
|
|
53
|
+
expect(q).toEqual({ x: 300, y: 600 });
|
|
54
|
+
});
|
|
55
|
+
it("builds query from multiple params", () => {
|
|
56
|
+
const q = buildQuery({ text: "Submit", testID: "btn" });
|
|
57
|
+
expect(q).toEqual({ text: "Submit", testID: "btn" });
|
|
58
|
+
});
|
|
59
|
+
it("builds query from all search params combined", () => {
|
|
60
|
+
const q = buildQuery({ text: "Submit", testID: "btn", component: "Button" });
|
|
61
|
+
expect(q).toEqual({ text: "Submit", testID: "btn", component: "Button" });
|
|
62
|
+
});
|
|
63
|
+
it("ignores non-query options like strategy and native", () => {
|
|
64
|
+
const q = buildQuery({ text: "Submit", strategy: "fiber", native: true, verify: true });
|
|
65
|
+
expect(q).toEqual({ text: "Submit" });
|
|
66
|
+
});
|
|
67
|
+
it("builds empty query from empty options", () => {
|
|
68
|
+
const q = buildQuery({});
|
|
69
|
+
expect(q).toEqual({});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("hasProblematicUnicode", () => {
|
|
73
|
+
it("returns false for ASCII text", () => {
|
|
74
|
+
expect(hasProblematicUnicode("Submit")).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
it("returns false for Polish accented text", () => {
|
|
77
|
+
expect(hasProblematicUnicode("Potwierdź")).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it("returns false for Vietnamese accented text", () => {
|
|
80
|
+
expect(hasProblematicUnicode("Tin nhắn")).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
it("returns false for German umlauts", () => {
|
|
83
|
+
expect(hasProblematicUnicode("Übersicht")).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
it("returns false for French accented text", () => {
|
|
86
|
+
expect(hasProblematicUnicode("Paramètres")).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
it("returns false for Cyrillic text", () => {
|
|
89
|
+
expect(hasProblematicUnicode("Отправить")).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
it("returns false for Chinese text", () => {
|
|
92
|
+
expect(hasProblematicUnicode("提交")).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it("returns false for Japanese text", () => {
|
|
95
|
+
expect(hasProblematicUnicode("送信")).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it("returns true for emoji", () => {
|
|
98
|
+
expect(hasProblematicUnicode("🔥")).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it("returns true for mixed text with emoji", () => {
|
|
101
|
+
expect(hasProblematicUnicode("Save 🔥")).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
it("returns true for flag emoji", () => {
|
|
104
|
+
expect(hasProblematicUnicode("🇺🇸")).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
it("returns true for zero-width joiner sequences", () => {
|
|
107
|
+
expect(hasProblematicUnicode("👨👩👧")).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it("returns true for weather symbols", () => {
|
|
110
|
+
expect(hasProblematicUnicode("☀")).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe("getAvailableStrategies", () => {
|
|
114
|
+
it("returns accessibility-first for text query", () => {
|
|
115
|
+
expect(getAvailableStrategies({ text: "Submit" }, "auto")).toEqual(["accessibility", "fiber", "ocr"]);
|
|
116
|
+
});
|
|
117
|
+
it("includes fiber for non-ASCII accented text", () => {
|
|
118
|
+
expect(getAvailableStrategies({ text: "Отправить" }, "auto")).toEqual(["accessibility", "fiber", "ocr"]);
|
|
119
|
+
});
|
|
120
|
+
it("includes fiber for Polish text", () => {
|
|
121
|
+
expect(getAvailableStrategies({ text: "Potwierdź" }, "auto")).toEqual(["accessibility", "fiber", "ocr"]);
|
|
122
|
+
});
|
|
123
|
+
it("includes fiber for Vietnamese text", () => {
|
|
124
|
+
expect(getAvailableStrategies({ text: "Tin nhắn" }, "auto")).toEqual(["accessibility", "fiber", "ocr"]);
|
|
125
|
+
});
|
|
126
|
+
it("skips fiber for emoji text", () => {
|
|
127
|
+
expect(getAvailableStrategies({ text: "🔥 Fire" }, "auto")).toEqual(["accessibility", "ocr"]);
|
|
128
|
+
});
|
|
129
|
+
it("returns accessibility+fiber for testID", () => {
|
|
130
|
+
expect(getAvailableStrategies({ testID: "btn" }, "auto")).toEqual(["accessibility", "fiber"]);
|
|
131
|
+
});
|
|
132
|
+
it("returns only fiber for component", () => {
|
|
133
|
+
expect(getAvailableStrategies({ component: "Button" }, "auto")).toEqual(["fiber"]);
|
|
134
|
+
});
|
|
135
|
+
it("returns coordinate for x,y", () => {
|
|
136
|
+
expect(getAvailableStrategies({ x: 100, y: 200 }, "auto")).toEqual(["coordinate"]);
|
|
137
|
+
});
|
|
138
|
+
it("returns explicit strategy with OCR fallback for text query", () => {
|
|
139
|
+
expect(getAvailableStrategies({ text: "Submit" }, "fiber")).toEqual(["fiber", "ocr"]);
|
|
140
|
+
expect(getAvailableStrategies({ text: "Submit" }, "accessibility")).toEqual(["accessibility", "ocr"]);
|
|
141
|
+
});
|
|
142
|
+
it("returns only OCR when explicitly set with text query", () => {
|
|
143
|
+
expect(getAvailableStrategies({ text: "Submit" }, "ocr")).toEqual(["ocr"]);
|
|
144
|
+
});
|
|
145
|
+
it("returns single strategy when explicitly set without text", () => {
|
|
146
|
+
expect(getAvailableStrategies({ testID: "btn" }, "fiber")).toEqual(["fiber"]);
|
|
147
|
+
});
|
|
148
|
+
it("returns accessibility+fiber+ocr for testID+text combo in auto mode", () => {
|
|
149
|
+
expect(getAvailableStrategies({ testID: "btn", text: "Submit" }, "auto")).toEqual([
|
|
150
|
+
"accessibility",
|
|
151
|
+
"fiber",
|
|
152
|
+
"ocr",
|
|
153
|
+
]);
|
|
154
|
+
});
|
|
155
|
+
it("returns fallback chain for component+text in auto mode", () => {
|
|
156
|
+
expect(getAvailableStrategies({ component: "Button", text: "OK" }, "auto")).toEqual([
|
|
157
|
+
"accessibility",
|
|
158
|
+
"fiber",
|
|
159
|
+
"ocr",
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
it("returns coordinate only even when text is also provided", () => {
|
|
163
|
+
expect(getAvailableStrategies({ x: 100, y: 200, text: "Submit" }, "auto")).toEqual(["coordinate"]);
|
|
164
|
+
});
|
|
165
|
+
it("returns fiber+ocr for explicit fiber with emoji text", () => {
|
|
166
|
+
expect(getAvailableStrategies({ text: "🔥 Fire" }, "fiber")).toEqual(["fiber", "ocr"]);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("convertScreenshotToTapCoords", () => {
|
|
170
|
+
it("converts iOS screenshot pixels to points (3x device)", () => {
|
|
171
|
+
expect(convertScreenshotToTapCoords(300, 600, "ios", 3)).toEqual({ x: 100, y: 200 });
|
|
172
|
+
});
|
|
173
|
+
it("converts iOS screenshot pixels to points (2x iPad)", () => {
|
|
174
|
+
expect(convertScreenshotToTapCoords(200, 400, "ios", 2)).toEqual({ x: 100, y: 200 });
|
|
175
|
+
});
|
|
176
|
+
it("undoes image downscaling before dividing by DPR", () => {
|
|
177
|
+
expect(convertScreenshotToTapCoords(219, 438, "ios", 3, 1.368)).toEqual({ x: 100, y: 200 });
|
|
178
|
+
});
|
|
179
|
+
it("passes through for Android with no downscaling", () => {
|
|
180
|
+
expect(convertScreenshotToTapCoords(300, 600, "android", 1)).toEqual({ x: 300, y: 600 });
|
|
181
|
+
});
|
|
182
|
+
it("undoes Android image downscaling", () => {
|
|
183
|
+
expect(convertScreenshotToTapCoords(250, 500, "android", 1, 1.2)).toEqual({ x: 300, y: 600 });
|
|
184
|
+
});
|
|
185
|
+
it("rounds to integers", () => {
|
|
186
|
+
expect(convertScreenshotToTapCoords(301, 599, "ios", 3)).toEqual({ x: 100, y: 200 });
|
|
187
|
+
});
|
|
188
|
+
it("handles origin coordinates (0, 0)", () => {
|
|
189
|
+
expect(convertScreenshotToTapCoords(0, 0, "ios", 3)).toEqual({ x: 0, y: 0 });
|
|
190
|
+
expect(convertScreenshotToTapCoords(0, 0, "android", 1)).toEqual({ x: 0, y: 0 });
|
|
191
|
+
});
|
|
192
|
+
it("handles large coordinates for high-res displays", () => {
|
|
193
|
+
const result = convertScreenshotToTapCoords(3840, 2160, "ios", 3);
|
|
194
|
+
expect(result.x).toBe(1280);
|
|
195
|
+
expect(result.y).toBe(720);
|
|
196
|
+
});
|
|
197
|
+
it("handles scaleFactor=1 as no-op for downscaling", () => {
|
|
198
|
+
expect(convertScreenshotToTapCoords(300, 600, "ios", 3, 1)).toEqual({ x: 100, y: 200 });
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
describe("formatTapSuccess", () => {
|
|
202
|
+
it("returns minimal success response", () => {
|
|
203
|
+
const result = formatTapSuccess({
|
|
204
|
+
method: "fiber",
|
|
205
|
+
query: { text: "Submit" },
|
|
206
|
+
pressed: "PrimaryButton",
|
|
207
|
+
text: "Submit",
|
|
208
|
+
path: "LoginScreen > Form > PrimaryButton",
|
|
209
|
+
});
|
|
210
|
+
expect(result.success).toBe(true);
|
|
211
|
+
expect(result.method).toBe("fiber");
|
|
212
|
+
expect(result.query).toEqual({ text: "Submit" });
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
describe("formatTapFailure", () => {
|
|
216
|
+
it("includes attempted strategies and suggestion", () => {
|
|
217
|
+
const result = formatTapFailure({
|
|
218
|
+
query: { text: "hamburger" },
|
|
219
|
+
attempted: [{ strategy: "fiber", reason: "No match" }],
|
|
220
|
+
suggestion: "Use screenshot",
|
|
221
|
+
});
|
|
222
|
+
expect(result.success).toBe(false);
|
|
223
|
+
expect(result.attempted).toHaveLength(1);
|
|
224
|
+
expect(result.suggestion).toBe("Use screenshot");
|
|
225
|
+
});
|
|
226
|
+
it("sets method to last attempted strategy for telemetry tracking", () => {
|
|
227
|
+
const result = formatTapFailure({
|
|
228
|
+
query: { text: "Submit" },
|
|
229
|
+
attempted: [
|
|
230
|
+
{ strategy: "fiber", reason: "No match" },
|
|
231
|
+
{ strategy: "accessibility", reason: "No match" },
|
|
232
|
+
{ strategy: "ocr", reason: "OCR did not find text" },
|
|
233
|
+
],
|
|
234
|
+
suggestion: "Use screenshot",
|
|
235
|
+
});
|
|
236
|
+
expect(result.method).toBe("ocr");
|
|
237
|
+
});
|
|
238
|
+
it("sets method to undefined when no strategies attempted", () => {
|
|
239
|
+
const result = formatTapFailure({
|
|
240
|
+
query: { text: "Submit" },
|
|
241
|
+
attempted: [],
|
|
242
|
+
suggestion: "Use screenshot",
|
|
243
|
+
});
|
|
244
|
+
expect(result.method).toBeUndefined();
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
describe("formatTapSuccess with coordinate conversion info", () => {
|
|
248
|
+
it("includes tappedAt and convertedTo when provided", () => {
|
|
249
|
+
const result = formatTapSuccess({
|
|
250
|
+
method: "fiber+native",
|
|
251
|
+
query: { text: "Submit" },
|
|
252
|
+
pressed: "PrimaryButton",
|
|
253
|
+
tappedAt: { x: 300, y: 600 },
|
|
254
|
+
convertedTo: { x: 100, y: 200, unit: "points" },
|
|
255
|
+
});
|
|
256
|
+
expect(result.tappedAt).toEqual({ x: 300, y: 600 });
|
|
257
|
+
expect(result.convertedTo).toEqual({ x: 100, y: 200, unit: "points" });
|
|
258
|
+
// platform was dropped from TapOptions in the device-targeting refactor;
|
|
259
|
+
// formatTapSuccess no longer surfaces it as a top-level field.
|
|
260
|
+
});
|
|
261
|
+
it("includes device name when provided", () => {
|
|
262
|
+
const result = formatTapSuccess({
|
|
263
|
+
method: "accessibility",
|
|
264
|
+
query: { testID: "btn" },
|
|
265
|
+
pressed: "Button",
|
|
266
|
+
device: "iPhone 16 Pro",
|
|
267
|
+
});
|
|
268
|
+
expect(result.device).toBe("iPhone 16 Pro");
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
describe("formatTapSuccess with screenshot and verification", () => {
|
|
272
|
+
it("includes screenshot field when provided", () => {
|
|
273
|
+
const result = formatTapSuccess({
|
|
274
|
+
method: "fiber",
|
|
275
|
+
query: { text: "Submit" },
|
|
276
|
+
pressed: "Button",
|
|
277
|
+
screenshot: {
|
|
278
|
+
image: "data:image/jpeg;base64,abc123",
|
|
279
|
+
width: 1170,
|
|
280
|
+
height: 2532,
|
|
281
|
+
scaleFactor: 1.0,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
expect(result.success).toBe(true);
|
|
285
|
+
expect(result.screenshot).toEqual({
|
|
286
|
+
image: "data:image/jpeg;base64,abc123",
|
|
287
|
+
width: 1170,
|
|
288
|
+
height: 2532,
|
|
289
|
+
scaleFactor: 1.0,
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
it("includes verification field when provided", () => {
|
|
293
|
+
const result = formatTapSuccess({
|
|
294
|
+
method: "coordinate",
|
|
295
|
+
query: { x: 300, y: 600 },
|
|
296
|
+
verification: {
|
|
297
|
+
meaningful: true,
|
|
298
|
+
changeRate: 0.12,
|
|
299
|
+
changedPixels: 48210,
|
|
300
|
+
totalPixels: 2961720,
|
|
301
|
+
explanation: "Tap caused a visible UI change (12.0% pixel diff). The screen updated as expected.",
|
|
302
|
+
},
|
|
303
|
+
screenshot: {
|
|
304
|
+
image: "data:image/jpeg;base64,abc123",
|
|
305
|
+
width: 1170,
|
|
306
|
+
height: 2532,
|
|
307
|
+
scaleFactor: 1.0,
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
expect(result.verification).toEqual({
|
|
311
|
+
meaningful: true,
|
|
312
|
+
changeRate: 0.12,
|
|
313
|
+
changedPixels: 48210,
|
|
314
|
+
totalPixels: 2961720,
|
|
315
|
+
explanation: "Tap caused a visible UI change (12.0% pixel diff). The screen updated as expected.",
|
|
316
|
+
});
|
|
317
|
+
expect(result.screenshot).toBeDefined();
|
|
318
|
+
});
|
|
319
|
+
it("omits screenshot and verification when not provided", () => {
|
|
320
|
+
const result = formatTapSuccess({
|
|
321
|
+
method: "fiber",
|
|
322
|
+
query: { text: "Submit" },
|
|
323
|
+
});
|
|
324
|
+
expect(result.screenshot).toBeUndefined();
|
|
325
|
+
expect(result.verification).toBeUndefined();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
describe("formatTapFailure with timeout error", () => {
|
|
329
|
+
it("includes error field when timeout info is provided", () => {
|
|
330
|
+
const result = formatTapFailure({
|
|
331
|
+
query: { text: "Submit" },
|
|
332
|
+
attempted: [
|
|
333
|
+
{ strategy: "fiber", reason: "fiber timed out after 5000ms" },
|
|
334
|
+
{ strategy: "accessibility", reason: "accessibility timed out after 3500ms" },
|
|
335
|
+
{ strategy: "ocr", reason: "Skipped — only 200ms remaining (budget 20000ms)" },
|
|
336
|
+
],
|
|
337
|
+
error: "Tap timed out after 20000ms (budget 20000ms)",
|
|
338
|
+
suggestion: "Use screenshot and retry",
|
|
339
|
+
});
|
|
340
|
+
expect(result.success).toBe(false);
|
|
341
|
+
expect(result.error).toContain("timed out");
|
|
342
|
+
expect(result.attempted).toHaveLength(3);
|
|
343
|
+
expect(result.attempted[0].reason).toContain("timed out after 5000ms");
|
|
344
|
+
expect(result.attempted[2].reason).toContain("Skipped");
|
|
345
|
+
});
|
|
346
|
+
it("uses default error message when no explicit error field", () => {
|
|
347
|
+
const result = formatTapFailure({
|
|
348
|
+
query: { text: "Submit" },
|
|
349
|
+
attempted: [{ strategy: "fiber", reason: "No match" }],
|
|
350
|
+
suggestion: "Try OCR",
|
|
351
|
+
});
|
|
352
|
+
expect(result.success).toBe(false);
|
|
353
|
+
expect(result.error).toBeDefined();
|
|
354
|
+
// Default error from buildErrorMessage, not timeout
|
|
355
|
+
expect(result.error).not.toContain("timed out");
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
describe("formatTapFailure with screenshot and verification", () => {
|
|
359
|
+
it("includes warning when verification shows not meaningful", () => {
|
|
360
|
+
const result = formatTapFailure({
|
|
361
|
+
query: { x: 300, y: 600 },
|
|
362
|
+
attempted: [{ strategy: "coordinate", reason: "executed" }],
|
|
363
|
+
suggestion: "Retry with adjusted coordinates",
|
|
364
|
+
verification: {
|
|
365
|
+
meaningful: false,
|
|
366
|
+
changeRate: 0.001,
|
|
367
|
+
changedPixels: 312,
|
|
368
|
+
totalPixels: 2961720,
|
|
369
|
+
explanation: "No visual change detected between before and after screenshots. The element may not respond visually or the tap may have missed.",
|
|
370
|
+
},
|
|
371
|
+
screenshot: {
|
|
372
|
+
image: "data:image/jpeg;base64,abc123",
|
|
373
|
+
width: 1170,
|
|
374
|
+
height: 2532,
|
|
375
|
+
scaleFactor: 1.0,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
expect(result.verification?.meaningful).toBe(false);
|
|
379
|
+
expect(result.screenshot).toBeDefined();
|
|
380
|
+
expect(result.warning).toContain("no visual change detected");
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
describe("toTapCoord returns image-pixel coordinates", () => {
|
|
384
|
+
it("returns raw OCR coordinate (scaleFactor ignored)", async () => {
|
|
385
|
+
const { toTapCoord } = await import("../../core/ocr.js");
|
|
386
|
+
// OCR coord 100 on downscaled image → returns 100 (image-pixel space)
|
|
387
|
+
// tap() handles un-downscaling and platform conversion
|
|
388
|
+
expect(toTapCoord(100, 1.368)).toBe(100);
|
|
389
|
+
});
|
|
390
|
+
it("rounds to integer", async () => {
|
|
391
|
+
const { toTapCoord } = await import("../../core/ocr.js");
|
|
392
|
+
expect(toTapCoord(100.6, 1.2)).toBe(101);
|
|
393
|
+
});
|
|
394
|
+
it("returns raw coord when scaleFactor is 1", async () => {
|
|
395
|
+
const { toTapCoord } = await import("../../core/ocr.js");
|
|
396
|
+
expect(toTapCoord(250, 1)).toBe(250);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
describe("buildVerificationExplanation", () => {
|
|
400
|
+
it("explains persistent visual change", () => {
|
|
401
|
+
const explanation = buildVerificationExplanation({
|
|
402
|
+
meaningful: true, changeRate: 0.032, changedPixels: 32000, totalPixels: 1000000,
|
|
403
|
+
});
|
|
404
|
+
expect(explanation).toContain("visible UI change");
|
|
405
|
+
expect(explanation).toContain("3.2%");
|
|
406
|
+
});
|
|
407
|
+
it("explains snap-back (tap burst with transient feedback)", () => {
|
|
408
|
+
const explanation = buildVerificationExplanation({
|
|
409
|
+
meaningful: false, changeRate: 0.001, changedPixels: 1000, totalPixels: 1000000,
|
|
410
|
+
transientChangeDetected: true, peakChangeRate: 0.041, peakFrame: 2,
|
|
411
|
+
kind: "snap_back",
|
|
412
|
+
});
|
|
413
|
+
expect(explanation).toContain("Transient visual feedback");
|
|
414
|
+
expect(explanation).toContain("frame 2");
|
|
415
|
+
expect(explanation).toContain("4.1%");
|
|
416
|
+
});
|
|
417
|
+
it("explains snap-back on swipe with scroll-fits-viewport hint", () => {
|
|
418
|
+
const explanation = buildVerificationExplanation({
|
|
419
|
+
meaningful: false, changeRate: 0.001, changedPixels: 1000, totalPixels: 1000000,
|
|
420
|
+
transientChangeDetected: true, peakChangeRate: 0.021, peakFrame: 1,
|
|
421
|
+
action: "swipe", kind: "snap_back",
|
|
422
|
+
});
|
|
423
|
+
expect(explanation).toContain("Snap-back detected");
|
|
424
|
+
expect(explanation).toContain("contentSize vs layoutSize");
|
|
425
|
+
});
|
|
426
|
+
it("explains no change in standard mode", () => {
|
|
427
|
+
const explanation = buildVerificationExplanation({
|
|
428
|
+
meaningful: false, changeRate: 0.0, changedPixels: 0, totalPixels: 1000000,
|
|
429
|
+
});
|
|
430
|
+
expect(explanation).toContain("No visual change");
|
|
431
|
+
expect(explanation).toContain("before and after");
|
|
432
|
+
});
|
|
433
|
+
it("explains no change in burst mode (missed)", () => {
|
|
434
|
+
const explanation = buildVerificationExplanation({
|
|
435
|
+
meaningful: false, changeRate: 0.0, changedPixels: 0, totalPixels: 1000000,
|
|
436
|
+
transientChangeDetected: false, peakChangeRate: 0.001, peakFrame: 0,
|
|
437
|
+
kind: "missed",
|
|
438
|
+
});
|
|
439
|
+
expect(explanation).toContain("No visual change");
|
|
440
|
+
expect(explanation).toContain("burst frames");
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
describe("getIOSDevicePixelRatio", () => {
|
|
444
|
+
it("calculates DPR from screenshot width and accessibility root frame", async () => {
|
|
445
|
+
const { calculateDPR } = await import("../../core/ios.js");
|
|
446
|
+
// iPhone 3x: 1260px screenshot, 420pt root frame width
|
|
447
|
+
expect(calculateDPR(1260, 420)).toBe(3);
|
|
448
|
+
// iPad 2x: 2048px screenshot, 1024pt root frame width
|
|
449
|
+
expect(calculateDPR(2048, 1024)).toBe(2);
|
|
450
|
+
// iPhone SE 2x: 750px screenshot, 375pt root frame width
|
|
451
|
+
expect(calculateDPR(750, 375)).toBe(2);
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
describe("tap orchestrator", () => {
|
|
455
|
+
it("attempts strategies when no app is connected", async () => {
|
|
456
|
+
const { tap } = await import("../../pro/tap.js");
|
|
457
|
+
const { connectedApps } = await import("../../core/state.js");
|
|
458
|
+
connectedApps.clear();
|
|
459
|
+
const result = await tap({ text: "Submit" });
|
|
460
|
+
// With Metro decoupling, tap may succeed via accessibility/OCR even without
|
|
461
|
+
// a Metro connection, or it may fail if no devices are available.
|
|
462
|
+
// Either way, the result should be a valid TapResult.
|
|
463
|
+
expect(result.error || result.method).toBeTruthy();
|
|
464
|
+
}, 15000);
|
|
465
|
+
it("validates that at least one search param is provided", async () => {
|
|
466
|
+
const { tap } = await import("../../pro/tap.js");
|
|
467
|
+
const result = await tap({});
|
|
468
|
+
expect(result.success).toBe(false);
|
|
469
|
+
expect(result.error).toContain("Must provide");
|
|
470
|
+
});
|
|
471
|
+
it("validates x and y are both provided for coordinate tap", async () => {
|
|
472
|
+
const { tap } = await import("../../pro/tap.js");
|
|
473
|
+
const result = await tap({ x: 100 });
|
|
474
|
+
expect(result.success).toBe(false);
|
|
475
|
+
expect(result.error).toContain("Both x and y");
|
|
476
|
+
});
|
|
477
|
+
// udid + platform=android conflict tests removed — both fields no longer
|
|
478
|
+
// exist in TapOptions (collapsed into the unified `device` field). The
|
|
479
|
+
// resolver disambiguates by identifier format (UDID → iOS, adb serial →
|
|
480
|
+
// Android) so the conflict can't be constructed.
|
|
481
|
+
it("surfaces ambiguous-device errors from getConnectedAppByDevice", async () => {
|
|
482
|
+
const { tap } = await import("../../pro/tap.js");
|
|
483
|
+
const { connectedApps } = await import("../../core/state.js");
|
|
484
|
+
const WebSocket = (await import("ws")).default;
|
|
485
|
+
connectedApps.clear();
|
|
486
|
+
const fakeWs = { readyState: WebSocket.OPEN };
|
|
487
|
+
const make = (key, deviceName) => ({
|
|
488
|
+
ws: fakeWs,
|
|
489
|
+
deviceInfo: {
|
|
490
|
+
id: key,
|
|
491
|
+
title: "Hermes React Native",
|
|
492
|
+
description: "",
|
|
493
|
+
appId: "com.test",
|
|
494
|
+
type: "node",
|
|
495
|
+
webSocketDebuggerUrl: `ws://localhost:8081/${key}`,
|
|
496
|
+
deviceName,
|
|
497
|
+
},
|
|
498
|
+
port: 8081,
|
|
499
|
+
platform: "ios",
|
|
500
|
+
});
|
|
501
|
+
connectedApps.set("a", make("a", "iPhone 17 Pro"));
|
|
502
|
+
connectedApps.set("b", make("b", "iPhone 17 Pro Max"));
|
|
503
|
+
const result = await tap({ text: "Submit", device: "iPhone 17" });
|
|
504
|
+
connectedApps.clear();
|
|
505
|
+
expect(result.success).toBe(false);
|
|
506
|
+
// resolveDeviceTarget emits "matches multiple connected devices" for
|
|
507
|
+
// registry-substring ambiguity. The previous getConnectedAppByDevice
|
|
508
|
+
// wrapper said "Multiple devices match"; the new contract is more
|
|
509
|
+
// explicit about where the conflict came from.
|
|
510
|
+
expect(result.error).toContain("matches multiple connected devices");
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
describe("verification thresholds", () => {
|
|
514
|
+
it("buildVerificationExplanation describes meaningful change at 0.1%", () => {
|
|
515
|
+
const explanation = buildVerificationExplanation({
|
|
516
|
+
meaningful: true,
|
|
517
|
+
changeRate: 0.001,
|
|
518
|
+
changedPixels: 1842,
|
|
519
|
+
totalPixels: 1842000,
|
|
520
|
+
});
|
|
521
|
+
expect(explanation).toContain("visible UI change");
|
|
522
|
+
});
|
|
523
|
+
it("buildVerificationExplanation describes no change at 0.03%", () => {
|
|
524
|
+
const explanation = buildVerificationExplanation({
|
|
525
|
+
meaningful: false,
|
|
526
|
+
changeRate: 0.0003,
|
|
527
|
+
changedPixels: 553,
|
|
528
|
+
totalPixels: 1842000,
|
|
529
|
+
});
|
|
530
|
+
expect(explanation).toContain("No visual change");
|
|
531
|
+
});
|
|
532
|
+
});
|
|
533
|
+
describe("formatTapFailure with all strategies exhausted", () => {
|
|
534
|
+
it("includes all attempted strategies in order", () => {
|
|
535
|
+
const result = formatTapFailure({
|
|
536
|
+
query: { text: "Submit" },
|
|
537
|
+
attempted: [
|
|
538
|
+
{ strategy: "accessibility", reason: "No iOS accessibility match" },
|
|
539
|
+
{ strategy: "fiber", reason: "pressElement failed" },
|
|
540
|
+
{ strategy: "ocr", reason: "OCR did not find text" },
|
|
541
|
+
],
|
|
542
|
+
suggestion: "Take a screenshot and use coordinates",
|
|
543
|
+
});
|
|
544
|
+
expect(result.attempted).toHaveLength(3);
|
|
545
|
+
expect(result.attempted[0].strategy).toBe("accessibility");
|
|
546
|
+
expect(result.attempted[1].strategy).toBe("fiber");
|
|
547
|
+
expect(result.attempted[2].strategy).toBe("ocr");
|
|
548
|
+
});
|
|
549
|
+
it("includes device name in failure response", () => {
|
|
550
|
+
const result = formatTapFailure({
|
|
551
|
+
query: { text: "Submit" },
|
|
552
|
+
attempted: [{ strategy: "fiber", reason: "No match" }],
|
|
553
|
+
suggestion: "Try OCR",
|
|
554
|
+
device: "Pixel 7",
|
|
555
|
+
});
|
|
556
|
+
expect(result.device).toBe("Pixel 7");
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
describe("tap without Metro connection", () => {
|
|
560
|
+
it("does not hard-fail when Metro is unavailable and strategy is accessibility", async () => {
|
|
561
|
+
const { tap } = await import("../../pro/tap.js");
|
|
562
|
+
const { connectedApps } = await import("../../core/state.js");
|
|
563
|
+
connectedApps.clear();
|
|
564
|
+
const result = await tap({
|
|
565
|
+
text: "Submit",
|
|
566
|
+
strategy: "accessibility",
|
|
567
|
+
});
|
|
568
|
+
// It may still fail (no simulator running in test env), but the error
|
|
569
|
+
// should NOT be "No connected app"
|
|
570
|
+
if (!result.success) {
|
|
571
|
+
expect(result.error).not.toContain("No connected app");
|
|
572
|
+
}
|
|
573
|
+
}, 15000);
|
|
574
|
+
it("does not hard-fail when Metro is unavailable and strategy is ocr", async () => {
|
|
575
|
+
const { tap } = await import("../../pro/tap.js");
|
|
576
|
+
const { connectedApps } = await import("../../core/state.js");
|
|
577
|
+
connectedApps.clear();
|
|
578
|
+
const result = await tap({
|
|
579
|
+
text: "Submit",
|
|
580
|
+
strategy: "ocr",
|
|
581
|
+
});
|
|
582
|
+
if (!result.success) {
|
|
583
|
+
expect(result.error).not.toContain("No connected app");
|
|
584
|
+
}
|
|
585
|
+
}, 15000);
|
|
586
|
+
it("still requires Metro for fiber strategy", async () => {
|
|
587
|
+
const { tap } = await import("../../pro/tap.js");
|
|
588
|
+
const { connectedApps } = await import("../../core/state.js");
|
|
589
|
+
connectedApps.clear();
|
|
590
|
+
const result = await tap({
|
|
591
|
+
text: "Submit",
|
|
592
|
+
strategy: "fiber",
|
|
593
|
+
});
|
|
594
|
+
// If Metro auto-connect succeeded, fiber may try and fail to find the
|
|
595
|
+
// query — that's also fine. If auto-connect failed and Metro is still
|
|
596
|
+
// absent, tap must explain *why* it failed. The device-targeting
|
|
597
|
+
// refactor lets resolveDeviceTarget reject early with a "no device"
|
|
598
|
+
// error before any strategy runs, so result.attempted can be absent —
|
|
599
|
+
// accept either a Metro-mention OR a device-resolution error as a
|
|
600
|
+
// valid no-Metro explanation. What's NOT acceptable is silent failure.
|
|
601
|
+
if (!result.success && connectedApps.size === 0) {
|
|
602
|
+
const errorMentionsMetro = result.error?.includes("Metro") ?? false;
|
|
603
|
+
const attemptedMentionsMetro = result.attempted?.some((a) => a.reason.includes("Metro")) ?? false;
|
|
604
|
+
const deviceResolutionError = result.error?.toLowerCase().includes("device") ?? false;
|
|
605
|
+
expect(errorMentionsMetro || attemptedMentionsMetro || deviceResolutionError).toBe(true);
|
|
606
|
+
}
|
|
607
|
+
}, 15000);
|
|
608
|
+
});
|
|
609
|
+
describe("isTapTimeout", () => {
|
|
610
|
+
it("returns true when a strategy wrapper fired at its cap", () => {
|
|
611
|
+
expect(isTapTimeout([
|
|
612
|
+
{ strategy: "fiber", reason: "fiber timed out after 5000ms" },
|
|
613
|
+
])).toBe(true);
|
|
614
|
+
});
|
|
615
|
+
it("returns true when a later strategy was skipped due to exhausted budget", () => {
|
|
616
|
+
expect(isTapTimeout([
|
|
617
|
+
{ strategy: "fiber", reason: "fiber timed out after 5000ms" },
|
|
618
|
+
{ strategy: "ocr", reason: "Skipped — only 200ms remaining (budget 20000ms)" },
|
|
619
|
+
])).toBe(true);
|
|
620
|
+
});
|
|
621
|
+
it("returns false when strategy errored quickly with a nested sub-op timeout", () => {
|
|
622
|
+
expect(isTapTimeout([
|
|
623
|
+
{ strategy: "fiber", reason: "CDP getProperties timed out after 150ms" },
|
|
624
|
+
])).toBe(false);
|
|
625
|
+
});
|
|
626
|
+
it("returns false when no element was found and no strategy actually timed out", () => {
|
|
627
|
+
expect(isTapTimeout([
|
|
628
|
+
{ strategy: "fiber", reason: "No element found matching testID=\"foo\"" },
|
|
629
|
+
{ strategy: "ocr", reason: "No OCR text matched" },
|
|
630
|
+
])).toBe(false);
|
|
631
|
+
});
|
|
632
|
+
it("returns false for an empty attempted list", () => {
|
|
633
|
+
expect(isTapTimeout([])).toBe(false);
|
|
634
|
+
});
|
|
635
|
+
it("accepts accessibility, ocr, and coordinate wrapper formats", () => {
|
|
636
|
+
expect(isTapTimeout([{ strategy: "accessibility", reason: "accessibility timed out after 3000ms" }])).toBe(true);
|
|
637
|
+
expect(isTapTimeout([{ strategy: "ocr", reason: "ocr timed out after 5000ms" }])).toBe(true);
|
|
638
|
+
expect(isTapTimeout([{ strategy: "coordinate", reason: "coordinate timed out after 3000ms" }])).toBe(true);
|
|
639
|
+
});
|
|
640
|
+
});
|
|
641
|
+
describe("findOcrMatch", () => {
|
|
642
|
+
function makeWord(text, tap) {
|
|
643
|
+
return {
|
|
644
|
+
text,
|
|
645
|
+
confidence: 0.99,
|
|
646
|
+
bbox: { x0: 0, y0: 0, x1: 10, y1: 10 },
|
|
647
|
+
center: tap,
|
|
648
|
+
tapCenter: tap,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function makeLine(text, tap) {
|
|
652
|
+
return {
|
|
653
|
+
text,
|
|
654
|
+
confidence: 0.99,
|
|
655
|
+
bbox: { x0: 0, y0: 0, x1: 100, y1: 10 },
|
|
656
|
+
center: tap,
|
|
657
|
+
tapCenter: tap,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function makeResult(opts) {
|
|
661
|
+
return {
|
|
662
|
+
success: true,
|
|
663
|
+
fullText: "",
|
|
664
|
+
confidence: 0.99,
|
|
665
|
+
words: opts.words ?? [],
|
|
666
|
+
lines: opts.lines ?? [],
|
|
667
|
+
processingTimeMs: 0,
|
|
668
|
+
engine: "cloud",
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
it("matches a single-word query against words (regression guard)", () => {
|
|
672
|
+
const result = makeResult({
|
|
673
|
+
words: [makeWord("Submit", { x: 50, y: 100 })],
|
|
674
|
+
});
|
|
675
|
+
const match = findOcrMatch(result, "Submit");
|
|
676
|
+
expect(match).not.toBeNull();
|
|
677
|
+
expect(match.text).toBe("Submit");
|
|
678
|
+
expect(match.tapCenter).toEqual({ x: 50, y: 100 });
|
|
679
|
+
});
|
|
680
|
+
it("matches a multi-word query against a line when the line equals the phrase", () => {
|
|
681
|
+
const result = makeResult({
|
|
682
|
+
words: [makeWord("Save", { x: 10, y: 20 }), makeWord("a", { x: 30, y: 20 })],
|
|
683
|
+
lines: [makeLine("Save a full copy", { x: 200, y: 300 })],
|
|
684
|
+
});
|
|
685
|
+
const match = findOcrMatch(result, "Save a full copy");
|
|
686
|
+
expect(match).not.toBeNull();
|
|
687
|
+
expect(match.tapCenter).toEqual({ x: 200, y: 300 });
|
|
688
|
+
});
|
|
689
|
+
it("matches a multi-word query against a line that contains the phrase as a substring", () => {
|
|
690
|
+
const result = makeResult({
|
|
691
|
+
lines: [makeLine("Please Sign In with Google to continue", { x: 400, y: 500 })],
|
|
692
|
+
});
|
|
693
|
+
const match = findOcrMatch(result, "Sign In with Google");
|
|
694
|
+
expect(match).not.toBeNull();
|
|
695
|
+
expect(match.tapCenter).toEqual({ x: 400, y: 500 });
|
|
696
|
+
});
|
|
697
|
+
it("is case-insensitive on both word and line paths", () => {
|
|
698
|
+
const wordOnly = makeResult({ words: [makeWord("SUBMIT", { x: 1, y: 2 })] });
|
|
699
|
+
expect(findOcrMatch(wordOnly, "submit").tapCenter).toEqual({ x: 1, y: 2 });
|
|
700
|
+
const lineOnly = makeResult({ lines: [makeLine("save A FULL copy", { x: 3, y: 4 })] });
|
|
701
|
+
expect(findOcrMatch(lineOnly, "Save a Full Copy").tapCenter).toEqual({ x: 3, y: 4 });
|
|
702
|
+
});
|
|
703
|
+
it("survives whitespace/punctuation differences and non-Latin characters", () => {
|
|
704
|
+
const result = makeResult({
|
|
705
|
+
lines: [
|
|
706
|
+
makeLine("Moeez's Cookbook", { x: 5, y: 6 }),
|
|
707
|
+
makeLine("Pozwalaj, gdy używam aplikacji", { x: 7, y: 8 }),
|
|
708
|
+
],
|
|
709
|
+
});
|
|
710
|
+
expect(findOcrMatch(result, " Moeez's Cookbook ").tapCenter).toEqual({ x: 5, y: 6 });
|
|
711
|
+
expect(findOcrMatch(result, "Pozwalaj, gdy używam aplikacji").tapCenter).toEqual({ x: 7, y: 8 });
|
|
712
|
+
});
|
|
713
|
+
it("returns null when neither words nor lines contain the query", () => {
|
|
714
|
+
const result = makeResult({
|
|
715
|
+
words: [makeWord("Hello", { x: 0, y: 0 })],
|
|
716
|
+
lines: [makeLine("World peace", { x: 0, y: 0 })],
|
|
717
|
+
});
|
|
718
|
+
expect(findOcrMatch(result, "Sign In with Google")).toBeNull();
|
|
719
|
+
});
|
|
720
|
+
it("reconstructs phrases from same-line words when the OCR engine fragmented them (gifted.co regression)", () => {
|
|
721
|
+
// Models the gifted.co login screen where iOS Vision returned "Continue", "with",
|
|
722
|
+
// "Google" as three separate word detections (the leading G icon disrupts the
|
|
723
|
+
// engine's line-baseline grouping). No OCRLine entry is provided.
|
|
724
|
+
function rowWord(text, x, y, x1, y1) {
|
|
725
|
+
const tap = { x: (x + x1) / 2, y: (y + y1) / 2 };
|
|
726
|
+
return { text, confidence: 0.95, bbox: { x0: x, y0: y, x1, y1 }, center: tap, tapCenter: tap };
|
|
727
|
+
}
|
|
728
|
+
const result = makeResult({
|
|
729
|
+
words: [
|
|
730
|
+
rowWord("Continue", 100, 480, 200, 510),
|
|
731
|
+
rowWord("with", 210, 480, 260, 510),
|
|
732
|
+
rowWord("Google", 270, 480, 360, 510),
|
|
733
|
+
// Adjacent button on a different y-band — must not get joined.
|
|
734
|
+
rowWord("Continue", 100, 540, 200, 570),
|
|
735
|
+
rowWord("with", 210, 540, 260, 570),
|
|
736
|
+
rowWord("Apple", 270, 540, 350, 570),
|
|
737
|
+
],
|
|
738
|
+
});
|
|
739
|
+
const match = findOcrMatch(result, "Continue with Google");
|
|
740
|
+
expect(match).not.toBeNull();
|
|
741
|
+
// tapCenter narrowed to the three matching words — average of their tap centers
|
|
742
|
+
// (not the whole reconstructed line, and definitely not the Apple row).
|
|
743
|
+
expect(match.tapCenter.x).toBeCloseTo((150 + 235 + 315) / 3, 0);
|
|
744
|
+
expect(match.tapCenter.y).toBeCloseTo(495, 0);
|
|
745
|
+
});
|
|
746
|
+
it("refines tap center to matched words inside a wider reconstructed line (horizontal-button-row case)", () => {
|
|
747
|
+
// Models Boardwise's "Yes Remotely No" row of three side-by-side buttons. Query
|
|
748
|
+
// for one of them should tap that button, not the centroid of all three.
|
|
749
|
+
function rowWord(text, x, y, x1, y1) {
|
|
750
|
+
const tap = { x: (x + x1) / 2, y: (y + y1) / 2 };
|
|
751
|
+
return { text, confidence: 0.95, bbox: { x0: x, y0: y, x1, y1 }, center: tap, tapCenter: tap };
|
|
752
|
+
}
|
|
753
|
+
const result = makeResult({
|
|
754
|
+
words: [
|
|
755
|
+
rowWord("Yes", 100, 100, 180, 130),
|
|
756
|
+
rowWord("Remotely", 220, 100, 380, 130),
|
|
757
|
+
rowWord("No", 420, 100, 480, 130),
|
|
758
|
+
],
|
|
759
|
+
});
|
|
760
|
+
const match = findOcrMatch(result, "Remotely");
|
|
761
|
+
expect(match).not.toBeNull();
|
|
762
|
+
// Should tap inside the "Remotely" bbox, not at the centroid of all three.
|
|
763
|
+
expect(match.tapCenter.x).toBe(300);
|
|
764
|
+
expect(match.tapCenter.y).toBe(115);
|
|
765
|
+
});
|
|
766
|
+
});
|
|
767
|
+
//# sourceMappingURL=tap.test.js.map
|