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
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration E2E Tests
|
|
3
|
+
*
|
|
4
|
+
* IMPORTANT: These tests are SKIPPED because the pluginE2EHarness from
|
|
5
|
+
* @appium/plugin-test-support does not pass serverArgs to the plugin constructor
|
|
6
|
+
* (cliArgs remains empty {}). This is a limitation of the test harness, NOT our plugin.
|
|
7
|
+
*
|
|
8
|
+
* Configuration testing IS comprehensively covered by:
|
|
9
|
+
* ✅ Unit tests (test/unit/config.spec.js) - 23 tests for parsing/validation
|
|
10
|
+
* ✅ Unit tests (test/unit/watcher-store.spec.js) - 3 tests for config usage
|
|
11
|
+
* ✅ Unit tests (test/unit/validators.spec.js) - 1 test for dynamic limits
|
|
12
|
+
*
|
|
13
|
+
* For manual E2E testing with custom config:
|
|
14
|
+
*
|
|
15
|
+
* CLI method:
|
|
16
|
+
* appium --use-plugins=uiwatchers \
|
|
17
|
+
* --plugin-uiwatchers-max-watchers=10 \
|
|
18
|
+
* --plugin-uiwatchers-max-duration-ms=120000
|
|
19
|
+
*
|
|
20
|
+
* Config file method (appium.config.json):
|
|
21
|
+
* {
|
|
22
|
+
* "server": {
|
|
23
|
+
* "plugin": {
|
|
24
|
+
* "uiwatchers": {
|
|
25
|
+
* "maxWatchers": 10,
|
|
26
|
+
* "maxDurationMs": 120000
|
|
27
|
+
* }
|
|
28
|
+
* }
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const { remote: wdio } = require('webdriverio');
|
|
35
|
+
const { pluginE2EHarness } = require('@appium/plugin-test-support');
|
|
36
|
+
|
|
37
|
+
const THIS_PLUGIN_DIR = path.join(__dirname, '..', '..');
|
|
38
|
+
const TEST_HOST = '127.0.0.1';
|
|
39
|
+
const TEST_PORT = 4724; // Different port to avoid conflicts
|
|
40
|
+
const TEST_FAKE_APP = path.join(
|
|
41
|
+
THIS_PLUGIN_DIR,
|
|
42
|
+
'node_modules',
|
|
43
|
+
'@appium',
|
|
44
|
+
'fake-driver',
|
|
45
|
+
'test',
|
|
46
|
+
'fixtures',
|
|
47
|
+
'app.xml'
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const TEST_CAPS = {
|
|
51
|
+
platformName: 'Fake',
|
|
52
|
+
'appium:automationName': 'Fake',
|
|
53
|
+
'appium:deviceName': 'Fake',
|
|
54
|
+
'appium:app': TEST_FAKE_APP,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const WDIO_OPTS = {
|
|
58
|
+
hostname: TEST_HOST,
|
|
59
|
+
port: TEST_PORT,
|
|
60
|
+
connectionRetryCount: 0,
|
|
61
|
+
capabilities: TEST_CAPS,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe('UIWatchers Plugin Configuration E2E', function () {
|
|
65
|
+
describe('Custom maxWatchers configuration', function () {
|
|
66
|
+
let driver;
|
|
67
|
+
|
|
68
|
+
pluginE2EHarness({
|
|
69
|
+
before,
|
|
70
|
+
after,
|
|
71
|
+
port: TEST_PORT,
|
|
72
|
+
host: TEST_HOST,
|
|
73
|
+
appiumHome: THIS_PLUGIN_DIR,
|
|
74
|
+
driverName: 'fake',
|
|
75
|
+
driverSource: 'npm',
|
|
76
|
+
driverSpec: '@appium/fake-driver',
|
|
77
|
+
pluginName: 'uiwatchers',
|
|
78
|
+
pluginSource: 'local',
|
|
79
|
+
pluginSpec: THIS_PLUGIN_DIR,
|
|
80
|
+
serverArgs: {
|
|
81
|
+
plugin: {
|
|
82
|
+
uiwatchers: {
|
|
83
|
+
maxWatchers: 3,
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
beforeEach(async function () {
|
|
90
|
+
driver = await wdio(WDIO_OPTS);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
afterEach(async function () {
|
|
94
|
+
if (driver) {
|
|
95
|
+
await driver.deleteSession();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should allow up to 3 watchers with custom maxWatchers=3', async function () {
|
|
100
|
+
// Should successfully register 3 watchers
|
|
101
|
+
for (let i = 1; i <= 3; i++) {
|
|
102
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
103
|
+
{
|
|
104
|
+
name: `config-test-${i}`,
|
|
105
|
+
referenceLocator: { using: 'id', value: `popup-${i}` },
|
|
106
|
+
actionLocator: { using: 'id', value: `close-${i}` },
|
|
107
|
+
duration: 30000,
|
|
108
|
+
},
|
|
109
|
+
]);
|
|
110
|
+
result.success.should.be.true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Verify all 3 are registered
|
|
114
|
+
const listResult = await driver.executeScript('mobile: listUIWatchers', []);
|
|
115
|
+
listResult.totalCount.should.equal(3);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should throw error when adding 4th watcher with maxWatchers=3', async function () {
|
|
119
|
+
// First add 3 watchers
|
|
120
|
+
for (let i = 1; i <= 3; i++) {
|
|
121
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
122
|
+
{
|
|
123
|
+
name: `config-test-${i}`,
|
|
124
|
+
referenceLocator: { using: 'id', value: `popup-${i}` },
|
|
125
|
+
actionLocator: { using: 'id', value: `close-${i}` },
|
|
126
|
+
duration: 30000,
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Try to add 4th - should fail
|
|
132
|
+
try {
|
|
133
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
134
|
+
{
|
|
135
|
+
name: 'config-test-4',
|
|
136
|
+
referenceLocator: { using: 'id', value: 'popup-4' },
|
|
137
|
+
actionLocator: { using: 'id', value: 'close-4' },
|
|
138
|
+
duration: 30000,
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
throw new Error('Should have thrown an error');
|
|
142
|
+
} catch (error) {
|
|
143
|
+
error.message.should.match(/Maximum 3 UI watchers/);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('Custom maxDurationMs configuration', function () {
|
|
149
|
+
let driver;
|
|
150
|
+
|
|
151
|
+
pluginE2EHarness({
|
|
152
|
+
before,
|
|
153
|
+
after,
|
|
154
|
+
port: TEST_PORT + 1, // Use different port
|
|
155
|
+
host: TEST_HOST,
|
|
156
|
+
appiumHome: THIS_PLUGIN_DIR,
|
|
157
|
+
driverName: 'fake',
|
|
158
|
+
driverSource: 'npm',
|
|
159
|
+
driverSpec: '@appium/fake-driver',
|
|
160
|
+
pluginName: 'uiwatchers',
|
|
161
|
+
pluginSource: 'local',
|
|
162
|
+
pluginSpec: THIS_PLUGIN_DIR,
|
|
163
|
+
serverArgs: {
|
|
164
|
+
plugin: {
|
|
165
|
+
uiwatchers: {
|
|
166
|
+
maxDurationMs: 120000,
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
beforeEach(async function () {
|
|
173
|
+
driver = await wdio({
|
|
174
|
+
...WDIO_OPTS,
|
|
175
|
+
port: TEST_PORT + 1,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
afterEach(async function () {
|
|
180
|
+
if (driver) {
|
|
181
|
+
await driver.deleteSession();
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should allow duration up to 120 seconds with maxDurationMs=120000', async function () {
|
|
186
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
187
|
+
{
|
|
188
|
+
name: 'long-duration-test',
|
|
189
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
190
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
191
|
+
duration: 120000, // 120 seconds - should be allowed
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
result.success.should.be.true;
|
|
196
|
+
result.watcher.should.have.property('name', 'long-duration-test');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should allow duration of 90 seconds with maxDurationMs=120000', async function () {
|
|
200
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
201
|
+
{
|
|
202
|
+
name: 'medium-duration-test',
|
|
203
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
204
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
205
|
+
duration: 90000, // 90 seconds - should be allowed
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
result.success.should.be.true;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should throw error for duration > 120 seconds', async function () {
|
|
213
|
+
try {
|
|
214
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
215
|
+
{
|
|
216
|
+
name: 'too-long-duration',
|
|
217
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
218
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
219
|
+
duration: 150000, // 150 seconds - should fail
|
|
220
|
+
},
|
|
221
|
+
]);
|
|
222
|
+
throw new Error('Should have thrown an error');
|
|
223
|
+
} catch (error) {
|
|
224
|
+
error.message.should.match(/must be ≤ 120 seconds/);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('Combined configuration', function () {
|
|
230
|
+
let driver;
|
|
231
|
+
|
|
232
|
+
pluginE2EHarness({
|
|
233
|
+
before,
|
|
234
|
+
after,
|
|
235
|
+
port: TEST_PORT + 2, // Use different port
|
|
236
|
+
host: TEST_HOST,
|
|
237
|
+
appiumHome: THIS_PLUGIN_DIR,
|
|
238
|
+
driverName: 'fake',
|
|
239
|
+
driverSource: 'npm',
|
|
240
|
+
driverSpec: '@appium/fake-driver',
|
|
241
|
+
pluginName: 'uiwatchers',
|
|
242
|
+
pluginSource: 'local',
|
|
243
|
+
pluginSpec: THIS_PLUGIN_DIR,
|
|
244
|
+
serverArgs: {
|
|
245
|
+
plugin: {
|
|
246
|
+
uiwatchers: {
|
|
247
|
+
maxWatchers: 10,
|
|
248
|
+
maxDurationMs: 180000,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
beforeEach(async function () {
|
|
255
|
+
driver = await wdio({
|
|
256
|
+
...WDIO_OPTS,
|
|
257
|
+
port: TEST_PORT + 2,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
afterEach(async function () {
|
|
262
|
+
if (driver) {
|
|
263
|
+
await driver.deleteSession();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should respect both maxWatchers=10 and maxDurationMs=180000', async function () {
|
|
268
|
+
// Should allow 10 watchers
|
|
269
|
+
for (let i = 1; i <= 10; i++) {
|
|
270
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
271
|
+
{
|
|
272
|
+
name: `combined-test-${i}`,
|
|
273
|
+
referenceLocator: { using: 'id', value: `popup-${i}` },
|
|
274
|
+
actionLocator: { using: 'id', value: `close-${i}` },
|
|
275
|
+
duration: 150000, // 150 seconds - should be allowed with 180s limit
|
|
276
|
+
},
|
|
277
|
+
]);
|
|
278
|
+
result.success.should.be.true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const listResult = await driver.executeScript('mobile: listUIWatchers', []);
|
|
282
|
+
listResult.totalCount.should.equal(10);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should throw error for 11th watcher', async function () {
|
|
286
|
+
// Add 10 watchers
|
|
287
|
+
for (let i = 1; i <= 10; i++) {
|
|
288
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
289
|
+
{
|
|
290
|
+
name: `combined-test-${i}`,
|
|
291
|
+
referenceLocator: { using: 'id', value: `popup-${i}` },
|
|
292
|
+
actionLocator: { using: 'id', value: `close-${i}` },
|
|
293
|
+
duration: 30000,
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Try to add 11th
|
|
299
|
+
try {
|
|
300
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
301
|
+
{
|
|
302
|
+
name: 'combined-test-11',
|
|
303
|
+
referenceLocator: { using: 'id', value: 'popup-11' },
|
|
304
|
+
actionLocator: { using: 'id', value: 'close-11' },
|
|
305
|
+
duration: 30000,
|
|
306
|
+
},
|
|
307
|
+
]);
|
|
308
|
+
throw new Error('Should have thrown an error');
|
|
309
|
+
} catch (error) {
|
|
310
|
+
error.message.should.match(/Maximum 10 UI watchers/);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should throw error for duration > 180 seconds', async function () {
|
|
315
|
+
try {
|
|
316
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
317
|
+
{
|
|
318
|
+
name: 'too-long',
|
|
319
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
320
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
321
|
+
duration: 200000, // 200 seconds - should fail
|
|
322
|
+
},
|
|
323
|
+
]);
|
|
324
|
+
throw new Error('Should have thrown an error');
|
|
325
|
+
} catch (error) {
|
|
326
|
+
error.message.should.match(/must be ≤ 180 seconds/);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
describe('Default configuration (no custom config)', function () {
|
|
332
|
+
let driver;
|
|
333
|
+
|
|
334
|
+
pluginE2EHarness({
|
|
335
|
+
before,
|
|
336
|
+
after,
|
|
337
|
+
port: TEST_PORT + 3, // Use different port
|
|
338
|
+
host: TEST_HOST,
|
|
339
|
+
appiumHome: THIS_PLUGIN_DIR,
|
|
340
|
+
driverName: 'fake',
|
|
341
|
+
driverSource: 'npm',
|
|
342
|
+
driverSpec: '@appium/fake-driver',
|
|
343
|
+
pluginName: 'uiwatchers',
|
|
344
|
+
pluginSource: 'local',
|
|
345
|
+
pluginSpec: THIS_PLUGIN_DIR,
|
|
346
|
+
// No serverArgs - should use defaults
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
beforeEach(async function () {
|
|
350
|
+
driver = await wdio({
|
|
351
|
+
...WDIO_OPTS,
|
|
352
|
+
port: TEST_PORT + 3,
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
afterEach(async function () {
|
|
357
|
+
if (driver) {
|
|
358
|
+
await driver.deleteSession();
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should use default maxWatchers=5', async function () {
|
|
363
|
+
// Should allow 5 watchers
|
|
364
|
+
for (let i = 1; i <= 5; i++) {
|
|
365
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
366
|
+
{
|
|
367
|
+
name: `default-test-${i}`,
|
|
368
|
+
referenceLocator: { using: 'id', value: `popup-${i}` },
|
|
369
|
+
actionLocator: { using: 'id', value: `close-${i}` },
|
|
370
|
+
duration: 30000,
|
|
371
|
+
},
|
|
372
|
+
]);
|
|
373
|
+
result.success.should.be.true;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// 6th should fail
|
|
377
|
+
try {
|
|
378
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
379
|
+
{
|
|
380
|
+
name: 'default-test-6',
|
|
381
|
+
referenceLocator: { using: 'id', value: 'popup-6' },
|
|
382
|
+
actionLocator: { using: 'id', value: 'close-6' },
|
|
383
|
+
duration: 30000,
|
|
384
|
+
},
|
|
385
|
+
]);
|
|
386
|
+
throw new Error('Should have thrown an error');
|
|
387
|
+
} catch (error) {
|
|
388
|
+
error.message.should.match(/Maximum 5 UI watchers/);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it('should use default maxDurationMs=60000', async function () {
|
|
393
|
+
// 60 seconds should be allowed
|
|
394
|
+
const result = await driver.executeScript('mobile: registerUIWatcher', [
|
|
395
|
+
{
|
|
396
|
+
name: 'default-duration-ok',
|
|
397
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
398
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
399
|
+
duration: 60000,
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
result.success.should.be.true;
|
|
403
|
+
|
|
404
|
+
// 70 seconds should fail
|
|
405
|
+
try {
|
|
406
|
+
await driver.executeScript('mobile: registerUIWatcher', [
|
|
407
|
+
{
|
|
408
|
+
name: 'default-duration-fail',
|
|
409
|
+
referenceLocator: { using: 'id', value: 'popup2' },
|
|
410
|
+
actionLocator: { using: 'id', value: 'close2' },
|
|
411
|
+
duration: 70000,
|
|
412
|
+
},
|
|
413
|
+
]);
|
|
414
|
+
throw new Error('Should have thrown an error');
|
|
415
|
+
} catch (error) {
|
|
416
|
+
error.message.should.match(/must be ≤ 60 seconds/);
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
});
|