Skip to main content

Testing Push Notifications with Appium

Push notifications are a critical part of many mobile applications. With TestingBot, you can simulate and interact with push notifications on both Android and iOS devices during your Appium tests.

Prerequisite: Your app must request push notification permission and the user must allow it before notifications can be displayed. To handle this automatically during tests, set the autoGrantPermissions capability (Android) or autoAcceptAlerts capability (iOS). If your app has not registered for notifications, none of the approaches below will show a visible notification.

// Android: auto-grant all permissions including notifications
options.setCapability("appium:autoGrantPermissions", true);

// iOS: auto-accept permission dialogs (e.g. "Allow Notifications")
options.setCapability("appium:autoAcceptAlerts", true);

Which method should I use?

  • Android — Trigger real push notifications via FCM or use ADB shell commands via Appium's mobile: shell on emulators and real devices.
  • iOS Simulators — Use Appium's mobile: pushNotification command to send simulated notifications with a JSON payload.
  • iOS Real Devices — Trigger push notifications via your backend server or a third-party service (APNs, Firebase) to the device under test.
  • Interact with Notifications — Open the notification shade or banner and tap on notifications during your test.

Android Push Notifications

There are two approaches to test push notifications on Android with TestingBot:

Trigger via your backend (recommended)
Send a real push notification through FCM (Firebase Cloud Messaging) to the app running on the device. The notification appears with the correct app icon and package attribution, exactly as your end users would see it.
Simulate via ADB shell
Use Appium's mobile: shell command to post a system notification. This is a quick way to test notification interaction (tapping, swiping), but the notification appears as a generic system notification without your app's icon.

Option 1: Trigger a real notification via FCM (recommended)

If your app uses Firebase Cloud Messaging, you can send a test push notification by calling the FCM HTTP v1 API during your test. The notification will be delivered to your app on the TestingBot device, showing the correct app icon and notification channel.

You'll need a Firebase service account key (JSON file) for your project and the FCM registration token from the device. Most apps log the registration token on startup — you can retrieve it from logcat or have your app expose it via a test endpoint.

Your app must already be set up to receive FCM notifications. This approach sends a real notification through your existing push infrastructure.

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.options.UiAutomator2Options;

import java.net.URL;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;

public class PushNotificationTest {
  public static void main(String[] args) throws Exception {
    UiAutomator2Options options = new UiAutomator2Options();
    options.setDeviceName("Pixel 8");
    options.setPlatformVersion("14.0");
    options.setApp("tb://your_app_id");

    Map<String, Object> tbOptions = new HashMap<>();
    tbOptions.put("key", "api_key");
    tbOptions.put("secret", "api_secret");
    tbOptions.put("name", "Push Notification Test");
    tbOptions.put("realDevice", true);
    options.setCapability("tb:options", tbOptions);

    AndroidDriver driver = new AndroidDriver(
        new URL("https://hub.testingbot.com/wd/hub"), options);

    // Retrieve the FCM registration token from your app
    // (e.g. via a test endpoint or logcat)
    String fcmToken = "DEVICE_FCM_REGISTRATION_TOKEN";

    // Send a real push notification via FCM HTTP v1 API
    String projectId = "your-firebase-project-id";
    String accessToken = "YOUR_OAUTH2_ACCESS_TOKEN";

    String json = "{"
        + "\"message\": {"
        + "  \"token\": \"" + fcmToken + "\","
        + "  \"notification\": {"
        + "    \"title\": \"Order Update\","
        + "    \"body\": \"Your order has been shipped!\""
        + "  }"
        + "}"
        + "}";

    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create("https://fcm.googleapis.com/v1/projects/"
            + projectId + "/messages:send"))
        .header("Authorization", "Bearer " + accessToken)
        .header("Content-Type", "application/json")
        .POST(HttpRequest.BodyPublishers.ofString(json))
        .build();

    HttpClient.newHttpClient().send(request,
        HttpResponse.BodyHandlers.ofString());

    Thread.sleep(3000);
    driver.quit();
  }
}
from appium import webdriver
from appium.options.android import UiAutomator2Options
import requests
import time

options = UiAutomator2Options()
options.device_name = 'Pixel 8'
options.platform_version = '14.0'
options.app = 'tb://your_app_id'
options.set_capability('tb:options', {
    'key': 'api_key',
    'secret': 'api_secret',
    'name': 'Push Notification Test',
    'realDevice': True
})

driver = webdriver.Remote(
    command_executor='https://hub.testingbot.com/wd/hub',
    options=options)

# Retrieve the FCM registration token from your app
fcm_token = 'DEVICE_FCM_REGISTRATION_TOKEN'

# Send a real push notification via FCM HTTP v1 API
project_id = 'your-firebase-project-id'
access_token = 'YOUR_OAUTH2_ACCESS_TOKEN'

response = requests.post(
    f'https://fcm.googleapis.com/v1/projects/{project_id}/messages:send',
    headers={
        'Authorization': f'Bearer {access_token}',
        'Content-Type': 'application/json'
    },
    json={
        'message': {
            'token': fcm_token,
            'notification': {
                'title': 'Order Update',
                'body': 'Your order has been shipped!'
            }
        }
    }
)

time.sleep(3)
driver.quit()
const { remote } = require('webdriverio');

const capabilities = {
  'platformName': 'Android',
  'appium:app': 'tb://your_app_id',
  'appium:deviceName': 'Pixel 8',
  'appium:platformVersion': '14.0',
  'appium:automationName': 'UiAutomator2',
  'tb:options': {
    'name': 'Push Notification Test',
    'realDevice': true
  }
};

const driver = await remote({
  hostname: 'hub.testingbot.com',
  port: 443,
  protocol: 'https',
  path: '/wd/hub',
  user: 'api_key',
  key: 'api_secret',
  capabilities
});

// Retrieve the FCM registration token from your app
const fcmToken = 'DEVICE_FCM_REGISTRATION_TOKEN';

// Send a real push notification via FCM HTTP v1 API
const projectId = 'your-firebase-project-id';
const accessToken = 'YOUR_OAUTH2_ACCESS_TOKEN';

await fetch(
  `https://fcm.googleapis.com/v1/projects/${projectId}/messages:send`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      message: {
        token: fcmToken,
        notification: {
          title: 'Order Update',
          body: 'Your order has been shipped!'
        }
      }
    })
  }
);

await driver.pause(3000);
await driver.deleteSession();
require 'appium_lib'
require 'net/http'
require 'json'

caps = {
  'platformName' => 'Android',
  'appium:app' => 'tb://your_app_id',
  'appium:deviceName' => 'Pixel 8',
  'appium:platformVersion' => '14.0',
  'appium:automationName' => 'UiAutomator2',
  'tb:options' => {
    'key' => 'api_key',
    'secret' => 'api_secret',
    'name' => 'Push Notification Test',
    'realDevice' => true
  }
}

driver = Appium::Driver.new({
  caps: caps,
  appium_lib: {
    server_url: 'https://hub.testingbot.com/wd/hub'
  }
}).start_driver

# Retrieve the FCM registration token from your app
fcm_token = 'DEVICE_FCM_REGISTRATION_TOKEN'

# Send a real push notification via FCM HTTP v1 API
project_id = 'your-firebase-project-id'
access_token = 'YOUR_OAUTH2_ACCESS_TOKEN'

uri = URI("https://fcm.googleapis.com/v1/projects/#{project_id}/messages:send")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri)
request['Authorization'] = "Bearer #{access_token}"
request['Content-Type'] = 'application/json'
request.body = {
  message: {
    token: fcm_token,
    notification: {
      title: 'Order Update',
      body: 'Your order has been shipped!'
    }
  }
}.to_json

http.request(request)

sleep 3
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Android;
using System.Text;
using System.Text.Json;

var options = new AppiumOptions();
options.PlatformName = "Android";
options.AddAdditionalAppiumOption("appium:app", "tb://your_app_id");
options.AddAdditionalAppiumOption("appium:deviceName", "Pixel 8");
options.AddAdditionalAppiumOption("appium:platformVersion", "14.0");
options.AddAdditionalAppiumOption("appium:automationName", "UiAutomator2");

var tbOptions = new Dictionary<string, object>
{
    ["key"] = "api_key",
    ["secret"] = "api_secret",
    ["name"] = "Push Notification Test",
    ["realDevice"] = true
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);

var driver = new AndroidDriver(
    new Uri("https://hub.testingbot.com/wd/hub"), options);

// Retrieve the FCM registration token from your app
var fcmToken = "DEVICE_FCM_REGISTRATION_TOKEN";

// Send a real push notification via FCM HTTP v1 API
var projectId = "your-firebase-project-id";
var accessToken = "YOUR_OAUTH2_ACCESS_TOKEN";

var payload = JsonSerializer.Serialize(new {
    message = new {
        token = fcmToken,
        notification = new {
            title = "Order Update",
            body = "Your order has been shipped!"
        }
    }
});

using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}");
var content = new StringContent(payload, Encoding.UTF8, "application/json");
await client.PostAsync(
    $"https://fcm.googleapis.com/v1/projects/{projectId}/messages:send",
    content);

Thread.Sleep(3000);
driver.Quit();

Option 2: Quick test with ADB shell

For quick testing of notification interaction (tapping, swiping away), you can post a system notification via ADB. This uses Appium's mobile: shell command.

Notifications created with cmd notification post are generic system notifications. They will not display your app's icon or appear as coming from your app. Use the FCM approach above if you need app-branded notifications.

// Post a generic system notification
Map<String, Object> shellArgs = new HashMap<>();
shellArgs.put("command", "cmd");
shellArgs.put("args", new String[]{
    "notification", "post", "-S", "messaging",
    "-t", "Test Title", "Test Message"});
driver.executeScript("mobile: shell", shellArgs);

// Post a notification targeting a specific package (shows app icon on some Android versions)
Map<String, Object> packageArgs = new HashMap<>();
packageArgs.put("command", "cmd");
packageArgs.put("args", new String[]{
    "notification", "post", "-S", "messaging",
    "--package", "com.example.myapp",
    "-t", "Order Update", "Your order has been shipped!"});
driver.executeScript("mobile: shell", packageArgs);

To use mobile: shell on TestingBot, set the adbExecTimeout capability if your command takes longer than the default timeout.

iOS Simulator Notifications

On iOS simulators, Appium provides the mobile: pushNotification command that accepts a JSON payload matching the Apple Push Notification format. This allows you to test how your app handles push notifications without needing a real APNs connection.

The mobile: pushNotification command only works on iOS simulators, not on real iOS devices. For real device testing, see the iOS Real Devices section.

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.ios.options.XCUITestOptions;

import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class IOSPushNotificationTest {
  public static void main(String[] args) throws Exception {
    XCUITestOptions options = new XCUITestOptions();
    options.setDeviceName("iPhone 16");
    options.setPlatformVersion("18.0");
    options.setApp("tb://your_app_id");

    Map<String, Object> tbOptions = new HashMap<>();
    tbOptions.put("key", "api_key");
    tbOptions.put("secret", "api_secret");
    tbOptions.put("name", "iOS Push Notification Test");
    options.setCapability("tb:options", tbOptions);

    IOSDriver driver = new IOSDriver(
        new URL("https://hub.testingbot.com/wd/hub"), options);

    // Simulate a push notification
    Map<String, Object> payload = new HashMap<>();
    payload.put("bundleId", "com.example.myapp");

    Map<String, Object> alert = new HashMap<>();
    alert.put("title", "New Message");
    alert.put("body", "You have a new message from TestingBot");
    alert.put("subtitle", "Testing");

    Map<String, Object> aps = new HashMap<>();
    aps.put("alert", alert);
    aps.put("badge", 1);
    aps.put("sound", "default");

    Map<String, Object> payloadData = new HashMap<>();
    payloadData.put("aps", aps);
    payload.put("payload", payloadData);

    driver.executeScript("mobile: pushNotification", payload);

    Thread.sleep(2000);
    driver.quit();
  }
}
from appium import webdriver
from appium.options.ios import XCUITestOptions
import time

options = XCUITestOptions()
options.device_name = 'iPhone 16'
options.platform_version = '18.0'
options.app = 'tb://your_app_id'
options.set_capability('tb:options', {
    'key': 'api_key',
    'secret': 'api_secret',
    'name': 'iOS Push Notification Test'
})

driver = webdriver.Remote(
    command_executor='https://hub.testingbot.com/wd/hub',
    options=options)

# Simulate a push notification
driver.execute_script('mobile: pushNotification', {
    'bundleId': 'com.example.myapp',
    'payload': {
        'aps': {
            'alert': {
                'title': 'New Message',
                'body': 'You have a new message from TestingBot',
                'subtitle': 'Testing'
            },
            'badge': 1,
            'sound': 'default'
        }
    }
})

time.sleep(2)
driver.quit()
const { remote } = require('webdriverio');

const capabilities = {
  'platformName': 'iOS',
  'appium:app': 'tb://your_app_id',
  'appium:deviceName': 'iPhone 16',
  'appium:platformVersion': '18.0',
  'appium:automationName': 'XCUITest',
  'tb:options': {
    'name': 'iOS Push Notification Test'
  }
};

const driver = await remote({
  hostname: 'hub.testingbot.com',
  port: 443,
  protocol: 'https',
  path: '/wd/hub',
  user: 'api_key',
  key: 'api_secret',
  capabilities
});

// Simulate a push notification
await driver.execute('mobile: pushNotification', {
  bundleId: 'com.example.myapp',
  payload: {
    aps: {
      alert: {
        title: 'New Message',
        body: 'You have a new message from TestingBot',
        subtitle: 'Testing'
      },
      badge: 1,
      sound: 'default'
    }
  }
});

await driver.pause(2000);
await driver.deleteSession();
require 'appium_lib'

caps = {
  'platformName' => 'iOS',
  'appium:app' => 'tb://your_app_id',
  'appium:deviceName' => 'iPhone 16',
  'appium:platformVersion' => '18.0',
  'appium:automationName' => 'XCUITest',
  'tb:options' => {
    'key' => 'api_key',
    'secret' => 'api_secret',
    'name' => 'iOS Push Notification Test'
  }
}

driver = Appium::Driver.new({
  caps: caps,
  appium_lib: {
    server_url: 'https://hub.testingbot.com/wd/hub'
  }
}).start_driver

# Simulate a push notification
driver.execute_script('mobile: pushNotification', {
  bundleId: 'com.example.myapp',
  payload: {
    aps: {
      alert: {
        title: 'New Message',
        body: 'You have a new message from TestingBot',
        subtitle: 'Testing'
      },
      badge: 1,
      sound: 'default'
    }
  }
})

sleep 2
driver.quit
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.iOS;

var options = new AppiumOptions();
options.PlatformName = "iOS";
options.AddAdditionalAppiumOption("appium:app", "tb://your_app_id");
options.AddAdditionalAppiumOption("appium:deviceName", "iPhone 16");
options.AddAdditionalAppiumOption("appium:platformVersion", "18.0");
options.AddAdditionalAppiumOption("appium:automationName", "XCUITest");

var tbOptions = new Dictionary<string, object>
{
    ["key"] = "api_key",
    ["secret"] = "api_secret",
    ["name"] = "iOS Push Notification Test"
};
options.AddAdditionalAppiumOption("tb:options", tbOptions);

var driver = new IOSDriver(
    new Uri("https://hub.testingbot.com/wd/hub"), options);

// Simulate a push notification
var payload = new Dictionary<string, object>
{
    ["bundleId"] = "com.example.myapp",
    ["payload"] = new Dictionary<string, object>
    {
        ["aps"] = new Dictionary<string, object>
        {
            ["alert"] = new Dictionary<string, object>
            {
                ["title"] = "New Message",
                ["body"] = "You have a new message from TestingBot",
                ["subtitle"] = "Testing"
            },
            ["badge"] = 1,
            ["sound"] = "default"
        }
    }
};
driver.ExecuteScript("mobile: pushNotification", payload);

Thread.Sleep(2000);
driver.Quit();

Payload format

The mobile: pushNotification command accepts a payload that follows the Apple Push Notification format. The key fields are:

bundleId
The bundle identifier of the target app (e.g. com.example.myapp).
payload.aps.alert.title
The title text shown in the notification banner.
payload.aps.alert.body
The main message text of the notification.
payload.aps.badge
The badge number to display on the app icon.
payload.aps.sound
The notification sound. Use "default" for the standard notification sound.

You can also include custom key-value pairs in the payload for your app to process:

Map<String, Object> alert = new HashMap<>();
alert.put("title", "Order Update");
alert.put("body", "Your order #1234 has been shipped");

Map<String, Object> aps = new HashMap<>();
aps.put("alert", alert);

Map<String, Object> payload = new HashMap<>();
payload.put("aps", aps);
payload.put("orderId", "1234");
payload.put("deepLink", "/orders/1234");

Map<String, Object> pushArgs = new HashMap<>();
pushArgs.put("bundleId", "com.example.myapp");
pushArgs.put("payload", payload);

driver.executeScript("mobile: pushNotification", pushArgs);

Wait for a notification with mobile: expectNotification

The XCUITest driver also provides mobile: expectNotification, which blocks test execution until a specific notification is delivered. This is useful when you want to verify that your app posts a particular notification in response to an action, without polling for UI elements.

name (required)
The notification name to wait for (e.g. com.example.orderComplete).
type
"plain" (default) for standard NSNotificationCenter notifications, or "darwin" for system-level Darwin notifications.
timeoutSeconds
Maximum time to wait in seconds. Defaults to 60. Throws a timeout error if the notification is not received.
// Wait up to 10 seconds for a specific notification
Map<String, Object> expectArgs = new HashMap<>();
expectArgs.put("name", "com.example.myapp.orderComplete");
expectArgs.put("timeoutSeconds", 10);
driver.executeScript("mobile: expectNotification", expectArgs);

mobile: expectNotification listens for internal notifications (NSNotificationCenter / Darwin), not push notification banners. Use it to verify that your app's code reacts correctly to events, alongside mobile: pushNotification which triggers the visible notification.

iOS Real Device Notifications

Appium's mobile: pushNotification command does not work on real iOS devices because Apple only allows push notifications through APNs (Apple Push Notification service). To test push notifications on real iOS devices, you need to trigger them from your backend or via FCM/APNs.

Disable app re-signing

When you upload an iOS app to TestingBot, it is automatically re-signed with a TestingBot provisioning profile so it can be installed on our devices. This re-signing removes your app's push notification entitlements, which means APNs will reject push tokens from the re-signed app and notifications will not be delivered.

To test push notifications on real iOS devices, you must disable re-signing by setting resigningEnabled to false. This requires your app to be signed with an Apple Developer Enterprise certificate, which allows installation on any device without TestingBot's provisioning profile.

// Disable re-signing so push notification entitlements are preserved
Map<String, Object> tbOptions = new HashMap<>();
tbOptions.put("key", "api_key");
tbOptions.put("secret", "api_secret");
tbOptions.put("resigningEnabled", false);
options.setCapability("tb:options", tbOptions);

Without "resigningEnabled": false, your app's push entitlements will be stripped during re-signing and push notifications will silently fail on real iOS devices.

Trigger the notification

Once re-signing is disabled and your app retains its push entitlements, trigger notifications from your backend:

  1. Use your app's backend API — If your app has an endpoint that triggers push notifications, call it from your test to send a real notification to the device under test.
  2. Use Firebase Cloud Messaging (FCM) — Send a push notification via the FCM API. Your app must be configured to receive FCM messages.
  3. Use APNs directly — Send a notification via the Apple Push Notification service using a valid APNs certificate or key.

Example: Trigger via your backend API

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// After your app is running and the device token is registered,
// trigger a notification from your backend
String json = "{\"device_token\": \"DEVICE_TOKEN_FROM_APP\","
    + "\"title\": \"New Message\","
    + "\"body\": \"You have a new message\"}";

HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://your-api.example.com/send-notification"))
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString(json))
    .build();

HttpClient.newHttpClient().send(request,
    HttpResponse.BodyHandlers.ofString());

// Wait for the notification to arrive
Thread.sleep(3000);

// Now interact with the notification in your Appium test
// (see the "Interact with Notifications" section below)
import requests
import time

# After your app is running and the device token is registered,
# trigger a notification from your backend
requests.post('https://your-api.example.com/send-notification', json={
    'device_token': 'DEVICE_TOKEN_FROM_APP',
    'title': 'New Message',
    'body': 'You have a new message'
})

# Wait for the notification to arrive
time.sleep(3)

# Now interact with the notification in your Appium test
# (see the "Interact with Notifications" section below)
// After your app is running and the device token is registered,
// trigger a notification from your backend
await fetch('https://your-api.example.com/send-notification', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    device_token: 'DEVICE_TOKEN_FROM_APP',
    title: 'New Message',
    body: 'You have a new message'
  })
});

// Wait for the notification to arrive
await driver.pause(3000);

// Now interact with the notification in your Appium test
require 'net/http'
require 'json'

# After your app is running and the device token is registered,
# trigger a notification from your backend
uri = URI('https://your-api.example.com/send-notification')
Net::HTTP.post(uri, {
  device_token: 'DEVICE_TOKEN_FROM_APP',
  title: 'New Message',
  body: 'You have a new message'
}.to_json, 'Content-Type' => 'application/json')

# Wait for the notification to arrive
sleep 3

# Now interact with the notification in your Appium test

Interact with Notifications

After a notification is displayed, you can interact with it in your Appium test. The approach differs between Android and iOS.

Android: Open the notification shade

On Android, use openNotifications() to pull down the notification shade, then find and tap the notification element.

// Open the notification shade
driver.openNotifications();

// Wait for the notification to appear
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement notification = wait.until(ExpectedConditions
    .presenceOfElementLocated(By.xpath(
        "//*[contains(@text, 'Test Title')]")));

// Tap the notification
notification.click();
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Open the notification shade
driver.open_notifications()

# Wait for the notification to appear
wait = WebDriverWait(driver, 10)
notification = wait.until(EC.presence_of_element_located((
    AppiumBy.XPATH, "//*[contains(@text, 'Test Title')]"
)))

# Tap the notification
notification.click()
// Open the notification shade (WebdriverIO)
await driver.execute('mobile: openNotifications');

// Wait for the notification to appear
const notification = await driver.$('//*[contains(@text, "Test Title")]');
await notification.waitForExist({ timeout: 10000 });

// Tap the notification
await notification.click();
# Open the notification shade
driver.open_notifications

# Wait for the notification to appear
wait = Selenium::WebDriver::Wait.new(timeout: 10)
notification = wait.until {
  driver.find_element(:xpath, "//*[contains(@text, 'Test Title')]")
}

# Tap the notification
notification.click
// Open the notification shade
driver.OpenNotifications();

// Wait for the notification to appear
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var notification = wait.Until(d =>
    d.FindElement(By.XPath("//*[contains(@text, 'Test Title')]")));

// Tap the notification
notification.Click();

iOS: Tap the notification banner

On iOS, notifications appear as banners at the top of the screen. You can interact with them using the Notification Center or by tapping the banner directly.

// Wait for the notification banner to appear
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement banner = wait.until(ExpectedConditions
    .presenceOfElementLocated(By.name("NotificationShortLookView")));

// Tap the banner to open the notification
banner.click();
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Wait for the notification banner to appear
wait = WebDriverWait(driver, 10)
banner = wait.until(EC.presence_of_element_located((
    AppiumBy.ACCESSIBILITY_ID, 'NotificationShortLookView'
)))

# Tap the banner to open the notification
banner.click()
// Wait for the notification banner to appear
const banner = await driver.$('~NotificationShortLookView');
await banner.waitForExist({ timeout: 10000 });

// Tap the banner to open the notification
await banner.click();
# Wait for the notification banner to appear
wait = Selenium::WebDriver::Wait.new(timeout: 10)
banner = wait.until {
  driver.find_element(:accessibility_id, 'NotificationShortLookView')
}

# Tap the banner to open the notification
banner.click

Tips & Best Practices

Grant notification permissions

Your app needs notification permissions before it can display push notifications. Use the autoGrantPermissions capability (Android) or autoAcceptAlerts capability (iOS) to automatically handle permission dialogs. See the Permission Popups documentation for more details.

// Android: auto-grant all permissions
options.setCapability("appium:autoGrantPermissions", true);

// iOS: auto-accept permission dialogs
options.setCapability("appium:autoAcceptAlerts", true);

Wait for notifications to arrive

Push notifications are asynchronous. Always use explicit waits instead of fixed delays when checking for notifications. Use WebDriverWait to poll for the notification element rather than sleep(). On iOS simulators, you can also use mobile: expectNotification to block until an internal notification is delivered.

Use unique notification content

When searching for a notification in the notification shade, use unique text content (such as a timestamp or test ID) to avoid matching stale or system notifications.

Dismiss notifications after testing

After interacting with a notification, dismiss the notification shade (Android) or return to the app to continue your test flow.

// Android: press Back to close the notification shade
((AndroidDriver) driver).pressKey(new KeyEvent(AndroidKey.BACK));

// iOS: swipe up on the notification banner to dismiss
Map<String, Object> swipeArgs = new HashMap<>();
swipeArgs.put("direction", "up");
driver.executeScript("mobile: swipe", swipeArgs);

Frequently Asked Questions

Can I test push notifications on real iOS devices?

Yes, but you cannot simulate them using Appium's mobile: pushNotification command — that only works on iOS simulators. For real iOS devices, you need to trigger actual push notifications through your backend, Firebase Cloud Messaging (FCM), or APNs (Apple Push Notification service). Your test can then interact with the notification banner or Notification Center using Appium's standard element interaction commands. See the iOS Real Devices section for examples.

How do I simulate a push notification on Android with Appium?

On Android, use Appium's mobile: shell command with the ADB cmd notification post command. This sends a notification to the Android notification shade. Example: driver.execute_script('mobile: shell', {'command': 'cmd', 'args': ['notification', 'post', '-S', 'messaging', '-t', 'Title', 'Message']}). This works on both emulators and real Android devices on TestingBot.

How do I open the notification shade in an Appium test?

On Android, use driver.openNotifications() (Java/Python/Ruby/C#) or driver.execute('mobile: openNotifications') (WebdriverIO). This pulls down the system notification shade. On iOS, notifications appear as banners at the top of the screen — wait for the banner element and tap it directly using its accessibility identifier.

Why is my push notification not appearing during the test?

Common causes:

  1. The app has not been granted notification permissions — use autoGrantPermissions (Android) or autoAcceptAlerts (iOS) to handle permission dialogs.
  2. On iOS real devices, mobile: pushNotification only works on simulators — use your backend or FCM/APNs instead.
  3. The notification may take a few seconds to appear — use explicit waits instead of fixed sleeps.

See the Tips section for more details.

Was this page helpful?

Looking for More Help?

Have questions or need more information?
You can reach us via the following channels: