Where XCUITest generates a sequence of low-level element interactions, Illuminator tests are expressed as sequences of aptly-named (according to you, the developer) actions. A detailed picture of what the test accomplishes should be apparent from the action names alone.
func testWithIlluminator() {
initialState
.apply(wf.welcome.continueAsGuest())
.apply(wf.pushNotificationPermissions.decline())
.apply(wf.home.searchFor("table"))
.apply(wf.browse.verifySubcategory("End tables", expected: true))
.apply(wf.browse.verifySubcategory("Bobby tables", expected: false))
.apply(wf.browse.openSubcategory("Coffee tables"))
.apply(wf.browse.openProduct(atIndex: 3))
.apply(wf.productPage.addToCart(quantity: 2))
.apply(wf.welcomePopup.signIn())
.apply(wf.signInModal.submitCredentials(testAccount, password: testAccountPassword))
.apply(wf.cart.verifyTotalItems(2))
.apply(wf.cart.submitOrder())
.apply(wf.orderConfirmation.verifyIsActive())
.finish(self)
}
Your tests don't need to explicitly check whether they're on the right screen before every UI element interaction; Illuminator does it for you. Illuminator provides a system for grouping actions into the "screens" where they are available, and centralizes the logic for indicating when that screen is visible.
In the example above, welcome
, pushNotificationPermissions
, home
, browse
, productPage
, welcomePopup
, signInModal
, cart
, and orderConfirmation
are the screens.
A sample app definition would include the following code:
public typealias AppTestState = [String: String]
public struct WayfairTestApp: IlluminatorApplication {
var welcome: WelcomeScreen {
get {
return WelcomeScreen(testCaseWrapper: testCaseWrapper)
}
}
/// snip
And the definition of the welcome screen would include:
public class WelcomeScreen: IlluminatorDelayedScreen<AppTestState> {
public override var isActive: Bool {
return app.navigationBars["Welcome"].exists
}
public func continueAsGuest() -> IlluminatorActionGeneric<AppTestState> {
return makeAction() {
try self.app.buttons["Continue as Guest"].ready().tap()
}
}
/// snip
Failing XCTestcase test functions indicate the file and line of the failure. That's not good enough. Illuminator tests can indicate the file and line of the failure as well as a detailed description of the app state at the time of the failure. For example, here is a sample of copy-pastable swift code generated by Illuminator between a test failure and the actual XCFail()
call:
app.tabBars.elementBoundByIndex(0).buttons["Home"]
app.tabBars.elementBoundByIndex(0).buttons["Sales"]
app.tabBars.elementBoundByIndex(0).buttons["My Boards"]
app.tabBars.elementBoundByIndex(0).buttons["Account"]
app.tabBars.elementBoundByIndex(0).buttons["Cart"]
app.navigationBars["Welcome"]
app.navigationBars["Welcome"].buttons["close"]
app.navigationBars["Welcome"].staticTexts["Welcome"]
app.images["UniformCanvasLogo-Wayfair_US.jpg"]
app.staticTexts["Please Register or Sign In with your Wayfair.com account."]
app.buttons["Register"]
app.buttons["Sign In"]
app.staticTexts["or"]
Of course, this is ordinarily impossible. Illuminator accomplishes this by meticulously working around XCUIElement operations that trigger immediate test failures (like XCUIElementQuery operations that return multiple elements or XCUIElement operations on nonexistent elements). In the previous example, this was accomplished by Illuminator's .ready()
function, which throws a catchable exception rather than trigger a test failure.
Bottom line? In many cases you can repair broken CI tests without re-running the failing test in your IDE; just find the relevant element reference in the console output and copy/paste it into your screen or action as appropriate.
See Docs/.
To run the example project, clone the repo, and run pod install
from the Example directory first.
Illuminator is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "Illuminator"
Illuminator is available under the Apache 2.0 license. See the LICENSE.txt file for more info.