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?
Asked Answered
S

3

7

I'm implementing push notifications in an iOS app, and as I'm doing so, I want to write a UI test that verifies that the app does the correct thing when it is launched with a certain push notification payload (i.e. the app navigates to the correct table view and highlights the correct cell).

Can this be done? I can't seem to find anyone who has done this before or has asked this question before.

Thankful for any pointers.

Sandysandye answered 22/2, 2017 at 16:2 Comment(2)
Can you please add the code that handles the push notification payload when the user opens the app via push notification?Toothpick
Well, sure, I could, but it's very project-specific. The app I'm working on accepts push notifications of 6 different types, so first it tries to determine which type of push notification it has received. Then, depending on which type of notification it received, it navigates to the correct view. Sometimes this means one thing, and sometimes it means doing several things.Sandysandye
T
9

With Xcode 9 you can now actually test Remote Notification handling in a UITest. I implemented that using a framework called NWPusher

I wrote a long blogpost about my implementation and added a demo project to github.

Here is a short description of what I did:

Preparation

  1. Add NWPusher to your UITest target (I used Carthage)
  2. Download a APN Development Certificate for your app from Apple's Dev Center
  3. Open that certificate in Keychain and export it as p12 file
  4. Add this file to the IUTest target
  5. Make the deviceToken available to the UITestRunner

Write the Test

The test does the following steps:

  1. Create a reference to the app and the Springboard
  2. Launch the app and close it by tapping the home button (dismiss the system dialog asking for permission if it pops up)
  3. Trigger a remote notification (using NWPusher)
  4. Query the remote notification banner from the Springboard and tap it
  5. Test if the remote notifications has been handled correctly by your app
  6. Close the app and test the next type of remote notification

In my demo the different types of notifications trigger differently colored modal View Controller in the app. So my test class looks like this

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

This only works on a physical device, because remote notifications do not work in the simulator.

Toothpick answered 7/9, 2017 at 10:22 Comment(6)
Side question which I assume yuo might know: how do we tap "Allow" in alerts like "“AppNameHere” Would Like to Send You Notifications" ?Meissen
@Meissen Have a look at the allowPushNotificationsIfNeeded function in above code. It does exactly that ;-)Toothpick
@joem Ok I did not see it at first, sorry about that, makes sense why Google Sensei showed me here. Anyway, I tried allowPushNotificationsIfNeeded but that method never works (trying on Xcode 9, iOS 11)Meissen
What I mean is "XXX Would Ike to Send You Notifications" alerts always are left hanging with any addUIInterruptionMonitor attempt I made so far, including yours.Meissen
What's the exact text that you see in the alert? Is it “APP_NAME” Would Like to Send You Notifications or something else? You might have to change the text in the function to the text you are actually seeing the alert. Are you including the quotation marks?Toothpick
I did change the appname for what shows up on device/simulator, but it never helped. Also, looking at the reference of said function, it says: "An explanation of the behavior and purpose of this handler, mainly used for debugging and analysis." ... so it does sound like it should not matter; in any case it never worked for me. What I ended up doing, which only works in Simulator apparently, is: let systemAlerts = XCUIApplication(bundleIdentifier: "com.apple.springboard").alerts if systemAlerts.buttons["Allow"].exists { systemAlerts.buttons["Allow"].tap() }"Meissen
H
3

Based on joern amazing article, I took a step forward and found a way to programmatically interact with the received notification, as it is identified by the XCTest framework as a XCUIElement.

As we can get a reference to the Sprinboard

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

For example, when putting the app on the background we could get a reference to the received notification (while it is being displayed in the top of the screen) like this:

let notification = springboard.otherElements["NotificationShortLookView"]

Allowing us to tap the notification:

notification.tap()

Pull it down to see its actions (if any available. Also by doing this could allow us to see the content of a Rich Notification):

notification.swipeDown()

Interact with its actions:

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

And even interact with a Text Input notification action (in the example, by getting a reference to the notification textfield by its placeholder, which you can define in your code):

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

At last, you can also get a reference to the notification's close button in order to dismiss it:

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

By being able to automate this interactions we could test, for example analytics, as described in this article.

Hollandia answered 28/2, 2018 at 9:38 Comment(2)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewInoperable
@Inoperable Thanks for the review. Hopefully the latest changes on the answer make it qualify as a clear and valuable one.Consideration
I
0

You can send notification with this library. Unfortunately, you should add test-related code into your AppDelegate-class. I use custom preprocessor macros in separate application target (UITEST=1).

Build Settings Tab Somewhere in your code:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // ...
    #if UITEST
        // setup listening
    #endif
}
Incite answered 24/4, 2017 at 5:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.