appium-uiwatchers-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.c8rc.json +12 -0
- package/.github/workflows/npm-publish.yml +28 -0
- package/.husky/pre-commit +4 -0
- package/.lintstagedrc.json +4 -0
- package/.mocharc.json +10 -0
- package/.prettierignore +6 -0
- package/.prettierrc +11 -0
- package/README.md +376 -0
- package/eslint.config.js +65 -0
- package/package.json +114 -0
- package/src/commands/clear.ts +28 -0
- package/src/commands/list.ts +23 -0
- package/src/commands/register.ts +47 -0
- package/src/commands/toggle.ts +43 -0
- package/src/commands/unregister.ts +43 -0
- package/src/config.ts +30 -0
- package/src/element-cache.ts +262 -0
- package/src/plugin.ts +437 -0
- package/src/types.ts +207 -0
- package/src/utils.ts +47 -0
- package/src/validators.ts +131 -0
- package/src/watcher-checker.ts +113 -0
- package/src/watcher-store.ts +210 -0
- package/test/e2e/config.e2e.spec.cjs +420 -0
- package/test/e2e/plugin.e2e.spec.cjs +312 -0
- package/test/unit/element-cache.spec.js +269 -0
- package/test/unit/plugin.spec.js +52 -0
- package/test/unit/utils.spec.js +85 -0
- package/test/unit/validators.spec.js +246 -0
- package/test/unit/watcher-checker.spec.js +274 -0
- package/test/unit/watcher-store.spec.js +405 -0
- package/tsconfig.json +31 -0
package/.c8rc.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"all": true,
|
|
3
|
+
"include": ["lib/**/*.js"],
|
|
4
|
+
"exclude": ["test/**", "**/*.spec.js", "**/*.test.js", "**/*.e2e.spec.cjs", "lib/types.js"],
|
|
5
|
+
"reporter": ["text", "html", "lcov"],
|
|
6
|
+
"report-dir": "./coverage",
|
|
7
|
+
"check-coverage": true,
|
|
8
|
+
"lines": 80,
|
|
9
|
+
"statements": 80,
|
|
10
|
+
"functions": 80,
|
|
11
|
+
"branches": 80
|
|
12
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Publish to NPM
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: '20.x'
|
|
19
|
+
registry-url: 'https://registry.npmjs.org'
|
|
20
|
+
|
|
21
|
+
- name: Install latest npm
|
|
22
|
+
run: npm install -g npm@latest
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: npm ci
|
|
26
|
+
|
|
27
|
+
- name: Publish to npm
|
|
28
|
+
run: npm publish --provenance --access public
|
package/.mocharc.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"require": ["@appium/support/build/lib/env"],
|
|
3
|
+
"timeout": 20000,
|
|
4
|
+
"slow": 10000,
|
|
5
|
+
"reporter": "spec",
|
|
6
|
+
"color": true,
|
|
7
|
+
"recursive": true,
|
|
8
|
+
"exit": true,
|
|
9
|
+
"spec": ["test/unit/**/*.spec.js", "test/integration/**/*.spec.js", "test/e2e/**/*.e2e.spec.cjs"]
|
|
10
|
+
}
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# Appium UI Watchers Plugin
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://appium.io/)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://www.npmjs.com/package/appium-uiwatchers-plugin)
|
|
7
|
+
[](https://github.com/rajvinodh/appium-uiwatchers-plugin/actions)
|
|
8
|
+
|
|
9
|
+
## Introduction
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
This plugin provides a centralized, declarative way to handle these interruptions automatically.
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
- **Zero Wait Overhead** — No waiting for UI elements that may never appear
|
|
18
|
+
- **Centralized Management** — Register watchers once, apply across entire session
|
|
19
|
+
- **Priority-Based Execution** — Higher priority watchers are checked first
|
|
20
|
+
- **Cooldown Support** — Wait for stable UI after dismissing an element
|
|
21
|
+
- **Auto-Expiry** — Watchers automatically expire after specified duration
|
|
22
|
+
- **One-Shot or Continuous** — Stop after first trigger or keep watching
|
|
23
|
+
- **Transparent Interception** — Handles on findElement, findElements, and element actions automatically
|
|
24
|
+
- **Session-Scoped** — Each session maintains its own independent set of watchers
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### From npm (coming soon)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
appium plugin install appium-uiwatchers-plugin
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### From local source
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
appium plugin install --source=local /path/to/appium-uiwatchers-plugin
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Verify installation
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
appium plugin list
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You should see `uiwatchers` in the installed plugins list.
|
|
47
|
+
|
|
48
|
+
### Activate the plugin
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
appium --use-plugins=uiwatchers
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
> **Note:** Ensure Appium server is started with `--use-plugins=uiwatchers`
|
|
57
|
+
|
|
58
|
+
**JavaScript**
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
// Register a watcher for cookie consent
|
|
62
|
+
await driver.execute('mobile: registerUIWatcher', {
|
|
63
|
+
name: 'cookie-consent',
|
|
64
|
+
referenceLocator: { using: 'id', value: 'com.app:id/cookie_banner' },
|
|
65
|
+
actionLocator: { using: 'id', value: 'com.app:id/accept_button' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Run tests - watchers trigger automatically on findElement/findElements
|
|
69
|
+
await driver.findElement('id', 'com.app:id/login_button');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Java**
|
|
73
|
+
|
|
74
|
+
```java
|
|
75
|
+
// Register a watcher for cookie consent
|
|
76
|
+
Map<String, Object> watcherParams = new HashMap<>();
|
|
77
|
+
watcherParams.put("name", "cookie-consent");
|
|
78
|
+
watcherParams.put("referenceLocator", Map.of("using", "id", "value", "com.app:id/cookie_banner"));
|
|
79
|
+
watcherParams.put("actionLocator", Map.of("using", "id", "value", "com.app:id/accept_button"));
|
|
80
|
+
|
|
81
|
+
driver.executeScript("mobile: registerUIWatcher", watcherParams);
|
|
82
|
+
|
|
83
|
+
// Run tests - watchers trigger automatically on findElement/findElements
|
|
84
|
+
driver.findElement(By.id("com.app:id/login_button"));
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## API Reference
|
|
88
|
+
|
|
89
|
+
### mobile: registerUIWatcher
|
|
90
|
+
|
|
91
|
+
Registers a new UI watcher for automatic element handling.
|
|
92
|
+
|
|
93
|
+
**Parameters**
|
|
94
|
+
|
|
95
|
+
| Parameter | Type | Required | Default | Description |
|
|
96
|
+
| ---------------- | ------- | -------- | ------- | ---------------------------------------- |
|
|
97
|
+
| name | string | Yes | - | Unique watcher identifier |
|
|
98
|
+
| referenceLocator | object | Yes | - | Element to detect (trigger condition) |
|
|
99
|
+
| actionLocator | object | Yes | - | Element to click when triggered |
|
|
100
|
+
| duration | number | Yes | - | Auto-expiry time in ms (max: 60000) |
|
|
101
|
+
| priority | number | No | 0 | Execution order (higher = first) |
|
|
102
|
+
| stopOnFound | boolean | No | false | Deactivate after first trigger |
|
|
103
|
+
| cooldownMs | number | No | 0 | Wait time after action before re-trigger |
|
|
104
|
+
|
|
105
|
+
**Locator Object**
|
|
106
|
+
|
|
107
|
+
| Property | Type | Description |
|
|
108
|
+
| -------- | ------ | ------------------------------------------------- |
|
|
109
|
+
| using | string | Strategy: `id`, `xpath`, `accessibility id`, etc. |
|
|
110
|
+
| value | string | Locator value |
|
|
111
|
+
|
|
112
|
+
**Example**
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"name": "cookie-consent",
|
|
117
|
+
"priority": 10,
|
|
118
|
+
"referenceLocator": {
|
|
119
|
+
"using": "id",
|
|
120
|
+
"value": "com.app:id/cookie_banner"
|
|
121
|
+
},
|
|
122
|
+
"actionLocator": {
|
|
123
|
+
"using": "id",
|
|
124
|
+
"value": "com.app:id/accept_button"
|
|
125
|
+
},
|
|
126
|
+
"duration": 60000,
|
|
127
|
+
"stopOnFound": false,
|
|
128
|
+
"cooldownMs": 5000
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Response**
|
|
133
|
+
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"success": true,
|
|
137
|
+
"watcher": {
|
|
138
|
+
"name": "cookie-consent",
|
|
139
|
+
"priority": 10,
|
|
140
|
+
"registeredAt": 1704067200000,
|
|
141
|
+
"expiresAt": 1704067260000,
|
|
142
|
+
"status": "active"
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### mobile: unregisterUIWatcher
|
|
148
|
+
|
|
149
|
+
Removes a registered watcher by name.
|
|
150
|
+
|
|
151
|
+
**Parameters**
|
|
152
|
+
|
|
153
|
+
| Parameter | Type | Required | Description |
|
|
154
|
+
| --------- | ------ | -------- | ----------------------------- |
|
|
155
|
+
| name | string | Yes | Name of the watcher to remove |
|
|
156
|
+
|
|
157
|
+
**Example**
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"name": "cookie-consent"
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Response**
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"success": true,
|
|
170
|
+
"removed": "cookie-consent"
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### mobile: listUIWatchers
|
|
175
|
+
|
|
176
|
+
Returns all registered watchers with their current state.
|
|
177
|
+
|
|
178
|
+
**Parameters**
|
|
179
|
+
|
|
180
|
+
None.
|
|
181
|
+
|
|
182
|
+
**Response**
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"success": true,
|
|
187
|
+
"watchers": [
|
|
188
|
+
{
|
|
189
|
+
"name": "cookie-consent",
|
|
190
|
+
"priority": 10,
|
|
191
|
+
"referenceLocator": { "using": "id", "value": "com.app:id/cookie_banner" },
|
|
192
|
+
"actionLocator": { "using": "id", "value": "com.app:id/accept_button" },
|
|
193
|
+
"duration": 60000,
|
|
194
|
+
"stopOnFound": false,
|
|
195
|
+
"cooldownMs": 5000,
|
|
196
|
+
"registeredAt": 1704067200000,
|
|
197
|
+
"expiresAt": 1704067260000,
|
|
198
|
+
"status": "active",
|
|
199
|
+
"triggerCount": 2,
|
|
200
|
+
"lastTriggeredAt": 1704067230000
|
|
201
|
+
}
|
|
202
|
+
],
|
|
203
|
+
"totalCount": 1
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### mobile: clearAllUIWatchers
|
|
208
|
+
|
|
209
|
+
Removes all registered watchers for the current session.
|
|
210
|
+
|
|
211
|
+
**Parameters**
|
|
212
|
+
|
|
213
|
+
None.
|
|
214
|
+
|
|
215
|
+
**Response**
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"success": true,
|
|
220
|
+
"removedCount": 3
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### mobile: enableUIWatchers
|
|
225
|
+
|
|
226
|
+
Enables watcher checking (enabled by default).
|
|
227
|
+
|
|
228
|
+
**Parameters**
|
|
229
|
+
|
|
230
|
+
None.
|
|
231
|
+
|
|
232
|
+
**Response**
|
|
233
|
+
|
|
234
|
+
```json
|
|
235
|
+
{
|
|
236
|
+
"success": true,
|
|
237
|
+
"message": "UI Watchers enabled"
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### mobile: disableUIWatchers
|
|
242
|
+
|
|
243
|
+
Temporarily disables watcher checking without removing watchers.
|
|
244
|
+
|
|
245
|
+
**Parameters**
|
|
246
|
+
|
|
247
|
+
None.
|
|
248
|
+
|
|
249
|
+
**Response**
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"success": true,
|
|
254
|
+
"message": "UI Watchers disabled"
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Configuration
|
|
259
|
+
|
|
260
|
+
Plugin behavior can be customized via CLI options when starting Appium server.
|
|
261
|
+
|
|
262
|
+
| Option | Type | Range | Default | Description |
|
|
263
|
+
| ------------------------------------- | ------- | ----------- | ------- | --------------------------------- |
|
|
264
|
+
| --plugin-uiwatchers-max-watchers | integer | 1-20 | 5 | Maximum watchers per session |
|
|
265
|
+
| --plugin-uiwatchers-max-duration-ms | integer | 1000-600000 | 60000 | Maximum watcher duration in ms |
|
|
266
|
+
| --plugin-uiwatchers-max-cache-entries | integer | 10-200 | 50 | Maximum cached element references |
|
|
267
|
+
| --plugin-uiwatchers-element-ttl-ms | integer | 5000-300000 | 60000 | Cache entry TTL in ms |
|
|
268
|
+
|
|
269
|
+
**Example**
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
appium --use-plugins=uiwatchers \
|
|
273
|
+
--plugin-uiwatchers-max-watchers=10 \
|
|
274
|
+
--plugin-uiwatchers-max-duration-ms=120000 \
|
|
275
|
+
--plugin-uiwatchers-max-cache-entries=100 \
|
|
276
|
+
--plugin-uiwatchers-element-ttl-ms=30000
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## How It Works
|
|
280
|
+
|
|
281
|
+
### Watcher Checking Flow
|
|
282
|
+
|
|
283
|
+
```mermaid
|
|
284
|
+
flowchart TD
|
|
285
|
+
A[findElement / findElements] --> B{Command Success?}
|
|
286
|
+
B -->|Yes| C[Cache element reference]
|
|
287
|
+
C --> D[Return result]
|
|
288
|
+
B -->|No / Empty| E[Check watchers by priority]
|
|
289
|
+
E --> F{Reference element found?}
|
|
290
|
+
F -->|No| G{More watchers?}
|
|
291
|
+
G -->|Yes| E
|
|
292
|
+
F -->|Yes| H[Click action element]
|
|
293
|
+
H --> I[Apply cooldown]
|
|
294
|
+
I --> G
|
|
295
|
+
G -->|No| J[Retry original command]
|
|
296
|
+
J --> D
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Stale Element Recovery Flow
|
|
300
|
+
|
|
301
|
+
```mermaid
|
|
302
|
+
flowchart TD
|
|
303
|
+
A[Element action: click, getText, etc.] --> B{StaleElementReferenceException?}
|
|
304
|
+
B -->|No| C[Return result]
|
|
305
|
+
B -->|Yes| D[Check watchers]
|
|
306
|
+
D --> E{Watcher triggered?}
|
|
307
|
+
E -->|No| F[Throw original exception]
|
|
308
|
+
E -->|Yes| G[Lookup cached locator]
|
|
309
|
+
G --> H{Found in cache?}
|
|
310
|
+
H -->|No| F
|
|
311
|
+
H -->|Yes| I[Re-find element using locator]
|
|
312
|
+
I --> J[Map old ID → new ID]
|
|
313
|
+
J --> K[Retry action with new element]
|
|
314
|
+
K --> C
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Development
|
|
318
|
+
|
|
319
|
+
### Prerequisites
|
|
320
|
+
|
|
321
|
+
- Node.js 18+
|
|
322
|
+
- Appium 3.x
|
|
323
|
+
|
|
324
|
+
### Setup
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
# Clone the repository
|
|
328
|
+
git clone https://github.com/rajvinodh/appium-uiwatchers-plugin.git
|
|
329
|
+
cd appium-uiwatchers-plugin
|
|
330
|
+
|
|
331
|
+
# Install dependencies
|
|
332
|
+
npm install
|
|
333
|
+
|
|
334
|
+
# Build
|
|
335
|
+
npm run build
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Scripts
|
|
339
|
+
|
|
340
|
+
| Script | Description |
|
|
341
|
+
| ----------------------- | ---------------------------------- |
|
|
342
|
+
| `npm run build` | Compile TypeScript to JavaScript |
|
|
343
|
+
| `npm run test` | Run all tests (unit + integration) |
|
|
344
|
+
| `npm run test:unit` | Run unit tests only |
|
|
345
|
+
| `npm run test:coverage` | Run tests with coverage report |
|
|
346
|
+
| `npm run lint` | Run ESLint and Prettier checks |
|
|
347
|
+
| `npm run lint:fix` | Auto-fix linting issues |
|
|
348
|
+
|
|
349
|
+
### Project Structure
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
├── src/ # TypeScript source code
|
|
353
|
+
│ ├── plugin.ts # Main plugin class
|
|
354
|
+
│ ├── watcher-store.ts # Watcher state management
|
|
355
|
+
│ ├── watcher-checker.ts# Watcher execution logic
|
|
356
|
+
│ ├── element-cache.ts # Element reference caching
|
|
357
|
+
│ ├── commands/ # Command implementations
|
|
358
|
+
│ ├── config.ts # Configuration defaults
|
|
359
|
+
│ ├── types.ts # Type definitions
|
|
360
|
+
│ └── utils.ts # Utility functions
|
|
361
|
+
├── test/
|
|
362
|
+
│ ├── unit/ # Unit tests
|
|
363
|
+
│ └── integration/ # Integration tests
|
|
364
|
+
└── lib/ # Compiled output
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Contributing
|
|
368
|
+
|
|
369
|
+
1. Fork the repository
|
|
370
|
+
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
|
371
|
+
3. Make your changes
|
|
372
|
+
4. Run tests (`npm run test`)
|
|
373
|
+
5. Run linting (`npm run lint`)
|
|
374
|
+
6. Commit your changes
|
|
375
|
+
7. Push to your branch
|
|
376
|
+
8. Open a Pull Request
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
|
3
|
+
import tsparser from '@typescript-eslint/parser';
|
|
4
|
+
import prettier from 'eslint-config-prettier';
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ['src/**/*.ts', 'test/**/*.js'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: tsparser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2020,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
},
|
|
16
|
+
globals: {
|
|
17
|
+
console: 'readonly',
|
|
18
|
+
process: 'readonly',
|
|
19
|
+
__dirname: 'readonly',
|
|
20
|
+
__filename: 'readonly',
|
|
21
|
+
Buffer: 'readonly',
|
|
22
|
+
module: 'readonly',
|
|
23
|
+
require: 'readonly',
|
|
24
|
+
exports: 'readonly',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
plugins: {
|
|
28
|
+
'@typescript-eslint': tseslint,
|
|
29
|
+
},
|
|
30
|
+
rules: {
|
|
31
|
+
...tseslint.configs.recommended.rules,
|
|
32
|
+
'@typescript-eslint/no-unused-vars': [
|
|
33
|
+
'error',
|
|
34
|
+
{
|
|
35
|
+
argsIgnorePattern: '^_',
|
|
36
|
+
varsIgnorePattern: '^_',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
40
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
41
|
+
'no-console': 'warn',
|
|
42
|
+
'prefer-const': 'error',
|
|
43
|
+
'no-var': 'error',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
files: ['test/**/*.js', 'test/**/*.cjs'],
|
|
48
|
+
languageOptions: {
|
|
49
|
+
globals: {
|
|
50
|
+
describe: 'readonly',
|
|
51
|
+
it: 'readonly',
|
|
52
|
+
before: 'readonly',
|
|
53
|
+
after: 'readonly',
|
|
54
|
+
beforeEach: 'readonly',
|
|
55
|
+
afterEach: 'readonly',
|
|
56
|
+
setTimeout: 'readonly',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
rules: {
|
|
60
|
+
'@typescript-eslint/no-unused-expressions': 'off',
|
|
61
|
+
'no-undef': 'off',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
prettier,
|
|
65
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "appium-uiwatchers-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./lib/plugin.js",
|
|
6
|
+
"types": "./lib/plugin.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"watch": "tsc --watch",
|
|
10
|
+
"clean": "rm -rf lib coverage",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"test": "npm run test:unit && npm run test:integration",
|
|
13
|
+
"test:unit": "mocha --config .mocharc.json \"test/unit/**/*.spec.js\"",
|
|
14
|
+
"test:integration": "mocha --config .mocharc.json \"test/integration/**/*.spec.js\"",
|
|
15
|
+
"test:e2e": "mocha --config .mocharc.json \"test/e2e/**/*.e2e.spec.cjs\"",
|
|
16
|
+
"test:coverage": "c8 npm run test",
|
|
17
|
+
"test:watch": "mocha --config .mocharc.json --watch",
|
|
18
|
+
"lint": "eslint src test && prettier --check .",
|
|
19
|
+
"lint:fix": "eslint src test --fix && prettier --write .",
|
|
20
|
+
"format": "prettier --write .",
|
|
21
|
+
"prepare": "husky",
|
|
22
|
+
"install:local": "npm run build && appium plugin install --source=local ."
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"appium",
|
|
26
|
+
"appium-plugin",
|
|
27
|
+
"automation",
|
|
28
|
+
"testing",
|
|
29
|
+
"mobile",
|
|
30
|
+
"ui-watchers",
|
|
31
|
+
"popup-handler"
|
|
32
|
+
],
|
|
33
|
+
"author": "Vinodh Raj R",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/rajvinodh/uiwatchers.git"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/rajvinodh/uiwatchers/issues"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/rajvinodh/uiwatchers#readme",
|
|
43
|
+
"description": "Appium plugin to automatically handle unexpected UI elements (popups, banners, dialogs) during test execution",
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"appium": "^3.1.2"
|
|
46
|
+
},
|
|
47
|
+
"appium": {
|
|
48
|
+
"pluginName": "uiwatchers",
|
|
49
|
+
"mainClass": "UIWatchersPlugin",
|
|
50
|
+
"schema": {
|
|
51
|
+
"$schema": "http://json-schema.org/draft-07/schema",
|
|
52
|
+
"type": "object",
|
|
53
|
+
"properties": {
|
|
54
|
+
"max-watchers": {
|
|
55
|
+
"type": "integer",
|
|
56
|
+
"minimum": 1,
|
|
57
|
+
"maximum": 20,
|
|
58
|
+
"default": 5,
|
|
59
|
+
"description": "Maximum number of UI watchers allowed per session"
|
|
60
|
+
},
|
|
61
|
+
"max-duration-ms": {
|
|
62
|
+
"type": "integer",
|
|
63
|
+
"minimum": 1000,
|
|
64
|
+
"maximum": 600000,
|
|
65
|
+
"default": 60000,
|
|
66
|
+
"description": "Maximum duration for each UI watcher in milliseconds"
|
|
67
|
+
},
|
|
68
|
+
"max-cache-entries": {
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"minimum": 1,
|
|
71
|
+
"maximum": 200,
|
|
72
|
+
"default": 50,
|
|
73
|
+
"description": "Maximum element references to cache for stale element recovery"
|
|
74
|
+
},
|
|
75
|
+
"element-ttl-ms": {
|
|
76
|
+
"type": "integer",
|
|
77
|
+
"minimum": 5000,
|
|
78
|
+
"maximum": 300000,
|
|
79
|
+
"default": 60000,
|
|
80
|
+
"description": "TTL for cached element references in milliseconds"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@appium/base-plugin": "^3.0.5",
|
|
87
|
+
"@appium/support": "^7.0.4"
|
|
88
|
+
},
|
|
89
|
+
"devDependencies": {
|
|
90
|
+
"@appium/fake-driver": "^6.0.1",
|
|
91
|
+
"@appium/plugin-test-support": "^1.0.4",
|
|
92
|
+
"@appium/test-support": "^4.0.4",
|
|
93
|
+
"@types/chai": "^5.2.3",
|
|
94
|
+
"@types/chai-as-promised": "^8.0.2",
|
|
95
|
+
"@types/mocha": "^10.0.10",
|
|
96
|
+
"@types/node": "^25.0.3",
|
|
97
|
+
"@types/sinon": "^21.0.0",
|
|
98
|
+
"@typescript-eslint/eslint-plugin": "^8.51.0",
|
|
99
|
+
"@typescript-eslint/parser": "^8.51.0",
|
|
100
|
+
"appium-inspector-plugin": "^2025.11.1",
|
|
101
|
+
"c8": "^10.1.3",
|
|
102
|
+
"chai": "^6.2.2",
|
|
103
|
+
"chai-as-promised": "^8.0.2",
|
|
104
|
+
"eslint": "^9.39.2",
|
|
105
|
+
"eslint-config-prettier": "^10.1.8",
|
|
106
|
+
"husky": "^9.1.7",
|
|
107
|
+
"lint-staged": "^16.2.7",
|
|
108
|
+
"mocha": "^11.7.5",
|
|
109
|
+
"prettier": "^3.7.4",
|
|
110
|
+
"sinon": "^21.0.1",
|
|
111
|
+
"typescript": "^5.9.3",
|
|
112
|
+
"webdriverio": "^9.22.0"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* clearAllUIWatchers command implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { logger } from '@appium/support';
|
|
6
|
+
import type { WatcherStore } from '../watcher-store.js';
|
|
7
|
+
import type { ClearAllWatchersResult } from '../types.js';
|
|
8
|
+
|
|
9
|
+
const log = logger.getLogger('AppiumUIWatchers');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Clear all UI watchers from the session
|
|
13
|
+
* @param store - Watcher store instance
|
|
14
|
+
* @returns Clear result with count of removed watchers
|
|
15
|
+
*/
|
|
16
|
+
export async function clearAllUIWatchers(store: WatcherStore): Promise<ClearAllWatchersResult> {
|
|
17
|
+
// Get count before clearing
|
|
18
|
+
const count = store.clear();
|
|
19
|
+
|
|
20
|
+
// Log successful clear
|
|
21
|
+
log.info(`[UIWatchers] All UI watchers cleared (removed ${count} watchers)`);
|
|
22
|
+
|
|
23
|
+
// Return success response
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
removedCount: count,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* listUIWatchers command implementation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { WatcherStore } from '../watcher-store.js';
|
|
6
|
+
import type { ListWatchersResult } from '../types.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* List all active UI watchers with their state
|
|
10
|
+
* @param store - Watcher store instance
|
|
11
|
+
* @returns List of all active watchers
|
|
12
|
+
*/
|
|
13
|
+
export async function listUIWatchers(store: WatcherStore): Promise<ListWatchersResult> {
|
|
14
|
+
// Get all active watchers (this automatically filters out expired ones)
|
|
15
|
+
const activeWatchers = store.getActiveWatchers();
|
|
16
|
+
|
|
17
|
+
// Return success response
|
|
18
|
+
return {
|
|
19
|
+
success: true,
|
|
20
|
+
watchers: activeWatchers,
|
|
21
|
+
totalCount: activeWatchers.length,
|
|
22
|
+
};
|
|
23
|
+
}
|