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,405 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import { WatcherStore } from '../../lib/watcher-store.js';
|
|
4
|
+
|
|
5
|
+
describe('WatcherStore', function () {
|
|
6
|
+
let store;
|
|
7
|
+
|
|
8
|
+
const createValidWatcher = (name = 'test-watcher', overrides = {}) => ({
|
|
9
|
+
name,
|
|
10
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
11
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
12
|
+
duration: 30000,
|
|
13
|
+
...overrides,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
beforeEach(function () {
|
|
17
|
+
// Use default config values (same as schema defaults)
|
|
18
|
+
store = new WatcherStore({ maxWatchers: 5, maxDurationMs: 60000 });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('add', function () {
|
|
22
|
+
it('should add a new watcher successfully', function () {
|
|
23
|
+
const watcher = createValidWatcher();
|
|
24
|
+
const result = store.add(watcher);
|
|
25
|
+
|
|
26
|
+
expect(result).to.have.property('name', 'test-watcher');
|
|
27
|
+
expect(result).to.have.property('priority', 0);
|
|
28
|
+
expect(result).to.have.property('status', 'active');
|
|
29
|
+
expect(result).to.have.property('triggerCount', 0);
|
|
30
|
+
expect(result.lastTriggeredAt).to.be.null;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should set priority to 0 if not provided', function () {
|
|
34
|
+
const watcher = createValidWatcher();
|
|
35
|
+
const result = store.add(watcher);
|
|
36
|
+
|
|
37
|
+
expect(result.priority).to.equal(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should use provided priority value', function () {
|
|
41
|
+
const watcher = createValidWatcher('test', { priority: 10 });
|
|
42
|
+
const result = store.add(watcher);
|
|
43
|
+
|
|
44
|
+
expect(result.priority).to.equal(10);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should set stopOnFound to false if not provided', function () {
|
|
48
|
+
const watcher = createValidWatcher();
|
|
49
|
+
const result = store.add(watcher);
|
|
50
|
+
|
|
51
|
+
expect(result.stopOnFound).to.equal(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should use provided stopOnFound value', function () {
|
|
55
|
+
const watcher = createValidWatcher('test', { stopOnFound: true });
|
|
56
|
+
const result = store.add(watcher);
|
|
57
|
+
|
|
58
|
+
expect(result.stopOnFound).to.equal(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should set cooldownMs to 0 if not provided', function () {
|
|
62
|
+
const watcher = createValidWatcher();
|
|
63
|
+
const result = store.add(watcher);
|
|
64
|
+
|
|
65
|
+
expect(result.cooldownMs).to.equal(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should use provided cooldownMs value', function () {
|
|
69
|
+
const watcher = createValidWatcher('test', { cooldownMs: 5000 });
|
|
70
|
+
const result = store.add(watcher);
|
|
71
|
+
|
|
72
|
+
expect(result.cooldownMs).to.equal(5000);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should calculate expiresAt correctly', function () {
|
|
76
|
+
const before = Date.now();
|
|
77
|
+
const watcher = createValidWatcher('test', { duration: 10000 });
|
|
78
|
+
const result = store.add(watcher);
|
|
79
|
+
const after = Date.now();
|
|
80
|
+
|
|
81
|
+
expect(result.expiresAt).to.be.at.least(before + 10000);
|
|
82
|
+
expect(result.expiresAt).to.be.at.most(after + 10000);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw error for duplicate watcher name', function () {
|
|
86
|
+
const watcher1 = createValidWatcher('duplicate');
|
|
87
|
+
const watcher2 = createValidWatcher('duplicate');
|
|
88
|
+
|
|
89
|
+
store.add(watcher1);
|
|
90
|
+
expect(() => store.add(watcher2)).to.throw("UIWatcher with name 'duplicate' already exists");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should throw error when adding 6th watcher', function () {
|
|
94
|
+
// Add 5 watchers
|
|
95
|
+
for (let i = 1; i <= 5; i++) {
|
|
96
|
+
store.add(createValidWatcher(`watcher-${i}`));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Try to add 6th watcher
|
|
100
|
+
expect(() => store.add(createValidWatcher('watcher-6'))).to.throw(
|
|
101
|
+
'Maximum 5 UI watchers allowed per session'
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should allow adding watcher if one has expired', function () {
|
|
106
|
+
// Add 5 watchers with very short duration
|
|
107
|
+
for (let i = 1; i <= 5; i++) {
|
|
108
|
+
store.add(createValidWatcher(`watcher-${i}`, { duration: 1 }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Wait for expiry
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
setTimeout(() => {
|
|
114
|
+
// Now we should be able to add a new watcher
|
|
115
|
+
expect(() => store.add(createValidWatcher('watcher-6'))).to.not.throw();
|
|
116
|
+
resolve();
|
|
117
|
+
}, 10);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('remove', function () {
|
|
123
|
+
it('should remove existing watcher', function () {
|
|
124
|
+
const watcher = createValidWatcher('to-remove');
|
|
125
|
+
store.add(watcher);
|
|
126
|
+
|
|
127
|
+
const result = store.remove('to-remove');
|
|
128
|
+
expect(result).to.be.true;
|
|
129
|
+
expect(store.get('to-remove')).to.be.undefined;
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should return false when removing non-existent watcher', function () {
|
|
133
|
+
const result = store.remove('non-existent');
|
|
134
|
+
expect(result).to.be.false;
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('get', function () {
|
|
139
|
+
it('should retrieve existing watcher', function () {
|
|
140
|
+
const watcher = createValidWatcher('retrieve-me');
|
|
141
|
+
store.add(watcher);
|
|
142
|
+
|
|
143
|
+
const result = store.get('retrieve-me');
|
|
144
|
+
expect(result).to.have.property('name', 'retrieve-me');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return undefined for non-existent watcher', function () {
|
|
148
|
+
const result = store.get('non-existent');
|
|
149
|
+
expect(result).to.be.undefined;
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('list', function () {
|
|
154
|
+
it('should return empty array when no watchers', function () {
|
|
155
|
+
const result = store.list();
|
|
156
|
+
expect(result).to.be.an('array').that.is.empty;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should return all watchers', function () {
|
|
160
|
+
store.add(createValidWatcher('watcher-1'));
|
|
161
|
+
store.add(createValidWatcher('watcher-2'));
|
|
162
|
+
store.add(createValidWatcher('watcher-3'));
|
|
163
|
+
|
|
164
|
+
const result = store.list();
|
|
165
|
+
expect(result).to.have.lengthOf(3);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('should include expired watchers', function () {
|
|
169
|
+
store.add(createValidWatcher('expired', { duration: 1 }));
|
|
170
|
+
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
setTimeout(() => {
|
|
173
|
+
const result = store.list();
|
|
174
|
+
expect(result).to.have.lengthOf(1);
|
|
175
|
+
resolve();
|
|
176
|
+
}, 10);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('getActiveWatchers', function () {
|
|
182
|
+
it('should return empty array when no watchers', function () {
|
|
183
|
+
const result = store.getActiveWatchers();
|
|
184
|
+
expect(result).to.be.an('array').that.is.empty;
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should return only non-expired watchers', function () {
|
|
188
|
+
store.add(createValidWatcher('active-1', { duration: 60000 }));
|
|
189
|
+
store.add(createValidWatcher('expired-1', { duration: 1 }));
|
|
190
|
+
|
|
191
|
+
return new Promise((resolve) => {
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
const result = store.getActiveWatchers();
|
|
194
|
+
expect(result).to.have.lengthOf(1);
|
|
195
|
+
expect(result[0].name).to.equal('active-1');
|
|
196
|
+
resolve();
|
|
197
|
+
}, 10);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should remove expired watchers from storage', function () {
|
|
202
|
+
store.add(createValidWatcher('expired', { duration: 1 }));
|
|
203
|
+
|
|
204
|
+
return new Promise((resolve) => {
|
|
205
|
+
setTimeout(() => {
|
|
206
|
+
store.getActiveWatchers();
|
|
207
|
+
expect(store.get('expired')).to.be.undefined;
|
|
208
|
+
resolve();
|
|
209
|
+
}, 10);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('should include inactive watchers if not expired', function () {
|
|
214
|
+
store.add(createValidWatcher('inactive', { duration: 60000 }));
|
|
215
|
+
store.markInactive('inactive');
|
|
216
|
+
|
|
217
|
+
const result = store.getActiveWatchers();
|
|
218
|
+
expect(result).to.have.lengthOf(1);
|
|
219
|
+
expect(result[0].status).to.equal('inactive');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
describe('getSortedWatchers', function () {
|
|
224
|
+
it('should sort by priority descending', function () {
|
|
225
|
+
store.add(createValidWatcher('low', { priority: 1 }));
|
|
226
|
+
store.add(createValidWatcher('high', { priority: 10 }));
|
|
227
|
+
store.add(createValidWatcher('medium', { priority: 5 }));
|
|
228
|
+
|
|
229
|
+
const result = store.getSortedWatchers();
|
|
230
|
+
expect(result[0].name).to.equal('high');
|
|
231
|
+
expect(result[1].name).to.equal('medium');
|
|
232
|
+
expect(result[2].name).to.equal('low');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should use FIFO for same priority', function () {
|
|
236
|
+
store.add(createValidWatcher('first', { priority: 10 }));
|
|
237
|
+
store.add(createValidWatcher('second', { priority: 10 }));
|
|
238
|
+
store.add(createValidWatcher('third', { priority: 10 }));
|
|
239
|
+
|
|
240
|
+
const result = store.getSortedWatchers();
|
|
241
|
+
expect(result[0].name).to.equal('first');
|
|
242
|
+
expect(result[1].name).to.equal('second');
|
|
243
|
+
expect(result[2].name).to.equal('third');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should filter out expired watchers', function () {
|
|
247
|
+
store.add(createValidWatcher('active', { priority: 10, duration: 60000 }));
|
|
248
|
+
store.add(createValidWatcher('expired', { priority: 20, duration: 1 }));
|
|
249
|
+
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
setTimeout(() => {
|
|
252
|
+
const result = store.getSortedWatchers();
|
|
253
|
+
expect(result).to.have.lengthOf(1);
|
|
254
|
+
expect(result[0].name).to.equal('active');
|
|
255
|
+
resolve();
|
|
256
|
+
}, 10);
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('clear', function () {
|
|
262
|
+
it('should remove all watchers and return count', function () {
|
|
263
|
+
store.add(createValidWatcher('watcher-1'));
|
|
264
|
+
store.add(createValidWatcher('watcher-2'));
|
|
265
|
+
store.add(createValidWatcher('watcher-3'));
|
|
266
|
+
|
|
267
|
+
const count = store.clear();
|
|
268
|
+
expect(count).to.equal(3);
|
|
269
|
+
expect(store.list()).to.be.empty;
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should return 0 when clearing empty store', function () {
|
|
273
|
+
const count = store.clear();
|
|
274
|
+
expect(count).to.equal(0);
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('markInactive', function () {
|
|
279
|
+
it('should mark watcher as inactive', function () {
|
|
280
|
+
store.add(createValidWatcher('test'));
|
|
281
|
+
store.markInactive('test');
|
|
282
|
+
|
|
283
|
+
const result = store.get('test');
|
|
284
|
+
expect(result.status).to.equal('inactive');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should do nothing for non-existent watcher', function () {
|
|
288
|
+
expect(() => store.markInactive('non-existent')).to.not.throw();
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
describe('incrementTriggerCount', function () {
|
|
293
|
+
it('should increment trigger count', function () {
|
|
294
|
+
store.add(createValidWatcher('test'));
|
|
295
|
+
|
|
296
|
+
store.incrementTriggerCount('test');
|
|
297
|
+
expect(store.get('test').triggerCount).to.equal(1);
|
|
298
|
+
|
|
299
|
+
store.incrementTriggerCount('test');
|
|
300
|
+
expect(store.get('test').triggerCount).to.equal(2);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it('should update lastTriggeredAt timestamp', function () {
|
|
304
|
+
store.add(createValidWatcher('test'));
|
|
305
|
+
const before = Date.now();
|
|
306
|
+
|
|
307
|
+
store.incrementTriggerCount('test');
|
|
308
|
+
|
|
309
|
+
const result = store.get('test');
|
|
310
|
+
expect(result.lastTriggeredAt).to.be.at.least(before);
|
|
311
|
+
expect(result.lastTriggeredAt).to.be.at.most(Date.now());
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should do nothing for non-existent watcher', function () {
|
|
315
|
+
expect(() => store.incrementTriggerCount('non-existent')).to.not.throw();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe('updateLastTriggered', function () {
|
|
320
|
+
it('should update lastTriggeredAt timestamp', function () {
|
|
321
|
+
store.add(createValidWatcher('test'));
|
|
322
|
+
const before = Date.now();
|
|
323
|
+
|
|
324
|
+
store.updateLastTriggered('test');
|
|
325
|
+
|
|
326
|
+
const result = store.get('test');
|
|
327
|
+
expect(result.lastTriggeredAt).to.be.at.least(before);
|
|
328
|
+
expect(result.lastTriggeredAt).to.be.at.most(Date.now());
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('should do nothing for non-existent watcher', function () {
|
|
332
|
+
expect(() => store.updateLastTriggered('non-existent')).to.not.throw();
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
describe('enable/disable', function () {
|
|
337
|
+
it('should be enabled by default', function () {
|
|
338
|
+
expect(store.isEnabled()).to.be.true;
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should disable watcher checking', function () {
|
|
342
|
+
store.disable();
|
|
343
|
+
expect(store.isEnabled()).to.be.false;
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it('should enable watcher checking', function () {
|
|
347
|
+
store.disable();
|
|
348
|
+
store.enable();
|
|
349
|
+
expect(store.isEnabled()).to.be.true;
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should toggle enable/disable multiple times', function () {
|
|
353
|
+
expect(store.isEnabled()).to.be.true;
|
|
354
|
+
|
|
355
|
+
store.disable();
|
|
356
|
+
expect(store.isEnabled()).to.be.false;
|
|
357
|
+
|
|
358
|
+
store.enable();
|
|
359
|
+
expect(store.isEnabled()).to.be.true;
|
|
360
|
+
|
|
361
|
+
store.disable();
|
|
362
|
+
expect(store.isEnabled()).to.be.false;
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('custom configuration', function () {
|
|
367
|
+
it('should respect custom maxWatchers limit', function () {
|
|
368
|
+
const customConfig = { maxWatchers: 3, maxDurationMs: 60000 };
|
|
369
|
+
const customStore = new WatcherStore(customConfig);
|
|
370
|
+
|
|
371
|
+
// Should allow 3 watchers
|
|
372
|
+
customStore.add(createValidWatcher('watcher-1'));
|
|
373
|
+
customStore.add(createValidWatcher('watcher-2'));
|
|
374
|
+
customStore.add(createValidWatcher('watcher-3'));
|
|
375
|
+
|
|
376
|
+
// 4th should fail
|
|
377
|
+
expect(() => customStore.add(createValidWatcher('watcher-4'))).to.throw(
|
|
378
|
+
'Maximum 3 UI watchers allowed per session'
|
|
379
|
+
);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should work with increased maxWatchers limit', function () {
|
|
383
|
+
const customConfig = { maxWatchers: 10, maxDurationMs: 60000 };
|
|
384
|
+
const customStore = new WatcherStore(customConfig);
|
|
385
|
+
|
|
386
|
+
// Should allow 10 watchers
|
|
387
|
+
for (let i = 1; i <= 10; i++) {
|
|
388
|
+
customStore.add(createValidWatcher(`watcher-${i}`));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// 11th should fail
|
|
392
|
+
expect(() => customStore.add(createValidWatcher('watcher-11'))).to.throw(
|
|
393
|
+
'Maximum 10 UI watchers allowed per session'
|
|
394
|
+
);
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should return config via getConfig()', function () {
|
|
398
|
+
const customConfig = { maxWatchers: 8, maxDurationMs: 120000 };
|
|
399
|
+
const customStore = new WatcherStore(customConfig);
|
|
400
|
+
|
|
401
|
+
const config = customStore.getConfig();
|
|
402
|
+
expect(config).to.deep.equal(customConfig);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ES2020",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./lib",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"declarationMap": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"moduleResolution": "node",
|
|
17
|
+
"noImplicitAny": true,
|
|
18
|
+
"strictNullChecks": true,
|
|
19
|
+
"strictFunctionTypes": true,
|
|
20
|
+
"strictBindCallApply": true,
|
|
21
|
+
"strictPropertyInitialization": true,
|
|
22
|
+
"noImplicitThis": true,
|
|
23
|
+
"alwaysStrict": true,
|
|
24
|
+
"noUnusedLocals": true,
|
|
25
|
+
"noUnusedParameters": true,
|
|
26
|
+
"noImplicitReturns": true,
|
|
27
|
+
"noFallthroughCasesInSwitch": true
|
|
28
|
+
},
|
|
29
|
+
"include": ["src/**/*"],
|
|
30
|
+
"exclude": ["node_modules", "lib", "test"]
|
|
31
|
+
}
|