Tag: xcode

  • Page Object Model

    Page Object Model

    1. Run test on different devices

    Run test on different devices

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    // File: TestCaseExtensions >  BaseTestCase.swift
    
    import XCTest
    
    class BaseTestCase: XCTestCase {
        override func setUp() {
            super.setUp()
            
            continueAfterFailure = false
        }
    
        override func tearDown() {
            super.tearDown()
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // File: Classes > BaseConfiguration > BaseConfiguration.swift
    
    import Foundation
    import XCTest
    
    class BaseConfiguration {
        let app: XCUIApplication
        let testCase: BaseTestCase
    
        init(testCase: BaseTestCase) {
            app           = testCase.app
            self.testCase = testCase
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: Classes > AppConfiguration > AppConfiguration.swift
    
    import Foundation
    import XCTest
    
    class AppConfiguration: BaseConfiguration {
        override init(testCase: SKKTestCase) {
            super.init(testCase: testCase)
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: TestCaseExtensions >  BaseTestCase.swift
    
    import XCTest
    
    class BaseTestCase: XCTestCase {
    ...
        lazy var appConfiguration: AppConfiguration = { [unowned self] in
            return AppConfiguration(testCase: self)
            }()
    }
    
    
  • Scrolling

    Xcode

    1. Scrolling
      1. Scroll to an item

    Scrolling

    Scroll to an item

    1
    2
    3
    4
    5
    
        func isVisible(_ app: XCUIApplication) -> Bool {
            let window = app.windows.element(boundBy: 0).firstMatch
    
            return waitForElementToBecomeHittable(timeout: .small) && !frame(app).isEmpty && window.frame(app).contains(frame(app))
        }
    

    Source: Scroll helper

    The helper function scrolls 1/2 the height of the collection View over and over until either the cell you are looking for is hittable, or the scroll doesn’t actually change anything (you hit the top or bottom of the collection view).

    Note that the first check it does for the cell, looks like this:

    1
    
    collectionViewElement.cells.matchingIdentifier(cellIdentifier).count > 0
    

    This lets you query the collectionView cells to see if the identifier is present without having the test fail by directly checking for the cell with collectionViewElement.cells[cellIdentifier], you would get a failure in the test, and it wouldn’t continue.

    The code checks to see if the touch changed anything by keeping track of the ‘middle’ cell in the list of cells for the collection view (which by-the-way, might include cells that are no longer displayed), and determines if it’s the same cell id and same frame to see if anything has changed.

    After the cell is found to be hittable, if you want it fully visible, there is a second loop that scrolls up or down by a smaller amount (1/2 the height of the cell) until the cell’s frame is fully contained by the collection view’s frame.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    
    import XCTest
    
    extension Page {
        @discardableResult
        func scrollTo(
            _ target: XCUIElement,
            scrollView collectionView: XCUIElement,
            scrollDirection: ScrollDirection = .bottomToTop,
            maxiumumAttempts: Int = 5,
            scrollDistance: CGFloat = 0.1
            ) -> Bool {
            
            return smartScroll(
                target: target,
                scrollView: collectionView,
                scrollDirection: scrollDirection,
                maxiumumAttempts: maxiumumAttempts,
                scrollDistance: scrollDistance
            )
        }
        
        private func smartScroll(
            target: XCUIElement,
            scrollView: XCUIElement,
            scrollDirection: ScrollDirection,
            maxiumumAttempts: Int,
            matchByLabel: String? = nil,
            scrollDistance: CGFloat
            ) -> Bool {
            
            /*
             * This currently works on the assumption that items are returned left to right and top to bottom.
             */
            func midItem() -> XCUIElement? {
                let children = scrollView.children(matching: .any)
                return children.count > 0 ? children.element(boundBy: children.count / 2) : nil
            }
            
            var scrollAttempt          = 0
            var lastMidChildIdentifier = Optional("")
            var lastMidChildRect       = Optional(CGRect.zero)
            
            var targetDistance: CGFloat = scrollDistance
            
            var currentMidChild = midItem()
            
            let elementIsNotInView = !(lastMidChildIdentifier == currentMidChild?.identifier &&
                (currentMidChild?.frame(app) ?? CGRect.infinite).equalTo(lastMidChildRect ?? CGRect.zero))
    
            // The default behaviour is to scroll the collection view until the element exits using the defined
            // scroll direction, but there are instances where it would make sense to reverse the scroll direction
            // if the element exists and the element is on the opposite side of the scroll direction.
            // An example of this is if we need the item to be centre with in the collection view but the item
            // is to the left of the centre item like in the a carousel.
            //
            // Example:
            //            Scroll Direction: Right -->
            //
            //                 Current Item
            //                       v
            // | Item 5 | Item 1 | Item 2 | Item 3 | Item 4 |
            //              ^
            //        Desired Item
            //
            //     <-- Reverse The Scroll Direction
            func normalize(scrollDirection: ScrollDirection) -> ScrollDirection {
                guard target.waitForElementToBecomeHittable(timeout: .small) else {
                    targetDistance = scrollDistance
                    
                    return scrollDirection
                }
                
                targetDistance = 0.5
                
                let elementFrame   = target.frame(app)
                let scollViewFrame = scrollView.frame(app)
                
                // Calculate the element position compared to the scrollView
                let elementIsToTheLeft   = elementFrame.midX < scollViewFrame.midX
                let elementIsToTheBottom = elementFrame.midY > scollViewFrame.midY
                
                switch scrollDirection {
                case .leftToRight where !elementIsToTheLeft:
                    return scrollDirection.inverted()
                case .rightToLeft where elementIsToTheLeft:
                    return scrollDirection.inverted()
                case .topToBottom where elementIsToTheBottom:
                    return scrollDirection.inverted()
                case .bottomToTop where !elementIsToTheBottom:
                    return scrollDirection.inverted()
                default:
                    return scrollDirection
                }
            }
            // Wait for ScrollView to exist
            _ = scrollView.waitForExistence(timeout: .small)
    
            while elementIsNotInView && scrollAttempt < maxiumumAttempts {
    
                let collectionViewValue = scrollView.value as? String ?? ""
    
                if target.isVisible(app) || matchByLabel != nil && collectionViewValue == matchByLabel {
                   return true
                }
                
                lastMidChildIdentifier = currentMidChild?.identifier
                lastMidChildRect       = currentMidChild?.frame(app)
                
                let (startOffset, endOffset) = normalize(scrollDirection: scrollDirection).vectors(targetDistance: targetDistance)
                
                scrollView
                    .coordinate(withNormalizedOffset: startOffset)
                    .press(forDuration: 0.01, thenDragTo: scrollView.coordinate(withNormalizedOffset: endOffset))
                
                scrollAttempt  += 1
                currentMidChild = midItem()
            }
            return false
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
        enum ScrollDirection {
            case topToBottom
            case bottomToTop
            case leftToRight
            case rightToLeft
            
            func inverted() -> ScrollDirection {
                switch self {
                case .topToBottom:
                    return .bottomToTop
                case .bottomToTop:
                    return .topToBottom
                case .leftToRight:
                    return .rightToLeft
                case .rightToLeft:
                    return .leftToRight
                }
            }
            
            func vectors(targetDistance: CGFloat) -> (start: CGVector, finish: CGVector) {
                switch self {
                case .topToBottom:
                    return (start: CGVector(dx: 0.99, dy: targetDistance), finish: CGVector(dx: 0.99, dy: 0.9))
                case .bottomToTop:
                    return (start: CGVector(dx: 0.99, dy: 0.9), finish: CGVector(dx: 0.99, dy: targetDistance))
                case .leftToRight:
                    return (start: CGVector(dx: targetDistance, dy: 0.99), finish: CGVector(dx: 0.9, dy: 0.99))
                case .rightToLeft:
                    return (start: CGVector(dx: 0.9, dy: 0.99), finish: CGVector(dx: targetDistance, dy: 0.99))
                }
            }
        }
    
  • Testing if elements exist

    Testing if an elements exist

    1. Assert an Element Exists
      1. Exists
      2. Wait For Existence
      3. Wait For Element To Become Hittable
      4. Wait For Element To Become Unhittable
      5. Wait For Elements Value To Match
    2. Case Study

    Assert an Element Exists

    Exists

    1
    
    XCTAssert(app.staticTexts["Apple"].exists)
    

    Wait For Existence

    1
    
    XCTAssert(app.staticTexts["Apple"].waitForExistence(timeout: 2))
    

    Wait For Element To Become Hittable

    Due to the way XCUITest works there might be instances where an element exists but the element is not tappable an example of this is an element exists but its off screen, waitForElementToBecomeHittable waits until you can interact with the element.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: XCTest > XCUIElement > XCUIElement.swift
    import XCTest
    
    extension XCUIElement {
    
        @discardableResult
        func waitForElementToBecomeHittable(timeout: Timeout) -> Bool {
            return waitForExistence(timeout: timeout) && isHittable
        }
    }
    
    

    Wait For Element To Become Unhittable

    Sometimes we need to validate if an element has disappeared rather than if it has appeared, within waitForElementToBecomeUnhittable we call NSPredicate to check if element does not exists and also is not hittable, Then we wait for a given period before we return the Boolean statement.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // File: XCTest > XCUIElement > XCUIElement.swift
    import XCTest
    
    extension XCUIElement {
    
        @discardableResult
        func waitForElementToBecomeUnhittable(timeout: Timeout) -> Bool {
            let predicate   = NSPredicate(format: "exists == false && isHittable == false")
            let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
    
            let result = XCTWaiter().wait(for: [ expectation ], timeout: timeout.rawValue)
    
            return result == .completed
        }
    }
    

    Wait For Elements Value To Match

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    // File: XCTestExtensions > XCUIElementExtensions.swift
    
    import XCTest
    
    extension XCUIElement {
    
        @discardableResult
        func waitForElementsValueToMatch(predicate: String, timeOut: Timeout = .medium) -> Bool {
            let predicate   = NSPredicate(format: "value BEGINSWITH '\(predicate)'", argumentArray: nil)
            let expectation = XCTNSPredicateExpectation(predicate: predicate, object: self)
    
            let result = XCTWaiter().wait(for: [ expectation ], timeout: timeout.rawValue)
    
            return result == .completed
        }
    }
    

    Case Study

    XCUItest allows you to set custom wait times for XCUIElements but having predefined timeouts comes in handy, this can be accomplished by creating an enum that contain different timeouts then this can be used instead of hardcoded numbers.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // File: Data > TimeOut.swift
    /// Data file
    enum Timeout: TimeInterval {
        case extraSmall = 1
        case small      = 5
        case medium     = 10
        case large      = 20
    }
    

    You can add an function in XCUIElement extensions that takes the Timeout enum and outputs an Bool. You get the rawValue for Timeout and pass it to the native waitForExistence which returns whether the element exists.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // File: XCTest > XCUIElement > XCUIElement.swift
    import XCTest
    
    extension XCUIElement {
    
        @discardableResult
        func waitForExistence(timeout: Timeout) -> Bool {
            return waitForExistence(timeout: timeout.rawValue)
        }
    }
    

    Many times functions returns a value, but sometimes you don’t care what the return value is – you might want to ignore it sometimes using @discardableResult.

    In the PageObject class we need to create a private variable that has the element identifier, this makes it very convenient way of grouping XCUIElements by screens. The variable name should describe the element that it will referenced, also note XCUIElements should always be unique and try to add .firstMatch on to the end of an element in order for XCUITest to find the element quickly.

    You can create a method that uses the variable to check if the element exists and the method then can be accessed by other classes, using this approach we remove code duplication but also it makes it easier to manage the UI tests.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    // File: PageObjects >  AppPreviewScreen.swift
    
    import XCTest
    
    class AppPreviewScreen: Page {
        private var okButton: XCUIElement { return app.buttons["ok"].firstMatch }
    
        func isOkButtonExists() -> Bool {
            okButton.waitForExistence(timeout: .small)
        }
    }
    
    

    In the UI test we can create an instance of the page object class AppPreviewScreen then reference the function we require in order to validate the screen.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // File: UITest >  ExampleTest.swift
    
    import XCTest
    
    class ExampleTest: XCTestCase {
        override func setUp() {
            super.setUp()
            
            continueAfterFailure = false
            var appPreview: AppPreviewScreen = AppPreviewScreen(testCase: self)
        }
    
        override func tearDown() {
            super.tearDown()
        }
    
        func testWaitForElement() {
            XCTAssert(appPreview.isOkButtonExists())
        }
    }
    
    
  • Testing on different devices

    Testing on different devices

    1. Run test on different devices

    Run test on different devices

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    import Foundation
    import XCTest
    
    public extension XCUIApplication {
        
        /// Check if application is running on simulator
        public var isRunningOnSimulator: Bool {
            #if targetEnvironment(simulator)
                return true
            #else
                return false
            #endif
        }
    
        public var isRunningOnRealDevice: Bool {
            return !isRunningOnSimulator
        }
    
        public var isRunningOnTablet: Bool {
            return UIDevice.current.userInterfaceIdiom == .pad
        }
    
        public var isRunningOnPhone: Bool {
            return UIDevice.current.userInterfaceIdiom == .phone
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    
    // File: TestCaseExtensions > DeviceTypeProtocol.swift
    
    protocol TestOnAnyDevice {}
    protocol TestOnPhone {}
    protocol TestOnTablet {}
    protocol TestOnRealDevice {}
    protocol TestOnSimulator {}
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    
    // File: TestCaseExtensions >  BaseTestCase.swift
    
    import XCTest
    
    class BaseTestCase: XCTestCase {
    
        // Check if tests are compatible with the current device
        override func perform(_ run: XCTestRun) {
            let test = run.test
    
            let testOnPhone      = test is TestOnPhone
            let testOnRealDevice = test is TestOnRealDevice
            let testOnSimulator  = test is TestOnSimulator
            let testOnTablet     = test is TestOnTablet
    
            if (testOnRealDevice && app.isRunningOnSimulator) || (testOnSimulator && app.isRunningOnRealDevice) {
                return reasonTestWasSkipped(test.name, message: "Not supported for the current hardware type")
            }
    
            if (testOnPhone && app.isRunningOnTablet) || (testOnTablet && app.isRunningOnPhone) {
                return reasonTestWasSkipped(test.name, message: "Not supported for the current device type")
            }
    
            super.perform(run)
        }
    
        private func reasonTestWasSkipped(_ testName: String, message: String) {
            let messageForSkipedTest = """
            -- Test Skipped --
            \(testName)       [\(message)]
            
            """
    
            return print(messageForSkipedTest)
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: UITest >  ExampleTest.swift
    
    import XCTest
    
    class ExampleTest: BaseTestCase, TestOnPhone {
    
        func testWaitForElement() {
            ...
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: UITest >  ExampleTestTablet.swift
    
    import XCTest
    
    class ExampleTestTwo: BaseTestCase, TestOnTablet, TestOnSimulator {
    
        func testWaitForElement() {
            ...
        }
    }
    
    
  • UI Interactions

    Exists

    1. Tap on elements
      1. Tap Element
      2. Wait For Element Before Tapping
      3. Force Tap On Element
    2. Swipe
    3. Text Input
      1. Type
      2. Clear And Enter Text
      3. Enter Text
    4. Alerts
      1. Dismissing Alerts
      2. Dismissing Action Sheets
      3. System alerts within the application
    5. Sliders
    6. Pickers
      1. Date picker
    7. Web Links
    8. Case Study

    Tap on elements

    Tap Element

    1
    
    app.staticTexts["Apple"].tap()
    

    Wait For Element Before Tapping

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    // File: XCTestExtensions > XCUIElementExtensions.swift
    
    import XCTest
    
    extension XCUIElement {
        // Custom method to wait before tapping on element
        func tapElement() {
            waitForElementToBecomeHittable(timeout: .medium)
            tap()
        }
    }
    

    Force Tap On Element

    Work around for XCUITest Bug to make certain elements tappable.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // File: XCTestExtensions > XCUIElementExtensions.swift
    
    import XCTest
    
    extension XCUIElement {
    
        func forceTap() {
            if isHittable {
                tap()
            } else {
                coordinate(withNormalizedOffset: CGVector(dx: 0.0, dy: 0.0)).tap()
            }
        }
    }
    
    

    Swipe

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    let element = app.collectionViews["Apple"]
    
    // Swipe up
    element.swipeUp()
    
    // Swipe Down
    element.swipeDown()
    
    // Swipe Right
    element.swipeRight()
    
    // Swipe Left
    element.swipeLeft()
    

    Text Input

    Type

    1
    2
    3
    4
    
    let textField = app.textFields["SearchBox"]
    
    textField.tap()
    textField.typeText("hello world")
    

    Clear And Enter Text

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // File: XCTestExtensions > XCUIElementExtensions.swift
    
    import XCTest
    
    extension XCUIElement {
    
        func clearAndEnterText(_ text: String) {
            if value != nil {
                guard let stringValue = value as? String else {
                    XCTFail("Tried to clear and enter text into a non string value")
                    return
                }
                
                tap()
                let deleteString = stringValue.map { _ in XCUIKeyboardKey.delete.rawValue }.joined()
                typeText(deleteString)
            }
    
            typeText(text)
        }
    }
    

    Enter Text

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    extension XCUIElement {
        // Workaround for keyboard issues when not visible
        func enterText(text: String) {
            tapElement()
            
            if !self.waitForExistence(timeout: .small) {
                tapElement()
            }
    
            let dismissKeyboardButton = XCUIKeyboardKey.enter
    
            dismissKeyboardButton.waitForExistence(timeout: .medium)
            
            clearAndEnterText(text)
            
            if dismissKeyboardButton.isHittable {
                dismissKeyboardButton.tapElement()
            }
            
            if elementType != .secureTextField {
                waitForElementsValueToMatch(predicate: text)
            }
        }
    }
    

    Alerts

    Dismissing Alerts

    1
    
    app.alerts["Alert_Title"].buttons["ok"].tap()
    

    Dismissing Action Sheets

    1
    
    app.sheets["Sheet_Title"].buttons["ok"].tap()
    

    System alerts within the application

    1
    2
    3
    4
    5
    6
    7
    
    addUIInterruptionMonitor(withDescription: "Location Services") { (alert) -> Bool in
      alert.buttons["Allow"].tap()
      return true
    }
    
    app.buttons["Request Location"].tap()
    app.activate()
    

    Sliders

    1
    
    app.sliders.element.adjust(toNormalizedSliderPosition: 0.7)
    

    validate slider value approximately

    1
    
    XCTAssertEqual(app.sliders["playerScrubberView"].normalizedSliderPosition, 0.7, accuracy: 0.1)
    

    Pickers

    1
    
    app.pickerWheels.element.adjust(toPickerWheelValue: "Picker Wheel Item Title")
    

    Date picker

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    // File: PageObjects > DatePickerScreen.swift
    
    import XCTest
    
    class DatePickerScreen: Page {
    
        private var dateField: XCUIElement { return app.textFields["Date"].firstMatch }
    
        // MARK: Date format - day-month-year i.e 13-March-2015
        func changePickerDate(_ date: String) {
    
            let newDateFormat = date.components(separatedBy: "-")
            let date  = newDateFormat[0]
            let month = newDateFormat[1]
            let year  = newDateFormat[2]
    
            dateField.tap()
    
            //Day
            app.datePickers.pickerWheels.element(boundBy: 0).adjust(toPickerWheelValue: date)
    
            // Month
            app.datePickers.pickerWheels.element(boundBy: 1).adjust(toPickerWheelValue: month)
    
            //Year
            app.datePickers.pickerWheels.element(boundBy: 2).adjust(toPickerWheelValue: year)
    
            // Dismiss dates picker
            dateField.forceTap()
        }
    }
    
    1
    
    app.links["Tweet this"].tap()
    

    Case Study

    XCUItest allows you to set custom wait times for XCUIElements but having predefined timeouts comes in handy, this can be accomplished by creating an enum that contain different timeouts then this can be used instead of hardcoded numbers.

    1
    2
    3
    4
    5
    6
    7
    8
    
    // File: Data > TimeOut.swift
    /// Data file
    enum Timeout: TimeInterval {
        case extraSmall = 1
        case small      = 5
        case medium     = 10
        case large      = 20
    }
    

    You can add an function in XCUIElement extensions that takes the Timeout enum and outputs an Bool. You get the rawValue for Timeout and pass it to the native waitForExistence which returns whether the element exists.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // File: XCTest > XCUIElement > XCUIElement.swift
    import XCTest
    
    extension XCUIElement {
    
        @discardableResult
        func waitForExistence(timeout: Timeout) -> Bool {
            return waitForExistence(timeout: timeout.rawValue)
        }
    }
    

    Many times functions returns a value, but sometimes you don’t care what the return value is – you might want to ignore it sometimes using @discardableResult.

    In the PageObject class we need to create a private variable that has the element identifier, this makes it very convenient way of grouping XCUIElements by screens. The variable name should describe the element that it will referenced, also note XCUIElements should always be unique and try to add .firstMatch on to the end of an element in order for XCUITest to find the element quickly.

    You can create a method that uses the variable to check if the element exists and the method then can be accessed by other classes, using this approach we remove code duplication but also it makes it easier to manage the UI tests.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    // File: PageObjects >  AppPreviewScreen.swift
    
    import XCTest
    
    class AppPreviewScreen: Page {
        private var okButton: XCUIElement { return app.buttons["ok"].firstMatch }
    
        func isOkButtonExists() -> Bool {
            okButton.waitForExistence(timeout: .small)
        }
    }
    
    

    In the UI test we can create an instance of the page object class AppPreviewScreen then reference the function we require in order to validate the screen.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    // File: UITest >  ExampleTest.swift
    
    import XCTest
    
    class ExampleTest: XCTestCase {
        override func setUp() {
            super.setUp()
            
            continueAfterFailure = false
            var appPreview: AppPreviewScreen = AppPreviewScreen(testCase: self)
        }
    
        override func tearDown() {
            super.tearDown()
        }
    
        func testWaitForElement() {
            XCTAssert(appPreview.isOkButtonExists())
        }
    }
    
    
  • Xcode setup

    Xcode

    1. Xcode setup for UI testing
      1. Create UI testing target.
      2. Create UI test file.

    Xcode setup for UI testing

    Create UI testing target.

    If you have an existing project and would like to add automated UI tests to it, first you need to create iOS UI testing target. This is how you do it.

    1. Open your Xcode project.
    2. Go to: File -> New -> Target
    3. From the window Choose a template for your new target: select iOS UI Testing Bundle and hit Next:
    4. From the window Choose options for your new target: select your Team and Target to be tested
    5. Select Finish button and new test target has been created.

    Create UI test file.

    1. Pick the location in your Project navigator where would you like your test file to be created.
    2. Right-click and select New File…
    3. From the window Choose a template for your new file select UI Test Case Class and hit Next button.
    4. In the Choose options for your new file: window provide class name and hit Next button.
    5. Select the location where you want the file to be created and hit Create button.
    6. You have just created your UI test class with setUp, tearDown and testExample methods.
    1
    2
    3
    
    def show
      ...
    end

    Though you can add as many categories as you like, I recommend not to exceed 10. Too much of anything is bad.

    1. google
    2. xcuitest