Ios Xcode 7 UI测试:如何在代码中消除一系列系统警报

Ios Xcode 7 UI测试:如何在代码中消除一系列系统警报,ios,objective-c,xctest,xcode7,xcode-ui-testing,Ios,Objective C,Xctest,Xcode7,Xcode Ui Testing,我正在使用新的Xcode 7 UI测试特性编写UI测试用例。在我的应用程序的某个地方,我请求用户允许我访问摄像头和推送通知。因此将显示两个iOS弹出窗口:“MyApp希望访问摄像头”弹出窗口和“MyApp希望向您发送通知”弹出窗口。我想让我的测试取消两个弹出窗口 UI录制为我生成了以下代码: [app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap]; 但是,[app.alerts[@“cameraAccessTi

我正在使用新的Xcode 7 UI测试特性编写UI测试用例。在我的应用程序的某个地方,我请求用户允许我访问摄像头和推送通知。因此将显示两个iOS弹出窗口:
“MyApp希望访问摄像头”
弹出窗口和
“MyApp希望向您发送通知”
弹出窗口。我想让我的测试取消两个弹出窗口

UI录制为我生成了以下代码:

[app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap];
但是,
[app.alerts[@“cameraAccessTitle”]存在]
解析为false,并且上面的代码生成一个错误:
断言失败:UI测试失败-获取刷新快照失败错误域=XCTestManagerErrorDomain code=13“错误复制属性-25202”

那么,在测试中消除一堆系统警报的最佳方法是什么?系统弹出窗口中断了我的应用程序流程,并立即使我的正常UI测试用例失败。事实上,任何关于如何绕过系统警报以便恢复正常流程测试的建议都值得赞赏

这个问题可能与这篇同样没有答案的SO帖子有关:


提前感谢。

听起来像是实现摄像头访问和通知的方法,正如您所说,是线程化的,但不是物理管理的,而是在何时以何种方式显示它们

我怀疑其中一个是由另一个触发的,当程序点击它时,它也会清除另一个(苹果可能永远不会允许)

想想看,你是在请求用户许可,然后代表他们做出决定?为什么?因为你可能无法让你的代码工作

如何修复-跟踪这两个组件触发弹出对话框的位置-调用它们的位置?重写以仅触发一个,在一个对话框完成时发送NSNotification以触发并显示剩余的一个

我严重反对通过编程方式单击用户专用的对话按钮。

Xcode 7.1 Xcode 7.1最终解决了系统警报问题。然而,有两个小问题

首先,您需要在显示警报之前设置“UI中断处理程序”。这是我们告诉框架在警报出现时如何处理警报的方式

其次,在显示警报后,必须与界面交互。只需点击应用程序就可以了,但这是必需的

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
“位置对话框”只是一个字符串,用于帮助开发人员识别访问了哪个处理程序,它不是特定于警报类型的

我相信从处理程序返回
true
会将其标记为“complete”,这意味着不会再次调用它。对于您的情况,我将尝试返回
false
,以便第二个警报将再次触发处理程序

Xcode 7.0 以下内容将消除Xcode 7 Beta 6中的单个“系统警报”:

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

app.alerts.element.collectionViews.buttons["Allow"].tap()
Beta 6为UI测试引入了一系列修复,我相信这就是其中之一


还请注意,我直接在
-alerts
上调用
-element
。在
xguielementquery
上调用
-element
会强制框架在屏幕上选择“唯一”匹配元素。这对于一次只能看到一个警报的警报非常有用。但是,如果您尝试对一个标签执行此操作,并且有两个标签,框架将引发异常。

上帝!我讨厌XTest在处理UIView警报时表现最差。我有一个应用程序,其中我收到2个警报。第一个警报希望我选择“允许”以启用应用程序权限的位置服务,然后在启动页面上,用户必须按一个名为“打开位置”的UIButton,最后在UIViewAlert中出现一个通知sms警报,用户必须选择“确定”。我们遇到的问题是无法与系统警报交互,而且是一种比赛条件,在这种情况下,行为及其在屏幕上的出现是不及时的。似乎如果您使用
alert.element.buttons[“whateverText”].tap
XCTest的逻辑是一直按到测试结束。因此,基本上一直按屏幕上的任何按钮,直到所有系统警报都消失

这是一个黑客,但这是什么工作为我

    func testGetPastTheStupidAlerts(){
    let app = XCUIApplication()
    app.launch()

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

    app.buttons["TURN ON MY LOCATION"].tap()
}
字符串“Allow”被完全忽略,
app.tap()


~完全弄糊涂了,谢谢苹果。

我发现唯一可靠的解决方法是设置两个单独的测试来处理警报。在第一个测试中,我调用
app.tap()
,不做任何其他操作。在第二个测试中,我再次调用
app.tap()
,然后执行真正的工作。

天哪。 它总是点击“不允许”,即使我故意说点击“允许”

至少

if app.alerts.element.collectionViews.buttons["Allow"].exists {
    app.tap()
}
允许我继续进行其他测试。

Objective-C 敏捷的
在xcode 9.1上,仅当测试设备具有iOS 11时才会处理警报。不适用于较旧的iOS版本,例如10.3等。参考:

要处理警报,请使用以下命令:

//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
}
//Copy paste if there are more than one alerts to handle in the app

对于那些正在寻找特定系统对话框的特定描述的人(像我一样),没有:)字符串只是为了测试人员跟踪的目的。相关苹果文档链接:


更新:xcode 9.2 该方法有时触发,有时不触发。对我来说,最好的解决办法是当我知道将出现系统警报时,我补充说:

sleep(2)
app.tap()

系统警报消失了

@Joe Masilotti的回答是正确的,谢谢你,这对我帮助很大:)

我只想指出一件事,那就是UIInterruptionMonitor捕获了一系列一起出现的所有系统警报,以便您在完成处理程序中应用的操作应用于每个警报(“不允许”或“确定”)。如果你想处理一个
//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
}
//Copy paste if there are more than one alerts to handle in the app
sleep(2)
app.tap()
addUIInterruptionMonitor(withDescription: "Access to sound recording") { (alert) -> Bool in
        if alert.staticTexts["MyApp would like to use your microphone for recording your sound."].exists {
            alert.buttons["Don’t Allow"].tap()
        } else {
            alert.buttons["OK"].tap()
        }
        return true
    }
app.tap()
func testLoginHappyPath() {
    let app = XCUIApplication()
    app.textFields["Username"].typeText["Billy"]
    app.secureTextFields["Password"].typeText["hunter2"]
    app.buttons["Log In"].tap()
}
func testLoginHappyPath() {
    let app = XCUIApplication()
    let springboardApp = XCUIApplication(bundleidentifier: "com.apple.springboard")

    if springboardApp.alerts[""FunHappyApp" would like permission to own your soul."].exists {
        springboardApp.alerts.buttons["Allow"].tap()
    }

    app.textFields["Username"].typeText["Billy"]
    app.secureTextFields["Password"].typeText["hunter2"]
    app.buttons["Log In"].tap()
}