Xcode7 | Xcode UI Tests | How to handle location service alert?
Asked Answered
R

8

32

I am writing UI Test Cases for one one of my app using the XCUIApplication, XCUIElement and XCUIElementQuery introduced in Xcode7/iOS 9.

I have hit a road block. One of the screens in test case requires iOS's Location Services. As expected the user is prompted about allowing use of location service with alert titled: Allow “App name” to access your location while you use the app? with Allow & Don't Allow buttons.

Problem is or so it seems that since the alert is presented by OS itself it is not present in Application's element sub-tree.

I have logged following:

print("XYZ:\(app.alerts.count)")//0
var existence = app.staticTexts["Allow “App Name” to access your location while you use the app?"].exists
print("XYZ:\(existence)")//false
existence  = app.buttons["Allow"].exists
print("XYZ:\(existence)") //false

Even UI recording generated similar code:

XCUIApplication().alerts["Allow “App Name” to access your location while you use the app?"].collectionViews.buttons["Allow"].tap()

I have not found any API that can get me past this problem. For example:

  • Tap at a position on the screen
  • Get alerts outside the app

So how can I get past this? Is there a way to configure Test Targets so that Location Service Authorization is not required.

Rhythmical answered 31/7, 2015 at 12:28 Comment(2)
For objective C, you can find the proper way here, https://mcmap.net/q/336569/-xcode-7-ui-testing-how-to-dismiss-a-series-of-system-alerts-in-codeBiotype
You can take a look at my note github.com/onmyway133/blog/issues/48Jeanajeanbaptiste
A
38

Xcode 9

    let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
    let allowBtn = springboard.buttons["Allow"]
    if allowBtn.exists {
        allowBtn.tap()
    }

Xcode 8.3.3

    _ = addUIInterruptionMonitor(withDescription: "Location Dialog") { (alert) -> Bool in
        alert.buttons["Allow"].tap()
        return true
    }
    app.buttons["Request Location"].tap()
    app.tap() // need to interact with the app for the handler to fire

Note that it is a bit different as the method name now is addUIInterruptionMonitor and takes withDescription as an argument

Xcode 7.1

Xcode 7.1 has finally fixed a issue with system alerts. There are, however, two small gotchas.

First, you need to set up a "UI Interuption Handler" before presenting the alert. This is our way of telling the framework how to handle an alert when it appears.

Second, after presenting the alert you must interact with the interface. Simply tapping the app works just fine, but is required.

addUIInterruptionMonitorWithDescription("Location Dialog") { (alert) -> Bool in
    alert.buttons["Allow"].tap()
    return true
}

app.buttons["Request Location"].tap()
app.tap() // need to interact with the app for the handler to fire

The "Location Dialog" is just a string to help the developer identify which handler was accessed, it is not specific to the type of alert.

Xcode 7.0

The following will dismiss a single "system alert" in Xcode 7 Beta 6:

let app = XCUIApplication()
app.launch()
// trigger location permission dialog

app.alerts.element.collectionViews.buttons["Allow"].tap()

Beta 6 introduced a slew of fixes for UI Testing and I believe this was one of them.

Also note that I am calling -element directly on -alerts. Calling -element on an XCUIElementQuery forces the framework to choose the "one and only" matching element on the screen. This works great for alerts where you can only have one visible at a time. However, if you try this for a label and have two labels the framework will raise an exception.

Autotomy answered 26/8, 2015 at 13:25 Comment(9)
It seems to crash in Xcode 7.1. I filed a bug report, rdar://22498241. I suggest anyone experiencing this duplicates it.Autotomy
I am faced with the push notification alert and it is NOT listed when I type "po XCUIApplicaction( )" Any ideas? I use Xcode 7.1Panpsychist
@Panpsychist I've updated my answer for Xcode 7.1 which no longer crashes the test suite.Autotomy
Yep thats how Ive done it and I can confirm that this works!Panpsychist
This working great for me. All I had to do was substitute addUIInterruptionMonitorWithDescription("APNS Alert") { (alert) -> Bool in alert.buttons["OK"].tap() return true} where app.alerts.collectionViews.buttons["OK"].tap() was before.Ducal
Any updates for this on 8.2? I noticed that I didn't even have to dismiss the alert now. It hits allow automatically. Anyone else notice this?Becalmed
I've had to use the method above on 8.2.1 to dismiss a notifications alert. Still works but function signature changed slightly to addUIInterruptionMonitor(withDescriptionMasque
Found that using xcode 9.1, alerts are only being handled if the test device has iOS 11. Doesn't work on older iOS versions e.g 10.3 etc. Reference: forums.developer.apple.com/thread/86989Ghazi
I know you can't upvote twice, but I feel like if you come back to an answer after a year or more, go to upvote it and find you already did, then it should be allowed to upvote again. All of which is to say thanks for a great answerPyrometallurgy
K
8

This was the only thing that worked for me. Using Xcode 9 fwiw.

Also probably relevant that I was already using addUIInterruptionMonitor for a different alert. I tried reordering them and it didn't make a difference. Could be that it's a problem in 9 when you have two, or could be I was using them wrong. In any event the code below worked. :)

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.exists {
    allowBtn.tap()
}
Kelley answered 2/11, 2017 at 21:53 Comment(3)
How awesome, this works for me like a charm. Old solutions don't work anymore on Xcode 9. This is the best I found for many hours looking for answers. This solution should be on top. :) Thanks Joe.Aglet
This works perfectly - this should be marked as the right answer as the apparently right answer doesn't!Leis
This worked like a champion for me in XCode 9.1, it appears broken in XCode 9.3 though.Fugate
B
3

If you want to check if the alert is showing, just check for the existence of the button:

if (app.alerts.element.collectionViews.buttons["Dismiss"].exists)
{
app.alerts.element.collectionViews.buttons["Dismiss"].tap()
}

it checks if the alert is showing, and if it's showing it will tap it

Boaz answered 2/11, 2015 at 16:38 Comment(0)
G
3

On xcode 9.1, alerts are only being handled if the test device has iOS 11. Doesn't work on older iOS versions e.g 10.3 etc. Reference: https://forums.developer.apple.com/thread/86989

To handle alerts use this:

//Use this before the alerts appear. I am doing it before app.launch()

let allowButtonPredicate = NSPredicate(format: "label == 'Always Allow' || label == 'Allow'")
//1st alert
_ = addUIInterruptionMonitor(withDescription: "Allow to access your location?") { (alert) -> Bool in
    let alwaysAllowButton = alert.buttons.matching(allowButtonPredicate).element.firstMatch
    if alwaysAllowButton.exists {
        alwaysAllowButton.tap()
        return true
    }
    return false
}
// One interruption monitor is sufficient for multiple alerts
Ghazi answered 20/12, 2017 at 13:30 Comment(0)
M
3

I got it to work with this on Xcode 9.4.1, the trick was to wait for the popup to appear.

// wait for location service popup to appear
    let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
    let allowBtn = springboard.buttons["Allow"]
    expectation(for: NSPredicate(format: "exists == true"), evaluatedWith: allowBtn, handler: nil)
    waitForExpectations(timeout: 10, handler: nil)

    //allow location service
    if allowBtn.exists {
      allowBtn.tap()
    }
Manuscript answered 7/8, 2018 at 6:8 Comment(0)
F
1

This works for all languages:

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons.element(boundBy: 1)
if allowBtn.exists {
    allowBtn.tap()
}
Foolery answered 18/8, 2018 at 8:49 Comment(2)
For me it works if I use index 1. It seems strange to me that anyone would have 3 buttonsInapt
The location permission alert is an example of a system alert with three buttons (as of iOS 15)Heterochromatin
M
0

To tap allow on location alert you can call element.tap() where element is any element on your screen. So after calling tap, accessibility will tap Allow on alert and than tap on your element

Minstrel answered 27/10, 2015 at 18:48 Comment(0)
P
0

Here's what I did to accept a notifications permission alert in any language by tapping the second button found in the (two button) dialog. The Allow button is on the right, therefore index 1.

let handler = addUIInterruptionMonitor(withDescription: "System Dialog") { (alert) -> Bool in
    alert.buttons.element(boundBy: 1).tap()
    return true
}
app.tap()
Penetrance answered 22/6, 2019 at 23:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.