Files
honeyDueKMP/iosApp/CaseraUITests/PageObjects/BaseScreen.swift
Trey t 7444f73b46 Close all 25 codex audit findings across KMP, iOS, and Android
Remediate all P0-S priority findings from cross-platform architecture audit:
- Harden token storage with EncryptedSharedPreferences (Android) and Keychain (iOS)
- Add SSL pinning and certificate validation to API clients
- Fix subscription cache race conditions and add thread-safe access
- Add input validation for document uploads and file type restrictions
- Refactor DocumentApi to use proper multipart upload flow
- Add rate limiting awareness and retry logic to API layer
- Harden subscription tier enforcement in SubscriptionHelper
- Add biometric prompt for sensitive actions (Login, Onboarding)
- Fix notification permission handling and device registration
- Add UI test infrastructure (page objects, fixtures, smoke tests)
- Add CI workflow for mobile builds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 13:15:34 -06:00

74 lines
3.2 KiB
Swift

import XCTest
/// Base class for all page objects providing common waiting and assertion utilities.
///
/// Replaces ad-hoc `sleep()` calls with condition-based waits for reliable,
/// non-flaky UI tests. All screen page objects should inherit from this class.
class BaseScreen {
let app: XCUIApplication
let timeout: TimeInterval
init(app: XCUIApplication, timeout: TimeInterval = 10) {
self.app = app
self.timeout = timeout
}
// MARK: - Wait Helpers (replaces fixed sleeps)
/// Waits for an element to exist within the timeout period.
/// Fails the test with a descriptive message if the element does not appear.
@discardableResult
func waitForElement(_ element: XCUIElement, timeout: TimeInterval? = nil) -> XCUIElement {
let t = timeout ?? self.timeout
XCTAssertTrue(element.waitForExistence(timeout: t), "Element \(element) did not appear within \(t)s")
return element
}
/// Waits for an element to disappear within the timeout period.
/// Fails the test if the element is still present after the timeout.
func waitForElementToDisappear(_ element: XCUIElement, timeout: TimeInterval? = nil) {
let t = timeout ?? self.timeout
let predicate = NSPredicate(format: "exists == false")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)
let result = XCTWaiter().wait(for: [expectation], timeout: t)
XCTAssertEqual(result, .completed, "Element \(element) did not disappear within \(t)s")
}
/// Waits for an element to become hittable (visible and interactable).
/// Returns the element for chaining.
@discardableResult
func waitForHittable(_ element: XCUIElement, timeout: TimeInterval? = nil) -> XCUIElement {
let t = timeout ?? self.timeout
let predicate = NSPredicate(format: "isHittable == true")
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: element)
_ = XCTWaiter().wait(for: [expectation], timeout: t)
return element
}
// MARK: - State Assertions
/// Asserts that an element with the given accessibility identifier exists.
func assertExists(_ identifier: String, file: StaticString = #file, line: UInt = #line) {
let element = app.descendants(matching: .any)[identifier]
XCTAssertTrue(element.waitForExistence(timeout: timeout), "Element '\(identifier)' not found", file: file, line: line)
}
/// Asserts that an element with the given accessibility identifier does not exist.
func assertNotExists(_ identifier: String, file: StaticString = #file, line: UInt = #line) {
let element = app.descendants(matching: .any)[identifier]
XCTAssertFalse(element.exists, "Element '\(identifier)' should not exist", file: file, line: line)
}
// MARK: - Navigation
/// Taps the first button in the navigation bar (typically the back button).
func tapBackButton() {
app.navigationBars.buttons.element(boundBy: 0).tap()
}
/// Subclasses must override this property to indicate whether the screen is currently displayed.
var isDisplayed: Bool {
fatalError("Subclasses must override isDisplayed")
}
}