appium-uiwatchers-plugin
v1.0.1
Published
Appium plugin to automatically handle unexpected UI elements (popups, banners, dialogs) during test execution
Maintainers
Readme
Appium UI Watchers Plugin
Introduction
Mobile apps often display unexpected UI elements — cookie consent dialogs, permission prompts, rating popups—that break test automation. Traditional approaches require explicit waits or try-catch blocks scattered throughout test code.
This plugin provides a centralized, declarative way to handle these interruptions automatically.
Motivation
Android's UiAutomator 1.0 had a UiWatcher API that automatically handled unexpected dialogs during tests. When the framework couldn't find an element, it would run registered watchers to dismiss blockers like ANR dialogs or permission prompts.
This feature was deprecated and never made it to UiAutomator 2.0. This plugin brings the same concept to Appium — working across both Android and iOS.
Features
- Zero Wait Overhead — No waiting for UI elements that may never appear
- Centralized Management — Register watchers once, apply across entire session
- Priority-Based Execution — Higher priority watchers are checked first
- Cooldown Support — Wait for stable UI after dismissing an element
- Auto-Expiry — Watchers automatically expire after specified duration
- One-Shot or Continuous — Stop after first trigger or keep watching
- Transparent Interception — Handles on findElement, findElements, and element actions automatically
- Session-Scoped — Each session maintains its own independent set of watchers
Installation
From npm
appium plugin install appium-uiwatchers-pluginFrom local source
appium plugin install --source=local /path/to/appium-uiwatchers-pluginVerify installation
appium plugin listYou should see uiwatchers in the installed plugins list.
Activate the plugin
appium --use-plugins=uiwatchersQuick Start
Note: Ensure Appium server is started with
--use-plugins=uiwatchers
JavaScript
// Register a watcher for cookie consent
await driver.execute('mobile: registerUIWatcher', {
name: 'cookie-consent',
referenceLocator: { using: 'id', value: 'com.app:id/cookie_banner' },
actionLocator: { using: 'id', value: 'com.app:id/accept_button' },
});
// Run tests - watchers trigger automatically on findElement/findElements
await driver.findElement('id', 'com.app:id/login_button');Java
// Register a watcher for cookie consent
Map<String, Object> watcherParams = new HashMap<>();
watcherParams.put("name", "cookie-consent");
watcherParams.put("referenceLocator", Map.of("using", "id", "value", "com.app:id/cookie_banner"));
watcherParams.put("actionLocator", Map.of("using", "id", "value", "com.app:id/accept_button"));
driver.executeScript("mobile: registerUIWatcher", watcherParams);
// Run tests - watchers trigger automatically on findElement/findElements
driver.findElement(By.id("com.app:id/login_button"));API Reference
mobile: registerUIWatcher
Registers a new UI watcher for automatic element handling.
Parameters
| Parameter | Type | Required | Default | Description | | ---------------- | ------- | -------- | ------- | ---------------------------------------- | | name | string | Yes | - | Unique watcher identifier | | referenceLocator | object | Yes | - | Element to detect (trigger condition) | | actionLocator | object | Yes | - | Element to click when triggered | | duration | number | Yes | - | Auto-expiry time in ms (max: 60000) | | priority | number | No | 0 | Execution order (higher = first) | | stopOnFound | boolean | No | false | Deactivate after first trigger | | cooldownMs | number | No | 0 | Wait time after action before re-trigger |
Locator Object
| Property | Type | Description |
| -------- | ------ | ------------------------------------------------- |
| using | string | Strategy: id, xpath, accessibility id, etc. |
| value | string | Locator value |
Example
{
"name": "cookie-consent",
"priority": 10,
"referenceLocator": {
"using": "id",
"value": "com.app:id/cookie_banner"
},
"actionLocator": {
"using": "id",
"value": "com.app:id/accept_button"
},
"duration": 60000,
"stopOnFound": false,
"cooldownMs": 5000
}Response
{
"success": true,
"watcher": {
"name": "cookie-consent",
"priority": 10,
"registeredAt": 1704067200000,
"expiresAt": 1704067260000,
"status": "active"
}
}mobile: unregisterUIWatcher
Removes a registered watcher by name.
Parameters
| Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------- | | name | string | Yes | Name of the watcher to remove |
Example
{
"name": "cookie-consent"
}Response
{
"success": true,
"removed": "cookie-consent"
}mobile: listUIWatchers
Returns all registered watchers with their current state.
Parameters
None.
Response
{
"success": true,
"watchers": [
{
"name": "cookie-consent",
"priority": 10,
"referenceLocator": { "using": "id", "value": "com.app:id/cookie_banner" },
"actionLocator": { "using": "id", "value": "com.app:id/accept_button" },
"duration": 60000,
"stopOnFound": false,
"cooldownMs": 5000,
"registeredAt": 1704067200000,
"expiresAt": 1704067260000,
"status": "active",
"triggerCount": 2,
"lastTriggeredAt": 1704067230000
}
],
"totalCount": 1
}mobile: clearAllUIWatchers
Removes all registered watchers for the current session.
Parameters
None.
Response
{
"success": true,
"removedCount": 3
}mobile: enableUIWatchers
Enables watcher checking (enabled by default).
Parameters
None.
Response
{
"success": true,
"message": "UI Watchers enabled"
}mobile: disableUIWatchers
Temporarily disables watcher checking without removing watchers.
Parameters
None.
Response
{
"success": true,
"message": "UI Watchers disabled"
}Configuration
Plugin behavior can be customized via CLI options when starting Appium server.
| Option | Type | Range | Default | Description | | ------------------------------------- | ------- | ----------- | ------- | --------------------------------- | | --plugin-uiwatchers-max-watchers | integer | 1-20 | 5 | Maximum watchers per session | | --plugin-uiwatchers-max-duration-ms | integer | 1000-600000 | 60000 | Maximum watcher duration in ms | | --plugin-uiwatchers-max-cache-entries | integer | 10-200 | 50 | Maximum cached element references | | --plugin-uiwatchers-element-ttl-ms | integer | 5000-300000 | 60000 | Cache entry TTL in ms |
Example
appium --use-plugins=uiwatchers \
--plugin-uiwatchers-max-watchers=10 \
--plugin-uiwatchers-max-duration-ms=120000 \
--plugin-uiwatchers-max-cache-entries=100 \
--plugin-uiwatchers-element-ttl-ms=30000How It Works
Watcher Checking Flow
flowchart TD
A[findElement / findElements] --> B{Command Success?}
B -->|Yes| C[Cache element reference]
C --> D[Return result]
B -->|No / Empty| E[Check watchers by priority]
E --> F{Reference element found?}
F -->|No| G{More watchers?}
G -->|Yes| E
F -->|Yes| H[Click action element]
H --> I[Apply cooldown]
I --> G
G -->|No| J[Retry original command]
J --> DStale Element Recovery Flow
flowchart TD
A[Element action: click, getText, etc.] --> B{StaleElementReferenceException?}
B -->|No| C[Return result]
B -->|Yes| D[Check watchers]
D --> E{Watcher triggered?}
E -->|No| F[Throw original exception]
E -->|Yes| G[Lookup cached locator]
G --> H{Found in cache?}
H -->|No| F
H -->|Yes| I[Re-find element using locator]
I --> J[Map old ID → new ID]
J --> K[Retry action with new element]
K --> CDevelopment
Prerequisites
- Node.js 18+
- Appium 3.x
Setup
# Clone the repository
git clone https://github.com/rajvinodh/appium-uiwatchers-plugin.git
cd appium-uiwatchers-plugin
# Install dependencies
npm install
# Build
npm run buildScripts
| Script | Description |
| ----------------------- | ---------------------------------- |
| npm run build | Compile TypeScript to JavaScript |
| npm run test | Run all tests (unit + integration) |
| npm run test:unit | Run unit tests only |
| npm run test:coverage | Run tests with coverage report |
| npm run lint | Run ESLint and Prettier checks |
| npm run lint:fix | Auto-fix linting issues |
Project Structure
├── src/ # TypeScript source code
│ ├── plugin.ts # Main plugin class
│ ├── watcher-store.ts # Watcher state management
│ ├── watcher-checker.ts# Watcher execution logic
│ ├── element-cache.ts # Element reference caching
│ ├── commands/ # Command implementations
│ ├── config.ts # Configuration defaults
│ ├── types.ts # Type definitions
│ └── utils.ts # Utility functions
├── test/
│ ├── unit/ # Unit tests
│ └── integration/ # Integration tests
└── lib/ # Compiled outputContributing
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Make your changes
- Run tests (
npm run test) - Run linting (
npm run lint) - Commit your changes
- Push to your branch
- Open a Pull Request
