diff --git a/Package.swift b/Package.swift index 22e4b0d..af4046e 100644 --- a/Package.swift +++ b/Package.swift @@ -6,18 +6,17 @@ import PackageDescription let package = Package( name: "XCUITestHelper", platforms: [ - .iOS(.v14), - .macOS(.v11) + .iOS(.v16), + .macOS(.v13), + .watchOS(.v9), + .tvOS(.v16) ], products: [ - // Products define the executables and libraries a package produces, making them visible to other packages. .library( name: "XCUITestHelper", targets: ["XCUITestHelper"]) ], targets: [ - // Targets are the basic building blocks of a package, defining a module or a test suite. - // Targets can depend on other targets in this package and products from dependencies. .target( name: "XCUITestHelper"), .testTarget( diff --git a/README.md b/README.md index 398d4ea..98d8fb7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # XCUITestHelper -XCUITestHelper is a package what contains extensions for XCUITest. +XCUITestHelper helps you writing UI tests within SwiftUI. It provides a set of useful extensions on [XCUIApplication](Sources/XCUITestHelper/XCUIApplication.swift), [XCUIElement](Sources/XCUITestHelper/XCUIElement.swift) and [XCUIElementQuery](Sources/XCUITestHelper/XCUIElementQuery.swift) to make your tests more readable and easier to maintain. [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F0xWDG%2FXCUITestHelper%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/0xWDG/XCUITestHelper) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2F0xWDG%2FXCUITestHelper%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/0xWDG/XCUITestHelper) @@ -29,9 +29,10 @@ targets: [ 1. In Xcode, open your project and navigate to **File** → **Swift Packages** → **Add Package Dependency...** 2. Paste the repository URL (`https://github.com/0xWDG/XCUITestHelper`) and click **Next**. -3. Click **Finish**. +3. Make sure you add it to the **UITest target**! +4. Click **Finish**. -## Usage +## Usage / Examples ```swift import XCTest @@ -45,21 +46,31 @@ final class MyAppUITests: XCTestCase { func testExample() throws { // UI tests must launch the application that they test. let app = XCUIApplication() - app.launchArguments += ["-AppleLanguages", "(en-US)"] - app.launchArguments += ["-AppleLocale", "\"en-US\""] + + // * Set the app language to English. + app.setLanguage(to: .english) + // Do this before launching the app. app.launch() - // Wait for 1 second to continue + // * `Wait` for 1 second to continue app.wait(for: 1) - // Go back to previous screen (navigation) + // * Tap a `random` cell in a collection view. + // Random works with any kind of element, not just buttons. + app.collectionViews.buttons.random.tap() + + // * Go back to previous screen (NavigationView) app.navigateBack() - // Tap on the last button + // * Tap on the last button app.buttons.lastMatch.tap() - // Tap on the second button + // * Tap on the second button app.buttons[1].tap() + + // * Type something, and then clear it. + let textfield = app.searchFields.firstMatch + app.type(in: textfield, text: "a", action: .clear) } } ``` diff --git a/Sources/XCUITestHelper/XCUIApplication.swift b/Sources/XCUITestHelper/XCUIApplication.swift index 5df231c..3df1cf5 100644 --- a/Sources/XCUITestHelper/XCUIApplication.swift +++ b/Sources/XCUITestHelper/XCUIApplication.swift @@ -11,6 +11,7 @@ #if canImport(XCTest) import XCTest +import Foundation extension XCUIApplication { /// Navigate back @@ -18,23 +19,12 @@ extension XCUIApplication { self.navigationBars.buttons.element(boundBy: 0).tap() } - /// Wait for a duration - /// - /// - Parameter duration: Duration to wait - public func wait(for duration: TimeInterval) { - let testCase = XCTestCase() - let waitExpectation = testCase.expectation( - description: "Waiting" - ) - - let when = DispatchTime.now() + duration - DispatchQueue.main.asyncAfter(deadline: when) { - waitExpectation.fulfill() - } - - testCase.waitForExpectations( - timeout: duration + 0.5 - ) + /// Set language of test to + /// + /// - Parameter to: language + public func setLanguage(to language: Locale.LanguageCode) { + self.launchArguments += ["-AppleLanguages", "(\(language.identifier))"] + self.launchArguments += ["-AppleLocale", "\"\(language.identifier)\""] } } diff --git a/Sources/XCUITestHelper/XCUIElement.swift b/Sources/XCUITestHelper/XCUIElement.swift new file mode 100644 index 0000000..aac161a --- /dev/null +++ b/Sources/XCUITestHelper/XCUIElement.swift @@ -0,0 +1,84 @@ +// +// XCUIElement.swift +// XCUITestHelper +// +// Created by Wesley de Groot on 2024-08-08. +// https://wesleydegroot.nl +// +// https://github.com/0xWDG/XCUITestHelper +// MIT License +// + +#if canImport(XCTest) +import XCTest + +/// Actions that can be performed on an XCUIElement. +public enum XCUIElementAction { + /// Send (last button) + case send + + /// Clear (last button on input field) + case clear + + /// Do nothing + case none +} + +extension XCUIElement { + /// Does the element has keyboard focus? + /// + /// - Returns: boolean to indicate if the element has the current keyboard focus + public var hasKeyboardFocus: Bool { + return (self.value(forKey: "hasKeyboardFocus") as? Bool) ?? false + } + + /// Type some text and submit + /// + /// - Parameter text: Text to type. + /// - Parameter press: Should press "Continue", "Send", "Search", ... + public func type(in textField: XCUIElement, text: String, action: XCUIElementAction = .send) { + if textField.elementType == .textField || + textField.elementType == .secureTextField || + textField.elementType == .searchField { + + // Focus keyboard + textField.tap() + + // Type text + textField.typeText(text) + + switch action { + case .send: + // Send (last button) + self.keyboards.buttons.lastMatch.tap() + case .clear: + // Clear (last button on input field) + textField.buttons.lastMatch.tap() + case .none: + // Do nothing + break + } + } else { + fatalError("This element is a \(textField.elementType), expected a *Field.") + } + } + + /// Print all elements + func printAllElements() { + print(self.debugDescription) + } + + /// Wait for a duration + /// + /// - Parameter duration: Duration to wait + public func wait(for duration: TimeInterval) { + let testCase = XCTestCase() + let waitExpectation = testCase.expectation(description: "Waiting") + DispatchQueue.main.asyncAfter(deadline: .now() + duration) { + waitExpectation.fulfill() + } + testCase.waitForExpectations(timeout: duration + 0.5) + } +} + +#endif diff --git a/Sources/XCUITestHelper/XCUIElementQuery.swift b/Sources/XCUITestHelper/XCUIElementQuery.swift index 9376151..a7c2be4 100644 --- a/Sources/XCUITestHelper/XCUIElementQuery.swift +++ b/Sources/XCUITestHelper/XCUIElementQuery.swift @@ -11,6 +11,7 @@ #if canImport(XCTest) import XCTest +import Foundation extension XCUIElementQuery { /// The last element that matches the query. @@ -18,6 +19,11 @@ extension XCUIElementQuery { return element(boundBy: count - 1) } + /// An radom element that matches the query. + public var random: XCUIElement { + return element(boundBy: Int.random(in: 0 ..< count)) + } + /// Returns an element that will use the index provided /// /// - Parameters: @@ -25,6 +31,11 @@ extension XCUIElementQuery { public subscript(_ index: Int) -> XCUIElement { return element(boundBy: index) } + + /// Print all elements + func printAllElements() { + print(self.debugDescription) + } } #endif