Add XCUITest suite with 27 test files covering unmapped P1 test cases
- Add 8 new test files: HeaderMoodLogging (TC-002), DayViewGrouping (TC-019), AllDayViewStyles (TC-021), MonthViewInteraction (TC-030), PaywallGate (TC-032/039/048), AppTheme (TC-070), IconPack (TC-072), PremiumCustomization (TC-075) - Add accessibility IDs for paywall overlays, icon packs, app theme cards, and day view section headers - Add --expire-trial launch argument to UITestMode for paywall gate testing - Update QA test plan spreadsheet with XCUITest names for 14 test cases - Include existing test infrastructure: screen objects, helpers, base class, and 19 previously written test files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
81
Tests iOS/Helpers/BaseUITestCase.swift
Normal file
81
Tests iOS/Helpers/BaseUITestCase.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// BaseUITestCase.swift
|
||||
// Tests iOS
|
||||
//
|
||||
// Base class for all UI tests. Handles launch arguments,
|
||||
// state reset, and screenshot capture on failure.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
class BaseUITestCase: XCTestCase {
|
||||
|
||||
var app: XCUIApplication!
|
||||
|
||||
// MARK: - Configuration (override in subclasses)
|
||||
|
||||
/// Fixture to seed. Override to use a specific data set.
|
||||
var seedFixture: String? { nil }
|
||||
|
||||
/// Whether to bypass the subscription paywall. Default: true.
|
||||
var bypassSubscription: Bool { true }
|
||||
|
||||
/// Whether to skip onboarding. Default: true.
|
||||
var skipOnboarding: Bool { true }
|
||||
|
||||
/// Whether to force the trial to be expired. Default: false.
|
||||
var expireTrial: Bool { false }
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
continueAfterFailure = false
|
||||
|
||||
app = XCUIApplication()
|
||||
app.launchArguments = buildLaunchArguments()
|
||||
app.launchEnvironment = buildLaunchEnvironment()
|
||||
app.launch()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
if let failure = testRun?.failureCount, failure > 0 {
|
||||
captureScreenshot(name: "FAILURE-\(name)")
|
||||
}
|
||||
app = nil
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Launch Configuration
|
||||
|
||||
private func buildLaunchArguments() -> [String] {
|
||||
var args = ["--ui-testing", "--reset-state", "--disable-animations"]
|
||||
if bypassSubscription {
|
||||
args.append("--bypass-subscription")
|
||||
}
|
||||
if skipOnboarding {
|
||||
args.append("--skip-onboarding")
|
||||
}
|
||||
if expireTrial {
|
||||
args.append("--expire-trial")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
private func buildLaunchEnvironment() -> [String: String] {
|
||||
var env = [String: String]()
|
||||
if let fixture = seedFixture {
|
||||
env["UI_TEST_FIXTURE"] = fixture
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// MARK: - Screenshots
|
||||
|
||||
func captureScreenshot(name: String) {
|
||||
let screenshot = XCTAttachment(screenshot: app.screenshot())
|
||||
screenshot.name = name
|
||||
screenshot.lifetime = .keepAlways
|
||||
add(screenshot)
|
||||
}
|
||||
}
|
||||
65
Tests iOS/Helpers/WaitHelpers.swift
Normal file
65
Tests iOS/Helpers/WaitHelpers.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
//
|
||||
// WaitHelpers.swift
|
||||
// Tests iOS
|
||||
//
|
||||
// Centralized, explicit wait helpers. No sleep() allowed.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
extension XCUIElement {
|
||||
|
||||
/// Wait for the element to exist in the hierarchy.
|
||||
/// - Parameters:
|
||||
/// - timeout: Maximum seconds to wait.
|
||||
/// - message: Custom failure message.
|
||||
/// - Returns: `true` if the element exists within the timeout.
|
||||
@discardableResult
|
||||
func waitForExistence(timeout: TimeInterval = 5, message: String? = nil) -> Bool {
|
||||
let result = waitForExistence(timeout: timeout)
|
||||
if !result, let message = message {
|
||||
XCTFail(message)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/// Wait until the element is hittable (exists and is enabled/visible).
|
||||
/// - Parameter timeout: Maximum seconds to wait.
|
||||
@discardableResult
|
||||
func waitUntilHittable(timeout: TimeInterval = 5) -> Bool {
|
||||
let predicate = NSPredicate(format: "isHittable == true")
|
||||
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
|
||||
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
|
||||
return result == .completed
|
||||
}
|
||||
|
||||
/// Tap the element after waiting for it to become hittable.
|
||||
/// - Parameter timeout: Maximum seconds to wait before tapping.
|
||||
func tapWhenReady(timeout: TimeInterval = 5, file: StaticString = #file, line: UInt = #line) {
|
||||
guard waitUntilHittable(timeout: timeout) else {
|
||||
XCTFail("Element \(identifier) not hittable after \(timeout)s", file: file, line: line)
|
||||
return
|
||||
}
|
||||
tap()
|
||||
}
|
||||
|
||||
/// Wait for the element to disappear from the hierarchy.
|
||||
/// - Parameter timeout: Maximum seconds to wait.
|
||||
@discardableResult
|
||||
func waitForDisappearance(timeout: TimeInterval = 5) -> Bool {
|
||||
let predicate = NSPredicate(format: "exists == false")
|
||||
let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
|
||||
let result = XCTWaiter.wait(for: [expectation], timeout: timeout)
|
||||
return result == .completed
|
||||
}
|
||||
}
|
||||
|
||||
extension XCUIApplication {
|
||||
|
||||
/// Wait for any element matching the identifier to exist.
|
||||
func waitForElement(identifier: String, timeout: TimeInterval = 5) -> XCUIElement {
|
||||
let element = descendants(matching: .any).matching(identifier: identifier).firstMatch
|
||||
_ = element.waitForExistence(timeout: timeout)
|
||||
return element
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user