@devicelab/maestro-runner-darwin-x64 1.1.25
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 +191 -0
- package/README.md +166 -0
- package/THIRD_PARTY_LICENSES +186 -0
- package/bin/maestro-runner +0 -0
- package/drivers/android/appium-uiautomator2-server-debug-androidTest.apk +0 -0
- package/drivers/android/appium-uiautomator2-server-v9.11.1.apk +0 -0
- package/drivers/android/devicelab-android-driver-test.apk +0 -0
- package/drivers/android/devicelab-android-driver.apk +0 -0
- package/drivers/android/settings_apk-debug.apk +0 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunner/App.swift +11 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunner.xcodeproj/project.pbxproj +523 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/ATTRIBUTION.md +73 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/Bridging-Header.h +4 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/PixelDiff.swift +72 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/PrivateHeaders/XCTest/XCPointerEventPath.h +43 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +32 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/PrivateHeaders/XCTest/XCUIApplication+ActiveApp.h +23 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/PrivateHeaders/XCTest/XCUIDevice.h +30 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerObjCExceptionCatcher.h +11 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerObjCExceptionCatcher.m +16 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Alert.swift +155 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+CommandExecution.swift +961 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Environment.swift +37 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Interaction.swift +1864 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Lifecycle.swift +351 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Media.swift +81 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Models.swift +258 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+ScreenRecorder.swift +279 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Snapshot.swift +703 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+SystemModal.swift +230 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Transport.swift +137 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+TvRemote.swift +185 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests.swift +197 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/SnapshotParams.h +28 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/SnapshotParams.m +49 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/SyntheticTyping.h +17 -0
- package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/SyntheticTyping.m +86 -0
- package/drivers/ios/DevicelabIOSRunner/PROTOCOL.md +258 -0
- package/drivers/ios/DevicelabIOSRunner/README.md +38 -0
- package/drivers/ios/DevicelabIOSRunner/project.yml +49 -0
- package/drivers/ios/WebDriverAgent/.github/ISSUE_TEMPLATE.md +4 -0
- package/drivers/ios/WebDriverAgent/.github/actions/xcode-setup/action.yml +32 -0
- package/drivers/ios/WebDriverAgent/.github/dependabot.yml +22 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/functional-test.yml +62 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/pr-title.yml +10 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/publish.js.yml +105 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/unit-test.yml +30 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-package.yml +107 -0
- package/drivers/ios/WebDriverAgent/.github/workflows/wda-tests.yml +221 -0
- package/drivers/ios/WebDriverAgent/.mocharc.js +4 -0
- package/drivers/ios/WebDriverAgent/.releaserc +45 -0
- package/drivers/ios/WebDriverAgent/CHANGELOG.md +1547 -0
- package/drivers/ios/WebDriverAgent/CONTRIBUTING.md +35 -0
- package/drivers/ios/WebDriverAgent/Configurations/IOSSettings.xcconfig +31 -0
- package/drivers/ios/WebDriverAgent/Configurations/IOSTestSettings.xcconfig +1 -0
- package/drivers/ios/WebDriverAgent/Configurations/TVOSSettings.xcconfig +31 -0
- package/drivers/ios/WebDriverAgent/Configurations/TVOSTestSettings.xcconfig +1 -0
- package/drivers/ios/WebDriverAgent/Fastlane/Fastfile +13 -0
- package/drivers/ios/WebDriverAgent/Gemfile +3 -0
- package/drivers/ios/WebDriverAgent/LICENSE +30 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/AccessibilityUtilities/AXSettings.h +15 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/MobileCoreServices/LSApplicationWorkspace.h +127 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/TextInput/TIPreferencesController.h +45 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/UIKitCore/UIKeyboardImpl.h +27 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/CDStructures.h +28 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/NSString-XCTAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/NSValue-XCTestAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UIGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UILongPressGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UIPanGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UIPinchGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UISwipeGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/UITapGestureRecognizer-RecordingAdditions.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCAXClient_iOS.h +55 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCActivityRecord.h +39 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCApplicationMonitor.h +44 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCApplicationMonitor_iOS.h +18 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCApplicationQuery.h +23 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCDebugLogDelegate-Protocol.h +11 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCDeviceEvent.h +27 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCEventGenerator.h +73 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCKeyMappingPath.h +36 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCKeyboardInputSolver.h +41 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCKeyboardKeyMap.h +101 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCKeyboardLayout.h +35 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCPointerEvent.h +30 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCPointerEventPath.h +43 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSourceCodeRecording.h +42 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSourceCodeTreeNode.h +84 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSourceCodeTreeNodeEnumerator.h +18 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSymbolicationRecord.h +29 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSymbolicatorHolder.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCSynthesizedEventRecord.h +32 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTAXClient-Protocol.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTAsyncActivity-Protocol.h +16 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTAsyncActivity.h +23 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTAutomationTarget-Protocol.h +12 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTDarwinNotificationExpectation.h +23 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTElementSetTransformer-Protocol.h +18 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTKVOExpectation.h +28 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTMetric.h +29 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTNSNotificationExpectation.h +27 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTNSPredicateExpectation.h +24 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTNSPredicateExpectationObject-Protocol.h +15 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTRunnerAutomationSession.h +21 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTRunnerDaemonSession.h +98 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTRunnerIDESession.h +65 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTTestRunSession.h +25 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTTestRunSessionDelegate-Protocol.h +17 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTUIApplicationMonitor-Protocol.h +17 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTWaiter.h +55 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTWaiterDelegate-Protocol.h +16 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTWaiterDelegatePrivate-Protocol.h +12 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTWaiterManagement-Protocol.h +13 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTWaiterManager.h +28 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTest.h +34 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestCase.h +102 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestCaseRun.h +18 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestCaseSuite.h +19 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestConfiguration.h +66 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestContext.h +26 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestContextScope.h +19 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestDriver.h +38 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestDriverInterface-Protocol.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestExpectation.h +39 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestExpectationDelegate-Protocol.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestExpectationWaiter.h +17 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestLog.h +32 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestManager_IDEInterface-Protocol.h +36 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestManager_ManagerInterface-Protocol.h +54 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestManager_TestsInterface-Protocol.h +12 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestMisuseObserver.h +37 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestObservation-Protocol.h +20 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestObservationCenter.h +36 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestObserver.h +20 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestProbe.h +13 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestRun.h +39 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestSuite.h +48 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestSuiteRun.h +28 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCTestWaiter.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIApplication.h +60 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIApplicationImpl.h +38 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIApplicationProcess.h +85 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUICoordinate.h +44 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIDevice.h +30 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIElement.h +74 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIElementAsynchronousHandlerWrapper.h +19 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIElementHitPointCoordinate.h +24 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIElementQuery.h +70 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIHitPointResult.h +20 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIRecorderNodeFinder.h +65 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIRecorderNodeFinderMatch.h +25 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIRecorderTimingMessage.h +20 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIRecorderUtilities.h +45 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIScreen.h +27 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/XCUIScreenDataSource-Protocol.h +19 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCInternalTestRun.h +43 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCKVOExpectationImplementation.h +32 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTDarwinNotificationExpectationImplementation.h +27 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTNSNotificationExpectationImplementation.h +30 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTNSPredicateExpectationImplementation.h +29 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTWaiterImpl.h +41 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestCaseImplementation.h +70 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestCaseInterruptionException.h +13 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestExpectationImplementation.h +40 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestImplementation.h +14 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestObservationCenterImplementation.h +19 -0
- package/drivers/ios/WebDriverAgent/PrivateHeaders/XCTest/_XCTestSuiteImplementation.h +23 -0
- package/drivers/ios/WebDriverAgent/README.md +56 -0
- package/drivers/ios/WebDriverAgent/Scripts/build-webdriveragent.mjs +85 -0
- package/drivers/ios/WebDriverAgent/Scripts/build.sh +124 -0
- package/drivers/ios/WebDriverAgent/Scripts/ci/build-real.sh +24 -0
- package/drivers/ios/WebDriverAgent/Scripts/ci/build-sim.sh +15 -0
- package/drivers/ios/WebDriverAgent/Scripts/fetch-prebuilt-wda.mjs +69 -0
- package/drivers/ios/WebDriverAgent/Scripts/update-wda-version.mjs +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.pbxproj +4547 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationApp.xcscheme +87 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_1.xcscheme +86 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_2.xcscheme +86 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/IntegrationTests_3.xcscheme +86 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib.xcscheme +125 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentLib_tvOS.xcscheme +95 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner-nodebug.xcscheme +87 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner.xcscheme +114 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgent.xcodeproj/xcshareddata/xcschemes/WebDriverAgentRunner_tvOS.xcscheme +123 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/FBXCElementSnapshotWrapper+Helpers.h +100 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/FBXCElementSnapshotWrapper+Helpers.m +192 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSDictionary+FBUtf8SafeDictionary.h +39 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSDictionary+FBUtf8SafeDictionary.m +87 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSExpression+FBFormat.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSExpression+FBFormat.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSString+FBVisualLength.h +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSString+FBVisualLength.m +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSString+FBXMLSafeString.h +27 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/NSString+FBXMLSafeString.m +30 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.h +25 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCAXClient_iOS+FBSnapshotReqParams.m +82 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.h +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCTIssue+FBPatcher.m +35 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.h +23 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBAlert.m +109 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBFocused.h +23 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.h +171 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m +645 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBQuiescence.h +24 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBQuiescence.m +28 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBTouchAction.m +75 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBUIInterruptions.h +22 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplication+FBUIInterruptions.m +31 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.h +27 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIApplicationProcess+FBQuiescence.m +118 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHealthCheck.h +30 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHealthCheck.m +47 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.h +193 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBHelpers.m +388 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.h +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIDevice+FBRotation.m +68 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBAccessibility.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBAccessibility.m +50 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBCaching.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBCaching.m +36 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBClassChain.h +57 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBClassChain.m +98 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.h +27 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBCustomActions.m +127 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBFind.h +75 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBFind.m +133 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBForceTouch.h +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBForceTouch.m +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBIsVisible.h +28 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBIsVisible.m +78 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBMinMax.h +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBMinMax.m +75 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.h +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBPickerWheel.m +58 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBResolve.h +37 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBResolve.m +51 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.h +98 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBScrolling.m +342 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBSwiping.h +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBSwiping.m +56 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTVFocuse.h +35 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTVFocuse.m +71 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.h +64 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBTyping.m +199 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUID.h +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUID.m +85 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.h +106 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBUtilities.m +170 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBVisibleFrame.h +35 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBVisibleFrame.m +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.h +32 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElement+FBWebDriverAttributes.m +294 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElementQuery+FBHelpers.h +28 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Categories/XCUIElementQuery+FBHelpers.m +46 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBAlertViewCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBAlertViewCommands.m +130 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBCustomCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBCustomCommands.m +633 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBDebugCommands.m +81 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBElementCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBElementCommands.m +705 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBFindElementCommands.h +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBFindElementCommands.m +186 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBOrientationCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBOrientationCommands.m +185 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBScreenshotCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBScreenshotCommands.m +40 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBSessionCommands.m +594 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBTouchActionCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBTouchActionCommands.m +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBTouchIDCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBTouchIDCommands.m +30 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBUnknownCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBUnknownCommands.m +39 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBVideoCommands.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Commands/FBVideoCommands.m +84 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/FBAlert.h +91 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/FBAlert.m +274 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Info.plist +26 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBCommandHandler.h +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBCommandStatus.h +80 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBCommandStatus.m +279 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBElement.h +95 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBElementCache.h +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBElementCache.m +100 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBElementUtils.h +76 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBElementUtils.m +153 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBExceptionHandler.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBExceptionHandler.m +58 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBExceptions.h +60 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBExceptions.m +24 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBHTTPStatusCodes.h +581 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBResponseJSONPayload.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBResponseJSONPayload.m +59 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBResponsePayload.h +74 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBResponsePayload.m +148 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBRoute.h +77 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBRoute.m +174 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBRouteRequest-Private.h +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBRouteRequest.h +39 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBRouteRequest.m +32 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingContainer.h +56 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingContainer.m +72 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingPromise.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingPromise.m +31 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingRequest.h +39 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBScreenRecordingRequest.m +94 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession-Private.h +26 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.h +141 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBSession.m +295 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.h +65 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBTCPSocket.m +85 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBWebServer.h +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBWebServer.m +248 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCAccessibilityElement.h +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCAccessibilityElement.m +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCDeviceEvent.h +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCDeviceEvent.m +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCElementSnapshot.h +82 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCElementSnapshot.m +9 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Routing/FBXCElementSnapshotWrapper.m +114 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBAccessibilityTraits.h +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBAccessibilityTraits.m +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.h +56 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBActiveAppDetectionPoint.m +87 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBAlertsMonitor.h +51 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBAlertsMonitor.m +94 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.h +131 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBBaseActionsSynthesizer.m +165 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBCapabilities.h +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBCapabilities.m +25 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBClassChainQueryParser.h +99 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBClassChainQueryParser.m +666 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.h +403 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBConfiguration.m +711 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBDebugLogDelegateDecorator.h +26 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBDebugLogDelegateDecorator.m +49 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBElementHelpers.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBElementHelpers.m +21 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBElementTypeTransformer.h +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBElementTypeTransformer.m +149 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBErrorBuilder.h +64 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBErrorBuilder.m +75 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBFailureProofTestCase.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBFailureProofTestCase.m +68 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageProcessor.h +55 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageProcessor.m +170 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageUtils.h +25 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBImageUtils.m +80 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBKeyboard.h +37 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBKeyboard.m +123 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBLogger.h +32 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBLogger.m +47 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMacros.h +57 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.h +36 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMathUtils.m +63 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMjpegServer.h +24 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBMjpegServer.m +151 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBNotificationsHelper.h +37 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBNotificationsHelper.m +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBPasteboard.h +40 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBPasteboard.m +172 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBProtocolHelpers.h +46 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBProtocolHelpers.m +144 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBReflectionUtils.h +23 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBReflectionUtils.m +31 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBRunLoopSpinner.h +77 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBRunLoopSpinner.m +94 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBRuntimeUtils.h +79 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBRuntimeUtils.m +112 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.h +22 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreen.m +21 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.h +44 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBScreenshot.m +227 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.h +48 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBSettings.m +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBTVNavigationTracker-Private.h +27 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBTVNavigationTracker.h +52 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBTVNavigationTracker.m +142 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBUnattachedAppLauncher.h +26 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBUnattachedAppLauncher.m +21 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.h +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsHelpers.m +119 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.h +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBW3CActionsSynthesizer.m +885 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBWebServerParams.h +22 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBWebServerParams.m +23 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCAXClientProxy.h +49 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCAXClientProxy.m +119 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.h +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCTestDaemonsProxy.m +359 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCodeCompatibility.h +80 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXCodeCompatibility.m +99 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXMLGenerationOptions.h +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXMLGenerationOptions.m +25 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath-Private.h +56 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath.h +59 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/FBXPath.m +979 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCache.h +66 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCache.m +146 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.h +43 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/LRUCache/LRUCacheNode.m +51 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/NSPredicate+FBFormat.h +42 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/NSPredicate+FBFormat.m +69 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.h +78 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/XCTestPrivateSymbols.m +107 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.h +37 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Utilities/XCUIApplicationProcessDelay.m +83 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.h +1220 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncSocket.m +8812 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.h +1036 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaAsyncSocket/GCDAsyncUdpSocket.m +5868 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.h +12 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDNumber.m +88 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.h +56 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Categories/DDRange.m +106 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.h +107 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPConnection.m +2238 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPLogging.h +122 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.h +53 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPMessage.m +357 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPResponse.h +149 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.h +126 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/HTTPServer.m +372 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/LICENSE +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.h +13 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPDataResponse.m +83 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.h +9 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/CocoaHTTPServer/Responses/HTTPErrorResponse.m +40 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.h +13 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/HTTPResponseProxy.m +84 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/LICENSE +19 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.h +18 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/Route.m +11 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.h +16 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteRequest.m +50 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.h +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RouteResponse.m +66 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.h +5 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingConnection.m +142 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.h +55 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/Vendor/RoutingHTTPServer/RoutingHTTPServer.m +303 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentLib/WebDriverAgentLib.h +59 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentRunner/Info.plist +30 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentRunner/UITestingUITests.m +58 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.h +14 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/AppDelegate.m +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.h +14 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBAlertViewController.m +82 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBNavigationController.h +12 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBNavigationController.m +25 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBScrollViewController.h +13 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBScrollViewController.m +39 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBTableDataSource.h +16 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/FBTableDataSource.m +35 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/SceneDelegate.h +20 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/SceneDelegate.m +48 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchSpotView.h +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchSpotView.m +28 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchViewController.h +23 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchViewController.m +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchableView.h +29 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/TouchableView.m +108 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/ViewController.h +12 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Classes/ViewController.m +88 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Info.plist +71 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/Resources/Base.lproj/Main.storyboard +620 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationApp/main.m +15 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBAlertTests.m +194 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBAutoAlertsHandlerTests.m +73 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBConfigurationTests.m +38 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementAttributeTests.m +264 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementSwipingTests.m +127 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBElementVisibilityTests.m +73 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBFailureProofTestCaseTests.m +41 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBForceTouchTests.m +85 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBImageProcessorTests.m +82 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.h +76 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBIntegrationTestCase.m +140 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBKeyboardTests.m +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBPasteboardTests.m +91 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBPickerWheelSelectTests.m +55 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBSafariAlertTests.m +73 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBScreenTests.m +31 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBScrollingTests.m +126 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBSessionIntegrationTests.m +131 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTapTest.m +103 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTestMacros.h +33 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBTypingTest.m +68 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBVideoRecordingTests.m +64 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CMultiTouchActionsIntegrationTests.m +124 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CTouchActionsIntegrationTests.m +491 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBW3CTypeActionsTests.m +184 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/FBXPathIntegrationTests.m +157 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/Info.plist +24 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCElementSnapshotHelperTests.m +211 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCElementSnapshotHitPointTests.m +30 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIApplicationHelperTests.m +162 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceHealthCheckTests.m +27 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceHelperTests.m +220 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIDeviceRotationTests.m +84 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementAttributesTests.m +248 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementFBFindTests.m +472 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/IntegrationTests/XCUIElementHelperIntegrationTests.m +51 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCElementSnapshotDouble.h +16 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCElementSnapshotDouble.m +110 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCUIApplicationDouble.h +17 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCUIApplicationDouble.m +66 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCUIElementDouble.h +51 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Doubles/XCUIElementDouble.m +104 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBClassChainTests.m +313 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBConfigurationTests.m +60 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementCacheTests.m +124 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementTypeTransformerTests.m +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBElementUtilitiesTests.m +36 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBErrorBuilderTests.m +61 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBExceptionHandlerTests.m +59 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBLRUCacheTests.m +127 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBMathUtilsTests.m +113 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBProtocolHelpersTests.m +81 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBRouteTests.m +129 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBRunLoopSpinnerTests.m +98 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBRuntimeUtilsTests.m +45 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBSDKVersionTests.m +66 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBSessionTests.m +67 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBXMLSafeStringTests.m +35 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/FBXPathTests.m +155 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/Info.plist +24 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/NSDictionaryFBUtf8SafeTests.m +34 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/NSExpressionFBFormatTests.m +60 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/NSPredicateFBFormatTests.m +72 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests/XCUIElementHelpersTests.m +50 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests_tvOS/Doubles/XCUIElementDouble.h +46 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests_tvOS/Doubles/XCUIElementDouble.m +80 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests_tvOS/FBTVNavigationTrackerTests.m +86 -0
- package/drivers/ios/WebDriverAgent/WebDriverAgentTests/UnitTests_tvOS/Info.plist +24 -0
- package/drivers/ios/WebDriverAgent/eslint.config.mjs +13 -0
- package/drivers/ios/WebDriverAgent/index.ts +7 -0
- package/drivers/ios/WebDriverAgent/lib/check-dependencies.ts +34 -0
- package/drivers/ios/WebDriverAgent/lib/constants.ts +18 -0
- package/drivers/ios/WebDriverAgent/lib/logger.ts +3 -0
- package/drivers/ios/WebDriverAgent/lib/no-session-proxy.ts +25 -0
- package/drivers/ios/WebDriverAgent/lib/types.ts +139 -0
- package/drivers/ios/WebDriverAgent/lib/utils.ts +342 -0
- package/drivers/ios/WebDriverAgent/lib/webdriveragent.ts +715 -0
- package/drivers/ios/WebDriverAgent/lib/xcodebuild.ts +488 -0
- package/drivers/ios/WebDriverAgent/package.json +102 -0
- package/drivers/ios/WebDriverAgent/test/functional/desired.ts +7 -0
- package/drivers/ios/WebDriverAgent/test/functional/helpers/simulator.ts +34 -0
- package/drivers/ios/WebDriverAgent/test/functional/webdriveragent-e2e-specs.ts +119 -0
- package/drivers/ios/WebDriverAgent/test/unit/utils-specs.ts +157 -0
- package/drivers/ios/WebDriverAgent/test/unit/webdriveragent-specs.ts +446 -0
- package/drivers/ios/WebDriverAgent/tsconfig.json +16 -0
- package/package.json +16 -0
package/drivers/ios/DevicelabIOSRunner/DevicelabIOSRunnerUITests/RunnerTests+Interaction.swift
ADDED
|
@@ -0,0 +1,1864 @@
|
|
|
1
|
+
import XCTest
|
|
2
|
+
|
|
3
|
+
extension RunnerTests {
|
|
4
|
+
struct TouchVisualizationFrame {
|
|
5
|
+
let x: Double
|
|
6
|
+
let y: Double
|
|
7
|
+
let referenceWidth: Double
|
|
8
|
+
let referenceHeight: Double
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
struct DragVisualizationFrame {
|
|
12
|
+
let x: Double
|
|
13
|
+
let y: Double
|
|
14
|
+
let x2: Double
|
|
15
|
+
let y2: Double
|
|
16
|
+
let referenceWidth: Double
|
|
17
|
+
let referenceHeight: Double
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct DragPoints {
|
|
21
|
+
let x: Double
|
|
22
|
+
let y: Double
|
|
23
|
+
let x2: Double
|
|
24
|
+
let y2: Double
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
struct SelectorElementMatch {
|
|
28
|
+
let element: XCUIElement?
|
|
29
|
+
let isAmbiguous: Bool
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
enum TextTypingRepairMode {
|
|
33
|
+
case none
|
|
34
|
+
case append
|
|
35
|
+
case replacement
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
enum TextEntryTiming {
|
|
39
|
+
static let focusTimeout: TimeInterval = 0.4
|
|
40
|
+
static let repairReadinessTimeout: TimeInterval = 1.0
|
|
41
|
+
static let readinessTimeout: TimeInterval = 2.0
|
|
42
|
+
static let hardwareKeyboardFallbackTimeout: TimeInterval = 0.35
|
|
43
|
+
static let pollInterval: TimeInterval = 0.02
|
|
44
|
+
static let warmupValueTimeout: TimeInterval = 0.4
|
|
45
|
+
static let verificationStabilityWindow: TimeInterval = 0.2
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
struct TextEntryResult {
|
|
49
|
+
let verified: Bool?
|
|
50
|
+
let repaired: Bool
|
|
51
|
+
let expectedText: String?
|
|
52
|
+
let observedText: String?
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
struct TextEntryTarget {
|
|
56
|
+
let element: XCUIElement?
|
|
57
|
+
let refreshPoint: CGPoint?
|
|
58
|
+
let prefersFocusedElement: Bool
|
|
59
|
+
|
|
60
|
+
func withElement(_ nextElement: XCUIElement?) -> TextEntryTarget {
|
|
61
|
+
guard let nextElement else {
|
|
62
|
+
return self
|
|
63
|
+
}
|
|
64
|
+
let frame = nextElement.frame
|
|
65
|
+
let point = frame.isEmpty ? refreshPoint : CGPoint(x: frame.midX, y: frame.midY)
|
|
66
|
+
return TextEntryTarget(
|
|
67
|
+
element: nextElement,
|
|
68
|
+
refreshPoint: point,
|
|
69
|
+
prefersFocusedElement: prefersFocusedElement
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// MARK: - Navigation Gestures
|
|
75
|
+
|
|
76
|
+
func tapInAppBackControl(app: XCUIApplication) -> Bool {
|
|
77
|
+
#if os(macOS)
|
|
78
|
+
if let back = macOSNavigationBackElement(app: app) {
|
|
79
|
+
tapElementCenter(app: app, element: back)
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
return false
|
|
83
|
+
#elseif os(tvOS)
|
|
84
|
+
_ = pressTvRemote(.menu)
|
|
85
|
+
return true
|
|
86
|
+
#else
|
|
87
|
+
let buttons = app.navigationBars.buttons.allElementsBoundByIndex
|
|
88
|
+
if let back = buttons.first(where: { $0.isHittable }) {
|
|
89
|
+
back.tap()
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
return false
|
|
93
|
+
#endif
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
func performBackGesture(app: XCUIApplication) {
|
|
97
|
+
if pressTvRemote(.menu) {
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
performCoordinateBackGesture(app: app)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private func performCoordinateBackGesture(app: XCUIApplication) {
|
|
104
|
+
#if !os(tvOS)
|
|
105
|
+
let target = app.windows.firstMatch.exists ? app.windows.firstMatch : app
|
|
106
|
+
let start = target.coordinate(withNormalizedOffset: CGVector(dx: 0.05, dy: 0.5))
|
|
107
|
+
let end = target.coordinate(withNormalizedOffset: CGVector(dx: 0.8, dy: 0.5))
|
|
108
|
+
start.press(forDuration: 0.05, thenDragTo: end)
|
|
109
|
+
#endif
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func performSystemBackAction(app: XCUIApplication) -> Bool {
|
|
113
|
+
#if os(macOS)
|
|
114
|
+
return false
|
|
115
|
+
#else
|
|
116
|
+
if pressTvRemote(.menu) {
|
|
117
|
+
return true
|
|
118
|
+
}
|
|
119
|
+
performBackGesture(app: app)
|
|
120
|
+
return true
|
|
121
|
+
#endif
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
func performAppSwitcherGesture(app: XCUIApplication) {
|
|
125
|
+
if pressTvRemote(.home) {
|
|
126
|
+
sleepFor(resolveTvRemoteDoublePressDelay())
|
|
127
|
+
_ = pressTvRemote(.home)
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
performCoordinateAppSwitcherGesture(app: app)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private func performCoordinateAppSwitcherGesture(app: XCUIApplication) {
|
|
134
|
+
#if !os(tvOS)
|
|
135
|
+
let target = app.windows.firstMatch.exists ? app.windows.firstMatch : app
|
|
136
|
+
let start = target.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.99))
|
|
137
|
+
let end = target.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.7))
|
|
138
|
+
start.press(forDuration: 0.6, thenDragTo: end)
|
|
139
|
+
#endif
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
func pressHomeButton() {
|
|
143
|
+
#if os(macOS)
|
|
144
|
+
return
|
|
145
|
+
#else
|
|
146
|
+
if pressTvRemote(.home) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
XCUIDevice.shared.press(.home)
|
|
150
|
+
#endif
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
func rotateDevice(to orientationName: String) -> Bool {
|
|
154
|
+
#if os(macOS) || os(tvOS)
|
|
155
|
+
return false
|
|
156
|
+
#else
|
|
157
|
+
switch orientationName {
|
|
158
|
+
case "portrait":
|
|
159
|
+
XCUIDevice.shared.orientation = .portrait
|
|
160
|
+
case "portrait-upside-down":
|
|
161
|
+
XCUIDevice.shared.orientation = .portraitUpsideDown
|
|
162
|
+
case "landscape-left":
|
|
163
|
+
XCUIDevice.shared.orientation = .landscapeLeft
|
|
164
|
+
case "landscape-right":
|
|
165
|
+
XCUIDevice.shared.orientation = .landscapeRight
|
|
166
|
+
default:
|
|
167
|
+
return false
|
|
168
|
+
}
|
|
169
|
+
sleepFor(0.2)
|
|
170
|
+
return true
|
|
171
|
+
#endif
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Maps XCUIDevice's appearance enum onto the wire strings the host uses.
|
|
175
|
+
//
|
|
176
|
+
// `unspecified` means no style is being forced rather than "dark", and it
|
|
177
|
+
// renders light, so it reports as light. That matches the reading the Android
|
|
178
|
+
// side gives for the auto/custom night-mode schedules: the only honest answer
|
|
179
|
+
// from the current state alone is "not currently dark".
|
|
180
|
+
func appearanceName(_ appearance: XCUIDevice.Appearance) -> String {
|
|
181
|
+
switch appearance {
|
|
182
|
+
case .dark:
|
|
183
|
+
return "dark"
|
|
184
|
+
default:
|
|
185
|
+
return "light"
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Returns nil for anything that is not a recognised appearance, so an
|
|
190
|
+
// unsupported value fails loudly instead of silently landing on a default.
|
|
191
|
+
func appearanceValue(_ name: String) -> XCUIDevice.Appearance? {
|
|
192
|
+
switch name.lowercased() {
|
|
193
|
+
case "dark":
|
|
194
|
+
return .dark
|
|
195
|
+
case "light":
|
|
196
|
+
return .light
|
|
197
|
+
default:
|
|
198
|
+
return nil
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
func findElement(app: XCUIApplication, text: String) -> XCUIElement? {
|
|
203
|
+
let predicate = NSPredicate(format: "label CONTAINS[c] %@ OR identifier CONTAINS[c] %@ OR value CONTAINS[c] %@", text, text, text)
|
|
204
|
+
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
205
|
+
return element.exists ? element : nil
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
func findElement(app: XCUIApplication, selectorKey: String, selectorValue: String) -> SelectorElementMatch {
|
|
209
|
+
let value = selectorValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
210
|
+
guard !value.isEmpty else {
|
|
211
|
+
return SelectorElementMatch(element: nil, isAmbiguous: false)
|
|
212
|
+
}
|
|
213
|
+
let predicate: NSPredicate
|
|
214
|
+
switch selectorKey {
|
|
215
|
+
case "id":
|
|
216
|
+
predicate = NSPredicate(format: "identifier ==[c] %@", value)
|
|
217
|
+
case "label":
|
|
218
|
+
predicate = NSPredicate(format: "label ==[c] %@", value)
|
|
219
|
+
case "value":
|
|
220
|
+
predicate = NSPredicate(format: "value ==[c] %@", value)
|
|
221
|
+
case "text":
|
|
222
|
+
predicate = NSPredicate(format: "label ==[c] %@ OR identifier ==[c] %@ OR value ==[c] %@", value, value, value)
|
|
223
|
+
default:
|
|
224
|
+
return SelectorElementMatch(element: nil, isAmbiguous: false)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var matchedElement: XCUIElement?
|
|
228
|
+
let matches = app.descendants(matching: .any).matching(predicate).allElementsBoundByIndex
|
|
229
|
+
for element in matches where element.exists {
|
|
230
|
+
guard element.isHittable else {
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
guard matchedElement == nil else {
|
|
234
|
+
return SelectorElementMatch(element: nil, isAmbiguous: true)
|
|
235
|
+
}
|
|
236
|
+
matchedElement = element
|
|
237
|
+
}
|
|
238
|
+
return SelectorElementMatch(element: matchedElement, isAmbiguous: false)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// MARK: - Liberal find-and-act
|
|
242
|
+
//
|
|
243
|
+
// The existing `findElement(selectorKey:selectorValue:)` above requires
|
|
244
|
+
// EXACT NSPredicate match + isHittable=true. That's too strict for many
|
|
245
|
+
// RN flows where target text is long (e.g. "What is Lorem Ipsum?") or
|
|
246
|
+
// non-hittable (StaticText headings). The Go-side snapshot+filter path
|
|
247
|
+
// is liberal (substring + case-insensitive + prefer-editable-inputs) but
|
|
248
|
+
// expensive (two HTTP round-trips: snapshot + tap, with 300-node cap).
|
|
249
|
+
//
|
|
250
|
+
// treeSignature builds a stable hash of the accessibility tree's
|
|
251
|
+
// geometry — the bits we care about for "is the UI ready for
|
|
252
|
+
// interaction": element type, identifier, label, and integer-rounded
|
|
253
|
+
// frame. Iterates depth-first, hashes each node into a Hasher, returns
|
|
254
|
+
// the final hash. Sub-pixel jitter from CALayer hairlines or display-
|
|
255
|
+
// link timing doesn't count as "still animating" (frames are rounded
|
|
256
|
+
// to int).
|
|
257
|
+
func treeSignature(of snapshot: XCUIElementSnapshot) -> UInt64 {
|
|
258
|
+
var hasher = Hasher()
|
|
259
|
+
func walk(_ s: XCUIElementSnapshot) {
|
|
260
|
+
hasher.combine(s.elementType.rawValue)
|
|
261
|
+
hasher.combine(s.identifier)
|
|
262
|
+
hasher.combine(s.label)
|
|
263
|
+
hasher.combine(Int(s.frame.origin.x))
|
|
264
|
+
hasher.combine(Int(s.frame.origin.y))
|
|
265
|
+
hasher.combine(Int(s.frame.size.width))
|
|
266
|
+
hasher.combine(Int(s.frame.size.height))
|
|
267
|
+
for child in s.children { walk(child) }
|
|
268
|
+
}
|
|
269
|
+
walk(snapshot)
|
|
270
|
+
return UInt64(bitPattern: Int64(hasher.finalize()))
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// tryTapAlertButton handles the very common case of "tap a button in an
|
|
274
|
+
// active UIAlertController". Alerts present in a separate UIWindow, so
|
|
275
|
+
// their button frames don't translate to the main app's coordinate space —
|
|
276
|
+
// tapAt(x, y) using those frames misses the button. Tapping the
|
|
277
|
+
// XCUIElement directly lets XCTest do the window-coordinate math.
|
|
278
|
+
//
|
|
279
|
+
// Returns nil if no alert is present or no alert button matches; caller
|
|
280
|
+
// should then fall through to the main snapshot+tap path.
|
|
281
|
+
func tryTapAlertButton(
|
|
282
|
+
app: XCUIApplication,
|
|
283
|
+
selectorKey: String,
|
|
284
|
+
selectorValue: String
|
|
285
|
+
) -> Response? {
|
|
286
|
+
let needle = selectorValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
287
|
+
if needle.isEmpty {
|
|
288
|
+
return nil
|
|
289
|
+
}
|
|
290
|
+
let alertQuery = app.alerts
|
|
291
|
+
let alertCount = alertQuery.count
|
|
292
|
+
if alertCount == 0 {
|
|
293
|
+
return nil
|
|
294
|
+
}
|
|
295
|
+
let needleLower = needle.lowercased()
|
|
296
|
+
|
|
297
|
+
for i in 0..<alertCount {
|
|
298
|
+
let alert = alertQuery.element(boundBy: i)
|
|
299
|
+
if !alert.exists {
|
|
300
|
+
continue
|
|
301
|
+
}
|
|
302
|
+
// Score candidate buttons: 0=exact-on-target-attribute, 1=substring-on-target,
|
|
303
|
+
// 2=any-other-match. For selectorKey "id", target attribute is identifier;
|
|
304
|
+
// for "text"/"label", target is label.
|
|
305
|
+
var best: (XCUIElement, Int)? = nil
|
|
306
|
+
let buttons = alert.buttons.allElementsBoundByIndex
|
|
307
|
+
for button in buttons {
|
|
308
|
+
if !button.exists { continue }
|
|
309
|
+
let label = button.label.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
310
|
+
let identifier = button.identifier.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
|
311
|
+
var score: Int? = nil
|
|
312
|
+
switch selectorKey {
|
|
313
|
+
case "id":
|
|
314
|
+
if identifier == needleLower { score = 0 }
|
|
315
|
+
else if identifier.contains(needleLower) && !identifier.isEmpty { score = 1 }
|
|
316
|
+
else if label == needleLower { score = 0 }
|
|
317
|
+
else if label.contains(needleLower) && !label.isEmpty { score = 2 }
|
|
318
|
+
default: // "text", "label", anything else
|
|
319
|
+
if label == needleLower { score = 0 }
|
|
320
|
+
else if identifier == needleLower { score = 0 }
|
|
321
|
+
else if label.contains(needleLower) && !label.isEmpty { score = 1 }
|
|
322
|
+
else if identifier.contains(needleLower) && !identifier.isEmpty { score = 2 }
|
|
323
|
+
}
|
|
324
|
+
if let s = score {
|
|
325
|
+
if best == nil || s < best!.1 {
|
|
326
|
+
best = (button, s)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
guard let (matchedButton, _) = best else { continue }
|
|
331
|
+
// Capture button frame (in screen coords) for response.
|
|
332
|
+
let frame = matchedButton.frame
|
|
333
|
+
let x = Double(frame.midX)
|
|
334
|
+
let y = Double(frame.midY)
|
|
335
|
+
let touchFrame = resolvedTouchVisualizationFrame(app: app, x: x, y: y)
|
|
336
|
+
let timing = measureGesture {
|
|
337
|
+
matchedButton.tap()
|
|
338
|
+
}
|
|
339
|
+
// Alert taps trigger a dismiss → underlying cascade (e.g. an
|
|
340
|
+
// alert "Discard" can kick off a navigation pop). There's a
|
|
341
|
+
// brief moment of "false stability" right after the alert
|
|
342
|
+
// dismisses but before the cascade animation actually starts.
|
|
343
|
+
// Wait for the tree to truly settle so the next tap (which
|
|
344
|
+
// will only do a pre-tap settle) doesn't get dropped by the
|
|
345
|
+
// gesture system mid-cascade.
|
|
346
|
+
waitForTreeStable(app: app, timeoutSeconds: 0.8)
|
|
347
|
+
return Response(
|
|
348
|
+
ok: true,
|
|
349
|
+
data: DataPayload(
|
|
350
|
+
message: "tapped alert button",
|
|
351
|
+
gestureStartUptimeMs: timing.gestureStartUptimeMs,
|
|
352
|
+
gestureEndUptimeMs: timing.gestureEndUptimeMs,
|
|
353
|
+
x: touchFrame.x,
|
|
354
|
+
y: touchFrame.y,
|
|
355
|
+
referenceWidth: touchFrame.referenceWidth,
|
|
356
|
+
referenceHeight: touchFrame.referenceHeight
|
|
357
|
+
)
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
return nil
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// waitForTreeStable polls app.snapshot() until two consecutive
|
|
364
|
+
// snapshots produce the same tree signature, or the budget expires.
|
|
365
|
+
// Same idea as the awaitIdle command but inline — used by paths that
|
|
366
|
+
// need to wait for animation completion right where they are (e.g.
|
|
367
|
+
// tryTapAlertButton after dismissing an alert, before returning).
|
|
368
|
+
func waitForTreeStable(app: XCUIApplication, timeoutSeconds: TimeInterval) {
|
|
369
|
+
let started = Date()
|
|
370
|
+
var prevSig: UInt64? = nil
|
|
371
|
+
while Date().timeIntervalSince(started) < timeoutSeconds {
|
|
372
|
+
guard let snap = try? app.snapshot() else { return }
|
|
373
|
+
let sig = treeSignature(of: snap)
|
|
374
|
+
if let prev = prevSig, prev == sig {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
prevSig = sig
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// findLiberalMatch walks the XCUI tree, applies the same selector
|
|
382
|
+
// semantics as the Go-side (substring + case-insensitive + exact-match-
|
|
383
|
+
// preference + prefer-editable-inputs ranking), and returns the best
|
|
384
|
+
// match.
|
|
385
|
+
//
|
|
386
|
+
// The loop ALSO doubles as the animation-end gate: it walks the tree,
|
|
387
|
+
// and in the same pass computes a tree signature (hash of all node
|
|
388
|
+
// geometries). When two consecutive snapshots produce the same
|
|
389
|
+
// signature → the screen has settled → the match's frame is trustworthy
|
|
390
|
+
// and we can return.
|
|
391
|
+
//
|
|
392
|
+
// Why this is one loop and not "find then refresh-frame": doing it in
|
|
393
|
+
// one walk per iteration saves O(N) per iteration vs the previous
|
|
394
|
+
// two-pass design (one walk to find, separate walks to verify frame
|
|
395
|
+
// stability). It also ensures the matched element comes from the SAME
|
|
396
|
+
// snapshot we judged stable — no race window between "screen settled"
|
|
397
|
+
// and "use match from older snapshot".
|
|
398
|
+
//
|
|
399
|
+
// Budget: up to 600ms (~15 iters of ~40ms each). For a fully static
|
|
400
|
+
// screen the loop exits at iter 2 (~80ms). Mid-animation extends until
|
|
401
|
+
// tree stops changing or budget exhausted (then we tap at whatever
|
|
402
|
+
// frame we last saw, same fallback as before).
|
|
403
|
+
|
|
404
|
+
struct LiberalMatchResult {
|
|
405
|
+
let matched: XCUIElementSnapshot?
|
|
406
|
+
let walkedNodes: [SnapshotNode]
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
func findLiberalMatch(
|
|
410
|
+
app: XCUIApplication,
|
|
411
|
+
selectorKey: String,
|
|
412
|
+
selectorValue: String
|
|
413
|
+
) -> LiberalMatchResult {
|
|
414
|
+
let needle = selectorValue.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
415
|
+
if needle.isEmpty {
|
|
416
|
+
return LiberalMatchResult(matched: nil, walkedNodes: [])
|
|
417
|
+
}
|
|
418
|
+
let needleLower = needle.lowercased()
|
|
419
|
+
|
|
420
|
+
func typeRank(_ t: XCUIElement.ElementType) -> Int {
|
|
421
|
+
switch t {
|
|
422
|
+
case .textField, .secureTextField, .searchField, .textView: return 0
|
|
423
|
+
case .button, .link, .cell, .menuItem, .switch, .checkBox: return 1
|
|
424
|
+
default: return 2
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
func nodeMatchKind(label: String, identifier: String, valueText: String) -> Int? {
|
|
429
|
+
let labLower = label.lowercased()
|
|
430
|
+
let idLower = identifier.lowercased()
|
|
431
|
+
let valLower = valueText.lowercased()
|
|
432
|
+
switch selectorKey {
|
|
433
|
+
case "text":
|
|
434
|
+
if labLower == needleLower || valLower == needleLower || idLower == needleLower {
|
|
435
|
+
return 0
|
|
436
|
+
}
|
|
437
|
+
if labLower.contains(needleLower) || valLower.contains(needleLower) || idLower.contains(needleLower) {
|
|
438
|
+
return 1
|
|
439
|
+
}
|
|
440
|
+
return nil
|
|
441
|
+
case "id":
|
|
442
|
+
if idLower == needleLower { return 0 }
|
|
443
|
+
if idLower.contains(needleLower) { return 1 }
|
|
444
|
+
return nil
|
|
445
|
+
case "label":
|
|
446
|
+
if labLower == needleLower { return 0 }
|
|
447
|
+
if labLower.contains(needleLower) { return 1 }
|
|
448
|
+
return nil
|
|
449
|
+
default:
|
|
450
|
+
return nil
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// walkOnce: one snapshot, one DFS — produces (match-candidates,
|
|
455
|
+
// tree-signature, flat node list) in a single pass. We accumulate
|
|
456
|
+
// hash bits into a Hasher as we visit each node and collect matches
|
|
457
|
+
// along the way.
|
|
458
|
+
func walkOnce(root: XCUIElementSnapshot) -> (XCUIElementSnapshot?, UInt64, [SnapshotNode]) {
|
|
459
|
+
var hasher = Hasher()
|
|
460
|
+
var allNodes: [SnapshotNode] = []
|
|
461
|
+
var matches: [(XCUIElementSnapshot, Int, Int)] = []
|
|
462
|
+
|
|
463
|
+
func walk(_ snap: XCUIElementSnapshot, depth: Int, parentIdx: Int?) {
|
|
464
|
+
// Tree-signature contribution: geometry + identity bits.
|
|
465
|
+
hasher.combine(snap.elementType.rawValue)
|
|
466
|
+
hasher.combine(snap.identifier)
|
|
467
|
+
hasher.combine(snap.label)
|
|
468
|
+
hasher.combine(Int(snap.frame.origin.x))
|
|
469
|
+
hasher.combine(Int(snap.frame.origin.y))
|
|
470
|
+
hasher.combine(Int(snap.frame.size.width))
|
|
471
|
+
hasher.combine(Int(snap.frame.size.height))
|
|
472
|
+
|
|
473
|
+
// Match-candidate collection + flat-node serialisation.
|
|
474
|
+
let label = snap.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
475
|
+
let identifier = snap.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
476
|
+
let valueText = String(describing: snap.value ?? "")
|
|
477
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
478
|
+
let nodeIdx = allNodes.count
|
|
479
|
+
allNodes.append(SnapshotNode(
|
|
480
|
+
index: nodeIdx,
|
|
481
|
+
type: elementTypeName(snap.elementType),
|
|
482
|
+
label: label.isEmpty ? nil : label,
|
|
483
|
+
identifier: identifier.isEmpty ? nil : identifier,
|
|
484
|
+
value: valueText.isEmpty ? nil : valueText,
|
|
485
|
+
placeholderValue: nil,
|
|
486
|
+
rect: snapshotRect(from: snap.frame),
|
|
487
|
+
enabled: snap.isEnabled,
|
|
488
|
+
focused: nil,
|
|
489
|
+
selected: snap.isSelected ? true : nil,
|
|
490
|
+
hittable: false,
|
|
491
|
+
depth: depth,
|
|
492
|
+
parentIndex: parentIdx,
|
|
493
|
+
hiddenContentAbove: nil,
|
|
494
|
+
hiddenContentBelow: nil
|
|
495
|
+
))
|
|
496
|
+
if let kind = nodeMatchKind(label: label, identifier: identifier, valueText: valueText) {
|
|
497
|
+
if !snap.frame.isNull && !snap.frame.isEmpty {
|
|
498
|
+
matches.append((snap, kind, typeRank(snap.elementType)))
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
for child in snap.children {
|
|
502
|
+
walk(child, depth: depth + 1, parentIdx: nodeIdx)
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
walk(root, depth: 0, parentIdx: nil)
|
|
506
|
+
|
|
507
|
+
matches.sort { ($0.1, $0.2) < ($1.1, $1.2) }
|
|
508
|
+
let sig = UInt64(bitPattern: Int64(hasher.finalize()))
|
|
509
|
+
return (matches.first?.0, sig, allNodes)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
let started = Date()
|
|
513
|
+
let timeout = 0.6 // 600ms budget for animation-end + find
|
|
514
|
+
var prevSig: UInt64? = nil
|
|
515
|
+
var lastMatch: XCUIElementSnapshot? = nil
|
|
516
|
+
var lastNodes: [SnapshotNode] = []
|
|
517
|
+
|
|
518
|
+
for _ in 0..<15 {
|
|
519
|
+
let root: XCUIElementSnapshot
|
|
520
|
+
do {
|
|
521
|
+
root = try app.snapshot()
|
|
522
|
+
} catch {
|
|
523
|
+
return LiberalMatchResult(matched: lastMatch, walkedNodes: lastNodes)
|
|
524
|
+
}
|
|
525
|
+
let (match, sig, nodes) = walkOnce(root: root)
|
|
526
|
+
lastMatch = match
|
|
527
|
+
lastNodes = nodes
|
|
528
|
+
// Two consecutive identical signatures = settled. Match's frame
|
|
529
|
+
// came from THIS snapshot, so it's trustworthy.
|
|
530
|
+
if let prev = prevSig, prev == sig {
|
|
531
|
+
return LiberalMatchResult(matched: match, walkedNodes: nodes)
|
|
532
|
+
}
|
|
533
|
+
prevSig = sig
|
|
534
|
+
if Date().timeIntervalSince(started) > timeout {
|
|
535
|
+
break
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
// Budget exhausted — return the last walk's data. Same fallback the
|
|
539
|
+
// previous code took on timeout.
|
|
540
|
+
return LiberalMatchResult(matched: lastMatch, walkedNodes: lastNodes)
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
func queryElement(app: XCUIApplication, selectorKey: String, selectorValue: String) -> Response {
|
|
544
|
+
let match = findElement(app: app, selectorKey: selectorKey, selectorValue: selectorValue)
|
|
545
|
+
if match.isAmbiguous {
|
|
546
|
+
return Response(ok: false, error: ErrorPayload(code: "AMBIGUOUS_MATCH", message: "selector matched multiple elements"))
|
|
547
|
+
}
|
|
548
|
+
guard let element = match.element else {
|
|
549
|
+
return Response(ok: true, data: DataPayload(found: false, nodes: []))
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
553
|
+
let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
554
|
+
let valueText = String(describing: element.value ?? "")
|
|
555
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
556
|
+
let placeholder = element.placeholderValue?
|
|
557
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
558
|
+
let node = SnapshotNode(
|
|
559
|
+
index: 0,
|
|
560
|
+
type: elementTypeName(element.elementType),
|
|
561
|
+
label: label.isEmpty ? nil : label,
|
|
562
|
+
identifier: identifier.isEmpty ? nil : identifier,
|
|
563
|
+
value: valueText.isEmpty ? nil : valueText,
|
|
564
|
+
placeholderValue: (placeholder?.isEmpty ?? true) ? nil : placeholder,
|
|
565
|
+
rect: snapshotRect(from: element.frame),
|
|
566
|
+
enabled: element.isEnabled,
|
|
567
|
+
focused: nil,
|
|
568
|
+
selected: element.isSelected ? true : nil,
|
|
569
|
+
hittable: element.isHittable,
|
|
570
|
+
depth: 0,
|
|
571
|
+
parentIndex: nil,
|
|
572
|
+
hiddenContentAbove: nil,
|
|
573
|
+
hiddenContentBelow: nil
|
|
574
|
+
)
|
|
575
|
+
return Response(
|
|
576
|
+
ok: true,
|
|
577
|
+
data: DataPayload(
|
|
578
|
+
text: readableText(for: element),
|
|
579
|
+
found: true,
|
|
580
|
+
nodes: [node]
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
func readTextAt(app: XCUIApplication, x: Double, y: Double) -> String? {
|
|
586
|
+
let point = CGPoint(x: x, y: y)
|
|
587
|
+
let candidates = app.descendants(matching: .any).allElementsBoundByIndex
|
|
588
|
+
.filter { element in
|
|
589
|
+
element.exists && !element.frame.isEmpty && element.frame.contains(point)
|
|
590
|
+
}
|
|
591
|
+
.sorted { left, right in
|
|
592
|
+
let leftArea = max(1, left.frame.width * left.frame.height)
|
|
593
|
+
let rightArea = max(1, right.frame.width * right.frame.height)
|
|
594
|
+
if leftArea != rightArea {
|
|
595
|
+
return leftArea < rightArea
|
|
596
|
+
}
|
|
597
|
+
if left.frame.minY != right.frame.minY {
|
|
598
|
+
return left.frame.minY < right.frame.minY
|
|
599
|
+
}
|
|
600
|
+
if left.frame.minX != right.frame.minX {
|
|
601
|
+
return left.frame.minX < right.frame.minX
|
|
602
|
+
}
|
|
603
|
+
return left.elementType.rawValue < right.elementType.rawValue
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
for element in candidates where prefersExpandedTextRead(element) {
|
|
607
|
+
if let text = readableText(for: element) {
|
|
608
|
+
return text
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
for element in candidates {
|
|
612
|
+
if let text = readableText(for: element) {
|
|
613
|
+
return text
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
return nil
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
func clearTextInput(_ element: XCUIElement) {
|
|
620
|
+
#if !os(tvOS)
|
|
621
|
+
moveCaretToEnd(element: element)
|
|
622
|
+
#endif
|
|
623
|
+
let count = estimatedDeleteCount(for: element)
|
|
624
|
+
let deletes = String(repeating: XCUIKeyboardKey.delete.rawValue, count: count)
|
|
625
|
+
element.typeText(deletes)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
func textInputAt(app: XCUIApplication, x: Double, y: Double) -> XCUIElement? {
|
|
629
|
+
let point = CGPoint(x: x, y: y)
|
|
630
|
+
var matched: XCUIElement?
|
|
631
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
632
|
+
// Type-filter via NSPredicate first — XCTest can evaluate this on
|
|
633
|
+
// the accessibility tree without iterating every descendant. The
|
|
634
|
+
// .any descendants + allElementsBoundByIndex form walks the entire
|
|
635
|
+
// tree O(n) — ~10s on dense screens, enough to time out the Go-side
|
|
636
|
+
// 10s callTimeout for inputText (NSPR/SPR Push-Input scenario).
|
|
637
|
+
let editablePredicate = NSPredicate(format:
|
|
638
|
+
"elementType == %d OR elementType == %d OR elementType == %d OR elementType == %d",
|
|
639
|
+
XCUIElement.ElementType.textField.rawValue,
|
|
640
|
+
XCUIElement.ElementType.secureTextField.rawValue,
|
|
641
|
+
XCUIElement.ElementType.searchField.rawValue,
|
|
642
|
+
XCUIElement.ElementType.textView.rawValue
|
|
643
|
+
)
|
|
644
|
+
let editableQuery = app.descendants(matching: .any).matching(editablePredicate)
|
|
645
|
+
// Fallback when no editable contains the point: prefer the only
|
|
646
|
+
// visible editable on screen. Common Maestro pattern: tap a button
|
|
647
|
+
// that navigates to an Input screen, then inputText — the button's
|
|
648
|
+
// old coords don't intersect the new screen's TextField, but the
|
|
649
|
+
// TextField is the obvious target.
|
|
650
|
+
let allEditables = editableQuery.allElementsBoundByIndex.filter { $0.exists }
|
|
651
|
+
let candidates = allEditables.filter { element in
|
|
652
|
+
let frame = element.frame
|
|
653
|
+
return !frame.isEmpty && frame.contains(point)
|
|
654
|
+
}
|
|
655
|
+
if candidates.isEmpty {
|
|
656
|
+
// No editable at the point. If exactly one editable is on screen,
|
|
657
|
+
// it's the intended target.
|
|
658
|
+
let onScreen = allEditables.filter { !$0.frame.isEmpty }
|
|
659
|
+
if onScreen.count == 1 {
|
|
660
|
+
matched = onScreen.first
|
|
661
|
+
}
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
let sorted = candidates
|
|
665
|
+
.sorted { left, right in
|
|
666
|
+
let leftArea = max(1, left.frame.width * left.frame.height)
|
|
667
|
+
let rightArea = max(1, right.frame.width * right.frame.height)
|
|
668
|
+
if leftArea != rightArea {
|
|
669
|
+
return leftArea < rightArea
|
|
670
|
+
}
|
|
671
|
+
if left.frame.minY != right.frame.minY {
|
|
672
|
+
return left.frame.minY < right.frame.minY
|
|
673
|
+
}
|
|
674
|
+
if left.frame.minX != right.frame.minX {
|
|
675
|
+
return left.frame.minX < right.frame.minX
|
|
676
|
+
}
|
|
677
|
+
return left.elementType.rawValue < right.elementType.rawValue
|
|
678
|
+
}
|
|
679
|
+
matched = sorted.first
|
|
680
|
+
})
|
|
681
|
+
if let exceptionMessage {
|
|
682
|
+
NSLog(
|
|
683
|
+
"AGENT_DEVICE_RUNNER_TEXT_INPUT_AT_POINT_IGNORED_EXCEPTION=%@",
|
|
684
|
+
exceptionMessage
|
|
685
|
+
)
|
|
686
|
+
return nil
|
|
687
|
+
}
|
|
688
|
+
return matched
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
func focusedTextInput(app: XCUIApplication) -> XCUIElement? {
|
|
692
|
+
var focused: XCUIElement?
|
|
693
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
694
|
+
let candidate = app
|
|
695
|
+
.descendants(matching: .any)
|
|
696
|
+
.matching(NSPredicate(format: "hasKeyboardFocus == 1"))
|
|
697
|
+
.firstMatch
|
|
698
|
+
guard candidate.exists else { return }
|
|
699
|
+
|
|
700
|
+
switch candidate.elementType {
|
|
701
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
702
|
+
focused = candidate
|
|
703
|
+
default:
|
|
704
|
+
return
|
|
705
|
+
}
|
|
706
|
+
})
|
|
707
|
+
if let exceptionMessage {
|
|
708
|
+
NSLog(
|
|
709
|
+
"AGENT_DEVICE_RUNNER_FOCUSED_INPUT_QUERY_IGNORED_EXCEPTION=%@",
|
|
710
|
+
exceptionMessage
|
|
711
|
+
)
|
|
712
|
+
return nil
|
|
713
|
+
}
|
|
714
|
+
return focused
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
func stabilizeTextInputBeforeTyping(app: XCUIApplication, target: XCUIElement?) -> XCUIElement? {
|
|
718
|
+
#if os(tvOS)
|
|
719
|
+
return target
|
|
720
|
+
#else
|
|
721
|
+
let latest = target
|
|
722
|
+
let deadline = Date().addingTimeInterval(TextEntryTiming.focusTimeout)
|
|
723
|
+
while Date() < deadline {
|
|
724
|
+
if let focused = focusedTextInput(app: app) {
|
|
725
|
+
return focused
|
|
726
|
+
}
|
|
727
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
728
|
+
}
|
|
729
|
+
return latest
|
|
730
|
+
#endif
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
func focusTextInputForTextEntry(app: XCUIApplication, x: Double?, y: Double?, identifier: String? = nil) -> TextEntryTarget {
|
|
734
|
+
// Local edit (not in upstream agent-device). When the caller supplies
|
|
735
|
+
// an `identifier` hint, resolve the editable element via the XCTest
|
|
736
|
+
// query DSL (predicate-based, indexed) instead of walking the full
|
|
737
|
+
// descendant tree via textInputAt's allElementsBoundByIndex. Saves
|
|
738
|
+
// ~1.4s per type call on dense screens. Falls back to the upstream
|
|
739
|
+
// x/y path when the identifier is empty or doesn't resolve to a text
|
|
740
|
+
// input.
|
|
741
|
+
if let identifier, !identifier.isEmpty {
|
|
742
|
+
var matched: XCUIElement?
|
|
743
|
+
_ = RunnerObjCExceptionCatcher.catchException({
|
|
744
|
+
// Composite predicate: identifier + element type filter. RN often
|
|
745
|
+
// gives the SAME testID to a wrapper Image (search icon) and the
|
|
746
|
+
// inner TextField — `firstMatch` on identifier alone can land on
|
|
747
|
+
// the wrong one. Constraining the predicate to editable types
|
|
748
|
+
// skips the icon directly without a slow descendant fallback.
|
|
749
|
+
let editablePredicate = NSPredicate(format:
|
|
750
|
+
"identifier == %@ AND (elementType == %d OR elementType == %d OR elementType == %d OR elementType == %d)",
|
|
751
|
+
identifier,
|
|
752
|
+
XCUIElement.ElementType.textField.rawValue,
|
|
753
|
+
XCUIElement.ElementType.secureTextField.rawValue,
|
|
754
|
+
XCUIElement.ElementType.searchField.rawValue,
|
|
755
|
+
XCUIElement.ElementType.textView.rawValue
|
|
756
|
+
)
|
|
757
|
+
let candidate = app.descendants(matching: .any).matching(editablePredicate).firstMatch
|
|
758
|
+
if candidate.exists {
|
|
759
|
+
matched = candidate
|
|
760
|
+
return
|
|
761
|
+
}
|
|
762
|
+
// Identifier didn't match any editable directly. Try matching the
|
|
763
|
+
// identifier loosely (any type) and look for an editable
|
|
764
|
+
// descendant — this catches RN's wrapper-view-with-inner-textinput
|
|
765
|
+
// pattern when both have different ids.
|
|
766
|
+
let looseMatch = app.descendants(matching: .any)
|
|
767
|
+
.matching(NSPredicate(format: "identifier == %@", identifier))
|
|
768
|
+
.firstMatch
|
|
769
|
+
guard looseMatch.exists else { return }
|
|
770
|
+
let typeFilter = NSPredicate(format:
|
|
771
|
+
"elementType == %d OR elementType == %d OR elementType == %d OR elementType == %d",
|
|
772
|
+
XCUIElement.ElementType.textField.rawValue,
|
|
773
|
+
XCUIElement.ElementType.secureTextField.rawValue,
|
|
774
|
+
XCUIElement.ElementType.searchField.rawValue,
|
|
775
|
+
XCUIElement.ElementType.textView.rawValue
|
|
776
|
+
)
|
|
777
|
+
let inner = looseMatch.descendants(matching: .any).matching(typeFilter).firstMatch
|
|
778
|
+
if inner.exists {
|
|
779
|
+
matched = inner
|
|
780
|
+
}
|
|
781
|
+
})
|
|
782
|
+
if let matched {
|
|
783
|
+
let frame = matched.frame
|
|
784
|
+
let refreshPoint: CGPoint?
|
|
785
|
+
if !frame.isEmpty {
|
|
786
|
+
_ = tapAt(app: app, x: frame.midX, y: frame.midY)
|
|
787
|
+
refreshPoint = CGPoint(x: frame.midX, y: frame.midY)
|
|
788
|
+
} else if let x, let y {
|
|
789
|
+
_ = tapAt(app: app, x: x, y: y)
|
|
790
|
+
refreshPoint = CGPoint(x: x, y: y)
|
|
791
|
+
} else {
|
|
792
|
+
refreshPoint = nil
|
|
793
|
+
}
|
|
794
|
+
// Local edit: skip stabilizeTextInputBeforeTyping (up to 400ms
|
|
795
|
+
// polling focusedTextInput) and waitForTextEntryReadiness (up to
|
|
796
|
+
// 2000ms polling keyboard visibility). Both exist for the
|
|
797
|
+
// x/y / unknown-target path, where the runner has to guess which
|
|
798
|
+
// element got tapped. We resolved the element by identifier and
|
|
799
|
+
// just tapped it — XCUI's typeText / synthetic-typing path will
|
|
800
|
+
// handle keyboard timing itself.
|
|
801
|
+
return TextEntryTarget(
|
|
802
|
+
element: matched,
|
|
803
|
+
refreshPoint: refreshPoint,
|
|
804
|
+
prefersFocusedElement: false
|
|
805
|
+
)
|
|
806
|
+
}
|
|
807
|
+
// Identifier didn't resolve — fall through to x/y / focused-element path.
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
guard let x, let y else {
|
|
811
|
+
let focused = waitForTextEntryReadiness(
|
|
812
|
+
app: app,
|
|
813
|
+
target: TextEntryTarget(
|
|
814
|
+
element: focusedTextInput(app: app),
|
|
815
|
+
refreshPoint: nil,
|
|
816
|
+
prefersFocusedElement: true
|
|
817
|
+
)
|
|
818
|
+
)
|
|
819
|
+
return TextEntryTarget(element: focused, refreshPoint: nil, prefersFocusedElement: true)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
let target = textInputAt(app: app, x: x, y: y)
|
|
823
|
+
let requestedPoint = CGPoint(x: x, y: y)
|
|
824
|
+
if let target {
|
|
825
|
+
let frame = target.frame
|
|
826
|
+
if !frame.isEmpty {
|
|
827
|
+
_ = tapAt(app: app, x: frame.midX, y: frame.midY)
|
|
828
|
+
} else {
|
|
829
|
+
_ = tapAt(app: app, x: x, y: y)
|
|
830
|
+
}
|
|
831
|
+
} else {
|
|
832
|
+
_ = tapAt(app: app, x: x, y: y)
|
|
833
|
+
}
|
|
834
|
+
let stabilized = stabilizeTextInputBeforeTyping(app: app, target: target)
|
|
835
|
+
let element = waitForTextEntryReadiness(
|
|
836
|
+
app: app,
|
|
837
|
+
target: TextEntryTarget(
|
|
838
|
+
element: stabilized ?? target,
|
|
839
|
+
refreshPoint: requestedPoint,
|
|
840
|
+
prefersFocusedElement: false
|
|
841
|
+
)
|
|
842
|
+
) ?? stabilized ?? target
|
|
843
|
+
return TextEntryTarget(
|
|
844
|
+
element: element,
|
|
845
|
+
refreshPoint: textEntryRefreshPoint(for: element) ?? requestedPoint,
|
|
846
|
+
prefersFocusedElement: false
|
|
847
|
+
)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
func resolveTextEntryMode(_ command: Command) -> TextTypingRepairMode {
|
|
851
|
+
switch command.textEntryMode {
|
|
852
|
+
case "append":
|
|
853
|
+
return .append
|
|
854
|
+
case "replace":
|
|
855
|
+
return .replacement
|
|
856
|
+
default:
|
|
857
|
+
return command.clearFirst == true ? .replacement : .none
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
func typeTextReliably(
|
|
862
|
+
app: XCUIApplication,
|
|
863
|
+
target: TextEntryTarget,
|
|
864
|
+
text: String,
|
|
865
|
+
delaySeconds: Double,
|
|
866
|
+
repairMode: TextTypingRepairMode = .none
|
|
867
|
+
) -> TextEntryResult {
|
|
868
|
+
guard !text.isEmpty else {
|
|
869
|
+
return TextEntryResult(verified: true, repaired: false, expectedText: "", observedText: "")
|
|
870
|
+
}
|
|
871
|
+
var activeTarget = target
|
|
872
|
+
let initialTarget = resolveTextEntryElement(app: app, target: activeTarget)
|
|
873
|
+
activeTarget = activeTarget.withElement(initialTarget)
|
|
874
|
+
let currentText = editableTextValue(for: initialTarget, treatingPlaceholderAsEmpty: true)
|
|
875
|
+
let initialText = repairMode == .append ? currentText : nil
|
|
876
|
+
let expectedText = expectedTextEntryValue(typedText: text, mode: repairMode, initialText: initialText)
|
|
877
|
+
|
|
878
|
+
if repairMode == .replacement {
|
|
879
|
+
guard let replacementTarget = initialTarget else {
|
|
880
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: expectedText, observedText: nil)
|
|
881
|
+
}
|
|
882
|
+
if currentText == nil || currentText?.isEmpty == false {
|
|
883
|
+
clearTextInput(replacementTarget)
|
|
884
|
+
activeTarget = activeTarget.withElement(replacementTarget)
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
func typeIntoCurrentTarget(_ value: String) -> XCUIElement? {
|
|
889
|
+
// Local edit (not in upstream agent-device). SecureTextField on RN
|
|
890
|
+
// iOS doesn't respond to coordinate-taps for focus transfer, AND
|
|
891
|
+
// its hasKeyboardFocus query throws or returns false. Two-part fix:
|
|
892
|
+
// 1. element.tap() to force OS first-responder onto the field
|
|
893
|
+
// 2. DLSendSyntheticTyping to inject characters at the HID layer
|
|
894
|
+
// For non-secure fields we keep upstream's app.typeText behaviour.
|
|
895
|
+
// We tried universalizing synthetic typing but it regressed Login
|
|
896
|
+
// flows — without the explicit pre-tap, synthetic events fire
|
|
897
|
+
// before the field has stable first-responder and characters land
|
|
898
|
+
// in the wrong field. app.typeText handles the focus dance
|
|
899
|
+
// implicitly and is more robust for non-secure fields.
|
|
900
|
+
let currentTarget = resolveTextEntryElement(app: app, target: activeTarget)
|
|
901
|
+
let isSecureField = currentTarget?.elementType == .secureTextField
|
|
902
|
+
if isSecureField, let secure = currentTarget {
|
|
903
|
+
_ = RunnerObjCExceptionCatcher.catchException({
|
|
904
|
+
secure.tap()
|
|
905
|
+
})
|
|
906
|
+
sleepFor(0.05)
|
|
907
|
+
var typingError: NSError?
|
|
908
|
+
let ok = DLSendSyntheticTyping(value, 240, &typingError)
|
|
909
|
+
if !ok {
|
|
910
|
+
NSLog(
|
|
911
|
+
"DEVICELAB_RUNNER_SYNTHETIC_TYPING_FAILED message=%@ fallback=app.typeText",
|
|
912
|
+
typingError?.localizedDescription ?? "(no error)"
|
|
913
|
+
)
|
|
914
|
+
app.typeText(value)
|
|
915
|
+
}
|
|
916
|
+
return currentTarget
|
|
917
|
+
}
|
|
918
|
+
app.typeText(value)
|
|
919
|
+
return currentTarget ?? resolveTextEntryElement(app: app, target: activeTarget)
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
func waitForWarmupValue(_ expectedValue: String?, target: TextEntryTarget) {
|
|
923
|
+
guard let expectedValue else {
|
|
924
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
925
|
+
return
|
|
926
|
+
}
|
|
927
|
+
let deadline = Date().addingTimeInterval(TextEntryTiming.warmupValueTimeout)
|
|
928
|
+
while Date() < deadline {
|
|
929
|
+
if editableTextValue(for: resolveTextEntryElement(app: app, target: target)) == expectedValue {
|
|
930
|
+
return
|
|
931
|
+
}
|
|
932
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
let characters = Array(text)
|
|
937
|
+
if delaySeconds > 0 && characters.count > 1 {
|
|
938
|
+
var typedTarget: XCUIElement?
|
|
939
|
+
for (index, character) in characters.enumerated() {
|
|
940
|
+
typedTarget = typeIntoCurrentTarget(String(character)) ?? typedTarget
|
|
941
|
+
if index + 1 < characters.count {
|
|
942
|
+
sleepFor(delaySeconds)
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if repairMode == .none {
|
|
946
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: nil, observedText: nil)
|
|
947
|
+
}
|
|
948
|
+
let repairResult = repairTextEntryIfNeeded(
|
|
949
|
+
app: app,
|
|
950
|
+
target: activeTarget.withElement(typedTarget),
|
|
951
|
+
expectedText: expectedText,
|
|
952
|
+
repairMode: repairMode
|
|
953
|
+
)
|
|
954
|
+
return verifyTextEntry(
|
|
955
|
+
app: app,
|
|
956
|
+
target: activeTarget.withElement(typedTarget),
|
|
957
|
+
expectedText: expectedText,
|
|
958
|
+
repaired: repairResult.repaired
|
|
959
|
+
)
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
let typedTarget: XCUIElement?
|
|
963
|
+
if repairMode != .none && characters.count > 1 {
|
|
964
|
+
let firstCharacter = String(characters[0])
|
|
965
|
+
var firstTypedTarget = typeIntoCurrentTarget(firstCharacter)
|
|
966
|
+
activeTarget = activeTarget.withElement(firstTypedTarget)
|
|
967
|
+
let warmupExpectedText = expectedTextEntryValue(
|
|
968
|
+
typedText: firstCharacter,
|
|
969
|
+
mode: repairMode,
|
|
970
|
+
initialText: initialText
|
|
971
|
+
)
|
|
972
|
+
waitForWarmupValue(warmupExpectedText, target: activeTarget)
|
|
973
|
+
let remainingText = String(characters.dropFirst())
|
|
974
|
+
firstTypedTarget = typeIntoCurrentTarget(remainingText) ?? firstTypedTarget
|
|
975
|
+
typedTarget = firstTypedTarget
|
|
976
|
+
} else {
|
|
977
|
+
typedTarget = typeIntoCurrentTarget(text)
|
|
978
|
+
}
|
|
979
|
+
if repairMode == .none {
|
|
980
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: nil, observedText: nil)
|
|
981
|
+
}
|
|
982
|
+
let repairResult = repairTextEntryIfNeeded(
|
|
983
|
+
app: app,
|
|
984
|
+
target: activeTarget.withElement(typedTarget),
|
|
985
|
+
expectedText: expectedText,
|
|
986
|
+
repairMode: repairMode
|
|
987
|
+
)
|
|
988
|
+
return verifyTextEntry(
|
|
989
|
+
app: app,
|
|
990
|
+
target: activeTarget.withElement(typedTarget),
|
|
991
|
+
expectedText: expectedText,
|
|
992
|
+
repaired: repairResult.repaired
|
|
993
|
+
)
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
private func repairTextEntryIfNeeded(
|
|
997
|
+
app: XCUIApplication,
|
|
998
|
+
target: TextEntryTarget,
|
|
999
|
+
expectedText: String?,
|
|
1000
|
+
repairMode: TextTypingRepairMode
|
|
1001
|
+
) -> TextEntryResult {
|
|
1002
|
+
#if os(iOS)
|
|
1003
|
+
guard let targetElement = resolveTextEntryElement(app: app, target: target) else {
|
|
1004
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: expectedText, observedText: nil)
|
|
1005
|
+
}
|
|
1006
|
+
guard let expectedText else {
|
|
1007
|
+
let observedText = editableTextValue(for: targetElement)
|
|
1008
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: nil, observedText: observedText)
|
|
1009
|
+
}
|
|
1010
|
+
guard shouldRepairTextEntry(
|
|
1011
|
+
app: app,
|
|
1012
|
+
target: target,
|
|
1013
|
+
expectedText: expectedText,
|
|
1014
|
+
repairMode: repairMode
|
|
1015
|
+
) else {
|
|
1016
|
+
return verifyTextEntry(app: app, target: target, expectedText: expectedText, repaired: false)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
guard let repairTarget = resolveTextEntryElement(app: app, target: target) else {
|
|
1020
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: expectedText, observedText: nil)
|
|
1021
|
+
}
|
|
1022
|
+
let observedText = editableTextValue(for: repairTarget) ?? ""
|
|
1023
|
+
NSLog(
|
|
1024
|
+
"AGENT_DEVICE_RUNNER_REPAIR_TEXT_ENTRY expectedLength=%d observedLength=%d",
|
|
1025
|
+
expectedText.count,
|
|
1026
|
+
observedText.count
|
|
1027
|
+
)
|
|
1028
|
+
clearTextInput(repairTarget)
|
|
1029
|
+
app.typeText(expectedText)
|
|
1030
|
+
return verifyTextEntry(app: app, target: target, expectedText: expectedText, repaired: true)
|
|
1031
|
+
#else
|
|
1032
|
+
return TextEntryResult(verified: nil, repaired: false, expectedText: expectedText, observedText: nil)
|
|
1033
|
+
#endif
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
private func verifyTextEntry(
|
|
1037
|
+
app: XCUIApplication,
|
|
1038
|
+
target: TextEntryTarget,
|
|
1039
|
+
expectedText: String?,
|
|
1040
|
+
repaired: Bool
|
|
1041
|
+
) -> TextEntryResult {
|
|
1042
|
+
let targetElement = resolveTextEntryElement(app: app, target: target)
|
|
1043
|
+
guard let expectedText else {
|
|
1044
|
+
return TextEntryResult(
|
|
1045
|
+
verified: nil,
|
|
1046
|
+
repaired: repaired,
|
|
1047
|
+
expectedText: nil,
|
|
1048
|
+
observedText: editableTextValue(for: targetElement)
|
|
1049
|
+
)
|
|
1050
|
+
}
|
|
1051
|
+
guard let observedText = editableTextValue(for: targetElement) else {
|
|
1052
|
+
return TextEntryResult(verified: nil, repaired: repaired, expectedText: expectedText, observedText: nil)
|
|
1053
|
+
}
|
|
1054
|
+
guard observedText == expectedText else {
|
|
1055
|
+
return TextEntryResult(
|
|
1056
|
+
verified: false,
|
|
1057
|
+
repaired: repaired,
|
|
1058
|
+
expectedText: expectedText,
|
|
1059
|
+
observedText: observedText
|
|
1060
|
+
)
|
|
1061
|
+
}
|
|
1062
|
+
let stableDeadline = Date().addingTimeInterval(TextEntryTiming.verificationStabilityWindow)
|
|
1063
|
+
var latestObservedText = observedText
|
|
1064
|
+
while Date() < stableDeadline {
|
|
1065
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
1066
|
+
guard let nextObservedText = editableTextValue(for: resolveTextEntryElement(app: app, target: target)) else {
|
|
1067
|
+
return TextEntryResult(verified: nil, repaired: repaired, expectedText: expectedText, observedText: nil)
|
|
1068
|
+
}
|
|
1069
|
+
latestObservedText = nextObservedText
|
|
1070
|
+
guard nextObservedText == expectedText else {
|
|
1071
|
+
return TextEntryResult(
|
|
1072
|
+
verified: false,
|
|
1073
|
+
repaired: repaired,
|
|
1074
|
+
expectedText: expectedText,
|
|
1075
|
+
observedText: nextObservedText
|
|
1076
|
+
)
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return TextEntryResult(
|
|
1080
|
+
verified: true,
|
|
1081
|
+
repaired: repaired,
|
|
1082
|
+
expectedText: expectedText,
|
|
1083
|
+
observedText: latestObservedText
|
|
1084
|
+
)
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
private func expectedTextEntryValue(
|
|
1088
|
+
typedText: String,
|
|
1089
|
+
mode: TextTypingRepairMode,
|
|
1090
|
+
initialText: String?
|
|
1091
|
+
) -> String? {
|
|
1092
|
+
switch mode {
|
|
1093
|
+
case .none:
|
|
1094
|
+
return nil
|
|
1095
|
+
case .append:
|
|
1096
|
+
guard let initialText else {
|
|
1097
|
+
return nil
|
|
1098
|
+
}
|
|
1099
|
+
return initialText + typedText
|
|
1100
|
+
case .replacement:
|
|
1101
|
+
return typedText
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
private func shouldRepairTextEntry(
|
|
1106
|
+
app: XCUIApplication,
|
|
1107
|
+
target: TextEntryTarget,
|
|
1108
|
+
expectedText: String,
|
|
1109
|
+
repairMode: TextTypingRepairMode
|
|
1110
|
+
) -> Bool {
|
|
1111
|
+
#if os(iOS)
|
|
1112
|
+
var latestObservedText: String?
|
|
1113
|
+
let deadline = Date().addingTimeInterval(TextEntryTiming.verificationStabilityWindow)
|
|
1114
|
+
repeat {
|
|
1115
|
+
guard let observedText = editableTextValue(for: resolveTextEntryElement(app: app, target: target)) else {
|
|
1116
|
+
return false
|
|
1117
|
+
}
|
|
1118
|
+
if observedText == expectedText {
|
|
1119
|
+
return false
|
|
1120
|
+
}
|
|
1121
|
+
latestObservedText = observedText
|
|
1122
|
+
if !isRepairableTextEntryMismatch(
|
|
1123
|
+
observedText: observedText,
|
|
1124
|
+
expectedText: expectedText,
|
|
1125
|
+
repairMode: repairMode
|
|
1126
|
+
) {
|
|
1127
|
+
return false
|
|
1128
|
+
}
|
|
1129
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
1130
|
+
} while Date() < deadline
|
|
1131
|
+
|
|
1132
|
+
guard let latestObservedText else {
|
|
1133
|
+
return false
|
|
1134
|
+
}
|
|
1135
|
+
guard latestObservedText != expectedText else {
|
|
1136
|
+
return false
|
|
1137
|
+
}
|
|
1138
|
+
return isRepairableTextEntryMismatch(
|
|
1139
|
+
observedText: latestObservedText,
|
|
1140
|
+
expectedText: expectedText,
|
|
1141
|
+
repairMode: repairMode
|
|
1142
|
+
)
|
|
1143
|
+
#else
|
|
1144
|
+
return false
|
|
1145
|
+
#endif
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
private func isRepairableTextEntryMismatch(
|
|
1149
|
+
observedText: String,
|
|
1150
|
+
expectedText: String,
|
|
1151
|
+
repairMode: TextTypingRepairMode
|
|
1152
|
+
) -> Bool {
|
|
1153
|
+
guard observedText != expectedText else {
|
|
1154
|
+
return false
|
|
1155
|
+
}
|
|
1156
|
+
if repairMode == .replacement {
|
|
1157
|
+
return true
|
|
1158
|
+
}
|
|
1159
|
+
return observedText.isEmpty || isLikelyDroppedCharacterTextEntryMismatch(
|
|
1160
|
+
observedText: observedText,
|
|
1161
|
+
expectedText: expectedText
|
|
1162
|
+
)
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
private func isLikelyDroppedCharacterTextEntryMismatch(observedText: String, expectedText: String) -> Bool {
|
|
1166
|
+
guard observedText.count < expectedText.count else {
|
|
1167
|
+
return false
|
|
1168
|
+
}
|
|
1169
|
+
let missingCharacterCount = expectedText.count - observedText.count
|
|
1170
|
+
guard missingCharacterCount <= max(2, expectedText.count / 4) else {
|
|
1171
|
+
return false
|
|
1172
|
+
}
|
|
1173
|
+
var expectedIndex = expectedText.startIndex
|
|
1174
|
+
for character in observedText {
|
|
1175
|
+
guard let matchIndex = expectedText[expectedIndex...].firstIndex(of: character) else {
|
|
1176
|
+
return false
|
|
1177
|
+
}
|
|
1178
|
+
expectedIndex = expectedText.index(after: matchIndex)
|
|
1179
|
+
}
|
|
1180
|
+
return true
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
private func resolveTextEntryElement(app: XCUIApplication, target: TextEntryTarget) -> XCUIElement? {
|
|
1184
|
+
if target.prefersFocusedElement {
|
|
1185
|
+
if let focused = focusedTextInput(app: app) {
|
|
1186
|
+
return focused
|
|
1187
|
+
}
|
|
1188
|
+
if let element = target.element, element.exists {
|
|
1189
|
+
return element
|
|
1190
|
+
}
|
|
1191
|
+
} else {
|
|
1192
|
+
if let element = target.element, element.exists {
|
|
1193
|
+
return element
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
if let refreshPoint = target.refreshPoint,
|
|
1197
|
+
let refreshed = textInputAt(app: app, x: refreshPoint.x, y: refreshPoint.y) {
|
|
1198
|
+
return refreshed
|
|
1199
|
+
}
|
|
1200
|
+
if let focused = focusedTextInput(app: app) {
|
|
1201
|
+
return focused
|
|
1202
|
+
}
|
|
1203
|
+
return nil
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
private func waitForTextEntryReadiness(
|
|
1207
|
+
app: XCUIApplication,
|
|
1208
|
+
target: TextEntryTarget,
|
|
1209
|
+
timeout: TimeInterval = TextEntryTiming.readinessTimeout
|
|
1210
|
+
) -> XCUIElement? {
|
|
1211
|
+
#if os(iOS)
|
|
1212
|
+
var latest = resolveTextEntryElement(app: app, target: target)
|
|
1213
|
+
let deadline = Date().addingTimeInterval(timeout)
|
|
1214
|
+
let hardwareKeyboardFallback = Date().addingTimeInterval(
|
|
1215
|
+
min(TextEntryTiming.hardwareKeyboardFallbackTimeout, timeout)
|
|
1216
|
+
)
|
|
1217
|
+
var sawSoftwareKeyboard = false
|
|
1218
|
+
while Date() < deadline {
|
|
1219
|
+
if let focused = focusedTextInput(app: app) {
|
|
1220
|
+
latest = focused
|
|
1221
|
+
if isKeyboardVisible(app: app) {
|
|
1222
|
+
return focused
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
sawSoftwareKeyboard = sawSoftwareKeyboard || keyboardElementExists(app: app)
|
|
1226
|
+
if !sawSoftwareKeyboard && Date() >= hardwareKeyboardFallback && latest != nil {
|
|
1227
|
+
return latest
|
|
1228
|
+
}
|
|
1229
|
+
sleepFor(TextEntryTiming.pollInterval)
|
|
1230
|
+
}
|
|
1231
|
+
return focusedTextInput(app: app) ?? latest
|
|
1232
|
+
#else
|
|
1233
|
+
return resolveTextEntryElement(app: app, target: target)
|
|
1234
|
+
#endif
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
private func textEntryRefreshPoint(for element: XCUIElement?) -> CGPoint? {
|
|
1238
|
+
guard let element else {
|
|
1239
|
+
return nil
|
|
1240
|
+
}
|
|
1241
|
+
let frame = element.frame
|
|
1242
|
+
guard !frame.isEmpty else {
|
|
1243
|
+
return nil
|
|
1244
|
+
}
|
|
1245
|
+
return CGPoint(x: frame.midX, y: frame.midY)
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
func isKeyboardVisible(app: XCUIApplication) -> Bool {
|
|
1249
|
+
return visibleKeyboardFrame(app: app) != nil
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
private func keyboardElementExists(app: XCUIApplication) -> Bool {
|
|
1253
|
+
#if os(iOS)
|
|
1254
|
+
var exists = false
|
|
1255
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1256
|
+
exists = app.keyboards.firstMatch.exists
|
|
1257
|
+
})
|
|
1258
|
+
if let exceptionMessage {
|
|
1259
|
+
NSLog(
|
|
1260
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_EXISTS_IGNORED_EXCEPTION=%@",
|
|
1261
|
+
exceptionMessage
|
|
1262
|
+
)
|
|
1263
|
+
return false
|
|
1264
|
+
}
|
|
1265
|
+
return exists
|
|
1266
|
+
#else
|
|
1267
|
+
return false
|
|
1268
|
+
#endif
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
func dismissKeyboard(app: XCUIApplication) -> (wasVisible: Bool, dismissed: Bool, visible: Bool) {
|
|
1272
|
+
let wasVisible = isKeyboardVisible(app: app)
|
|
1273
|
+
guard wasVisible else {
|
|
1274
|
+
return (wasVisible: false, dismissed: false, visible: false)
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
#if os(tvOS)
|
|
1278
|
+
_ = pressTvRemote(.menu)
|
|
1279
|
+
sleepFor(0.2)
|
|
1280
|
+
let visible = isKeyboardVisible(app: app)
|
|
1281
|
+
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
1282
|
+
#else
|
|
1283
|
+
let keyboard = app.keyboards.firstMatch
|
|
1284
|
+
keyboard.swipeDown()
|
|
1285
|
+
sleepFor(0.2)
|
|
1286
|
+
if !isKeyboardVisible(app: app) {
|
|
1287
|
+
return (wasVisible: true, dismissed: true, visible: false)
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
if tapKeyboardDismissControl(app: app) {
|
|
1291
|
+
sleepFor(0.2)
|
|
1292
|
+
let visible = isKeyboardVisible(app: app)
|
|
1293
|
+
return (wasVisible: true, dismissed: !visible, visible: visible)
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
return (wasVisible: true, dismissed: false, visible: isKeyboardVisible(app: app))
|
|
1297
|
+
#endif
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
private func tapKeyboardDismissControl(app: XCUIApplication) -> Bool {
|
|
1301
|
+
#if os(tvOS)
|
|
1302
|
+
return false
|
|
1303
|
+
#else
|
|
1304
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
|
|
1305
|
+
return false
|
|
1306
|
+
}
|
|
1307
|
+
for label in ["Hide keyboard", "Dismiss keyboard", "Done"] {
|
|
1308
|
+
let candidates = [
|
|
1309
|
+
app.keyboards.buttons[label],
|
|
1310
|
+
app.keyboards.keys[label],
|
|
1311
|
+
app.keyboards.toolbars.buttons[label],
|
|
1312
|
+
]
|
|
1313
|
+
if let hittable = candidates.first(where: { $0.exists && $0.isHittable }) {
|
|
1314
|
+
hittable.tap()
|
|
1315
|
+
return true
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
let toolbarButtonPredicate = NSPredicate(
|
|
1319
|
+
format: "label == %@ OR identifier == %@",
|
|
1320
|
+
label,
|
|
1321
|
+
label
|
|
1322
|
+
)
|
|
1323
|
+
let toolbarButtons = app.toolbars.buttons
|
|
1324
|
+
.matching(toolbarButtonPredicate)
|
|
1325
|
+
.allElementsBoundByIndex
|
|
1326
|
+
if let hittable = toolbarButtons.first(where: {
|
|
1327
|
+
$0.exists && $0.isHittable && isKeyboardAccessoryControl($0, keyboardFrame: keyboardFrame)
|
|
1328
|
+
}) {
|
|
1329
|
+
hittable.tap()
|
|
1330
|
+
return true
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
return false
|
|
1334
|
+
#endif
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
private func isKeyboardAccessoryControl(_ element: XCUIElement, keyboardFrame: CGRect) -> Bool {
|
|
1338
|
+
let frame = element.frame
|
|
1339
|
+
guard !frame.isEmpty && !keyboardFrame.isEmpty else {
|
|
1340
|
+
return false
|
|
1341
|
+
}
|
|
1342
|
+
return frame.intersects(keyboardFrame) || abs(frame.maxY - keyboardFrame.minY) <= 80
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
private func moveCaretToEnd(element: XCUIElement) {
|
|
1346
|
+
#if os(tvOS)
|
|
1347
|
+
return
|
|
1348
|
+
#else
|
|
1349
|
+
let frame = element.frame
|
|
1350
|
+
guard !frame.isEmpty else {
|
|
1351
|
+
element.tap()
|
|
1352
|
+
return
|
|
1353
|
+
}
|
|
1354
|
+
let origin = element.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
|
|
1355
|
+
let target = origin.withOffset(
|
|
1356
|
+
CGVector(dx: max(2, frame.width - 4), dy: max(2, frame.height / 2))
|
|
1357
|
+
)
|
|
1358
|
+
target.tap()
|
|
1359
|
+
#endif
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
private func estimatedDeleteCount(for element: XCUIElement) -> Int {
|
|
1363
|
+
let valueText = normalizedElementText(element.value)
|
|
1364
|
+
let base = valueText.isEmpty ? 24 : (valueText.count + 8)
|
|
1365
|
+
return max(24, min(120, base))
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
private func normalizedElementText(_ value: Any?) -> String {
|
|
1369
|
+
String(describing: value ?? "")
|
|
1370
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
private func editableTextValue(
|
|
1374
|
+
for element: XCUIElement?,
|
|
1375
|
+
treatingPlaceholderAsEmpty: Bool = false
|
|
1376
|
+
) -> String? {
|
|
1377
|
+
guard let element else {
|
|
1378
|
+
return nil
|
|
1379
|
+
}
|
|
1380
|
+
switch element.elementType {
|
|
1381
|
+
case .textField, .searchField, .textView:
|
|
1382
|
+
let value = String(describing: element.value ?? "")
|
|
1383
|
+
if treatingPlaceholderAsEmpty && isPlaceholderValue(value, for: element) {
|
|
1384
|
+
return ""
|
|
1385
|
+
}
|
|
1386
|
+
return value
|
|
1387
|
+
case .secureTextField:
|
|
1388
|
+
return nil
|
|
1389
|
+
default:
|
|
1390
|
+
return nil
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
private func isPlaceholderValue(_ value: String, for element: XCUIElement) -> Bool {
|
|
1395
|
+
let normalizedValue = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1396
|
+
guard !normalizedValue.isEmpty else {
|
|
1397
|
+
return false
|
|
1398
|
+
}
|
|
1399
|
+
guard let placeholder = element.placeholderValue?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
1400
|
+
!placeholder.isEmpty else {
|
|
1401
|
+
return false
|
|
1402
|
+
}
|
|
1403
|
+
return normalizedValue == placeholder
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
private func readableText(for element: XCUIElement) -> String? {
|
|
1407
|
+
let label = element.label.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1408
|
+
let identifier = element.identifier.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1409
|
+
let valueText = String(describing: element.value ?? "")
|
|
1410
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
1411
|
+
switch element.elementType {
|
|
1412
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
1413
|
+
if !valueText.isEmpty { return valueText }
|
|
1414
|
+
if !label.isEmpty { return label }
|
|
1415
|
+
return identifier.isEmpty ? nil : identifier
|
|
1416
|
+
default:
|
|
1417
|
+
if !label.isEmpty { return label }
|
|
1418
|
+
if !valueText.isEmpty { return valueText }
|
|
1419
|
+
return identifier.isEmpty ? nil : identifier
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
private func prefersExpandedTextRead(_ element: XCUIElement) -> Bool {
|
|
1424
|
+
switch element.elementType {
|
|
1425
|
+
case .textField, .secureTextField, .searchField, .textView:
|
|
1426
|
+
return true
|
|
1427
|
+
default:
|
|
1428
|
+
return false
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
func findScopeElement(app: XCUIApplication, scope: String) -> XCUIElement? {
|
|
1433
|
+
let predicate = NSPredicate(
|
|
1434
|
+
format: "label CONTAINS[c] %@ OR identifier CONTAINS[c] %@",
|
|
1435
|
+
scope,
|
|
1436
|
+
scope
|
|
1437
|
+
)
|
|
1438
|
+
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
1439
|
+
return element.exists ? element : nil
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
func tapAt(app: XCUIApplication, x: Double, y: Double) -> RunnerInteractionOutcome {
|
|
1443
|
+
if let outcome = selectFocusedTvElement(app: app, point: CGPoint(x: x, y: y), action: "tap") {
|
|
1444
|
+
return outcome
|
|
1445
|
+
}
|
|
1446
|
+
return performCoordinateTap(app: app, x: x, y: y)
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
func mouseClickAt(app: XCUIApplication, x: Double, y: Double, button: String) throws {
|
|
1450
|
+
#if os(macOS)
|
|
1451
|
+
let coordinate = interactionCoordinate(app: app, x: x, y: y)
|
|
1452
|
+
switch button {
|
|
1453
|
+
case "primary":
|
|
1454
|
+
coordinate.tap()
|
|
1455
|
+
case "secondary":
|
|
1456
|
+
coordinate.rightClick()
|
|
1457
|
+
case "middle":
|
|
1458
|
+
throw NSError(
|
|
1459
|
+
domain: "AgentDeviceRunner",
|
|
1460
|
+
code: 1,
|
|
1461
|
+
userInfo: [NSLocalizedDescriptionKey: "middle mouse button is not supported"]
|
|
1462
|
+
)
|
|
1463
|
+
default:
|
|
1464
|
+
throw NSError(
|
|
1465
|
+
domain: "AgentDeviceRunner",
|
|
1466
|
+
code: 1,
|
|
1467
|
+
userInfo: [NSLocalizedDescriptionKey: "unsupported mouse button: \(button)"]
|
|
1468
|
+
)
|
|
1469
|
+
}
|
|
1470
|
+
#elseif os(tvOS)
|
|
1471
|
+
throw NSError(
|
|
1472
|
+
domain: "AgentDeviceRunner",
|
|
1473
|
+
code: 1,
|
|
1474
|
+
userInfo: [NSLocalizedDescriptionKey: "mouseClick is not supported on tvOS"]
|
|
1475
|
+
)
|
|
1476
|
+
#else
|
|
1477
|
+
throw NSError(
|
|
1478
|
+
domain: "AgentDeviceRunner",
|
|
1479
|
+
code: 1,
|
|
1480
|
+
userInfo: [NSLocalizedDescriptionKey: "mouseClick is only supported on macOS"]
|
|
1481
|
+
)
|
|
1482
|
+
#endif
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
func doubleTapAt(app: XCUIApplication, x: Double, y: Double) -> RunnerInteractionOutcome {
|
|
1486
|
+
if let outcome = selectFocusedTvElement(app: app, point: CGPoint(x: x, y: y), action: "double tap") {
|
|
1487
|
+
guard case .performed = outcome else { return outcome }
|
|
1488
|
+
sleepFor(0.1)
|
|
1489
|
+
_ = pressTvRemote(.select)
|
|
1490
|
+
return .performed
|
|
1491
|
+
}
|
|
1492
|
+
return performCoordinateDoubleTap(app: app, x: x, y: y)
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
func longPressAt(app: XCUIApplication, x: Double, y: Double, duration: TimeInterval) -> RunnerInteractionOutcome {
|
|
1496
|
+
if let outcome = longSelectFocusedTvElement(app: app, point: CGPoint(x: x, y: y), duration: duration) {
|
|
1497
|
+
return outcome
|
|
1498
|
+
}
|
|
1499
|
+
return performCoordinateLongPress(app: app, x: x, y: y, duration: duration)
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
func dragAt(
|
|
1503
|
+
app: XCUIApplication,
|
|
1504
|
+
x: Double,
|
|
1505
|
+
y: Double,
|
|
1506
|
+
x2: Double,
|
|
1507
|
+
y2: Double,
|
|
1508
|
+
holdDuration: TimeInterval,
|
|
1509
|
+
moveDuration: TimeInterval? = nil
|
|
1510
|
+
) -> RunnerInteractionOutcome {
|
|
1511
|
+
// tvOS has no coordinate drag. Preserve the direction as a focus move.
|
|
1512
|
+
let dx = x2 - x
|
|
1513
|
+
let dy = y2 - y
|
|
1514
|
+
let button: TvRemoteButton = abs(dx) > abs(dy)
|
|
1515
|
+
? (dx > 0 ? .right : .left)
|
|
1516
|
+
: (dy > 0 ? .down : .up)
|
|
1517
|
+
if pressTvRemote(button) {
|
|
1518
|
+
return .performed
|
|
1519
|
+
}
|
|
1520
|
+
return performCoordinateDrag(
|
|
1521
|
+
app: app, x: x, y: y, x2: x2, y2: y2,
|
|
1522
|
+
holdDuration: holdDuration, moveDuration: moveDuration)
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
func keyboardAvoidingDragPoints(
|
|
1526
|
+
app: XCUIApplication,
|
|
1527
|
+
x: Double,
|
|
1528
|
+
y: Double,
|
|
1529
|
+
x2: Double,
|
|
1530
|
+
y2: Double
|
|
1531
|
+
) -> DragPoints {
|
|
1532
|
+
let original = DragPoints(x: x, y: y, x2: x2, y2: y2)
|
|
1533
|
+
#if os(iOS)
|
|
1534
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app) else {
|
|
1535
|
+
return original
|
|
1536
|
+
}
|
|
1537
|
+
let minX = min(x, x2)
|
|
1538
|
+
let minY = min(y, y2)
|
|
1539
|
+
let gestureBounds = CGRect(
|
|
1540
|
+
x: CGFloat(minX),
|
|
1541
|
+
y: CGFloat(minY),
|
|
1542
|
+
width: CGFloat(max(abs(x2 - x), 1)),
|
|
1543
|
+
height: CGFloat(max(abs(y2 - y), 1))
|
|
1544
|
+
)
|
|
1545
|
+
guard gestureBounds.intersects(keyboardFrame) else {
|
|
1546
|
+
return original
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
let window = app.windows.firstMatch
|
|
1550
|
+
let appFrame = window.exists && !window.frame.isEmpty ? window.frame : app.frame
|
|
1551
|
+
guard !appFrame.isEmpty else {
|
|
1552
|
+
return original
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
let padding: Double = 12
|
|
1556
|
+
let targetMaxY = Double(keyboardFrame.minY) - padding
|
|
1557
|
+
let currentMaxY = max(y, y2)
|
|
1558
|
+
let shift = currentMaxY - targetMaxY
|
|
1559
|
+
guard shift > 0 else {
|
|
1560
|
+
return original
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
let adjustedY = y - shift
|
|
1564
|
+
let adjustedY2 = y2 - shift
|
|
1565
|
+
guard min(adjustedY, adjustedY2) >= Double(appFrame.minY) + padding else {
|
|
1566
|
+
return original
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
NSLog(
|
|
1570
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_AVOIDING_DRAG from=(%.1f,%.1f)->(%.1f,%.1f) adjusted=(%.1f,%.1f)->(%.1f,%.1f) keyboardMinY=%.1f",
|
|
1571
|
+
x,
|
|
1572
|
+
y,
|
|
1573
|
+
x2,
|
|
1574
|
+
y2,
|
|
1575
|
+
x,
|
|
1576
|
+
adjustedY,
|
|
1577
|
+
x2,
|
|
1578
|
+
adjustedY2,
|
|
1579
|
+
Double(keyboardFrame.minY)
|
|
1580
|
+
)
|
|
1581
|
+
return DragPoints(x: x, y: adjustedY, x2: x2, y2: adjustedY2)
|
|
1582
|
+
#else
|
|
1583
|
+
return original
|
|
1584
|
+
#endif
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
func resolvedTouchVisualizationFrame(app: XCUIApplication, x: Double, y: Double) -> TouchVisualizationFrame {
|
|
1588
|
+
let appFrame = app.frame
|
|
1589
|
+
let referenceFrame = resolvedTouchReferenceFrame(app: app, appFrame: appFrame)
|
|
1590
|
+
let originX = appFrame.isEmpty ? referenceFrame.minX : appFrame.minX
|
|
1591
|
+
let originY = appFrame.isEmpty ? referenceFrame.minY : appFrame.minY
|
|
1592
|
+
return TouchVisualizationFrame(
|
|
1593
|
+
x: originX + x,
|
|
1594
|
+
y: originY + y,
|
|
1595
|
+
referenceWidth: referenceFrame.width,
|
|
1596
|
+
referenceHeight: referenceFrame.height
|
|
1597
|
+
)
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
func resolvedDragVisualizationFrame(
|
|
1601
|
+
app: XCUIApplication,
|
|
1602
|
+
x: Double,
|
|
1603
|
+
y: Double,
|
|
1604
|
+
x2: Double,
|
|
1605
|
+
y2: Double
|
|
1606
|
+
) -> DragVisualizationFrame {
|
|
1607
|
+
let start = resolvedTouchVisualizationFrame(app: app, x: x, y: y)
|
|
1608
|
+
let end = resolvedTouchVisualizationFrame(app: app, x: x2, y: y2)
|
|
1609
|
+
return DragVisualizationFrame(
|
|
1610
|
+
x: start.x,
|
|
1611
|
+
y: start.y,
|
|
1612
|
+
x2: end.x,
|
|
1613
|
+
y2: end.y,
|
|
1614
|
+
referenceWidth: start.referenceWidth,
|
|
1615
|
+
referenceHeight: start.referenceHeight
|
|
1616
|
+
)
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
func resolvedTouchReferenceFrame(app: XCUIApplication, appFrame: CGRect) -> CGRect {
|
|
1620
|
+
let window = app.windows.firstMatch
|
|
1621
|
+
if window.exists {
|
|
1622
|
+
let windowFrame = window.frame
|
|
1623
|
+
if !windowFrame.isEmpty {
|
|
1624
|
+
return frameAvoidingKeyboard(app: app, frame: windowFrame)
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
if !appFrame.isEmpty {
|
|
1628
|
+
return frameAvoidingKeyboard(app: app, frame: appFrame)
|
|
1629
|
+
}
|
|
1630
|
+
return CGRect(x: 0, y: 0, width: 0, height: 0)
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
private func frameAvoidingKeyboard(app: XCUIApplication, frame: CGRect) -> CGRect {
|
|
1634
|
+
#if os(iOS)
|
|
1635
|
+
guard let keyboardFrame = visibleKeyboardFrame(app: app), !frame.isEmpty else {
|
|
1636
|
+
return frame
|
|
1637
|
+
}
|
|
1638
|
+
let intersection = frame.intersection(keyboardFrame)
|
|
1639
|
+
guard !intersection.isNull && intersection.height > 0 else {
|
|
1640
|
+
return frame
|
|
1641
|
+
}
|
|
1642
|
+
let keyboardCoverage = intersection.width / max(frame.width, 1)
|
|
1643
|
+
guard keyboardCoverage >= 0.5 else {
|
|
1644
|
+
return frame
|
|
1645
|
+
}
|
|
1646
|
+
let safeHeight = keyboardFrame.minY - frame.minY
|
|
1647
|
+
guard safeHeight >= frame.height * 0.25 else {
|
|
1648
|
+
return frame
|
|
1649
|
+
}
|
|
1650
|
+
return CGRect(x: frame.minX, y: frame.minY, width: frame.width, height: safeHeight)
|
|
1651
|
+
#else
|
|
1652
|
+
return frame
|
|
1653
|
+
#endif
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
private func visibleKeyboardFrame(app: XCUIApplication) -> CGRect? {
|
|
1657
|
+
#if os(iOS)
|
|
1658
|
+
var frame: CGRect?
|
|
1659
|
+
let exceptionMessage = RunnerObjCExceptionCatcher.catchException({
|
|
1660
|
+
let keyboard = app.keyboards.firstMatch
|
|
1661
|
+
guard keyboard.exists else { return }
|
|
1662
|
+
let keyboardFrame = keyboard.frame
|
|
1663
|
+
guard !keyboardFrame.isEmpty else { return }
|
|
1664
|
+
frame = keyboardFrame
|
|
1665
|
+
})
|
|
1666
|
+
if let exceptionMessage {
|
|
1667
|
+
NSLog(
|
|
1668
|
+
"AGENT_DEVICE_RUNNER_KEYBOARD_FRAME_IGNORED_EXCEPTION=%@",
|
|
1669
|
+
exceptionMessage
|
|
1670
|
+
)
|
|
1671
|
+
return nil
|
|
1672
|
+
}
|
|
1673
|
+
return frame
|
|
1674
|
+
#else
|
|
1675
|
+
return nil
|
|
1676
|
+
#endif
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
func runSeries(count: Int, pauseMs: Double, operation: (Int) -> Void) {
|
|
1680
|
+
let total = max(count, 1)
|
|
1681
|
+
let pause = max(pauseMs, 0)
|
|
1682
|
+
for idx in 0..<total {
|
|
1683
|
+
operation(idx)
|
|
1684
|
+
if idx < total - 1 && pause > 0 {
|
|
1685
|
+
sleepFor(pause / 1000.0)
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
func swipe(app: XCUIApplication, direction: String) -> DragVisualizationFrame? {
|
|
1691
|
+
if performTvRemoteSwipeIfAvailable(direction: direction) {
|
|
1692
|
+
let frame = resolvedTouchReferenceFrame(app: app, appFrame: app.frame)
|
|
1693
|
+
let midX = frame.midX
|
|
1694
|
+
let midY = frame.midY
|
|
1695
|
+
return DragVisualizationFrame(
|
|
1696
|
+
x: midX,
|
|
1697
|
+
y: midY,
|
|
1698
|
+
x2: midX,
|
|
1699
|
+
y2: midY,
|
|
1700
|
+
referenceWidth: frame.width,
|
|
1701
|
+
referenceHeight: frame.height
|
|
1702
|
+
)
|
|
1703
|
+
}
|
|
1704
|
+
return nil
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
private func performTvRemoteSwipeIfAvailable(direction: String) -> Bool {
|
|
1708
|
+
switch direction {
|
|
1709
|
+
case "up":
|
|
1710
|
+
return pressTvRemote(.up)
|
|
1711
|
+
case "down":
|
|
1712
|
+
return pressTvRemote(.down)
|
|
1713
|
+
case "left":
|
|
1714
|
+
return pressTvRemote(.left)
|
|
1715
|
+
case "right":
|
|
1716
|
+
return pressTvRemote(.right)
|
|
1717
|
+
default:
|
|
1718
|
+
return false
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
func pinch(app: XCUIApplication, scale: Double, x: Double?, y: Double?) -> RunnerInteractionOutcome {
|
|
1723
|
+
return performCoordinatePinch(app: app, scale: scale, x: x, y: y)
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
private func performCoordinatePinch(app: XCUIApplication, scale: Double, x: Double?, y: Double?) -> RunnerInteractionOutcome {
|
|
1727
|
+
#if os(tvOS)
|
|
1728
|
+
return .unsupported("pinch is not supported on tvOS")
|
|
1729
|
+
#else
|
|
1730
|
+
let target = app.windows.firstMatch.exists ? app.windows.firstMatch : app
|
|
1731
|
+
|
|
1732
|
+
// Use double-tap + drag gesture for reliable map zoom
|
|
1733
|
+
// Zoom in (scale > 1): tap then drag UP
|
|
1734
|
+
// Zoom out (scale < 1): tap then drag DOWN
|
|
1735
|
+
|
|
1736
|
+
// Determine center point (use provided x/y or screen center)
|
|
1737
|
+
let centerX = x.map { $0 / target.frame.width } ?? 0.5
|
|
1738
|
+
let centerY = y.map { $0 / target.frame.height } ?? 0.5
|
|
1739
|
+
let center = target.coordinate(withNormalizedOffset: CGVector(dx: centerX, dy: centerY))
|
|
1740
|
+
|
|
1741
|
+
// Calculate drag distance based on scale (clamped to reasonable range)
|
|
1742
|
+
// Larger scale = more drag distance
|
|
1743
|
+
let dragAmount: CGFloat
|
|
1744
|
+
if scale > 1.0 {
|
|
1745
|
+
// Zoom in: drag up (negative Y direction in normalized coords)
|
|
1746
|
+
dragAmount = min(0.4, CGFloat(scale - 1.0) * 0.2)
|
|
1747
|
+
} else {
|
|
1748
|
+
// Zoom out: drag down (positive Y direction)
|
|
1749
|
+
dragAmount = min(0.4, CGFloat(1.0 - scale) * 0.4)
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
let endY = scale > 1.0 ? (centerY - Double(dragAmount)) : (centerY + Double(dragAmount))
|
|
1753
|
+
let endPoint = target.coordinate(withNormalizedOffset: CGVector(dx: centerX, dy: max(0.1, min(0.9, endY))))
|
|
1754
|
+
|
|
1755
|
+
// Tap first (first tap of double-tap)
|
|
1756
|
+
center.tap()
|
|
1757
|
+
|
|
1758
|
+
// Immediately press and drag (second tap + drag)
|
|
1759
|
+
center.press(forDuration: 0.05, thenDragTo: endPoint)
|
|
1760
|
+
return .performed
|
|
1761
|
+
#endif
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
private func interactionRoot(app: XCUIApplication) -> XCUIElement {
|
|
1765
|
+
let windows = app.windows.allElementsBoundByIndex
|
|
1766
|
+
if let window = windows.first(where: { $0.exists && !$0.frame.isEmpty }) {
|
|
1767
|
+
return window
|
|
1768
|
+
}
|
|
1769
|
+
return app
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
private func performCoordinateTap(app: XCUIApplication, x: Double, y: Double) -> RunnerInteractionOutcome {
|
|
1773
|
+
#if os(tvOS)
|
|
1774
|
+
return .unsupported("coordinate tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element")
|
|
1775
|
+
#else
|
|
1776
|
+
interactionCoordinate(app: app, x: x, y: y).tap()
|
|
1777
|
+
return .performed
|
|
1778
|
+
#endif
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
private func performCoordinateDoubleTap(app: XCUIApplication, x: Double, y: Double) -> RunnerInteractionOutcome {
|
|
1782
|
+
#if os(tvOS)
|
|
1783
|
+
return .unsupported("coordinate double tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element")
|
|
1784
|
+
#else
|
|
1785
|
+
interactionCoordinate(app: app, x: x, y: y).doubleTap()
|
|
1786
|
+
return .performed
|
|
1787
|
+
#endif
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
private func performCoordinateLongPress(app: XCUIApplication, x: Double, y: Double, duration: TimeInterval) -> RunnerInteractionOutcome {
|
|
1791
|
+
#if os(tvOS)
|
|
1792
|
+
return .unsupported("coordinate long press is not supported on tvOS; move focus with swipe or scroll, then long-select the focused element")
|
|
1793
|
+
#else
|
|
1794
|
+
interactionCoordinate(app: app, x: x, y: y).press(forDuration: duration)
|
|
1795
|
+
return .performed
|
|
1796
|
+
#endif
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
private func performCoordinateDrag(
|
|
1800
|
+
app: XCUIApplication,
|
|
1801
|
+
x: Double,
|
|
1802
|
+
y: Double,
|
|
1803
|
+
x2: Double,
|
|
1804
|
+
y2: Double,
|
|
1805
|
+
holdDuration: TimeInterval,
|
|
1806
|
+
moveDuration: TimeInterval? = nil
|
|
1807
|
+
) -> RunnerInteractionOutcome {
|
|
1808
|
+
#if os(tvOS)
|
|
1809
|
+
return .unsupported("coordinate drag is not supported on tvOS")
|
|
1810
|
+
#else
|
|
1811
|
+
let start = interactionCoordinate(app: app, x: x, y: y)
|
|
1812
|
+
let end = interactionCoordinate(app: app, x: x2, y: y2)
|
|
1813
|
+
if let moveDuration, moveDuration > 0 {
|
|
1814
|
+
// XCUITest exposes drag speed as velocity, so derive it from the
|
|
1815
|
+
// requested movement time. The trailing hold lets the drop target
|
|
1816
|
+
// register the finger before release — releasing mid-motion reads
|
|
1817
|
+
// as a fling to reorder UIs.
|
|
1818
|
+
let distance = (CGFloat(x2 - x) * CGFloat(x2 - x) + CGFloat(y2 - y) * CGFloat(y2 - y)).squareRoot()
|
|
1819
|
+
let velocity = XCUIGestureVelocity(max(distance / CGFloat(moveDuration), 1))
|
|
1820
|
+
start.press(
|
|
1821
|
+
forDuration: holdDuration,
|
|
1822
|
+
thenDragTo: end,
|
|
1823
|
+
withVelocity: velocity,
|
|
1824
|
+
thenHoldForDuration: 0.25
|
|
1825
|
+
)
|
|
1826
|
+
} else {
|
|
1827
|
+
start.press(forDuration: holdDuration, thenDragTo: end)
|
|
1828
|
+
}
|
|
1829
|
+
return .performed
|
|
1830
|
+
#endif
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
#if !os(tvOS)
|
|
1834
|
+
private func interactionCoordinate(app: XCUIApplication, x: Double, y: Double) -> XCUICoordinate {
|
|
1835
|
+
let root = interactionRoot(app: app)
|
|
1836
|
+
let origin = root.coordinate(withNormalizedOffset: CGVector(dx: 0, dy: 0))
|
|
1837
|
+
let rootFrame = root.frame
|
|
1838
|
+
let offsetX = x - Double(rootFrame.origin.x)
|
|
1839
|
+
let offsetY = y - Double(rootFrame.origin.y)
|
|
1840
|
+
return origin.withOffset(CGVector(dx: offsetX, dy: offsetY))
|
|
1841
|
+
}
|
|
1842
|
+
#endif
|
|
1843
|
+
|
|
1844
|
+
private func tapElementCenter(app: XCUIApplication, element: XCUIElement) {
|
|
1845
|
+
let frame = element.frame
|
|
1846
|
+
if !frame.isEmpty {
|
|
1847
|
+
_ = tapAt(app: app, x: frame.midX, y: frame.midY)
|
|
1848
|
+
return
|
|
1849
|
+
}
|
|
1850
|
+
#if !os(tvOS)
|
|
1851
|
+
element.tap()
|
|
1852
|
+
#endif
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
private func macOSNavigationBackElement(app: XCUIApplication) -> XCUIElement? {
|
|
1856
|
+
let predicate = NSPredicate(
|
|
1857
|
+
format: "identifier == %@ OR label == %@",
|
|
1858
|
+
"go back",
|
|
1859
|
+
"Back"
|
|
1860
|
+
)
|
|
1861
|
+
let element = app.descendants(matching: .any).matching(predicate).firstMatch
|
|
1862
|
+
return element.exists ? element : nil
|
|
1863
|
+
}
|
|
1864
|
+
}
|