@appium/driver-test-support 0.2.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/LICENSE +201 -0
- package/README.md +112 -0
- package/build/lib/e2e-suite.d.ts +77 -0
- package/build/lib/e2e-suite.d.ts.map +1 -0
- package/build/lib/e2e-suite.js +389 -0
- package/build/lib/helpers.d.ts +19 -0
- package/build/lib/helpers.d.ts.map +1 -0
- package/build/lib/helpers.js +49 -0
- package/build/lib/index.d.ts +7 -0
- package/build/lib/index.d.ts.map +1 -0
- package/build/lib/index.js +47 -0
- package/build/lib/unit-suite.d.ts +12 -0
- package/build/lib/unit-suite.d.ts.map +1 -0
- package/build/lib/unit-suite.js +551 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/index.js +1 -0
- package/lib/e2e-suite.js +465 -0
- package/lib/helpers.js +68 -0
- package/lib/index.js +9 -0
- package/lib/unit-suite.js +632 -0
- package/package.json +70 -0
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import B from 'bluebird';
|
|
3
|
+
import {createSandbox} from 'sinon';
|
|
4
|
+
|
|
5
|
+
import chai from 'chai';
|
|
6
|
+
|
|
7
|
+
const should = chai.should();
|
|
8
|
+
const {expect} = chai;
|
|
9
|
+
|
|
10
|
+
// wrap these tests in a function so we can export the tests and re-use them
|
|
11
|
+
// for actual driver implementations
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates unit test suites for a driver.
|
|
15
|
+
* @param {DriverClass} DriverClass
|
|
16
|
+
* @param {AppiumW3CCapabilities} [defaultCaps]
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function driverUnitTestSuite(DriverClass, defaultCaps = {}) {
|
|
20
|
+
// to display the driver under test in report
|
|
21
|
+
const className = DriverClass.name ?? '(unknown driver)';
|
|
22
|
+
|
|
23
|
+
describe(`BaseDriver unit suite (as ${className})`, function () {
|
|
24
|
+
/** @type {InstanceType<typeof DriverClass>} */
|
|
25
|
+
let d;
|
|
26
|
+
/** @type {W3CCapabilities} */
|
|
27
|
+
let w3cCaps;
|
|
28
|
+
/** @type {import('sinon').SinonSandbox} */
|
|
29
|
+
let sandbox;
|
|
30
|
+
|
|
31
|
+
beforeEach(function () {
|
|
32
|
+
sandbox = createSandbox();
|
|
33
|
+
d = new DriverClass();
|
|
34
|
+
w3cCaps = {
|
|
35
|
+
alwaysMatch: {
|
|
36
|
+
...defaultCaps,
|
|
37
|
+
platformName: 'Fake',
|
|
38
|
+
'appium:deviceName': 'Commodore 64',
|
|
39
|
+
},
|
|
40
|
+
firstMatch: [{}],
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
afterEach(async function () {
|
|
44
|
+
sandbox.restore();
|
|
45
|
+
await d.deleteSession();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('static property', function () {
|
|
49
|
+
describe('baseVersion', function () {
|
|
50
|
+
it('should exist', function () {
|
|
51
|
+
DriverClass.baseVersion.should.exist;
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('Log prefix', function () {
|
|
57
|
+
it('should setup log prefix', async function () {
|
|
58
|
+
const d = new DriverClass();
|
|
59
|
+
const previousPrefix = d.log.prefix;
|
|
60
|
+
await d.createSession({
|
|
61
|
+
alwaysMatch: {...defaultCaps, platformName: 'Fake', 'appium:deviceName': 'Commodore 64'},
|
|
62
|
+
firstMatch: [{}],
|
|
63
|
+
});
|
|
64
|
+
try {
|
|
65
|
+
expect(previousPrefix).not.to.eql(d.log.prefix);
|
|
66
|
+
} finally {
|
|
67
|
+
await d.deleteSession();
|
|
68
|
+
expect(previousPrefix).to.eql(d.log.prefix);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should return an empty status object', async function () {
|
|
74
|
+
let status = await d.getStatus();
|
|
75
|
+
status.should.eql({});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should return a sessionId from createSession', async function () {
|
|
79
|
+
let [sessId] = await d.createSession(w3cCaps);
|
|
80
|
+
should.exist(sessId);
|
|
81
|
+
sessId.should.be.a('string');
|
|
82
|
+
sessId.length.should.be.above(5);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should not be able to start two sessions without closing the first', async function () {
|
|
86
|
+
await d.createSession(_.cloneDeep(w3cCaps));
|
|
87
|
+
await d.createSession(_.cloneDeep(w3cCaps)).should.be.rejectedWith('session');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should be able to delete a session', async function () {
|
|
91
|
+
let sessionId1 = await d.createSession(_.cloneDeep(w3cCaps));
|
|
92
|
+
await d.deleteSession();
|
|
93
|
+
should.equal(d.sessionId, null);
|
|
94
|
+
let sessionId2 = await d.createSession(_.cloneDeep(w3cCaps));
|
|
95
|
+
sessionId1.should.not.eql(sessionId2);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should get the current session', async function () {
|
|
99
|
+
let [, caps] = await d.createSession(w3cCaps);
|
|
100
|
+
caps.should.equal(await d.getSession());
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return sessions if no session exists', async function () {
|
|
104
|
+
let sessions = await d.getSessions();
|
|
105
|
+
sessions.length.should.equal(0);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return sessions', async function () {
|
|
109
|
+
const caps = _.cloneDeep(w3cCaps);
|
|
110
|
+
await d.createSession(caps);
|
|
111
|
+
let sessions = await d.getSessions();
|
|
112
|
+
|
|
113
|
+
sessions.length.should.equal(1);
|
|
114
|
+
sessions[0].should.include({
|
|
115
|
+
id: d.sessionId,
|
|
116
|
+
});
|
|
117
|
+
sessions[0].capabilities.should.include({
|
|
118
|
+
deviceName: 'Commodore 64',
|
|
119
|
+
platformName: 'Fake',
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should fulfill an unexpected driver quit promise', async function () {
|
|
124
|
+
// make a command that will wait a bit so we can crash while it's running
|
|
125
|
+
sandbox.stub(d, 'getStatus').callsFake(async () => {
|
|
126
|
+
await B.delay(1000);
|
|
127
|
+
return 'good status';
|
|
128
|
+
});
|
|
129
|
+
let cmdPromise = d.executeCommand('getStatus');
|
|
130
|
+
await B.delay(10);
|
|
131
|
+
const p = new B((resolve, reject) => {
|
|
132
|
+
setTimeout(
|
|
133
|
+
() =>
|
|
134
|
+
reject(
|
|
135
|
+
new Error(
|
|
136
|
+
'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout'
|
|
137
|
+
)
|
|
138
|
+
),
|
|
139
|
+
5000
|
|
140
|
+
);
|
|
141
|
+
d.onUnexpectedShutdown(resolve);
|
|
142
|
+
});
|
|
143
|
+
d.startUnexpectedShutdown(new Error('We crashed'));
|
|
144
|
+
await cmdPromise.should.be.rejectedWith(/We crashed/);
|
|
145
|
+
await p;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should not allow commands in middle of unexpected shutdown', async function () {
|
|
149
|
+
// make a command that will wait a bit so we can crash while it's running
|
|
150
|
+
sandbox.stub(d, 'deleteSession').callsFake(async function () {
|
|
151
|
+
await B.delay(100);
|
|
152
|
+
DriverClass.prototype.deleteSession.call(this);
|
|
153
|
+
});
|
|
154
|
+
await d.createSession(w3cCaps);
|
|
155
|
+
const p = new B((resolve, reject) => {
|
|
156
|
+
setTimeout(
|
|
157
|
+
() =>
|
|
158
|
+
reject(
|
|
159
|
+
new Error(
|
|
160
|
+
'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout'
|
|
161
|
+
)
|
|
162
|
+
),
|
|
163
|
+
5000
|
|
164
|
+
);
|
|
165
|
+
d.onUnexpectedShutdown(resolve);
|
|
166
|
+
});
|
|
167
|
+
d.startUnexpectedShutdown(new Error('We crashed'));
|
|
168
|
+
await p;
|
|
169
|
+
await d.executeCommand('getSession').should.be.rejectedWith(/shut down/);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('should allow new commands after done shutting down', async function () {
|
|
173
|
+
// make a command that will wait a bit so we can crash while it's running
|
|
174
|
+
sandbox.stub(d, 'deleteSession').callsFake(async function () {
|
|
175
|
+
await B.delay(100);
|
|
176
|
+
DriverClass.prototype.deleteSession.call(this);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
await d.createSession(_.cloneDeep(w3cCaps));
|
|
180
|
+
const p = new B((resolve, reject) => {
|
|
181
|
+
setTimeout(
|
|
182
|
+
() =>
|
|
183
|
+
reject(
|
|
184
|
+
new Error(
|
|
185
|
+
'onUnexpectedShutdown event is expected to be fired within 5 seconds timeout'
|
|
186
|
+
)
|
|
187
|
+
),
|
|
188
|
+
5000
|
|
189
|
+
);
|
|
190
|
+
d.onUnexpectedShutdown(resolve);
|
|
191
|
+
});
|
|
192
|
+
d.startUnexpectedShutdown(new Error('We crashed'));
|
|
193
|
+
await p;
|
|
194
|
+
|
|
195
|
+
await d.executeCommand('getSession').should.be.rejectedWith(/shut down/);
|
|
196
|
+
await B.delay(500);
|
|
197
|
+
|
|
198
|
+
await d.executeCommand('createSession', null, null, _.cloneDeep(w3cCaps));
|
|
199
|
+
await d.deleteSession();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should distinguish between W3C and JSONWP session', async function () {
|
|
203
|
+
// Test W3C (leave first 2 args null because those are the JSONWP args)
|
|
204
|
+
await d.executeCommand('createSession', null, null, {
|
|
205
|
+
alwaysMatch: {
|
|
206
|
+
...defaultCaps,
|
|
207
|
+
platformName: 'Fake',
|
|
208
|
+
'appium:deviceName': 'Commodore 64',
|
|
209
|
+
},
|
|
210
|
+
firstMatch: [{}],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(d.protocol).to.equal('W3C');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe('protocol detection', function () {
|
|
217
|
+
it('should use W3C if only W3C caps are provided', async function () {
|
|
218
|
+
await d.createSession({
|
|
219
|
+
alwaysMatch: _.clone(defaultCaps),
|
|
220
|
+
firstMatch: [{}],
|
|
221
|
+
});
|
|
222
|
+
expect(d.protocol).to.equal('W3C');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should have a method to get driver for a session', async function () {
|
|
227
|
+
let [sessId] = await d.createSession(w3cCaps);
|
|
228
|
+
expect(d.driverForSession(sessId)).to.eql(d);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe('command queue', function () {
|
|
232
|
+
/** @type {InstanceType<DriverClass>} */
|
|
233
|
+
let d;
|
|
234
|
+
let waitMs = 10;
|
|
235
|
+
|
|
236
|
+
beforeEach(function () {
|
|
237
|
+
d = new DriverClass();
|
|
238
|
+
sandbox.stub(d, 'getStatus').callsFake(async () => {
|
|
239
|
+
await B.delay(waitMs);
|
|
240
|
+
return Date.now();
|
|
241
|
+
});
|
|
242
|
+
sandbox.stub(d, 'getSessions').callsFake(async () => {
|
|
243
|
+
await B.delay(waitMs);
|
|
244
|
+
throw new Error('multipass');
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
afterEach(async function () {
|
|
249
|
+
await d.clearNewCommandTimeout();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should queue commands and.executeCommand/respond in the order received', async function () {
|
|
253
|
+
let numCmds = 10;
|
|
254
|
+
let cmds = [];
|
|
255
|
+
for (let i = 0; i < numCmds; i++) {
|
|
256
|
+
cmds.push(d.executeCommand('getStatus'));
|
|
257
|
+
}
|
|
258
|
+
let results = await B.all(cmds);
|
|
259
|
+
for (let i = 1; i < numCmds; i++) {
|
|
260
|
+
if (results[i] <= results[i - 1]) {
|
|
261
|
+
throw new Error('Got result out of order');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should handle errors correctly when queuing', async function () {
|
|
267
|
+
let numCmds = 10;
|
|
268
|
+
let cmds = [];
|
|
269
|
+
for (let i = 0; i < numCmds; i++) {
|
|
270
|
+
if (i === 5) {
|
|
271
|
+
cmds.push(d.executeCommand('getSessions'));
|
|
272
|
+
} else {
|
|
273
|
+
cmds.push(d.executeCommand('getStatus'));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
let results = /** @type {PromiseFulfilledResult<any>[]} */ (
|
|
277
|
+
// eslint-disable-next-line promise/no-native
|
|
278
|
+
await Promise.allSettled(cmds)
|
|
279
|
+
);
|
|
280
|
+
for (let i = 1; i < 5; i++) {
|
|
281
|
+
if (results[i].value <= results[i - 1].value) {
|
|
282
|
+
throw new Error('Got result out of order');
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/** @type {PromiseRejectedResult} */ (
|
|
286
|
+
/** @type {unknown} */ (results[5])
|
|
287
|
+
).reason.message.should.contain('multipass');
|
|
288
|
+
for (let i = 7; i < numCmds; i++) {
|
|
289
|
+
if (results[i].value <= results[i - 1].value) {
|
|
290
|
+
throw new Error('Got result out of order');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should not care if queue empties for a bit', async function () {
|
|
296
|
+
let numCmds = 10;
|
|
297
|
+
let cmds = [];
|
|
298
|
+
for (let i = 0; i < numCmds; i++) {
|
|
299
|
+
cmds.push(d.executeCommand('getStatus'));
|
|
300
|
+
}
|
|
301
|
+
let results = await B.all(cmds);
|
|
302
|
+
cmds = [];
|
|
303
|
+
for (let i = 0; i < numCmds; i++) {
|
|
304
|
+
cmds.push(d.executeCommand('getStatus'));
|
|
305
|
+
}
|
|
306
|
+
results = await B.all(cmds);
|
|
307
|
+
for (let i = 1; i < numCmds; i++) {
|
|
308
|
+
if (results[i] <= results[i - 1]) {
|
|
309
|
+
throw new Error('Got result out of order');
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe('timeouts', function () {
|
|
316
|
+
before(async function () {
|
|
317
|
+
await d.createSession(w3cCaps);
|
|
318
|
+
});
|
|
319
|
+
describe('command', function () {
|
|
320
|
+
it('should exist by default', function () {
|
|
321
|
+
d.newCommandTimeoutMs.should.equal(60000);
|
|
322
|
+
});
|
|
323
|
+
it('should be settable through `timeouts`', async function () {
|
|
324
|
+
await d.timeouts('command', 20);
|
|
325
|
+
d.newCommandTimeoutMs.should.equal(20);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
describe('implicit', function () {
|
|
329
|
+
it('should not exist by default', function () {
|
|
330
|
+
d.implicitWaitMs.should.equal(0);
|
|
331
|
+
});
|
|
332
|
+
it('should be settable through `timeouts`', async function () {
|
|
333
|
+
await d.timeouts('implicit', 20);
|
|
334
|
+
d.implicitWaitMs.should.equal(20);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
describe('timeouts (W3C)', function () {
|
|
340
|
+
beforeEach(async function () {
|
|
341
|
+
await d.createSession(w3cCaps);
|
|
342
|
+
});
|
|
343
|
+
afterEach(async function () {
|
|
344
|
+
await d.deleteSession();
|
|
345
|
+
});
|
|
346
|
+
it('should get timeouts that we set', async function () {
|
|
347
|
+
// @ts-expect-error
|
|
348
|
+
await d.timeouts(undefined, undefined, undefined, undefined, 1000);
|
|
349
|
+
await d.getTimeouts().should.eventually.have.property('implicit', 1000);
|
|
350
|
+
await d.timeouts('command', 2000);
|
|
351
|
+
await d.getTimeouts().should.eventually.deep.equal({
|
|
352
|
+
implicit: 1000,
|
|
353
|
+
command: 2000,
|
|
354
|
+
});
|
|
355
|
+
// @ts-expect-error
|
|
356
|
+
await d.timeouts(undefined, undefined, undefined, undefined, 3000);
|
|
357
|
+
await d.getTimeouts().should.eventually.deep.equal({
|
|
358
|
+
implicit: 3000,
|
|
359
|
+
command: 2000,
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe('reset compatibility', function () {
|
|
365
|
+
it('should not allow both fullReset and noReset to be true', async function () {
|
|
366
|
+
const newCaps = {
|
|
367
|
+
alwaysMatch: {
|
|
368
|
+
...defaultCaps,
|
|
369
|
+
platformName: 'Fake',
|
|
370
|
+
'appium:deviceName': 'Commodore 64',
|
|
371
|
+
'appium:fullReset': true,
|
|
372
|
+
'appium:noReset': true,
|
|
373
|
+
},
|
|
374
|
+
firstMatch: [{}],
|
|
375
|
+
};
|
|
376
|
+
await d.createSession(newCaps).should.be.rejectedWith(/noReset.+fullReset/);
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
describe('proxying', function () {
|
|
381
|
+
let sessId;
|
|
382
|
+
beforeEach(async function () {
|
|
383
|
+
[sessId] = await d.createSession(w3cCaps);
|
|
384
|
+
});
|
|
385
|
+
describe('#proxyActive', function () {
|
|
386
|
+
it('should exist', function () {
|
|
387
|
+
d.proxyActive.should.be.an.instanceof(Function);
|
|
388
|
+
});
|
|
389
|
+
it('should return false', function () {
|
|
390
|
+
d.proxyActive(sessId).should.be.false;
|
|
391
|
+
});
|
|
392
|
+
it('should throw an error when sessionId is wrong', function () {
|
|
393
|
+
(() => {
|
|
394
|
+
d.proxyActive('aaa');
|
|
395
|
+
}).should.throw;
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
describe('#getProxyAvoidList', function () {
|
|
400
|
+
it('should exist', function () {
|
|
401
|
+
d.getProxyAvoidList.should.be.an.instanceof(Function);
|
|
402
|
+
});
|
|
403
|
+
it('should return an array', function () {
|
|
404
|
+
d.getProxyAvoidList(sessId).should.be.an.instanceof(Array);
|
|
405
|
+
});
|
|
406
|
+
it('should throw an error when sessionId is wrong', function () {
|
|
407
|
+
(() => {
|
|
408
|
+
d.getProxyAvoidList('aaa');
|
|
409
|
+
}).should.throw;
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe('#canProxy', function () {
|
|
414
|
+
it('should have a #canProxy method', function () {
|
|
415
|
+
d.canProxy.should.be.an.instanceof(Function);
|
|
416
|
+
});
|
|
417
|
+
it('should return a boolean from #canProxy', function () {
|
|
418
|
+
d.canProxy(sessId).should.be.a('boolean');
|
|
419
|
+
});
|
|
420
|
+
it('should throw an error when sessionId is wrong', function () {
|
|
421
|
+
(() => {
|
|
422
|
+
d.canProxy();
|
|
423
|
+
}).should.throw;
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
describe('#proxyRouteIsAvoided', function () {
|
|
428
|
+
it('should validate form of avoidance list', function () {
|
|
429
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
430
|
+
// @ts-expect-error
|
|
431
|
+
avoidStub.returns([['POST', /\/foo/], ['GET']]);
|
|
432
|
+
(() => {
|
|
433
|
+
// @ts-expect-error
|
|
434
|
+
d.proxyRouteIsAvoided();
|
|
435
|
+
}).should.throw;
|
|
436
|
+
avoidStub.returns([
|
|
437
|
+
['POST', /\/foo/],
|
|
438
|
+
// @ts-expect-error
|
|
439
|
+
['GET', /^foo/, 'bar'],
|
|
440
|
+
]);
|
|
441
|
+
(() => {
|
|
442
|
+
// @ts-expect-error
|
|
443
|
+
d.proxyRouteIsAvoided();
|
|
444
|
+
}).should.throw;
|
|
445
|
+
});
|
|
446
|
+
it('should reject bad http methods', function () {
|
|
447
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
448
|
+
avoidStub.returns([
|
|
449
|
+
['POST', /^foo/],
|
|
450
|
+
// @ts-expect-error
|
|
451
|
+
['BAZETE', /^bar/],
|
|
452
|
+
]);
|
|
453
|
+
(() => {
|
|
454
|
+
// @ts-expect-error
|
|
455
|
+
d.proxyRouteIsAvoided();
|
|
456
|
+
}).should.throw;
|
|
457
|
+
});
|
|
458
|
+
it('should reject non-regex routes', function () {
|
|
459
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
460
|
+
avoidStub.returns([
|
|
461
|
+
['POST', /^foo/],
|
|
462
|
+
// @ts-expect-error
|
|
463
|
+
['GET', '/bar'],
|
|
464
|
+
]);
|
|
465
|
+
(() => {
|
|
466
|
+
// @ts-expect-error
|
|
467
|
+
d.proxyRouteIsAvoided();
|
|
468
|
+
}).should.throw;
|
|
469
|
+
});
|
|
470
|
+
it('should return true for routes in the avoid list', function () {
|
|
471
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
472
|
+
avoidStub.returns([['POST', /^\/foo/]]);
|
|
473
|
+
d.proxyRouteIsAvoided('foo', 'POST', '/foo/bar').should.be.true;
|
|
474
|
+
});
|
|
475
|
+
it('should strip away any wd/hub prefix', function () {
|
|
476
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
477
|
+
avoidStub.returns([['POST', /^\/foo/]]);
|
|
478
|
+
d.proxyRouteIsAvoided('foo', 'POST', '/foo/bar').should.be.true;
|
|
479
|
+
});
|
|
480
|
+
it('should return false for routes not in the avoid list', function () {
|
|
481
|
+
const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
|
|
482
|
+
avoidStub.returns([['POST', /^\/foo/]]);
|
|
483
|
+
d.proxyRouteIsAvoided('foo', 'GET', '/foo/bar').should.be.false;
|
|
484
|
+
d.proxyRouteIsAvoided('foo', 'POST', '/boo').should.be.false;
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
describe('event timing framework', function () {
|
|
490
|
+
let beforeStartTime;
|
|
491
|
+
beforeEach(async function () {
|
|
492
|
+
beforeStartTime = Date.now();
|
|
493
|
+
d.shouldValidateCaps = false;
|
|
494
|
+
await d.executeCommand('createSession', null, null, {
|
|
495
|
+
alwaysMatch: {...defaultCaps},
|
|
496
|
+
firstMatch: [{}],
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
describe('#eventHistory', function () {
|
|
500
|
+
it('should have an eventHistory property', function () {
|
|
501
|
+
should.exist(d.eventHistory);
|
|
502
|
+
should.exist(d.eventHistory.commands);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('should have a session start timing after session start', function () {
|
|
506
|
+
let {newSessionRequested, newSessionStarted} = d.eventHistory;
|
|
507
|
+
newSessionRequested.should.have.length(1);
|
|
508
|
+
newSessionStarted.should.have.length(1);
|
|
509
|
+
newSessionRequested[0].should.be.a('number');
|
|
510
|
+
newSessionStarted[0].should.be.a('number');
|
|
511
|
+
(newSessionRequested[0] >= beforeStartTime).should.be.true;
|
|
512
|
+
(newSessionStarted[0] >= newSessionRequested[0]).should.be.true;
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('should include a commands list', async function () {
|
|
516
|
+
await d.executeCommand('getStatus', []);
|
|
517
|
+
d.eventHistory.commands.length.should.equal(2);
|
|
518
|
+
d.eventHistory.commands[1].cmd.should.equal('getStatus');
|
|
519
|
+
d.eventHistory.commands[1].startTime.should.be.a('number');
|
|
520
|
+
d.eventHistory.commands[1].endTime.should.be.a('number');
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
describe('#logEvent', function () {
|
|
524
|
+
it('should allow logging arbitrary events', function () {
|
|
525
|
+
d.logEvent('foo');
|
|
526
|
+
d.eventHistory.foo[0].should.be.a('number');
|
|
527
|
+
(d.eventHistory.foo[0] >= beforeStartTime).should.be.true;
|
|
528
|
+
});
|
|
529
|
+
it('should not allow reserved or oddly formed event names', function () {
|
|
530
|
+
(() => {
|
|
531
|
+
d.logEvent('commands');
|
|
532
|
+
}).should.throw();
|
|
533
|
+
(() => {
|
|
534
|
+
// @ts-expect-error
|
|
535
|
+
d.logEvent(1);
|
|
536
|
+
}).should.throw();
|
|
537
|
+
(() => {
|
|
538
|
+
// @ts-expect-error
|
|
539
|
+
d.logEvent({});
|
|
540
|
+
}).should.throw();
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
it('should allow logging the same event multiple times', function () {
|
|
544
|
+
d.logEvent('bar');
|
|
545
|
+
d.logEvent('bar');
|
|
546
|
+
d.eventHistory.bar.should.have.length(2);
|
|
547
|
+
d.eventHistory.bar[1].should.be.a('number');
|
|
548
|
+
(d.eventHistory.bar[1] >= d.eventHistory.bar[0]).should.be.true;
|
|
549
|
+
});
|
|
550
|
+
describe('getSession decoration', function () {
|
|
551
|
+
it('should decorate getSession response if opt-in cap is provided', async function () {
|
|
552
|
+
let res = await d.getSession();
|
|
553
|
+
should.not.exist(res.events);
|
|
554
|
+
|
|
555
|
+
_.set(d, 'caps.eventTimings', true);
|
|
556
|
+
res = await d.getSession();
|
|
557
|
+
should.exist(res.events);
|
|
558
|
+
should.exist(res.events?.newSessionRequested);
|
|
559
|
+
expect(res.events?.newSessionRequested[0]).to.be.a('number');
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
describe('.reset', function () {
|
|
564
|
+
it('should reset as W3C if the original session was W3C', async function () {
|
|
565
|
+
const caps = {
|
|
566
|
+
alwaysMatch: {
|
|
567
|
+
'appium:app': 'Fake',
|
|
568
|
+
'appium:deviceName': 'Fake',
|
|
569
|
+
'appium:automationName': 'Fake',
|
|
570
|
+
platformName: 'Fake',
|
|
571
|
+
...defaultCaps,
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
firstMatch: [{}],
|
|
575
|
+
};
|
|
576
|
+
await d.createSession(caps);
|
|
577
|
+
expect(d.protocol).to.equal('W3C');
|
|
578
|
+
await d.reset();
|
|
579
|
+
expect(d.protocol).to.equal('W3C');
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
describe('.isFeatureEnabled', function () {
|
|
585
|
+
let d;
|
|
586
|
+
|
|
587
|
+
beforeEach(function () {
|
|
588
|
+
d = new DriverClass();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('should say a feature is enabled when it is explicitly allowed', function () {
|
|
592
|
+
d.allowInsecure = ['foo', 'bar'];
|
|
593
|
+
d.isFeatureEnabled('foo').should.be.true;
|
|
594
|
+
d.isFeatureEnabled('bar').should.be.true;
|
|
595
|
+
d.isFeatureEnabled('baz').should.be.false;
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('should say a feature is not enabled if it is not enabled', function () {
|
|
599
|
+
d.allowInsecure = [];
|
|
600
|
+
d.isFeatureEnabled('foo').should.be.false;
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it('should prefer denyInsecure to allowInsecure', function () {
|
|
604
|
+
d.allowInsecure = ['foo', 'bar'];
|
|
605
|
+
d.denyInsecure = ['foo'];
|
|
606
|
+
d.isFeatureEnabled('foo').should.be.false;
|
|
607
|
+
d.isFeatureEnabled('bar').should.be.true;
|
|
608
|
+
d.isFeatureEnabled('baz').should.be.false;
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('should allow global setting for insecurity', function () {
|
|
612
|
+
d.relaxedSecurityEnabled = true;
|
|
613
|
+
d.isFeatureEnabled('foo').should.be.true;
|
|
614
|
+
d.isFeatureEnabled('bar').should.be.true;
|
|
615
|
+
d.isFeatureEnabled('baz').should.be.true;
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('global setting should be overrideable', function () {
|
|
619
|
+
d.relaxedSecurityEnabled = true;
|
|
620
|
+
d.denyInsecure = ['foo', 'bar'];
|
|
621
|
+
d.isFeatureEnabled('foo').should.be.false;
|
|
622
|
+
d.isFeatureEnabled('bar').should.be.false;
|
|
623
|
+
d.isFeatureEnabled('baz').should.be.true;
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* @typedef {import('@appium/types').DriverClass} DriverClass
|
|
630
|
+
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
|
631
|
+
* @typedef {import('@appium/types').AppiumW3CCapabilities} AppiumW3CCapabilities
|
|
632
|
+
*/
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@appium/driver-test-support",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Test utilities for Appium drivers",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"automation",
|
|
7
|
+
"javascript",
|
|
8
|
+
"selenium",
|
|
9
|
+
"webdriver",
|
|
10
|
+
"ios",
|
|
11
|
+
"android",
|
|
12
|
+
"firefoxos",
|
|
13
|
+
"testing"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://appium.io",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/appium/appium/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/appium/appium.git",
|
|
22
|
+
"directory": "packages/driver-test-support"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"author": "https://github.com/appium",
|
|
26
|
+
"main": "index.js",
|
|
27
|
+
"directories": {
|
|
28
|
+
"lib": "lib"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"index.js",
|
|
32
|
+
"lib",
|
|
33
|
+
"build"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "babel lib --root-mode=upward --out-dir=build/lib",
|
|
37
|
+
"dev": "npm run build -- --watch",
|
|
38
|
+
"fix": "npm run lint -- --fix",
|
|
39
|
+
"lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
|
|
40
|
+
"prepare": "npm run build",
|
|
41
|
+
"test": "npm run test:unit",
|
|
42
|
+
"test:smoke": "node ./index.js",
|
|
43
|
+
"test:unit": "mocha \"test/unit/**/*.spec.js\""
|
|
44
|
+
},
|
|
45
|
+
"types": "./build/lib/index.d.ts",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@appium/types": "^0.4.1",
|
|
48
|
+
"@babel/runtime": "7.19.0",
|
|
49
|
+
"@types/lodash": "4.14.184",
|
|
50
|
+
"axios": "0.27.2",
|
|
51
|
+
"bluebird": "3.7.2",
|
|
52
|
+
"chai": "4.3.6",
|
|
53
|
+
"get-port": "5.1.1",
|
|
54
|
+
"lodash": "4.17.21",
|
|
55
|
+
"sinon": "14.0.0",
|
|
56
|
+
"source-map-support": "0.5.21"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"appium": "^2.0.0-beta.43",
|
|
60
|
+
"mocha": "*"
|
|
61
|
+
},
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=14",
|
|
64
|
+
"npm": ">=8"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public"
|
|
68
|
+
},
|
|
69
|
+
"gitHead": "c26af8f85230ac65cbc19f08f763942f74b0eb0c"
|
|
70
|
+
}
|