Xcode 7 UI测试:如何在代码

时间:2015-08-21 20:42:33

标签: ios objective-c xctest xcode7 xcode-ui-testing

我正在使用新的Xcode 7 UI测试功能编写UI测试用例。在我的应用程序的某些时刻,我要求用户允许相机访问和推送通知。因此,两个iOS弹出窗口将显示:"MyApp Would Like to Access the Camera"弹出窗口和"MyApp Would Like to Send You Notifications"弹出窗口。我喜欢我的测试来解雇这两个弹出窗口。

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

[app.alerts[@"cameraAccessTitle"].collectionViews.buttons[@"OK"] tap];

但是,[app.alerts[@"cameraAccessTitle"] exists]解析为false,上面的代码会生成错误:Assertion Failure: UI Testing Failure - Failure getting refresh snapshot Error Domain=XCTestManagerErrorDomain Code=13 "Error copying attributes -25202"

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

此问题可能与此SO帖子有关,该帖子也没有答案:Xcode7 | Xcode UI Tests | How to handle location service alert?

提前致谢。

10 个答案:

答案 0 :(得分:48)

Xcode 7.1

Xcode 7.1最终修复了系统警报的问题。然而,有两个小问题。

首先,您需要在显示警报之前设置“UI Interuption Handler”。这是告诉框架如何处理警报的方式。

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

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会将其标记为“完成”,这意味着它不会再被调用。根据您的情况,我会尝试返回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测试引入了一系列修复程序,我相信这是其中之一。

另请注意,我直接在-element上致电-alerts。在-element上调用XCUIElementQuery会强制框架在屏幕上选择“唯一的”匹配元素。这适用于一次只能显示一个警报的警报。但是,如果您为标签尝试此操作并且有两个标签,则框架将引发异常。

答案 1 :(得分:3)

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

至少

popup.open({
    popup: myTooltipDialog,
    around: dom.byId('thenode') 
});

允许我继续进行其他测试。

答案 2 :(得分:3)

目标 - C

-(void) registerHandlerforDescription: (NSString*) description {

    [self addUIInterruptionMonitorWithDescription:description handler:^BOOL(XCUIElement * _Nonnull interruptingElement) {

        XCUIElement *element = interruptingElement;
        XCUIElement *allow = element.buttons[@"Allow"];
        XCUIElement *ok = element.buttons[@"OK"];

        if ([ok exists]) {
            [ok tap];
            return YES;
        }

        if ([allow exists]) {
            [allow tap];
            return YES;
        }

        return NO;
    }];
}

-(void)setUp {

    [super setUp];

    self.continueAfterFailure = NO;
    self.app = [[XCUIApplication alloc] init];
    [self.app launch];

    [self registerHandlerforDescription:@"“MyApp” would like to make data available to nearby Bluetooth devices even when you're not using app."];
    [self registerHandlerforDescription:@"“MyApp” Would Like to Access Your Photos"];
    [self registerHandlerforDescription:@"“MyApp” Would Like to Access the Camera"];
}

夫特

addUIInterruptionMonitorWithDescription("Description") { (alert) -> Bool in
    alert.buttons["Allow"].tap()
    alert.buttons["OK"].tap()
    return true
}

答案 3 :(得分:1)

天啊!我讨厌XCTest处理UIView警报的最糟糕时间。我有一个应用程序,我收到2个警报,第一个要我选择"允许"为启用应用程序权限的位置服务,然后在启动页面上,用户必须按下名为&#34的UIButton;打开位置"最后在UIViewAlert中有一个通知短信警报,用户必须选择" OK"。我们遇到的问题是无法与系统警报进行交互,而是一种竞争条件,其中行为及其在屏幕上的外观是不合时宜的。似乎如果你使用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()
}

字符串"允许"被完全忽略,app.tap()的逻辑称为evreytime,警报在视野中,最后我想要到达的按钮["打开位置"]可以访问并且测试通过

〜完全糊涂了,谢谢Apple。

答案 4 :(得分:1)

我发现可靠地修复此问题的唯一方法是设置两个单独的测试来处理警报。在第一次测试中,我调用app.tap()而不执行任何其他操作。在第二次测试中,我再次致电app.tap(),然后进行实际工作。

答案 5 :(得分:1)

对于那些正在寻找特定系统对话框(如我所做)的特定描述的人来说,没有:)这个字符串仅用于测试人员跟踪目的。相关的苹果文档链接:https://developer.apple.com/documentation/xctest/xctestcase/1496273-adduiinterruptionmonitor

更新:xcode 9.2

有时不会触发该方法。对我来说最好的解决方法是,当我知道会有系统警报时,我添加:

sleep(2)
app.tap()

并且系统警报消失了

答案 6 :(得分:0)

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

答案 7 :(得分:0)

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

我只想指出一件事,那就是 UIInterruptionMonitor 捕获 TOGETHER 系列警告所有系统警报,所以您在完成处理程序中应用的操作将应用于每个警报("不允许" " OK" )。如果要以不同方式处理警报操作,则必须在完成处理程序内检查当前呈现的警报,例如通过检查其静态文本,然后该操作将仅应用于该警报。

这是一个小代码段,用于在第二个警报中应用"不允许" 操作,以及 "好的" 对剩下的两个采取行动:

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()

答案 8 :(得分:0)

这是一个古老的问题,但是现在有了另一种处理这些警报的方法。

无法从启动应用程序的 app上下文访问系统警报,但是您仍然可以访问该应用程序上下文。看这个简单的例子:

func testLoginHappyPath() {
    let app = XCUIApplication()
    app.textFields["Username"].typeText["Billy"]
    app.secureTextFields["Password"].typeText["hunter2"]
    app.buttons["Log In"].tap()
}

在已经启动了模拟器并且已经授予或拒绝了权限的真空中,这将起作用。但是,如果我们将其放在具有全新模拟器的CI管道中,突然之间将无法找到该用户名字段,因为会弹出通知警报。

因此,现在有3种处理方式的选择:

含蓄地

已经有一个默认的系统警报中断处理程序。因此,从理论上讲,仅尝试在该第一个字段上键入Text即可检查是否有中断事件,并肯定地处理该事件。

如果一切都按设计工作,则无需编写任何代码,但是您会看到日志记录并处理了中断,并且测试将花费几秒钟。

通过中断监视器明确显示

我不会重写以前的工作,但是您可以在此处明确设置一个中断监视器来处理弹出的特定警报-或您希望发生的任何警报。

如果内置处理程序无法执行您想要的操作-或根本不起作用,这很有用。

通过XCUITest框架明确

在xCode 9.0及更高版本中,您只需定义多个XCUIApplication()实例即可在应用程序上下文之间流畅地切换。然后,您可以通过熟悉的方法找到所需的字段。因此,明确地执行此操作应如下所示:

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()
}

答案 9 :(得分:-1)

听起来像实现摄像头访问和通知的方法是如您所说的那样是线程化的,但没有物理管理,并且不知何时以及如何显示它们。

我怀疑一个是由另一个触发的,当它以编程方式点击时,它也消灭了另一个(Apple可能永远不会允许)

想想你是否要求用户许可然后代表他们做出决定?为什么?因为您无法让代码工作。

如何修复 - 追踪这两个组件触发弹出对话的位置 - 它们在哪里被调用?,重写只触发一个,当一个对话完成时发送NSNotification以触发并显示剩下的一个。< / p>

我会严重阻止以编程方式点击为用户设置的对话框按钮的方法。