我如何编写一个 UI 测试来启动带有推送通知有效负载的应用程序并验证您是否被路由到正确的视图?

How do I write a UI test that launches the app with a push notification payload and verifies that you are routed to the correct view?

我正在 iOS 应用程序中实现推送通知,在这样做的同时,我想编写一个 UI 测试来验证该应用程序在它出现时是否执行正确的操作使用特定的推送通知负载启动(即应用程序导航到正确的 table 视图并突出显示正确的单元格)。

这能做到吗?我似乎找不到任何人以前做过这个或以前问过这个问题。

感谢任何指点。

您可以使用此 library 发送通知。不幸的是,您应该将与测试相关的代码添加到您的 AppDelegate-class 中。我在单独的应用程序目标 (UITEST=1) 中使用自定义预处理器宏。

您代码中的某处:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // ...
    #if UITEST
        // setup listening
    #endif
}

使用 Xcode 9,您现在可以在 UITest 中实际测试远程通知处理。我使用一个名为 NWPusher

的框架实现了它

我写了很长blogpost about my implementation and added a demo project to github.

以下是我所做的简短描述:

准备

  1. 将 NWPusher 添加到您的 UITest 目标(我使用的是 Carthage)
  2. 从 Apple 的开发中心为您的应用程序下载 APN 开发证书
  3. 在钥匙串中打开该证书并将其导出为 p12 文件
  4. 将此文件添加到 IUTest 目标
  5. 使 deviceToken 可用于 UITestRunner

编写测试

测试执行以下步骤:

  1. 创建对应用程序和 Springboard 的引用
  2. 启动应用程序并点击主页按钮将其关闭(如果系统弹出请求权限的对话框,请将其关闭)
  3. 触发远程通知(使用 NWPusher)
  4. 从 Springboard 查询远程通知横幅并点击它
  5. 测试您的应用是否正确处理了远程通知
  6. 关闭应用程序并测试下一种远程通知

在我的演示中,不同类型的通知会在应用程序中触发不同颜色的模态视图控制器。所以我的测试 class 看起来像这样

import XCTest
import PusherKit

class PushNotificationUITests: XCTestCase {

    override func setUp() {
        super.setUp()
        continueAfterFailure = false
    }

    func testPushNotifications() {
        let app = XCUIApplication()
        app.launchArguments.append("isRunningUITests")
        let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

        app.launch()

        // dismiss the system dialog if it pops up
        allowPushNotificationsIfNeeded()

        // get the current deviceToken from the app
        let deviceToken = app.staticTexts.element(matching: .any, identifier: "tokenLabel").label

        // close app
        XCUIDevice.shared.press(XCUIDevice.Button.home)
        sleep(1)

        // trigger red Push Notification
        triggerPushNotification(
            withPayload: "{\"aps\":{\"alert\":\"Hello Red\"}, \"vcType\":\"red\"}",
            deviceToken: deviceToken)

        // tap on the notification when it is received
        springboard.otherElements["PUSHNOTIFICATION, now, Hello Red"].tap()

        // check if the red view controller is shown
        XCTAssert(app.staticTexts["Red"].exists)

        // dismiss modal view controller and close app
        app.buttons["Close"].tap()
        XCUIDevice.shared.press(XCUIDevice.Button.home)
        sleep(1)

        // trigger green Push Notification
        triggerPushNotification(
            withPayload: "{\"aps\":{\"alert\":\"Hello Green\"}, \"vcType\":\"green\"}",
            deviceToken: deviceToken)

        // tap on the notification when it is received
        springboard.otherElements["PUSHNOTIFICATION, now, Hello Green"].tap()

        // check if the green view controller is shown
        XCTAssert(app.staticTexts["Green"].exists)

        // dismiss modal view controller and close app
        app.buttons["Close"].tap()
        XCUIDevice.shared.press(XCUIDevice.Button.home)
        sleep(1)

        // trigger blue Push Notification
        triggerPushNotification(
            withPayload: "{\"aps\":{\"alert\":\"Hello Blue\"}, \"vcType\":\"blue\"}",
            deviceToken: deviceToken)

        // tap on the notification when it is received
        springboard.otherElements["PUSHNOTIFICATION, now, Hello Blue"].tap()

        // check if the blue view controller is shown
        XCTAssert(app.staticTexts["Blue"].exists)

        // dismiss modal view controller 
        app.buttons["Close"].tap()
    }
}

extension XCTestCase {
    func triggerPushNotification(withPayload payload: String, deviceToken: String) {
        let uiTestBundle = Bundle(for: PushNotificationUITests.self)
        guard let url = uiTestBundle.url(forResource: "pusher.p12", withExtension: nil) else { return }

        do {
            let data = try Data(contentsOf: url)
            let pusher = try NWPusher.connect(withPKCS12Data: data, password: "pusher", environment: .auto)
            try pusher.pushPayload(payload, token: deviceToken, identifier: UInt(arc4random_uniform(UInt32(999))))
        } catch {
            print(error)
        }
    }

    func allowPushNotificationsIfNeeded() {
        addUIInterruptionMonitor(withDescription: "“RemoteNotification” Would Like to Send You Notifications") { (alerts) -> Bool in
            if(alerts.buttons["Allow"].exists){
                alerts.buttons["Allow"].tap();
            }
            return true;
        }
        XCUIApplication().tap()
    }
}

这只适用于物理设备,因为远程通知在模拟器中不起作用。

基于 joern amazing article, I took a step forward 并找到了一种以编程方式与接收到的通知进行交互的方法,因为它被 XCTest 框架标识为 XCUIElement。

因为我们可以获得对 Spinboard 的引用

let springboard = XCUIApplication(bundleIdentifier: "com.apple.springboard")

例如,当将应用程序置于后台时,我们可以获得对收到的通知的引用(当它显示在屏幕顶部时),如下所示:

let notification = springboard.otherElements["NotificationShortLookView"]

允许我们点击通知:

notification.tap()

将其下拉以查看其操作(如果有的话。也可以通过这样做让我们看到富通知的内容):

notification.swipeDown()

与其动作互动:

let action = springboard.buttons["ACTION BUTTON TITLE"]
action.tap()

甚至与文本输入通知操作交互(在示例中,通过其占位符获取对通知文本字段的引用,您可以在代码中定义):

let notificationTextfield = springboard.textFields["Placeholder"]
notificationTextfield.typeText("this is a test message")

最后,您还可以获取通知关闭按钮的引用以关闭它:

let closeButton = springboard.buttons["Dismiss"]
closeButton.tap()

通过能够自动化这种交互,我们可以测试,例如分析,如本 article 中所述。