3

I'm working on a UI unit test for my app, and am trying to figure out how to automatically have the test framework click "OK" when the system presents an alert asking for permission to access contacts.

So far, I've looked at these four SO posts, and tried the various suggestions, but am still unable to get this to work:

XCTest app tests and permissions alerts

Xcode 7 UI Testing: how to dismiss a series of system alerts in code

Xcode UI Testing allow system alerts series

Xcode 7 UI Testing: Dismiss Push and Location alerts

Here's what I currently am trying - however, the permissions dialog is still not being automatically accepted; the test waits for me to click "OK" before moving forward: func testApp() {

    self.addUIInterruptionMonitor(withDescription: "MyDescription", handler: { (alert) -> Bool in
        let button = alert.buttons["OK"]
        if button.exists {
            button.tap()
            return true
        }
        return false
    })

    let app = XCUIApplication()
    app.launch()
    ...
    app.tap()
    ...
}

EDIT: Here's the change I've made based on @ad-johnson's suggestion:

var app: XCUIApplication!

override func setUp() {
    super.setUp()

    continueAfterFailure = false
    app = XCUIApplication()

    addUIInterruptionMonitor(withDescription: "Contact Auth")
    { (alert) -> Bool in if alert.buttons["OK"].exists {
        alert.buttons["OK"].tap()
        }
        return true }

    app.launch()
}


func testScreenshots() {
    app.tap()
    ...
}
narner
  • 2,908
  • 3
  • 26
  • 63
  • May not be too helpful, but this matches what I have (for allowing access to Location Services, so I wait for an "Allow" button.) The only difference is that the order I have is 1) let app= 2) add the monitor 3) launch app. All in setup(). The app.tap is in the func testApp(). XCUIApplication() creates a new instance of the app each time it is called: I think I'd try moving that before the monitor in the first instance. Stating the obvious: the button isn't called "Ok" is it?? – ad-johnson Oct 29 '17 at 23:00
  • @ad-johnson the button is called "OK", actually! Would you mind putting what you have as an answer? That way if it works I can mark as correct - cheers! – narner Oct 29 '17 at 23:04
  • 1
    I don't see anything wrong with the code you have posted. Does your test continue after whatever action causes the alert to show? – N Brown Oct 30 '17 at 03:03
  • @NBrown It doesn't - I have to manually click on the user alert in order for the test to continue. – narner Oct 30 '17 at 12:24
  • @NBrown My understanding is that the UI interruption handler should be able to detect that system alert and press the button automatically. – narner Oct 30 '17 at 14:46
  • 1
    I agree @narner. But what does your test do after the alert gets manually dismissed? – N Brown Oct 30 '17 at 15:03
  • @NBrown After it's dismissed, a new view controller appears, and then the tests go through clicking some buttons on the top of a navigation bar. – narner Oct 30 '17 at 15:49
  • @narner, I created a test project and the code I used worked. I requested access to contacts using CNContactStore, then navigated back a view controller. The exact code in your setup method worked. The one difference I can see is that I don't use app.tap(). – N Brown Oct 30 '17 at 16:37
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157822/discussion-between-n-brown-and-narner). – N Brown Oct 30 '17 at 16:38

1 Answers1

2

May not be too helpful, but this matches what I have (for allowing access to Location Services, so I wait for an "Allow" button.) The only difference is that the order I have is 1) let app= 2) add the monitor 3) launch app. All in setup(). The app.tap is in the func testApp(). XCUIApplication() creates a new instance of the app each time it is called: I think I'd try moving that before the monitor in the first instance. Here's my setup method (ignore the UITestHelper call):

override func setUp() {
    super.setUp()

    continueAfterFailure = false
    app = XCUIApplication()

    // ensure app is currently authorised.  If the first install is to
    // happen then the settings won't exist yet but that's ok, the test
    // will handle the Location Services prompt and allow.
    UITestHelper.resetAuthorisation(setToInUse: true)

    addUIInterruptionMonitor(withDescription: "Location Services")
    { (alert) -> Bool in if alert.buttons["Allow"].exists {
        alert.buttons["Allow"].tap()
        }
        return true }

    app.launch()
}

and my test:

func testDoesNotAlertReminderIfAuthorised() {

    // given

    // When
    app.tap()
 ....
ad-johnson
  • 555
  • 3
  • 17
  • Are you setting `app` as a global variable somehow? – narner Oct 29 '17 at 23:11
  • 1
    It's just a var on the Test class. The thing to remember is that XCUIApplication isn't a pointer to your running app, it's a brand new instance each time. That got me at first as I was expecting each call to it to return the same instance and it doesn't. I store it as a variable so I can reference it in my tests in that test class. – ad-johnson Oct 29 '17 at 23:13
  • 1
    class MyTestClass: XCTestCase { var app: XCUIApplication! – ad-johnson Oct 29 '17 at 23:14
  • I kind of follow - currently seeing `app` as nil (I updated my question with what I have currently) – narner Oct 29 '17 at 23:18
  • Ack sorry; fixed my nil error/updated question, but for some reason the system dialog still isn't being dismissed :| – narner Oct 29 '17 at 23:23
  • 1
    Sometimes timing is everything with Xcode tests. Can you try putting a sleep(5) after app.launch(). Apart from that I don't have much more to add I'm afraid. – ad-johnson Oct 29 '17 at 23:28