Xcode UI测试|如何处理位置服务警报



我正在为我的一个应用程序编写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()

最新更新