Swift UI Test - User Notifications System Alert

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?

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.

Tapping system alert using EarlGrey 2.0 & Swift

Here's the full code of a test accepting native system alert in Swift

func testExample() {
let app = XCUIApplication()
app.launch()
XCTAssertTrue(grey_wait(forAlertVisibility: true, withTimeout: 2))
XCTAssertTrue(grey_acceptSystemDialogWithError(nil))
XCTAssertTrue(grey_wait(forAlertVisibility: false, withTimeout: 1))
}

Xcode 7 UI Testing: how to dismiss a series of system alerts in code

Xcode 7.1

Xcode 7.1 has finally fixed the issue with system alerts. There are, however, two small gotchas.

First, you need to set up a "UI Interuption Handler" before presenting the alert. This is our way of telling the framework how to handle an alert when it appears.

Second, after presenting the alert you must interact with the interface. Simply tapping the app works just fine, but is required.

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

The "Location Dialog" is just a string to help the developer identify which handler was accessed, it is not specific to the type of alert.

I believe that returning true from the handler marks it as "complete", which means it won't be called again. For your situation I would try returning false so the second alert will trigger the handler again.

Xcode 7.0

The following will dismiss a single "system alert" in Xcode 7 Beta 6:

let app = XCUIApplication()
app.launch()
// trigger location permission dialog

app.alerts.element.collectionViews.buttons["Allow"].tap()

Beta 6 introduced a slew of fixes for UI Testing and I believe this was one of them.

Also note that I am calling -element directly on -alerts. Calling -element on an XCUIElementQuery forces the framework to choose the "one and only" matching element on the screen. This works great for alerts where you can only have one visible at a time. However, if you try this for a label and have two labels the framework will raise an exception.



Related Topics



Leave a reply



Submit