Transcripted Summary

In the last chapter, we implemented our step definitions, using Swift extensions. However, if you can see that we have used XCUIElement, directly within our step definitions, which is not great, because whenever UI changes, we need to explicitly find the element, and update that element.

If you have used page object or screen object pattern from any other test automation framework, we can do something similar in Swift, but in much more light-weight and much smarter way. In Swift, we have a feature called enumeration, or “enum”.

With Swift enums, we can store the UI element locators with their raw values as accessibility identifiers, and then we can use that enum everywhere within the UI test target.

So, in our case we can define an enum, let's say enum TAUscreen, let's return the string:


enum TAUScreen: String {

}

As your app has only one screen, so we can define all the cases like this.

case welcomeMessage = "welcomeMessage"
case taulogo = "TAUlogo"
case enterCityLabel = "enterCity"
case cityTextField = "city"
case enrollButton = "enrollButton"
case thankYouMessage = "Thanks for Joining!"
case errorMessage = "Please Enter City"

As you can see here, I have defined most of the UI element of TAU app, so you can see that welcomeMessage, TAUlogo, enterCity. I have associated the strings which are the accessibility identifier of that UI element. So now we have accessibility identifier for every element.

If you don't have it, it is strongly recommended that you should have that accessibility identifier for every UI element for better and stable XCUITest, because XCUITest framework itself is based on the accessibility API. If you don't have accessibility identifiers, then your XCUITest might be flaky

We have defined all our UI element in enumeration cases.

The next thing we can do is we need to define the element variable, which dynamically returns the XCUI element that you can use in our step definitions.

What I mean is, basically I wrote an enum variable called element, which is dynamically returning the XCUI element.

var element: XCUIElement {

}

What I mean is I have grouped the UI element using their types, so we have some static text, we have some text fields, we have some buttons, or we have some images.


var element: XCUIElement {

    switch self {
        case .welcomeMessage, .enterCityLabel, .thankYouMessage, .errorMessage:
            return XCUIApplication().staticTexts[self.rawValue]
        case .cityTextField:
            return XCUIApplication().textFields[self.rawValue]
        case .enrollButton:
            return XCUIApplication().buttons[self.rawValue]
        case .taulogo:
            return XCUIApplication().images[self.rawValue]
    }
}

What we are doing here is whenever we call the dot UI element, for example welcomeMessage, it will return that static test.

This approach is very useful in returning the dynamic elements so that we can use the same UI element for iPhone and iPad, or if the iPad element is different, then we can define explicitly in this enum.

Now we that got our UI elements in place, let's use those elements straight into our step definitions.

You can see our step definitions using “enrollButton”, so we can access that element from TAUScreen. And we can say... enrollButton dot element, so we can access that element using TAUScreen now.

func givenAppIsReady() {
    XCTContext.runActivity(named: "Given App is ready ") { _ in
        XCTAssertTrue(TAUScreen.enrollButton.element.exists)
    }
}

So, whenever the UI changes, then we need to update the enum, and we don't have to worry about updating the step definitions.

Now I will update rest of the element using the same method.

extension TAUUITestBase {

    func givenAppIsReady() {
        XCTContext.runActivity(named: "Given App is ready ") { _ in
            XCTAssertTrue(TAUScreen.enrollButton.element.exists)
        }

    }

    func whenIEnter(city: String) {
        XCTContext.runActivity(named: "When I enter city \(city) ") { _ in
            TAUScreen.cityTextField.element.tap()
            TAUScreen.cityTextField.element.typeText(city)
        }
    }

    func whenIEnrolled() {
        XCTContext.runActivity(named: "When I Enrolled ") { _ in
            TAUScreen.enrollButton.element.tap()
        }
    }

    func thenIShouldSeeThankYouMessage() {
        XCTContext.runActivity(named: "Then I Should See Thanks message ") { _ in
            XCTAssertTrue(TAUScreen.thankYouMessage.element.exists)
        }
    }
}

Now, we are accessing every element using the TAUScreen.

If you run the test now, the test wouldn't affect because we are just using the XCUI element from the enum, rather than hard coding inside the step.

import XCTest

class BDDTest: TAUUITestBase {

    func testThankYouMessageInBDStyle() {
        givenAppIsReady()
        whenIEnter(city: "London")
        whenIEnrolled()
        thenIShouldSeeThankYouMessage()
    }
}

Let's run the test in the background.




Meanwhile, what we can do is we can separate our steps, our elements, and our tests, so that our framework looks much better.

Let's create a new file, and let's call it “TAUSteps”. Let's create another file called “TAUElements” where we will store all of our XCUI elements.

Now let's import XCTest in the elements, what we can do is we can copy all our enum and cut it and put it inside the element, and very similarly we can get our steps, which are our Swift extensions, we can cut it. and put it on steps file. We've just separated our test with our steps, and our steps and the UI elements.

Now what we can do is we can go further and create groups. Let's create a new group where we store the base class, or base elements. We can create a separate group for storing our XCUI elements. We can create a separate group called “Steps.” And we can create a separate group for our tests.

Basically, in Xcode, groups are like directories, but it also has a reference to your physical location.

I will move our tests inside the “Tests” directory, so we have the test here so let's move it there. I will move the element part inside XCUI elements. The base, I will move to the “Base” folder, and the step definitions that will go into to “Step” group folder.

Now our framework looks much cleaner and looks much better than the previous one. So now if you want to just execute the test, you can go there and run your test.




You'll get the similar report, but now our framework looks much better, and much nicer in terms of directory structure.

In this chapter we wrote XCUIElements in the form of enum. We have also separated out our steps, our XCUIElement, and the test, so that our directory’s structures look clean and nicer.

That's it for this chapter, and in the next chapter we'll be exploring the XCUITest APIs. We'll be looking at how to handle the base, how to find the elements on the screen, so see you there.



Resources