@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.
@@ -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
+ }