测试人社区

106_Retrieving Status Bar Notifications for Android Devices

From by appiumpro


For most of the history of Appium, it has not been possible to inspect notifications received on an Android device, especially the “status bar” notifications you can only look at by pulling them down from the status bar. To automate verification of notifications received, the brute-force approach meant navigating to this notifications view and hoping to get lucky reading the text elements on that page. More recently, the Appium team figured out a way to read notifications via the Appium Settings helper app. This is a little app which is installed alongside an Appium session, and facilitates device-level functionality that is not possible merely through UiAutomator2 or Espresso.

Basically, the Appium Settings app can be granted the permission to read any incoming notifications. It can then save these notifications for retrieval by the main Appium process using ADB. All of this complexity is wrapped up in one Appium command (first released in Appium 1.16):

driver.executeScript("mobile: getNotifications");

For this command to work, the Appium Settings app must of course be granted notification read permissions. This can be done manually in the “Notification Access” area of your Android system settings (the precise way to navigate to this view differs based on device). Or, you could automate it using Appium as part of your device provisioning steps.

At the moment, the return value for this method is a bit complex. In JSON, it would look something like this:

{
"statusBarNotifications": [
{
"isGroup":false,
"packageName":"io.appium.settings",
"isClearable":false,
"isOngoing":true,
"id":1,
"tag":null,
"notification":{
"title":null,
"bigTitle":"Appium Settings",
"text":null,
"bigText":"Keep this service running, so Appium for Android can properly interact with several system APIs",
"tickerText":null,
"subText":null,
"infoText":null,
"template":"android.app.Notification$BigTextStyle"
},
"userHandle":0,
"groupKey":"0|io.appium.settings|1|null|10133",
"overrideGroupKey":null,
"postTime":1576853518850,
"key":"0|io.appium.settings|1|null|10133",
"isRemoved":false
}
]
}

But of course, in the Appium Java client, we will not get a JSON response, so it will be our job to cast the response appropriately:

Map<String, Object> notifications = (Map<String, Object>)driver.executeScript("mobile: getNotifications");

And we’ll need to keep casting as we navigate through the object. Basically, we have a main key statusBarNotifications , which returns an array of notification objects. Each of these notification objects itself has a lot of metadata associated with it (you can use this metadata to filter by the package ID of your app, for example). The notification key of this object has the main content we’re looking for, including the title and the text of the notification.

This technique is especially powerful because, having granted the special permission to Appium, you can read notifications for any app on the device.

That’s it! Check out a full example where we walk through each notification received since Appium started, and print it out to the console, making sure we appropriately choose the type of notification text and title which has content:

public class Edition106_Android_Notifications {
private String APP = "https://github.com/cloudgrey-io/the-app/releases/download/v1.10.0/TheApp-v1.10.0.apk";
private AndroidDriver driver;

@Before
public void setUp() throws Exception {
DesiredCapabilities capabilities = new DesiredCapabilities();

capabilities.setCapability("platformName", "Android");
capabilities.setCapability("deviceName", "Android Emulator");
capabilities.setCapability("automationName", "UiAutomator2");
capabilities.setCapability("app", APP);

driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"), capabilities);
}

@After
public void tearDown() {
if (driver != null) {
driver.quit();
}
}

@Test
@SuppressWarnings("unchecked")
public void testNotifications() {
Map<String, Object> res = (Map<String, Object>)driver.executeScript("mobile: getNotifications");
List<Map<String, Object>> notifications = (List<Map<String, Object>>)res.get("statusBarNotifications");
for (Map<String, Object> notification : notifications) {
Map<String, String> innerNotification = (Map<String, String>)notification.get("notification");
if (innerNotification.get("bigTitle") != null) {
System.out.println(innerNotification.get("bigTitle"));
} else {
System.out.println(innerNotification.get("title"));
}
if (innerNotification.get("bigText") != null) {
System.out.println(innerNotification.get("bigText"));
} else {
System.out.println(innerNotification.get("text"));
}
}
}
}