我正在为我的一个应用程序编写UI测试用例,使用Xcode7/iOS 9中引入的XCUIApplication, XCUIElement和XCUIElementQuery。
我遇到了一个障碍。测试用例中的一个屏幕需要iOS的定位服务。正如预期的那样,用户会被提示是否允许使用位置服务,并提示标题为:Allow “App name” to access your location while you use the app?
with Allow
&Don't Allow
按钮。
问题似乎是,由于警报是由操作系统本身呈现的,因此它不存在于应用程序的元素子树中。
我已经记录了以下内容:
print("XYZ:(app.alerts.count)")//0
var existence = app.staticTexts["Allow “App Name” to access your location while you use the app?"].exists
print("XYZ:(existence)")//false
existence = app.buttons["Allow"].exists
print("XYZ:(existence)") //false
甚至UI记录也生成了类似的代码:
XCUIApplication().alerts["Allow “App Name” to access your location while you use the app?"].collectionViews.buttons["Allow"].tap()
我还没有找到任何API可以让我过去这个问题。例如:
- 轻按屏幕上的某个位置
- 获取应用程序外的警报
那么我怎么才能通过这个呢?是否有一种方法可以配置测试目标,从而不需要位置服务授权?
Xcode 9
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.exists {
allowBtn.tap()
}
Xcode 8.3.3
_ = addUIInterruptionMonitor(withDescription: "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
请注意,这是一个有点不同的方法名称现在是addUIInterruptionMonitor和withDescription作为参数
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
"Location Dialog"只是一个字符串,用于帮助开发人员识别访问了哪个处理程序,它不是特定于警报类型的。
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
。在XCUIElementQuery
上调用-element
会强制框架选择屏幕上"唯一的"匹配元素。这对于一次只能有一个可见的警报非常有用。但是,如果您尝试对一个标签执行此操作并且有两个标签,则框架将引发异常。
这是唯一对我有效的方法。
也可能是相关的,我已经使用addUIInterruptionMonitor
不同的警报。我试着重新订购,但没有效果。可能是9的时候有两个,也可能是我用错了。无论如何,下面的代码都可以工作。:)
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
if allowBtn.exists {
allowBtn.tap()
}
如果您想检查警报是否显示,只需检查按钮是否存在:
if (app.alerts.element.collectionViews.buttons["Dismiss"].exists)
{
app.alerts.element.collectionViews.buttons["Dismiss"].tap()
}
它检查警报是否显示,如果显示它会点击
我让它在Xcode 9.4.1上工作,诀窍是等待弹出窗口出现。
// wait for location service popup to appear
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons["Allow"]
expectation(for: NSPredicate(format: "exists == true"), evaluatedWith: allowBtn, handler: nil)
waitForExpectations(timeout: 10, handler: nil)
//allow location service
if allowBtn.exists {
allowBtn.tap()
}
在xcode 9.1上,只有当测试设备有iOS 11时,警报才会被处理。不工作在旧的iOS版本,如10.3等。参考:https://forums.developer.apple.com/thread/86989
要处理警报,请使用:
//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
}
// One interruption monitor is sufficient for multiple alerts
适用于所有语言:
let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")
let allowBtn = springboard.buttons.element(boundBy: 1)
if allowBtn.exists {
allowBtn.tap()
}
点击允许您可以呼叫的位置提示tap(),其中element是屏幕上的任何元素。调用tap之后,辅助功能会点击Allow on alert然后点击元素
我通过点击(两个按钮)对话框中的第二个按钮来接受任何语言的通知权限警报。Allow按钮在右边,因此索引为1。
let handler = addUIInterruptionMonitor(withDescription: "System Dialog") { (alert) -> Bool in
alert.buttons.element(boundBy: 1).tap()
return true
}
app.tap()