207 lines
5.5 KiB
Swift
Executable file
207 lines
5.5 KiB
Swift
Executable file
//
|
|
// BaseUITestCase.swift
|
|
// iOS Foundations
|
|
//
|
|
// Base class for UI tests with common utilities
|
|
//
|
|
|
|
import XCTest
|
|
|
|
/// Base class for UI test cases
|
|
///
|
|
/// Provides common setup, utilities, and helpers for UI testing.
|
|
/// Extend this class for your UI test cases to get consistent behavior.
|
|
///
|
|
/// ## Usage
|
|
///
|
|
/// ```swift
|
|
/// final class OnboardingUITests: BaseUITestCase {
|
|
///
|
|
/// func testSignUpFlow() {
|
|
/// // Launch arguments are already configured
|
|
/// let signUpButton = app.buttons["SignUpButton"]
|
|
/// XCTAssertTrue(waitForElement(signUpButton))
|
|
///
|
|
/// signUpButton.tap()
|
|
///
|
|
/// let emailField = app.textFields["EmailField"]
|
|
/// emailField.tap()
|
|
/// emailField.typeText("user@example.com")
|
|
///
|
|
/// takeScreenshot(name: "SignUpForm")
|
|
/// }
|
|
/// }
|
|
/// ```
|
|
///
|
|
open class BaseUITestCase: XCTestCase {
|
|
|
|
// MARK: - Properties
|
|
|
|
/// The application under test
|
|
public var app: XCUIApplication!
|
|
|
|
/// Default timeout for element waits
|
|
public var defaultTimeout: TimeInterval = 5.0
|
|
|
|
// MARK: - Setup & Teardown
|
|
|
|
open override func setUp() {
|
|
super.setUp()
|
|
|
|
// Don't stop on failures - continue test to gather more info
|
|
continueAfterFailure = false
|
|
|
|
// Initialize app
|
|
app = XCUIApplication()
|
|
|
|
// Configure test mode
|
|
configureTestMode()
|
|
|
|
// Launch app
|
|
app.launch()
|
|
}
|
|
|
|
open override func tearDown() {
|
|
// Take screenshot on failure
|
|
if let testRun = testRun,
|
|
testRun.failureCount > 0 {
|
|
takeScreenshot(name: "FAILURE-\(name)")
|
|
}
|
|
|
|
app = nil
|
|
super.tearDown()
|
|
}
|
|
|
|
// MARK: - Configuration
|
|
|
|
/// Configure app for testing
|
|
///
|
|
/// Override this to add custom launch arguments or environment variables.
|
|
///
|
|
/// ## Example
|
|
///
|
|
/// ```swift
|
|
/// override func configureTestMode() {
|
|
/// super.configureTestMode()
|
|
/// app.launchArguments += ["--skip-onboarding"]
|
|
/// app.launchEnvironment["API_URL"] = "https://staging.api.com"
|
|
/// }
|
|
/// ```
|
|
///
|
|
open func configureTestMode() {
|
|
app.launchArguments = ["--uitesting"]
|
|
}
|
|
|
|
// MARK: - Element Waiting
|
|
|
|
/// Wait for an element to exist
|
|
///
|
|
/// - Parameters:
|
|
/// - element: The element to wait for
|
|
/// - timeout: Maximum time to wait (defaults to defaultTimeout)
|
|
///
|
|
/// - Returns: true if element exists within timeout, false otherwise
|
|
///
|
|
@discardableResult
|
|
public func waitForElement(
|
|
_ element: XCUIElement,
|
|
timeout: TimeInterval? = nil
|
|
) -> Bool {
|
|
let timeoutValue = timeout ?? defaultTimeout
|
|
return element.waitForExistence(timeout: timeoutValue)
|
|
}
|
|
|
|
/// Wait for an element to disappear
|
|
///
|
|
/// - Parameters:
|
|
/// - element: The element to wait for
|
|
/// - timeout: Maximum time to wait
|
|
///
|
|
/// - Returns: true if element disappeared within timeout, false otherwise
|
|
///
|
|
@discardableResult
|
|
public func waitForElementToDisappear(
|
|
_ element: XCUIElement,
|
|
timeout: TimeInterval? = nil
|
|
) -> Bool {
|
|
let timeoutValue = timeout ?? defaultTimeout
|
|
let predicate = NSPredicate(format: "exists == false")
|
|
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)
|
|
|
|
let result = XCTWaiter().wait(for: [expectation], timeout: timeoutValue)
|
|
return result == .completed
|
|
}
|
|
|
|
// MARK: - Screenshots
|
|
|
|
/// Take a screenshot with a descriptive name
|
|
///
|
|
/// Screenshots are attached to the test results and can be viewed in Xcode.
|
|
///
|
|
/// - Parameter name: Descriptive name for the screenshot
|
|
///
|
|
public func takeScreenshot(name: String) {
|
|
let screenshot = XCUIScreen.main.screenshot()
|
|
let attachment = XCTAttachment(screenshot: screenshot)
|
|
attachment.name = name
|
|
attachment.lifetime = .keepAlways
|
|
add(attachment)
|
|
}
|
|
|
|
// MARK: - Common Actions
|
|
|
|
/// Tap an element and wait for it to exist first
|
|
///
|
|
/// - Parameters:
|
|
/// - element: The element to tap
|
|
/// - timeout: Maximum time to wait for element
|
|
///
|
|
/// - Returns: true if tap succeeded, false if element didn't appear
|
|
///
|
|
@discardableResult
|
|
public func tapWhenReady(
|
|
_ element: XCUIElement,
|
|
timeout: TimeInterval? = nil
|
|
) -> Bool {
|
|
guard waitForElement(element, timeout: timeout) else {
|
|
return false
|
|
}
|
|
element.tap()
|
|
return true
|
|
}
|
|
|
|
/// Type text into a field after waiting for it
|
|
///
|
|
/// - Parameters:
|
|
/// - text: Text to type
|
|
/// - element: The text field element
|
|
/// - timeout: Maximum time to wait for element
|
|
///
|
|
/// - Returns: true if typing succeeded, false if element didn't appear
|
|
///
|
|
@discardableResult
|
|
public func typeText(
|
|
_ text: String,
|
|
into element: XCUIElement,
|
|
timeout: TimeInterval? = nil
|
|
) -> Bool {
|
|
guard waitForElement(element, timeout: timeout) else {
|
|
return false
|
|
}
|
|
element.tap()
|
|
element.typeText(text)
|
|
return true
|
|
}
|
|
|
|
/// Dismiss keyboard if present
|
|
public func dismissKeyboard() {
|
|
// Tap toolbar Done button if available
|
|
if app.toolbars.buttons["Done"].exists {
|
|
app.toolbars.buttons["Done"].tap()
|
|
return
|
|
}
|
|
|
|
// Or tap outside keyboard area
|
|
app.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.1)).tap()
|
|
}
|
|
}
|