From aad6c161a09d658f30c7170deb4a61e8916a4a4c Mon Sep 17 00:00:00 2001 From: Andreas Bauer Date: Mon, 19 Aug 2024 20:22:05 +0200 Subject: [PATCH] Use simple textField selection by default; provide more customization options (#25) # Use simple textField selection by default; provide more customization options ## :recycle: Current situation & Problem Text entry in UI tests is still not trivial. This package has provided some great utilities for text entry. However, sometimes there needs to be more customization with text entry. Further, the "tap from the far right" for textfield selection is not always ideal or not always needed. Refer to the release notes for a list of changes. ## :gear: Release Notes * The new default for textField selection is now simply tapping the text field. Tapping from the right is now an opt-in option. * All customization points are now part of the `TextInputOptions` that are passed to `delete` and `enter` methods. * `checkIfTextWasEnteredCorrectly` was replaced by the `skipTextInputValidation` option. This option stays enabled by default. * `dismissKeyboard` was replaced by the `disableKeyboardDismiss` option. Keyboard is no longer dismissed before text field selection. Please do that manually. * Show the test title in the navigation bar for `TestAppTests`. * The deprecated `disablePasswordAutofill()` method was removed. ## :books: Documentation Documentation was updated. ## :white_check_mark: Testing Tests were slightly updated. ### Code of Conduct & Contributing Guidelines By submitting creating this pull request, you agree to follow our [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md): - [x] I agree to follow the [Code of Conduct](https://github.com/StanfordBDHG/.github/blob/main/CODE_OF_CONDUCT.md) and [Contributing Guidelines](https://github.com/StanfordBDHG/.github/blob/main/CONTRIBUTING.md). --- Sources/XCTestApp/TestAppTestsView.swift | 7 +- Sources/XCTestApp/TestAppView.swift | 2 +- .../XCTestCase+DisablePasswordAutofill.swift | 97 -------- .../XCTestExtensions.docc/XCTestExtensions.md | 7 +- .../XCUIElement+TextEntry.swift | 222 +++++++++--------- .../TestAppUITests/XCTestAppTests.swift | 11 +- .../XCTestExtensionsTests.swift | 42 ++-- 7 files changed, 154 insertions(+), 234 deletions(-) delete mode 100644 Sources/XCTestExtensions/XCTestCase+DisablePasswordAutofill.swift diff --git a/Sources/XCTestApp/TestAppTestsView.swift b/Sources/XCTestApp/TestAppTestsView.swift index 3537971..432eff7 100644 --- a/Sources/XCTestApp/TestAppTestsView.swift +++ b/Sources/XCTestApp/TestAppTestsView.swift @@ -24,7 +24,12 @@ public struct TestAppTestsView: View { NavigationLink(test.rawValue, value: test) } .navigationDestination(for: Tests.self) { test in - test.view(withNavigationPath: $path) + test + .view(withNavigationPath: $path) + .navigationTitle(test.rawValue) +#if !os(macOS) && !os(tvOS) + .navigationBarTitleDisplayMode(.inline) +#endif } .navigationTitle(String(describing: Tests.self)) .toolbar { diff --git a/Sources/XCTestApp/TestAppView.swift b/Sources/XCTestApp/TestAppView.swift index 27dfd5d..3637626 100644 --- a/Sources/XCTestApp/TestAppView.swift +++ b/Sources/XCTestApp/TestAppView.swift @@ -14,7 +14,7 @@ public struct TestAppView: View { @State private var testState = "Running ..." private let testCase: any TestAppTestCase - + public var body: some View { Text(testState) .task { diff --git a/Sources/XCTestExtensions/XCTestCase+DisablePasswordAutofill.swift b/Sources/XCTestExtensions/XCTestCase+DisablePasswordAutofill.swift deleted file mode 100644 index c748552..0000000 --- a/Sources/XCTestExtensions/XCTestCase+DisablePasswordAutofill.swift +++ /dev/null @@ -1,97 +0,0 @@ -// -// This source file is part of the Stanford XCTestExtensions open-source project -// -// SPDX-FileCopyrightText: 2022 Stanford University and the project authors (see CONTRIBUTORS.md) -// -// SPDX-License-Identifier: MIT -// - -import OSLog -import XCTest - - -extension XCTestCase { - /// Navigate to the iOS settings app and turn off the password autofill functionality. - /// - /// The iOS Simulator has the password autofill feature enabled by default, which makes it challenging to write automated UI tests for password fields: - /// The iOS Simulator does not properly display the password autofill UI, and the UI test can no longer enter text in text fields that contain the autogenerated password with a yellow background. - /// - /// Use this function used to to disable password autofill by navigating to the iOS settings app and turning off the password autofill functionality in the settings UI. - /// - /// > Warning: While this workaround worked well until 17.2, we experienced a crash of the passwords section in the IOS 17.2 passwords app on the iOS simulator, which no longer allows us to use this workaround. - /// We recommend using a custom setup script to skip password-related functionality in your UI tests until there is a better workaround. Please inspect the logic to setup simulators in the [xcodebuild-or-fastlane.yml](https://github.com/StanfordBDHG/.github/blob/main/.github/workflows/xcodebuild-or-fastlane.yml) workflow and be sure to `setupSimulators: true` if you use the GitHub action as a reusable workflow. - @available( - iOS, - deprecated: 17.2, - message: """ - To avoid having the password autofill interfere with your UI test, \ - avoid specifying the password text content type for simulator builds. - This method will be removed in a future version. - """ - ) - @available(watchOS, unavailable) - @available(macOS, unavailable) - @available(tvOS, unavailable) - @available(visionOS, unavailable) - @MainActor - public func disablePasswordAutofill() throws { - let settingsApp = XCUIApplication(bundleIdentifier: "com.apple.Preferences") - settingsApp.terminate() - settingsApp.launch() - - XCTAssert(settingsApp.staticTexts["PASSWORDS"].waitForExistence(timeout: 5.0)) - settingsApp.staticTexts["PASSWORDS"].tap() - - if #available(iOS 17.2, *) { - sleep(2) - - guard settingsApp.navigationBars.staticTexts["Passwords"].waitForExistence(timeout: 2.0) else { - os_log("Could not open the passwords section in the iOS 17.2 simulator due to a bug that immediately closed the passwords section.") - return - } - } else { - XCTAssert(settingsApp.navigationBars.staticTexts["Passwords"].waitForExistence(timeout: 2.0)) - } - - let springboard = XCUIApplication(bundleIdentifier: XCUIApplication.homeScreenBundle) - - sleep(1) - if springboard.secureTextFields["Passcode field"].exists { - let passcodeInput = springboard.secureTextFields["Passcode field"] - passcodeInput.tap() - - sleep(2) - - passcodeInput.typeText("1234\r") - - sleep(2) - } else if #unavailable(iOS 17.4) { - // other versions just don't need a passcode anymore - os_log("Could not enter the passcode in the device to enter the password section in the settings app.") - throw XCTestError(.failureWhileWaiting) - } - - var counter = 0 - let passwordOptionsCell = settingsApp.tables.cells["PasswordOptionsCell"] - while !passwordOptionsCell.isHittable || counter > 10 { - _ = settingsApp.tables.cells["PasswordOptionsCell"].waitForExistence(timeout: 2.0) - counter += 1 - } - - sleep(3) - - settingsApp.tables.cells["PasswordOptionsCell"].tap() - - let autoFillPasswords: String - if #available(iOS 17.0, *) { - autoFillPasswords = "AutoFill Passwords and Passkeys" - } else { - autoFillPasswords = "AutoFill Passwords" - } - - XCTAssert(settingsApp.switches[autoFillPasswords].waitForExistence(timeout: 5)) - if settingsApp.switches[autoFillPasswords].value as? String == "1" { - settingsApp.switches[autoFillPasswords].tap() - } - } -} diff --git a/Sources/XCTestExtensions/XCTestExtensions.docc/XCTestExtensions.md b/Sources/XCTestExtensions/XCTestExtensions.docc/XCTestExtensions.md index bfc9b46..015d5c1 100644 --- a/Sources/XCTestExtensions/XCTestExtensions.docc/XCTestExtensions.md +++ b/Sources/XCTestExtensions/XCTestExtensions.docc/XCTestExtensions.md @@ -51,12 +51,13 @@ The `enter(value:)` and `delete(count:)` methods provide the `checkIfTextWasEnte ### Assertions -- ``XCTAssertThrowsErrorAsync`` +- ``XCTAssertThrowsErrorAsync(_:_:file:line:_:)`` ### Text Entry -- ``XCTest/XCUIElement/enter(value:checkIfTextWasEnteredCorrectly:dismissKeyboard:)`` -- ``XCTest/XCUIElement/delete(count:checkIfTextWasDeletedCorrectly:dismissKeyboard:)`` +- ``TextInputOptions`` +- ``XCTest/XCUIElement/enter(value:options:)`` +- ``XCTest/XCUIElement/delete(count:options:)`` ### App Interaction diff --git a/Sources/XCTestExtensions/XCUIElement+TextEntry.swift b/Sources/XCTestExtensions/XCUIElement+TextEntry.swift index 1a515fd..08d2ecb 100644 --- a/Sources/XCTestExtensions/XCUIElement+TextEntry.swift +++ b/Sources/XCTestExtensions/XCUIElement+TextEntry.swift @@ -16,6 +16,51 @@ import XCTest @MainActor var simulateFlakySimulatorTextEntry = false +/// Modify the behavior of text entry and text deletion. +public struct TextInputOptions: OptionSet, Sendable { + /// Disable the automatic dismiss of the keyboard at the end of text entry. + /// + /// By default the keyboard will be automatically dismissed after text entry. If you want to disable this behavior, e.g., entering input into the text field + /// in multiple steps, you can disable that behavior by specifying this option. + public static let disableKeyboardDismiss = TextInputOptions(rawValue: 1 << 0) + // on visionOS the keyboard app running state is always foreground and the keyboards query doesn't do anything. + + /// Place the cursor on the rightmost position in the text field. + /// + /// By default, the the textfield is selected by performing standard [`tap()`](https://developer.apple.com/documentation/xctest/xcuielement/1618666-tap) + /// on the text field which puts the cursor in the middle of the text field. + /// This might be an issue when working with text fields that have existing input, as the cursor will be place in the middle of the text. + /// When using this option, the text field is selected by continuously tapping from the rightmost edge of the text field until the keyboard exists. + /// + /// - Note: This method doesn't work on visionOS or macOS. For macOS you can manually call [`coordinate(withNormalizedOffset:)`](https://developer.apple.com/documentation/xctest/xcuielement/1500960-coordinate) + /// to tap on a fitting coordinate and then use the ``skipTextFieldSelection`` option. For visionOS `coordinate(withNormalizedOffset:)` doesn't work. Aim to not + /// test with longer text entry on visionOS. + @available(visionOS, unavailable, message: "Tapping from the far right is unsupported on visionOS") + @available(macOS, unavailable, message: "Tapping from the far right is unsupported on macOS") + public static let tapFromRight = TextInputOptions(rawValue: 1 << 1) + /// Do not verify if text was enter correctly. + /// + /// Unfortunately, the iOS simulator sometimes has flaky behavior when entering text in a simulator with low computation resources. + /// By default, it will be verified if the characteristic were enter/deleted correctly. + /// Use this option to disable this behavior. + public static let skipTextInputValidation = TextInputOptions(rawValue: 1 << 2) + /// Do not automatically select the text field before typing. + /// + /// By default the text field is automatically selected before typing text. Use this option to disable this behavior, if you + /// know the keyboard to already be present. + public static let skipTextFieldSelection = TextInputOptions(rawValue: 1 << 3) + + /// Same option as ``tapFromRight``, but accessible for us internally without compiler conditions. + fileprivate static let _tapFromRight = TextInputOptions(rawValue: 1 << 1) + + public let rawValue: UInt16 + + public init(rawValue: UInt16) { + self.rawValue = rawValue + } +} + + extension XCUIElement { /// Get the current value so we can assert if the text entry was correct. private var currentValue: String { @@ -29,64 +74,51 @@ extension XCUIElement { /// Delete a fixed number of characters in a text field or secure text field. - /// - Parameter count: The number of characters that should be deleted. - /// - Parameter checkIfTextWasDeletedCorrectly: Check if the text was deleted correctly. - /// - Parameter dismissKeyboard: Press the return key after deleting the text. - /// - Throws: Throws an `XCTestError` of the number of characters could not be deleted. - /// - /// Unfortunately, the iOS simulator sometimes has flaky behavior when entering text in a simulator with low computation resources. - /// The method provides the `checkIfTextWasDeletedCorrectly` parameter that is set to true by default to check if the characters were deleted correctly. - /// If your text entry does fail to do so, e.g., a deletion in a secure text field, set the `checkIfTextWasDeletedCorrectly` parameter to `false`. - public func delete( - count: Int, - checkIfTextWasDeletedCorrectly: Bool = true, - dismissKeyboard: Bool = true - ) throws { - // Select the textfield - selectField(dismissKeyboard: dismissKeyboard) - - try performDelete(count: count, checkIfTextWasDeletedCorrectly: checkIfTextWasDeletedCorrectly, recursiveDepth: 0) - - if dismissKeyboard { + /// + /// ```swift + /// try app.textFields["enter first name"].delete(4) + /// ``` + /// + /// - Parameters: + /// - count: The number of characters that should be deleted. + /// - options: Control additional behavior how text deletion should be performed. + /// - Throws: Throws an `XCTestError`, if the number of characters could not be deleted. + public func delete(count: Int, options: TextInputOptions = []) throws { + if !options.contains(.skipTextFieldSelection) { + selectField(options: options) + } + + try performDelete(count: count, options: options, recursiveDepth: 0) + + if !options.contains(.disableKeyboardDismiss) { XCUIApplication().dismissKeyboard() } } /// Type a text in a text field or secure text field. - /// - Parameter newValue: The text that should be typed. - /// - Parameter checkIfTextWasEnteredCorrectly: Check if the text was entered correctly. - /// - Parameter dismissKeyboard: Press the return key after entering the text. - /// - Throws: Throws an `XCTestError` of the text could not be entered in the text field. /// - /// Unfortunately, the iOS simulator sometimes has flaky behavior when entering text in a simulator with low computation resources. - /// The method provides the `checkIfTextWasEnteredCorrectly` parameter that is set to true by default to check if the characters were entered correctly. - /// If your text entry does fail to do so, e.g., an entry in a secure text field, set the `checkIfTextWasEnteredCorrectly` parameter to `false`. - public func enter( - value newValue: String, - checkIfTextWasEnteredCorrectly: Bool = true, - dismissKeyboard: Bool = true - ) throws { - // Select the textfield - selectField(dismissKeyboard: dismissKeyboard) - - try performEnter( - value: newValue, - checkIfTextWasEnteredCorrectly: checkIfTextWasEnteredCorrectly, - dismissKeyboard: dismissKeyboard, - recursiveDepth: 0 - ) - - if dismissKeyboard { + /// ```swift + /// try app.textFields["enter first name"].enter("Hello World") + /// ``` + /// + /// - Parameters: + /// - newValue: The text that should be typed. + /// - options: Control additional behavior how text entry should be performed. + /// - Throws: Throws an `XCTestError`, if the text could not be entered in the text field. + public func enter(value newValue: String, options: TextInputOptions = []) throws { + if !options.contains(.skipTextFieldSelection) { + selectField(options: options) + } + + try performEnter(value: newValue, options: options, recursiveDepth: 0) + + if !options.contains(.disableKeyboardDismiss) { XCUIApplication().dismissKeyboard() } } - private func performDelete( - count: Int, - checkIfTextWasDeletedCorrectly: Bool, - recursiveDepth: Int - ) throws { + private func performDelete(count: Int, options: TextInputOptions, recursiveDepth: Int) throws { guard recursiveDepth <= 2 else { os_log("Could not successfully delete \(count) characters in the textfield \(self.debugDescription)") throw XCTestError(.failureWhileWaiting) @@ -103,25 +135,17 @@ extension XCUIElement { } // Check of the text was deleted correctly: - if checkIfTextWasDeletedCorrectly { + if !options.contains(.skipTextInputValidation) { let countAfterDeletion = currentValue.count if max(currentValueCount - count, 0) < countAfterDeletion { - try performDelete( - count: countAfterDeletion - ( currentValueCount - count), - checkIfTextWasDeletedCorrectly: true, - recursiveDepth: recursiveDepth + 1 - ) + tap() // cursor might not be placed at the rightmost position of the text field + try performDelete(count: countAfterDeletion - ( currentValueCount - count), options: options, recursiveDepth: recursiveDepth + 1) } } } - private func performEnter( - value textToEnter: String, - checkIfTextWasEnteredCorrectly: Bool, - dismissKeyboard: Bool, - recursiveDepth: Int - ) throws { + private func performEnter(value textToEnter: String, options: TextInputOptions, recursiveDepth: Int) throws { guard recursiveDepth <= 2 else { os_log("Could not successfully verify entering \"\(textToEnter)\" in the textfield \(self.debugDescription)") throw XCTestError(.failureWhileWaiting) @@ -138,22 +162,16 @@ extension XCUIElement { } // Check if the text was entered correctly - if checkIfTextWasEnteredCorrectly { + if !options.contains(.skipTextInputValidation) { let valueAfterTextEntry = currentValue if self.elementType == .secureTextField { if previousValue.isEmpty && textToEnter.count != valueAfterTextEntry.count { - // We delete the text twice to ensure that we have an empty text field even if the textfield might go beyond the current scope - try performDelete(count: valueAfterTextEntry.count, checkIfTextWasDeletedCorrectly: false, recursiveDepth: 0) - XCUIApplication().dismissKeyboard() - try delete(count: valueAfterTextEntry.count, checkIfTextWasDeletedCorrectly: false, dismissKeyboard: false) - - try performEnter( - value: previousValue + textToEnter, - checkIfTextWasEnteredCorrectly: true, - dismissKeyboard: dismissKeyboard, - recursiveDepth: recursiveDepth + 1 - ) + try performDelete(count: valueAfterTextEntry.count, options: [], recursiveDepth: 0) + tap() // cursor might not be placed at the rightmost position of the text field + try performDelete(count: valueAfterTextEntry.count, options: [], recursiveDepth: 0) + + try performEnter(value: previousValue + textToEnter, options: options, recursiveDepth: recursiveDepth + 1) } else if previousValue.count + textToEnter.count != valueAfterTextEntry.count { os_log( """ @@ -165,17 +183,11 @@ extension XCUIElement { } } else { if previousValue + textToEnter != valueAfterTextEntry { - // We delete the text twice to ensure that we have an empty text field even if the textfield might go beyond the current scope - try performDelete(count: valueAfterTextEntry.count, checkIfTextWasDeletedCorrectly: false, recursiveDepth: 0) - XCUIApplication().dismissKeyboard() - try delete(count: valueAfterTextEntry.count, checkIfTextWasDeletedCorrectly: false, dismissKeyboard: false) - - try performEnter( - value: previousValue + textToEnter, - checkIfTextWasEnteredCorrectly: true, - dismissKeyboard: dismissKeyboard, - recursiveDepth: recursiveDepth + 1 - ) + try performDelete(count: valueAfterTextEntry.count, options: [], recursiveDepth: 0) + tap() // cursor might not be placed at the rightmost position of the text field + try performDelete(count: valueAfterTextEntry.count, options: [], recursiveDepth: 0) + + try performEnter(value: previousValue + textToEnter, options: options, recursiveDepth: recursiveDepth + 1) } } } @@ -187,34 +199,30 @@ extension XCUIElement { /// - Note: This will not necessarily bring up the keyboard in the simulator. Don't expect buttons to show there. /// If the user interacted with the Simulator (e.g. Mouse clicks) the keyboard won't show as the simulator expects input via the Mac Keyboard. /// This is controlled via I/O -> Keyboard -> Connect Hardware Keyboard / Toggle Software Keyboard - func selectField(dismissKeyboard: Bool) { + func selectField(options: TextInputOptions = []) { let app = XCUIApplication() - // With visionOS we can't detect if the keyboard is currently shown. Interacting with a keyboard that is not shown will fail though. - // So we have to build around the assumption that the keyboard is not shown when we select a text field. - #if !os(visionOS) - // Press the return button if the keyboard is currently active. - if dismissKeyboard { - app.dismissKeyboard() + if options.contains(._tapFromRight) { + // Select the text field, see https://stackoverflow.com/questions/38523125/place-cursor-at-the-end-of-uitextview-under-uitest + + XCTAssertFalse(app.keyboards.firstMatch.exists, "Keyboard must not exist when selecting text field from the right.") + + var offset = 0.99 + repeat { + coordinate(withNormalizedOffset: CGVector(dx: offset, dy: 0.5)).tap() + offset -= 0.05 + } while offset >= 0 && !app.keyboards.firstMatch.waitForExistence(timeout: 2.0) + + XCTAssertTrue(app.keyboards.firstMatch.waitForExistence(timeout: 2.0), "Keyboard does not exist.") + } else { + tap() + + #if os(visionOS) + XCTAssertTrue(app.visionOSKeyboard.wait(for: .runningForeground, timeout: 2.0)) + #elseif !os(macOS) && !targetEnvironment(macCatalyst) + XCTAssertTrue(app.keyboards.firstMatch.waitForExistence(timeout: 2.0)) + #endif } - #endif - - #if os(visionOS) - tap() - _ = app.visionOSKeyboard.waitForExistence(timeout: 2.0) // this will always succeed - #elseif os(macOS) || targetEnvironment(macCatalyst) - // this should hit the keyboard most likely. The `.keyboard` query doesn't exist on macOS. - coordinate(withNormalizedOffset: CGVector(dx: 0.95, dy: 0.5)).tap() - #else - let keyboard = app.keyboards.firstMatch - - // Select the text field, see https://stackoverflow.com/questions/38523125/place-cursor-at-the-end-of-uitextview-under-uitest - var offset = 0.99 - repeat { - coordinate(withNormalizedOffset: CGVector(dx: offset, dy: 0.5)).tap() - offset -= 0.05 - } while !keyboard.waitForExistence(timeout: 2.0) && offset > 0 - #endif // With latest simulator releases it seems like the "swift to type" tutorial isn't popping up anymore. // For more information see https://developer.apple.com/forums/thread/650826. diff --git a/Tests/UITests/TestAppUITests/XCTestAppTests.swift b/Tests/UITests/TestAppUITests/XCTestAppTests.swift index 5e16b4c..bc3753b 100644 --- a/Tests/UITests/TestAppUITests/XCTestAppTests.swift +++ b/Tests/UITests/TestAppUITests/XCTestAppTests.swift @@ -13,10 +13,13 @@ class XCTestAppTests: XCTestCase { func testTestAppTestCaseTest() throws { let app = XCUIApplication() app.launch() - - XCTAssert(app.buttons["XCTestApp"].waitForExistence(timeout: 5.0)) + + XCTAssert(app.wait(for: .runningForeground, timeout: 2.0)) + + XCTAssert(app.buttons["XCTestApp"].waitForExistence(timeout: 2.0)) app.buttons["XCTestApp"].tap() - - XCTAssert(app.staticTexts["Passed"].waitForExistence(timeout: 5)) + + XCTAssert(app.navigationBars.staticTexts["XCTestApp"].waitForExistence(timeout: 2.0)) + XCTAssert(app.staticTexts["Passed"].waitForExistence(timeout: 5.0)) } } diff --git a/Tests/UITests/TestAppUITests/XCTestExtensionsTests.swift b/Tests/UITests/TestAppUITests/XCTestExtensionsTests.swift index 47e5dbb..4aedb8e 100644 --- a/Tests/UITests/TestAppUITests/XCTestExtensionsTests.swift +++ b/Tests/UITests/TestAppUITests/XCTestExtensionsTests.swift @@ -25,14 +25,13 @@ class XCTestExtensionsTests: XCTestCase { #endif let app = XCUIApplication() - app.deleteAndLaunch(withSpringboardAppName: "TestApp") XCTAssert(app.buttons["XCTestExtensions"].waitForExistence(timeout: 5.0)) app.buttons["XCTestExtensions"].tap() XCTAssert(app.staticTexts["No text set ..."].waitForExistence(timeout: 5)) - XCTAssert(app.staticTexts["No secure text set ..."].waitForExistence(timeout: 5)) + XCTAssert(app.staticTexts["No secure text set ..."].exists) } @available(macOS, unavailable) @@ -56,14 +55,7 @@ class XCTestExtensionsTests: XCTestCase { app.buttons["XCTestExtensions"].tap() XCTAssert(app.staticTexts["No text set ..."].waitForExistence(timeout: 5)) - XCTAssert(app.staticTexts["No secure text set ..."].waitForExistence(timeout: 5)) - } - - @MainActor - func testDisablePasswordAutofill() throws { - #if os(iOS) - try disablePasswordAutofill() - #endif + XCTAssert(app.staticTexts["No secure text set ..."].exists) } func testTextEntry() throws { @@ -100,10 +92,17 @@ class XCTestExtensionsTests: XCTestCase { let textField = app.textFields["TextField"] let message = "This is a very long text and some more" - try textField.enter(value: message, checkIfTextWasEnteredCorrectly: false) + try textField.enter(value: message, options: .skipTextInputValidation) XCTAssert(app.staticTexts[message].waitForExistence(timeout: 5)) - try textField.enter(value: " ...", checkIfTextWasEnteredCorrectly: false) +#if os(visionOS) + try textField.enter(value: " ...", options: [.skipTextInputValidation]) +#else + try textField.enter(value: " ...", options: [.skipTextInputValidation, .tapFromRight]) +#endif XCTAssert(app.staticTexts["\(message) ..."].waitForExistence(timeout: 5)) + + // Test text field deletion with longer text input + try textField.delete(count: message.count + 4) } func testKeyboardBehavior() throws { @@ -115,21 +114,22 @@ class XCTestExtensionsTests: XCTestCase { app.buttons["DismissKeyboard"].tap() let checkLabel = { (label: String) in XCTAssert(app.textFields[label].exists) - app.textFields[label].selectField(dismissKeyboard: false) + app.textFields[label].selectField() #if os(visionOS) - XCTAssert(app.visionOSKeyboard.exists) + XCTAssert(app.visionOSKeyboard.wait(for: .runningForeground, timeout: 2.0)) #elseif !os(macOS) - print(app.textFields[label].isSelected) - XCTAssertTrue(app.keyboards.firstMatch.exists) + XCTAssertTrue(app.keyboards.firstMatch.waitForExistence(timeout: 2.0)) #endif - sleep(1) app.dismissKeyboard() - sleep(1) #if !os(visionOS) && !os(macOS) - // we currently can't check if the keyboard app is not launched +#if compiler(<6) + sleep(1) XCTAssertFalse(app.keyboards.firstMatch.exists) -#endif +#else // compiler(<6) + XCTAssertTrue(app.keyboards.firstMatch.waitForNonExistence(timeout: 2.0)) +#endif // compiler(<6) +#endif // !os(visionOS) && !os(macOS) } // this way we know exactly which button failed by line numbers. @@ -173,6 +173,6 @@ extension XCUIApplication { try secureTextField.delete(count: 42) XCTAssert(staticTexts["No secure text set ..."].waitForExistence(timeout: 5)) - XCTAssertFalse(staticTexts["Button was pressed!"].waitForExistence(timeout: 5.0)) + XCTAssertFalse(staticTexts["Button was pressed!"].exists) } }