XCUITesting权限弹出:出现警报,但UIInterruptionMonitor不会触发

时间:2016-03-04 18:51:29

标签: ios swift xcode-ui-testing xcode7.2

我想写一个这样的测试:

当我的应用转到某个窗格时,它应该请求使用相机的权限。

我想测试窗格是否出现。我正在使用XC的内置UITest框架来执行此操作。根据我在谷歌和这里发现的情况,似乎我应该做以下事情:

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)")
}

测试始于失败,很棒。我实现了goToCameraPage,它正确地导致出现“给予权限”弹出窗口。但是,我希望这会触发中断监视器。但是,没有发现这种中断,也不会实现。

我在某个地方读到了对话框出现后你应该app.tap()。但是,当我这样做时,它会单击“允许”按钮。对话框消失,仍然没有处理中断。

是否有某种方式将权限对话框视为“警报”或无法处理?我甚至进去并用一个只看app.alerts的东西替换了中断位,但结果是空的,即使我正在看模拟器中的弹出窗口。

谢谢!我正在为iPhone 6s使用Xcode7.2,iOS 9.2模拟器。

3 个答案:

答案 0 :(得分:5)

我也注意到了这个问题。看起来中断处理程序是异步运行的,并且没有办法断言它们是否被调用。等待期望似乎也阻止了中断监视器的运行。看起来系统正在等待期望实现并期望等待中断监视器触发。一个经典的死锁案例。

但是,我找到了一个使用基于NSPredicate的预测的相当古怪的解决方案:

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)")
}

显然,在谓词块内部XCUIApplication().tap()以某种方式允许运行中断监视器,即使测试用例正在等待期望。

我希望这对你有用,就像对我一样!

答案 1 :(得分:2)

pancake的答案有效,但只有在第一次测试应用程序时才会这样。如果该应用程序之前已在同一模拟器中进行了测试,则该权限将已授予该应用程序,因此警报将永远不会出现,并且测试将失败。

我的方法是等待应该出现在应用程序中的元素,而不是等待处理警报对话框。如果警告对话框在应用程序上,则应用程序的元素将不会“存在”,因为它无法访问/可点击。

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)

答案 2 :(得分:1)

所以pancake's answer为我工作。但是,我认为它可以简化。出现系统警报时,确实出现了某种奇怪的死锁或争用情况。

在显示系统警报之后和尝试NSPredicate之前,我只是使用sleep(2)而不是XCUIApplication().tap()的期望。

我还决定使用XCUIApplication().swipeUp(),因为它不太可能干扰测试。

通过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()
    }
}