11

我想写一个这样的测试:

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

我想测试窗格是否出现。我正在使用 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 模拟器。

4

3 回答 3

8

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

但是,我发现了一个相当古怪的解决方案,它使用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()谓词块内部以某种方式允许运行中断监视器,即使测试用例正在等待期望。

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

于 2016-12-02T14:47:24.397 回答
4

所以煎饼的答案对我有用。但是,我认为它可以简化。在显示系统警报时,似乎确实存在某种奇怪的死锁或竞争条件。

而不是我刚刚在系统警报出现之后和尝试之前使用的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()
    }
}
于 2019-08-09T15:09:14.190 回答
3

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)
于 2018-04-21T19:46:01.647 回答