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,85 @@
|
|
|
1
|
+
import { describe, it } from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import { extractElementId, W3C_ELEMENT_KEY } from '../../lib/utils.js';
|
|
4
|
+
|
|
5
|
+
describe('Utils', function () {
|
|
6
|
+
describe('W3C_ELEMENT_KEY', function () {
|
|
7
|
+
it('should be the correct W3C WebDriver element identifier', function () {
|
|
8
|
+
expect(W3C_ELEMENT_KEY).to.equal('element-6066-11e4-a52e-4f735466cecf');
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('extractElementId', function () {
|
|
13
|
+
describe('W3C format', function () {
|
|
14
|
+
it('should extract element ID from W3C format', function () {
|
|
15
|
+
const element = { [W3C_ELEMENT_KEY]: 'abc123' };
|
|
16
|
+
expect(extractElementId(element)).to.equal('abc123');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should handle W3C format with additional properties', function () {
|
|
20
|
+
const element = { [W3C_ELEMENT_KEY]: 'xyz789', someOther: 'prop' };
|
|
21
|
+
expect(extractElementId(element)).to.equal('xyz789');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('JSONWP format', function () {
|
|
26
|
+
it('should extract element ID from JSONWP format', function () {
|
|
27
|
+
const element = { ELEMENT: 'def456' };
|
|
28
|
+
expect(extractElementId(element)).to.equal('def456');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should handle JSONWP format with additional properties', function () {
|
|
32
|
+
const element = { ELEMENT: 'ghi012', someOther: 'prop' };
|
|
33
|
+
expect(extractElementId(element)).to.equal('ghi012');
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('both formats present', function () {
|
|
38
|
+
it('should prefer W3C format when both are present', function () {
|
|
39
|
+
const element = { [W3C_ELEMENT_KEY]: 'w3c-id', ELEMENT: 'jsonwp-id' };
|
|
40
|
+
expect(extractElementId(element)).to.equal('w3c-id');
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('direct string ID', function () {
|
|
45
|
+
it('should return string ID directly', function () {
|
|
46
|
+
expect(extractElementId('direct-string-id')).to.equal('direct-string-id');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle empty string', function () {
|
|
50
|
+
expect(extractElementId('')).to.be.undefined;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('null and undefined', function () {
|
|
55
|
+
it('should return undefined for null', function () {
|
|
56
|
+
expect(extractElementId(null)).to.be.undefined;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should return undefined for undefined', function () {
|
|
60
|
+
expect(extractElementId(undefined)).to.be.undefined;
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('invalid inputs', function () {
|
|
65
|
+
it('should return undefined for empty object', function () {
|
|
66
|
+
expect(extractElementId({})).to.be.undefined;
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return undefined for object without element keys', function () {
|
|
70
|
+
const element = { foo: 'bar', baz: 'qux' };
|
|
71
|
+
expect(extractElementId(element)).to.be.undefined;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should return undefined for object with empty W3C value', function () {
|
|
75
|
+
const element = { [W3C_ELEMENT_KEY]: '' };
|
|
76
|
+
expect(extractElementId(element)).to.be.undefined;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return undefined for object with empty JSONWP value', function () {
|
|
80
|
+
const element = { ELEMENT: '' };
|
|
81
|
+
expect(extractElementId(element)).to.be.undefined;
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { describe, it } from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import { validateWatcherParams, validateLocator } from '../../lib/validators.js';
|
|
4
|
+
|
|
5
|
+
describe('Validators', function () {
|
|
6
|
+
describe('validateLocator', function () {
|
|
7
|
+
it('should pass for valid locator', function () {
|
|
8
|
+
const validLocator = { using: 'id', value: 'com.app:id/button' };
|
|
9
|
+
expect(() => validateLocator(validLocator, 'testLocator')).to.not.throw();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should throw error if locator is null', function () {
|
|
13
|
+
expect(() => validateLocator(null, 'testLocator')).to.throw('testLocator is required');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should throw error if locator is undefined', function () {
|
|
17
|
+
expect(() => validateLocator(undefined, 'testLocator')).to.throw('testLocator is required');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should throw error if locator is not an object', function () {
|
|
21
|
+
expect(() => validateLocator('invalid', 'testLocator')).to.throw('testLocator is required');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should throw error if using field is missing', function () {
|
|
25
|
+
const locator = { value: 'test' };
|
|
26
|
+
expect(() => validateLocator(locator, 'testLocator')).to.throw(
|
|
27
|
+
"'using' is mandatory for testLocator"
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should throw error if value field is missing', function () {
|
|
32
|
+
const locator = { using: 'id' };
|
|
33
|
+
expect(() => validateLocator(locator, 'testLocator')).to.throw(
|
|
34
|
+
"'value' is mandatory for testLocator"
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should throw error if using is not a string', function () {
|
|
39
|
+
const locator = { using: 123, value: 'test' };
|
|
40
|
+
expect(() => validateLocator(locator, 'testLocator')).to.throw(
|
|
41
|
+
"'using' must be string for testLocator"
|
|
42
|
+
);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should throw error if value is not a string', function () {
|
|
46
|
+
const locator = { using: 'id', value: 123 };
|
|
47
|
+
expect(() => validateLocator(locator, 'testLocator')).to.throw(
|
|
48
|
+
"'value' must be string for testLocator"
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should throw error for invalid locator strategy', function () {
|
|
53
|
+
const locator = { using: 'invalid-strategy', value: 'test' };
|
|
54
|
+
expect(() => validateLocator(locator, 'testLocator')).to.throw(
|
|
55
|
+
'Invalid locator strategy for testLocator'
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should accept all valid locator strategies', function () {
|
|
60
|
+
const strategies = [
|
|
61
|
+
'id',
|
|
62
|
+
'accessibility id',
|
|
63
|
+
'class name',
|
|
64
|
+
'xpath',
|
|
65
|
+
'name',
|
|
66
|
+
'-android uiautomator',
|
|
67
|
+
'-ios predicate string',
|
|
68
|
+
'-ios class chain',
|
|
69
|
+
'css selector',
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
strategies.forEach((strategy) => {
|
|
73
|
+
const locator = { using: strategy, value: 'test' };
|
|
74
|
+
expect(() => validateLocator(locator, 'testLocator')).to.not.throw();
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should be case-insensitive for locator strategies', function () {
|
|
79
|
+
const locator = { using: 'ID', value: 'test' };
|
|
80
|
+
expect(() => validateLocator(locator, 'testLocator')).to.not.throw();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe('validateWatcherParams', function () {
|
|
85
|
+
const validParams = {
|
|
86
|
+
name: 'test-watcher',
|
|
87
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
88
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
89
|
+
duration: 30000,
|
|
90
|
+
};
|
|
91
|
+
const defaultMaxDuration = 60000; // Default max duration
|
|
92
|
+
|
|
93
|
+
it('should pass for valid watcher params', function () {
|
|
94
|
+
expect(() => validateWatcherParams(validParams, defaultMaxDuration)).to.not.throw();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should throw error if params is null', function () {
|
|
98
|
+
expect(() => validateWatcherParams(null, defaultMaxDuration)).to.throw(
|
|
99
|
+
'Invalid watcher parameters'
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should throw error if params is undefined', function () {
|
|
104
|
+
expect(() => validateWatcherParams(undefined, defaultMaxDuration)).to.throw(
|
|
105
|
+
'Invalid watcher parameters'
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should throw error if name is missing', function () {
|
|
110
|
+
const params = { ...validParams };
|
|
111
|
+
delete params.name;
|
|
112
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
113
|
+
'UIWatcher name is required'
|
|
114
|
+
);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should throw error if name is empty string', function () {
|
|
118
|
+
const params = { ...validParams, name: '' };
|
|
119
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
120
|
+
'UIWatcher name is required'
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should throw error if name is whitespace only', function () {
|
|
125
|
+
const params = { ...validParams, name: ' ' };
|
|
126
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
127
|
+
'UIWatcher name is empty'
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should throw error if referenceLocator is missing', function () {
|
|
132
|
+
const params = { ...validParams };
|
|
133
|
+
delete params.referenceLocator;
|
|
134
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
135
|
+
'UIWatcher referenceLocator is required'
|
|
136
|
+
);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should throw error if actionLocator is missing', function () {
|
|
140
|
+
const params = { ...validParams };
|
|
141
|
+
delete params.actionLocator;
|
|
142
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
143
|
+
'UIWatcher actionLocator is required'
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should throw error if duration is missing', function () {
|
|
148
|
+
const params = { ...validParams };
|
|
149
|
+
delete params.duration;
|
|
150
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
151
|
+
'UIWatcher duration is required'
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should throw error if duration is 0', function () {
|
|
156
|
+
const params = { ...validParams, duration: 0 };
|
|
157
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
158
|
+
'UIWatcher duration must be a positive number'
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should throw error if duration is negative', function () {
|
|
163
|
+
const params = { ...validParams, duration: -1000 };
|
|
164
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
165
|
+
'UIWatcher duration must be a positive number'
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should throw error if duration > 60000', function () {
|
|
170
|
+
const params = { ...validParams, duration: 60001 };
|
|
171
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
172
|
+
'UIWatcher duration must be ≤ 60 seconds'
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should accept duration = 60000', function () {
|
|
177
|
+
const params = { ...validParams, duration: 60000 };
|
|
178
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.not.throw();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('should throw error if priority is not a number', function () {
|
|
182
|
+
const params = { ...validParams, priority: 'high' };
|
|
183
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
184
|
+
'UIWatcher priority must be a number'
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('should accept negative priority values', function () {
|
|
189
|
+
const params = { ...validParams, priority: -10 };
|
|
190
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.not.throw();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should throw error if stopOnFound is not a boolean', function () {
|
|
194
|
+
const params = { ...validParams, stopOnFound: 'true' };
|
|
195
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
196
|
+
'UIWatcher stopOnFound must be a boolean'
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should accept stopOnFound as true or false', function () {
|
|
201
|
+
const params1 = { ...validParams, stopOnFound: true };
|
|
202
|
+
const params2 = { ...validParams, stopOnFound: false };
|
|
203
|
+
expect(() => validateWatcherParams(params1, defaultMaxDuration)).to.not.throw();
|
|
204
|
+
expect(() => validateWatcherParams(params2, defaultMaxDuration)).to.not.throw();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should throw error if cooldownMs is not a number', function () {
|
|
208
|
+
const params = { ...validParams, cooldownMs: '5000' };
|
|
209
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
210
|
+
'UIWatcher cooldownMs must be a number'
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('should throw error if cooldownMs is negative', function () {
|
|
215
|
+
const params = { ...validParams, cooldownMs: -100 };
|
|
216
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
217
|
+
'UIWatcher cooldownMs must be ≥ 0'
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should accept cooldownMs = 0', function () {
|
|
222
|
+
const params = { ...validParams, cooldownMs: 0 };
|
|
223
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.not.throw();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should accept all valid optional parameters', function () {
|
|
227
|
+
const params = {
|
|
228
|
+
...validParams,
|
|
229
|
+
priority: 10,
|
|
230
|
+
stopOnFound: true,
|
|
231
|
+
cooldownMs: 5000,
|
|
232
|
+
};
|
|
233
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.not.throw();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should respect custom maxDurationMs parameter', function () {
|
|
237
|
+
const params = { ...validParams, duration: 90000 };
|
|
238
|
+
// Should fail with default (60000)
|
|
239
|
+
expect(() => validateWatcherParams(params, defaultMaxDuration)).to.throw(
|
|
240
|
+
'UIWatcher duration must be ≤'
|
|
241
|
+
);
|
|
242
|
+
// Should pass with custom limit (120000)
|
|
243
|
+
expect(() => validateWatcherParams(params, 120000)).to.not.throw();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
});
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { describe, it, beforeEach } from 'mocha';
|
|
2
|
+
import { expect } from 'chai';
|
|
3
|
+
import sinon from 'sinon';
|
|
4
|
+
import { WatcherStore } from '../../lib/watcher-store.js';
|
|
5
|
+
import { checkWatchers } from '../../lib/watcher-checker.js';
|
|
6
|
+
|
|
7
|
+
describe('WatcherChecker', function () {
|
|
8
|
+
let store;
|
|
9
|
+
let mockDriver;
|
|
10
|
+
|
|
11
|
+
beforeEach(function () {
|
|
12
|
+
// Use default config values (same as schema defaults)
|
|
13
|
+
store = new WatcherStore({ maxWatchers: 5, maxDurationMs: 60000 });
|
|
14
|
+
|
|
15
|
+
// Create mock driver
|
|
16
|
+
mockDriver = {
|
|
17
|
+
findElement: sinon.stub(),
|
|
18
|
+
click: sinon.stub(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('checkWatchers', function () {
|
|
23
|
+
it('should skip checking when watchers are disabled', async function () {
|
|
24
|
+
store.disable();
|
|
25
|
+
store.add({
|
|
26
|
+
name: 'test',
|
|
27
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
28
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
29
|
+
duration: 30000,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await checkWatchers(mockDriver, store);
|
|
33
|
+
|
|
34
|
+
expect(mockDriver.findElement.called).to.be.false;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should skip checking when no active watchers', async function () {
|
|
38
|
+
await checkWatchers(mockDriver, store);
|
|
39
|
+
|
|
40
|
+
expect(mockDriver.findElement.called).to.be.false;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should check watchers in priority order', async function () {
|
|
44
|
+
const calls = [];
|
|
45
|
+
|
|
46
|
+
store.add({
|
|
47
|
+
name: 'low',
|
|
48
|
+
priority: 1,
|
|
49
|
+
referenceLocator: { using: 'id', value: 'popup1' },
|
|
50
|
+
actionLocator: { using: 'id', value: 'close1' },
|
|
51
|
+
duration: 30000,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
store.add({
|
|
55
|
+
name: 'high',
|
|
56
|
+
priority: 10,
|
|
57
|
+
referenceLocator: { using: 'id', value: 'popup2' },
|
|
58
|
+
actionLocator: { using: 'id', value: 'close2' },
|
|
59
|
+
duration: 30000,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
mockDriver.findElement.callsFake((using, value) => {
|
|
63
|
+
calls.push(value);
|
|
64
|
+
throw new Error('not found');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await checkWatchers(mockDriver, store);
|
|
68
|
+
|
|
69
|
+
// Should check high priority first
|
|
70
|
+
expect(calls[0]).to.equal('popup2');
|
|
71
|
+
expect(calls[1]).to.equal('popup1');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should skip inactive watchers', async function () {
|
|
75
|
+
store.add({
|
|
76
|
+
name: 'test',
|
|
77
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
78
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
79
|
+
duration: 30000,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
store.markInactive('test');
|
|
83
|
+
|
|
84
|
+
await checkWatchers(mockDriver, store);
|
|
85
|
+
|
|
86
|
+
expect(mockDriver.findElement.called).to.be.false;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('should continue to next watcher if reference not found', async function () {
|
|
90
|
+
store.add({
|
|
91
|
+
name: 'watcher1',
|
|
92
|
+
referenceLocator: { using: 'id', value: 'popup1' },
|
|
93
|
+
actionLocator: { using: 'id', value: 'close1' },
|
|
94
|
+
duration: 30000,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
store.add({
|
|
98
|
+
name: 'watcher2',
|
|
99
|
+
referenceLocator: { using: 'id', value: 'popup2' },
|
|
100
|
+
actionLocator: { using: 'id', value: 'close2' },
|
|
101
|
+
duration: 30000,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
mockDriver.findElement.throws(new Error('not found'));
|
|
105
|
+
|
|
106
|
+
await checkWatchers(mockDriver, store);
|
|
107
|
+
|
|
108
|
+
// Should attempt both watchers
|
|
109
|
+
expect(mockDriver.findElement.callCount).to.equal(2);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should click action element when reference found', async function () {
|
|
113
|
+
store.add({
|
|
114
|
+
name: 'test',
|
|
115
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
116
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
117
|
+
duration: 30000,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
121
|
+
mockDriver.click.resolves();
|
|
122
|
+
|
|
123
|
+
await checkWatchers(mockDriver, store);
|
|
124
|
+
|
|
125
|
+
expect(mockDriver.findElement.callCount).to.equal(2); // reference + action
|
|
126
|
+
expect(mockDriver.click.calledOnce).to.be.true;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should increment trigger count on successful click', async function () {
|
|
130
|
+
store.add({
|
|
131
|
+
name: 'test',
|
|
132
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
133
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
134
|
+
duration: 30000,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
138
|
+
mockDriver.click.resolves();
|
|
139
|
+
|
|
140
|
+
await checkWatchers(mockDriver, store);
|
|
141
|
+
|
|
142
|
+
const watcher = store.get('test');
|
|
143
|
+
expect(watcher.triggerCount).to.equal(1);
|
|
144
|
+
expect(watcher.lastTriggeredAt).to.be.a('number');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should mark inactive when stopOnFound is true', async function () {
|
|
148
|
+
store.add({
|
|
149
|
+
name: 'test',
|
|
150
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
151
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
152
|
+
duration: 30000,
|
|
153
|
+
stopOnFound: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
157
|
+
mockDriver.click.resolves();
|
|
158
|
+
|
|
159
|
+
await checkWatchers(mockDriver, store);
|
|
160
|
+
|
|
161
|
+
const watcher = store.get('test');
|
|
162
|
+
expect(watcher.status).to.equal('inactive');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should execute cooldown wait when configured', async function () {
|
|
166
|
+
this.timeout(5000);
|
|
167
|
+
|
|
168
|
+
store.add({
|
|
169
|
+
name: 'test',
|
|
170
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
171
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
172
|
+
duration: 30000,
|
|
173
|
+
cooldownMs: 100,
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
177
|
+
mockDriver.click.resolves();
|
|
178
|
+
|
|
179
|
+
const start = Date.now();
|
|
180
|
+
await checkWatchers(mockDriver, store);
|
|
181
|
+
const elapsed = Date.now() - start;
|
|
182
|
+
|
|
183
|
+
// Should have waited at least 100ms
|
|
184
|
+
expect(elapsed).to.be.at.least(100);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should continue to next watcher if action click fails', async function () {
|
|
188
|
+
store.add({
|
|
189
|
+
name: 'watcher1',
|
|
190
|
+
referenceLocator: { using: 'id', value: 'popup1' },
|
|
191
|
+
actionLocator: { using: 'id', value: 'close1' },
|
|
192
|
+
duration: 30000,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
store.add({
|
|
196
|
+
name: 'watcher2',
|
|
197
|
+
referenceLocator: { using: 'id', value: 'popup2' },
|
|
198
|
+
actionLocator: { using: 'id', value: 'close2' },
|
|
199
|
+
duration: 30000,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
203
|
+
mockDriver.click.onFirstCall().rejects(new Error('Click failed'));
|
|
204
|
+
mockDriver.click.onSecondCall().resolves();
|
|
205
|
+
|
|
206
|
+
await checkWatchers(mockDriver, store);
|
|
207
|
+
|
|
208
|
+
// Should attempt both watchers
|
|
209
|
+
expect(mockDriver.click.callCount).to.equal(2);
|
|
210
|
+
|
|
211
|
+
// First watcher should not be counted as triggered
|
|
212
|
+
const watcher1 = store.get('watcher1');
|
|
213
|
+
expect(watcher1.triggerCount).to.equal(0);
|
|
214
|
+
|
|
215
|
+
// Second watcher should be counted
|
|
216
|
+
const watcher2 = store.get('watcher2');
|
|
217
|
+
expect(watcher2.triggerCount).to.equal(1);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should not execute cooldown if click fails', async function () {
|
|
221
|
+
store.add({
|
|
222
|
+
name: 'test',
|
|
223
|
+
referenceLocator: { using: 'id', value: 'popup' },
|
|
224
|
+
actionLocator: { using: 'id', value: 'close' },
|
|
225
|
+
duration: 30000,
|
|
226
|
+
cooldownMs: 1000,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
230
|
+
mockDriver.click.rejects(new Error('Click failed'));
|
|
231
|
+
|
|
232
|
+
const start = Date.now();
|
|
233
|
+
await checkWatchers(mockDriver, store);
|
|
234
|
+
const elapsed = Date.now() - start;
|
|
235
|
+
|
|
236
|
+
// Should not have waited for cooldown
|
|
237
|
+
expect(elapsed).to.be.below(500);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should handle multiple watchers triggering sequentially', async function () {
|
|
241
|
+
this.timeout(5000);
|
|
242
|
+
|
|
243
|
+
store.add({
|
|
244
|
+
name: 'watcher1',
|
|
245
|
+
referenceLocator: { using: 'id', value: 'popup1' },
|
|
246
|
+
actionLocator: { using: 'id', value: 'close1' },
|
|
247
|
+
duration: 30000,
|
|
248
|
+
cooldownMs: 50,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
store.add({
|
|
252
|
+
name: 'watcher2',
|
|
253
|
+
referenceLocator: { using: 'id', value: 'popup2' },
|
|
254
|
+
actionLocator: { using: 'id', value: 'close2' },
|
|
255
|
+
duration: 30000,
|
|
256
|
+
cooldownMs: 50,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
mockDriver.findElement.resolves({ ELEMENT: 'element-1' });
|
|
260
|
+
mockDriver.click.resolves();
|
|
261
|
+
|
|
262
|
+
const start = Date.now();
|
|
263
|
+
await checkWatchers(mockDriver, store);
|
|
264
|
+
const elapsed = Date.now() - start;
|
|
265
|
+
|
|
266
|
+
// Should have waited for both cooldowns (50ms + 50ms = 100ms minimum)
|
|
267
|
+
expect(elapsed).to.be.at.least(100);
|
|
268
|
+
|
|
269
|
+
// Both should be triggered
|
|
270
|
+
expect(store.get('watcher1').triggerCount).to.equal(1);
|
|
271
|
+
expect(store.get('watcher2').triggerCount).to.equal(1);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
});
|