0

Background

The following tests call a method that is an extension of XCTestCase. The goal:

  • The waitForElementExists method returns because the element exists or
  • The waitForElementExists method fails the test case/setUp method that called it because the element did not exist within the specified time

UI Automation XCTestCase extension to wait for a method:

extension XCTestCase
{
    /**
    Wait for the view to load
    Note: Must be a part of XCTestCase in order to utilize expectationForPredicate and waitForExpectationsWithTimeout

    - Parameter
    - element:    The XCUIElement representing the view
    - timeout: The NSTimeInterval for how long you want to wait for the view to be loaded
    - file: The file where this method was called
    - line: The line where this method was called
    */
    func waitForElementExists(element: XCUIElement, timeout: NSTimeInterval = 60,
                       file: String = #file, line: UInt = #line)
    {
        let exists = NSPredicate(format: "exists == true")

        expectationForPredicate(exists, evaluatedWithObject: element, handler: nil)
        waitForExpectationsWithTimeout(timeout) { (error) -> Void in
            if (error != nil)
            {
                let message = "Failed to find \(element) after \(timeout) seconds."
                self.recordFailureWithDescription(message,
                                                  inFile: file, atLine: line, expected: true)
            }
        }
    }
}

An Example Where waitForExpectationsWithTimeout Works Correctly

Test Case

override func setUp()
{
    super.setUp()

    // Stop immediately when a failure occurs.
    continueAfterFailure = false

    XCUIApplication().launch()

    waitForElementExists(XCUIApplication().buttons["Foo"])
}

func testSample()
{
    print("Success")
}

This works! testSample is never called.

But what if we move the waitForElementExists call to a helper method?

An Example Where waitForExpectationsWithTimeout Returns Successfully, but Shouldn't

Here, the test case continues as if the assertion never happened. If I put a breakpoint in waitForElementExists, I see that continueAfterFailure is set to true, so it's clear that it's not hooked up to the same code as the main test case.

Test Case

lazy var SomeHelper = SomeHelperClass()

override func setUp()
{
    super.setUp()

    // Stop immediately when a failure occurs.
    continueAfterFailure = false

    XCUIApplication().launch()

    SomeHelper.waitForReady()

}

func testSample()
{
    print("Success")
}

Helper File

class SomeHelperClass: XCTestCase
{
    /**
    Wait for the button to be loaded
    */
    func waitForReady()
    {
        waitForElementExists(XCUIApplication().buttons["Foo"])
    }
}
Jon Reid
  • 20,545
  • 2
  • 64
  • 95
Teresa Peters
  • 229
  • 3
  • 14

1 Answers1

0

Since your helper class subclasses XCTestCase, it has its own continueAfterFailure property, which is true by default.

If you want a helper class, it shouldn't descend from XCTestCase, as XCTestCase subclasses should implement test methods. If you need to access functionality from your XCTestCase extension in your helper class, pass in your test case object by composition on creation of your helper class.

class SomeHelper {

  let testCase: XCTestCase

  init(for testCase: XCTestCase) {
    self.testCase = testCase
  }

  func await(_ element: XCUIElement) {
    testCase.waitForElementExists(element)
  }

}

class MyTests: XCTestCase {

  let app = XCUIApplication()
  var helper: SomeHelper!

  func setUp() {
    continueAfterFailure = false
    helper = SomeHelper(for: self)
    helper.await(app.buttons["foo"])
  }

}
Oletha
  • 7,324
  • 1
  • 26
  • 46