I would like to write a test like so:
When my app goes to a certain pane, it should request permission to use the camera.
I want to test whether or not the pane appears. I am using XC's builtin UITest framework to do this. Per what I found on google and here, it seems like I should do the following:
let dialogAppearedExpectation = expectationWithDescription("Camera Permission Dialog Appears")
addUIInterruptionMonitorWithDescription("Camera Permission Alert") { (alert) -> Bool in
dialogAppearedExpectation.fulfill()
return true
}
goToCameraPage()
waitForExpectationsWithTimeout(10) { (error: NSError?) -> Void in
print("Error: \(error?.localizedDescription)")
}
The test began with failing, great. I implemented goToCameraPage, which correctly causes the "give permission" popup to appear. However, I would expect this to trigger the interruption monitor. No such interruption is caught, however, and fulfillment does not occur.
I read somewhere that you should do app.tap()
after the dialog appears. However, when I do that, it clicks the "allow" button. The dialog disappears and still no interruption is handled.
Is there some way in which permission dialogs are not considered "alerts" or can't be handled? I even went in and replaced the interruption bit with a thing which just looks at app.alerts
, but that turns out to be empty, even as I'm looking right at the popup in Simulator.
Thanks! I am using Xcode7.2, iOS 9.2 simulator for iPhone 6s.
I have noticed this problem as well. It seems like the interruption handlers are run asynchronously and there is no way to assert whether they were called. Also waiting for an expectation seems to prevent the interruption monitor from running at all. It looks like the system is waiting for the expectation to fulfil and the expectation is waiting for the the interruption monitor to fire. A classic case of deadlock.
However, I have found a rather quirky solution that uses NSPredicate
-based expecations:
var didShowDialog = false
expectation(for: NSPredicate() {(_,_) in
XCUIApplication().tap() // this is the magic tap that makes it work
return didShowDialog
}, evaluatedWith: NSNull(), handler: nil)
addUIInterruptionMonitor(withDescription: "Camera Permission Alert") { (alert) -> Bool in
alert.buttons.element(boundBy: 0).tap() // not sure if allow = 0 or 1
didShowDialog = true
return true
}
goToCameraPage()
waitForExpectations(timeout: 10) { (error: Error?) -> Void in
print("Error: \(error?.localizedDescription)")
}
Apparently, doing the XCUIApplication().tap()
inside the predicate block somehow allows the interruption monitor to be run, even though the test case is waiting for an expectation.
I hope this works as well for you as it did for me!
So pancake's answer worked for me. However, I think it can be simplified. There does appear to be some sort of weird deadlock or race condition when presenting a system alert.
Instead of the NSPredicate
expectation I just used sleep(2)
after the system alert should be presented and before trying XCUIApplication().tap()
.
I also decided to use XCUIApplication().swipeUp()
since it's less likely to interfere with the test.
Example using Login with Facebook
class LoginWithFacebookTest: XCTestCase {
let app = XCUIApplication()
var interruptionMonitor: NSObjectProtocol!
let alertDescription = "“APP_NAME” Wants to Use “facebook.com” to Sign In"
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
self.removeUIInterruptionMonitor(interruptionMonitor)
}
func loginWithFacebookTest() {
app.launch()
self.interruptionMonitor = addUIInterruptionMonitor(withDescription: self.alertDescription) { (alert) -> Bool in
// check for a specific button
if alert.buttons["Continue"].exists {
alert.buttons["Continue"].tap()
return true
}
return false
}
let loginWithFacebook = app.otherElements["login with facebook"]
loginWithFacebook.tap()
// Sleep to give the alert time to show up
sleep(2)
// Interact with the app to get the above monitor to fire
app.swipeUp()
}
}
pancake's answer works, but only if the application is being tested for the first time. If the app has been previously tested in the same simulator, the permission will already be granted to the app, so the alert will never appear, and the test will fail.
My approach is to instead wait for an element that should appear in the app rather than waiting for the alert dialog to have been handled. If the alert dialog is over the app, the app's element will not "exist" because it's not reachable/tappable.
let alertHandler = addUIInterruptionMonitor(withDescription: "Photos or Camera Permission Alert") { (alert) -> Bool in
if alert.buttons.matching(identifier: "OK").count > 0 {
alert.buttons["OK"].tap()
// Required to return focus to app
app.tap()
return true
} else {
return false
}
}
app.buttons["Change Avatar"].tap()
if !app.buttons["Use Camera"].waitForExistence(timeout: 5.0) {
// Cause the alert handler to be invoked if the alert is currently shown.
XCUIApplication().swipeUp()
}
_ = app.buttons["Use Camera"].waitForExistence(timeout: 2.0)
removeUIInterruptionMonitor(alertHandler)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With