diff --git a/Sources/ScreenObject/ScreenObject.swift b/Sources/ScreenObject/ScreenObject.swift index eaec62d..222b3d8 100644 --- a/Sources/ScreenObject/ScreenObject.swift +++ b/Sources/ScreenObject/ScreenObject.swift @@ -6,6 +6,10 @@ import XCTest // and those are obviously specific to each screen, hence should be added by each subclass. open class ScreenObject { + public enum WaitForScreenError: Equatable, Error { + case timedOut + } + /// The `XCUIApplication` instance this screen is part of. This is the value passed at /// initialization time. public let app: XCUIApplication @@ -34,17 +38,31 @@ open class ScreenObject { @discardableResult func waitForScreen() throws -> Self { - XCTContext.runActivity(named: "Confirm screen \(self) is loaded") { (activity) in - let result = waitFor(element: expectedElement, predicate: "isEnabled == true", timeout: 20) - XCTAssert(result, "Screen \(self) is not loaded.") + try XCTContext.runActivity(named: "Confirm screen \(self) is loaded") { (activity) in + let result = waitFor( + element: expectedElement, + predicate: "isEnabled == true", + timeout: self.waitTimeout + ) + + guard result == .completed else { throw WaitForScreenError.timedOut } } return self } - private func waitFor(element: XCUIElement, predicate: String, timeout: Int = 5) -> Bool { - let elementPredicate = XCTNSPredicateExpectation(predicate: NSPredicate(format: predicate), object: element) - let result = XCTWaiter.wait(for: [elementPredicate], timeout: TimeInterval(timeout)) - - return result == .completed + private func waitFor( + element: XCUIElement, + predicate: String, + timeout: TimeInterval + ) -> XCTWaiter.Result { + XCTWaiter.wait( + for: [ + XCTNSPredicateExpectation( + predicate: NSPredicate(format: predicate), + object: element + ) + ], + timeout: timeout + ) } } diff --git a/Tests/TestAppUITests/TestAppUITests.swift b/Tests/TestAppUITests/TestAppUITests.swift index 43605a1..3fe859a 100644 --- a/Tests/TestAppUITests/TestAppUITests.swift +++ b/Tests/TestAppUITests/TestAppUITests.swift @@ -3,17 +3,26 @@ import XCTest class TestAppUITests: XCTestCase { + let app = XCUIApplication() + override func setUpWithError() throws { + app.launch() continueAfterFailure = false } - func testExample() throws { - let app = XCUIApplication() - app.launch() - + func testIsLoadedReturnsTrueWhenScreenIsLoaded() throws { let screen = try HelloWorldScreen() XCTAssertTrue(screen.isLoaded) } + + func testScreenInitThrowsWhenScreenIsNotLoaded() throws { + do { + _ = try MissingScreen(app: app) + XCTFail("Expected `ScreenObject` `init` to throw, but it didn't") + } catch { + XCTAssertEqual(error as? ScreenObject.WaitForScreenError, .timedOut) + } + } } final class HelloWorldScreen: ScreenObject { @@ -22,3 +31,15 @@ final class HelloWorldScreen: ScreenObject { try super.init(expectedElementGetter: { $0.staticTexts["Hello, world!"] }) } } + +/// A screen that doesn't exist. Use it to test the init failure behavior. +class MissingScreen: ScreenObject { + + init(app: XCUIApplication) throws { + try super.init( + expectedElementGetter: { $0.staticTexts["this screen does not exist"] }, + app: app, + waitTimeout: 1 + ) + } +}