XCUITesting for permission popup: alert appears, but UIInterruptionMonitor does not fire

XCUITesting for permission popup: alert appears, but UIInterruptionMonitor does not fire

我想写一个这样的测试:

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

我想测试窗格是否出现。我正在使用 XC 的内置 UITest 框架来执行此操作。根据我在 google 和此处找到的内容,我似乎应该执行以下操作:

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,它正确地导致 "give permission" 弹出窗口出现。但是,我希望这会触发中断监视器。但是,没有捕获到此类中断,并且不会发生实现。

我在某处读到您应该在对话框出现后 app.tap() 执行此操作。但是,当我这样做时,它会单击 "allow" 按钮。对话框消失,仍然没有处理中断。

有没有什么方法可以不考虑 "alerts" 或无法处理权限对话框?我什至进去用一个只看 app.alerts 的东西替换了中断位,但结果是空的,即使我正在看模拟器中的弹出窗口。

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

我也注意到了这个问题。似乎中断处理程序是 运行 异步的,无法断言它们是否被调用。同样等待期望似乎完全可以防止中断监视器 运行ning。看起来系统正在等待期望的实现,而期望正在等待中断监视器触发。典型的死锁案例。

但是,我发现了一个使用基于 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() 以某种方式允许中断监视器 运行,即使测试用例正在等待期望。

我希望这对你和我一样有效!

pancake 的答案有效,但前提是应用程序是第一次测试。如果应用之前已经在同一个模拟器上进行过测试,则权限已经授予应用,因此永远不会出现警告,测试将失败。

我的方法是等待应该出现在应用程序中的元素,而不是等待处理完警报对话框。如果警告对话框在应用程序上方,应用程序的元素将不会 "exist" 因为它不是 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)

所以 对我有用。但是,我认为它可以简化。出现系统警报时,确实出现了某种奇怪的死锁或竞争条件。

而不是 NSPredicate 我只是在应该显示系统警报之后和尝试 XCUIApplication().tap() 之前使用了 sleep(2)

我还决定使用 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()
    }
}