Abdalrahman Shatou
Abdalrahman Shatou

Reputation: 4748

PageControl in Xcode UITest

I have an app where a Page Control is used to indicate multiple of screens. When the user taps the page control (for example: to the right the of the current selected page), the scroll view scrolls to the following screen. It works fine.

I wanted to write a UI Test for this scenario. I found out that I cannot tap a specific page control "dot" to trigger that action. What I can check is that the Page Control exists and the current selected page.

Another issue is that even if I am able to do so, how can I check that the scroll view has scrolled already? I can only access the frame of the scroll view, not its bounds.

I have just started using Xcode UITest and it's letting me down. I thought it would be powerful by now (it was introduced by Xcode 7 and it was available before as UI Automation), but it seems it's not yet.

I will revert back to Unit Testing + manual functional testing. If anyone has other thoughts, please share it.

Upvotes: 4

Views: 2267

Answers (3)

Alexander Vasenin
Alexander Vasenin

Reputation: 12953

UIPageControl doesn't allow a user to tap on a specific dot. According to the official documentation

The page control advances only one page in either direction

So, you can tap on the right part of it to go to the next page:

let rightOffset = CGVector(dx: 0.75, dy: 0.5)
let rightCoordinate = pageControl.coordinate(withNormalizedOffset: rightOffset)
rightCoordinate.tap()

Or, you can tap on the left part of it to go to the previous page:

let leftOffset = CGVector(dx: 0.25, dy: 0.5)
let leftCoordinate = pageControl.coordinate(withNormalizedOffset: leftOffset)
leftCoordinate.tap()

Or you can just swipe left or right:

pageControl.swipeLeft()
pageControl.swipeRight()

Upvotes: 0

nacho4d
nacho4d

Reputation: 45098

This is a extension I have for getting the number of pages and current page. It uses regex to find two numbers in the string value and assumes the bigger one is the number of pages and the smaller one is current page.

What is nice about it is it works even if your device/simulator is not set in English.

extension XCUIElement {

    func currentPageAndNumberOfPages() -> (Int, Int)? {
        guard elementType == .pageIndicator, let pageIndicatorValue = value as? String else {
            return nil
        }
        // Extract two numbers. This should be language agnostic.
        // Examples: en:"3 of 5", ja:"全15ページ中10ページ目", es:"4 de 10", etc
        let regex = try! NSRegularExpression(pattern: "^\\D*(\\d+)\\D+(\\d+)\\D*$", options: [])
        let matches = regex.matches(in: pageIndicatorValue, options: [], range: NSRange(location: 0, length: pageIndicatorValue.count))
        if matches.isEmpty {
            return nil
        }
        let group1 = matches[0].range(at: 1)
        let group2 = matches[0].range(at: 2)
        let numberString1 = pageIndicatorValue[Range(group1, in: pageIndicatorValue)!]
        let numberString2 = pageIndicatorValue[Range(group2, in: pageIndicatorValue)!]
        let number1 = Int(numberString1) ?? 0
        let number2 = Int(numberString2) ?? 0
        let numberOfPages = max(number1, number2)
        var currentPage = min(number1, number2)
        // Make it 0 based index
        currentPage = currentPage > 0 ? (currentPage - 1) : currentPage
        return (currentPage, numberOfPages)
    }

    /// return current page index (0 based) of an UIPageControl referred by XCUIElement
    func currentPage() -> Int? {
        return currentPageAndNumberOfPages()?.0 ?? nil
    }

    /// return number of pages of an UIPageControl referred by XCUIElement
    func numberOfPages() -> Int? {
        return currentPageAndNumberOfPages()?.1 ?? nil
    }
}

And you can use it like:

let pageCount = app.pageIndicators.element(boundBy: 0).numberOfPages() ?? 0
for _ in 0 ..< pageCount {
    app.pageIndicators.element(boundBy: 0).swipeLeft()
}

Upvotes: 1

joern
joern

Reputation: 27620

Don't give up so soon, Xcode UITest can be frustrating at times but once you figured out what tricks to use, they are quite powerful:

You test your UIPageControl like this:

func testPageIndicator() {
    let app = XCUIApplication()
    app.launch()

    let scrollView = app.scrollViews.element(boundBy: 0)
    let pageIndicator = app.pageIndicators.element(boundBy: 0)

    // test that page indicator is updated when user scrolls to page 2
    scrollView.swipeLeft()
    XCTAssertEqual(pageIndicator.value as? String, "page 2 of 3")

    // test that scrollview scrolls to page 3 when user taps right of the current page in page indicator
    pageIndicator.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.2)).tap()
    XCTAssert(app.otherElements["page_3"].waitForExistence(timeout: 2))
    XCTAssertFalse(app.otherElements["page_2"].exists)
    XCTAssertEqual(pageIndicator.value as? String, "page 3 of 3")
}

You can tell the test where to tap on a UIElement by using the coordinate method. You hand a CGVector object to that method that describes where to tap.

For example a CGVector(dx: 0.9, dy: 0.2) tells the test to tap the element at the point that lies at 90% of its width and 20% of its height.

You can use this to tap left of right of the current page in the UIPageIndicator(see above code example)

To test if you are on the correct page in the UIScrollView you can set each pages accessibilityIdentifier to a unique string and then assert that the UIElement with that identifier exists after you tapped the page control.

In my case the pages are 3 UIViews. I set their accessibilityIdentifier to "page_1", "page_2" and "page_3" and then assert that "page_3" exists and "page_2" doesn't.

Upvotes: 7

Related Questions