@appium/base-driver 8.3.1 → 8.4.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.
Files changed (115) hide show
  1. package/build/lib/basedriver/capabilities.d.ts +80 -0
  2. package/build/lib/basedriver/capabilities.d.ts.map +1 -0
  3. package/build/lib/basedriver/capabilities.js +11 -10
  4. package/build/lib/basedriver/commands/event.d.ts +9 -0
  5. package/build/lib/basedriver/commands/event.d.ts.map +1 -0
  6. package/build/lib/basedriver/commands/event.js +21 -21
  7. package/build/lib/basedriver/commands/find.d.ts +11 -0
  8. package/build/lib/basedriver/commands/find.d.ts.map +1 -0
  9. package/build/lib/basedriver/commands/find.js +44 -37
  10. package/build/lib/basedriver/commands/index.d.ts +8 -0
  11. package/build/lib/basedriver/commands/index.d.ts.map +1 -0
  12. package/build/lib/basedriver/commands/index.js +17 -14
  13. package/build/lib/basedriver/commands/log.d.ts +12 -0
  14. package/build/lib/basedriver/commands/log.d.ts.map +1 -0
  15. package/build/lib/basedriver/commands/log.js +24 -26
  16. package/build/lib/basedriver/commands/session.d.ts +11 -0
  17. package/build/lib/basedriver/commands/session.d.ts.map +1 -0
  18. package/build/lib/basedriver/commands/session.js +21 -147
  19. package/build/lib/basedriver/commands/settings.d.ts +10 -0
  20. package/build/lib/basedriver/commands/settings.d.ts.map +1 -0
  21. package/build/lib/basedriver/commands/settings.js +19 -17
  22. package/build/lib/basedriver/commands/timeout.d.ts +8 -0
  23. package/build/lib/basedriver/commands/timeout.d.ts.map +1 -0
  24. package/build/lib/basedriver/commands/timeout.js +118 -137
  25. package/build/lib/basedriver/core.d.ts +235 -0
  26. package/build/lib/basedriver/core.d.ts.map +1 -0
  27. package/build/lib/basedriver/core.js +283 -0
  28. package/build/lib/basedriver/desired-caps.d.ts +5 -0
  29. package/build/lib/basedriver/desired-caps.d.ts.map +1 -0
  30. package/build/lib/basedriver/desired-caps.js +1 -1
  31. package/build/lib/basedriver/device-settings.d.ts +32 -0
  32. package/build/lib/basedriver/device-settings.d.ts.map +1 -0
  33. package/build/lib/basedriver/device-settings.js +19 -11
  34. package/build/lib/basedriver/driver.d.ts +83 -0
  35. package/build/lib/basedriver/driver.d.ts.map +1 -0
  36. package/build/lib/basedriver/driver.js +100 -266
  37. package/build/lib/basedriver/helpers.d.ts +132 -0
  38. package/build/lib/basedriver/helpers.d.ts.map +1 -0
  39. package/build/lib/basedriver/helpers.js +17 -1
  40. package/build/lib/basedriver/logger.d.ts +3 -0
  41. package/build/lib/basedriver/logger.d.ts.map +1 -0
  42. package/build/lib/constants.d.ts +9 -0
  43. package/build/lib/constants.d.ts.map +1 -0
  44. package/build/lib/express/crash.d.ts +3 -0
  45. package/build/lib/express/crash.d.ts.map +1 -0
  46. package/build/lib/express/express-logging.d.ts +3 -0
  47. package/build/lib/express/express-logging.d.ts.map +1 -0
  48. package/build/lib/express/idempotency.d.ts +2 -0
  49. package/build/lib/express/idempotency.d.ts.map +1 -0
  50. package/build/lib/express/logger.d.ts +3 -0
  51. package/build/lib/express/logger.d.ts.map +1 -0
  52. package/build/lib/express/middleware.d.ts +9 -0
  53. package/build/lib/express/middleware.d.ts.map +1 -0
  54. package/build/lib/express/server.d.ts +10 -0
  55. package/build/lib/express/server.d.ts.map +1 -0
  56. package/build/lib/express/static.d.ts +6 -0
  57. package/build/lib/express/static.d.ts.map +1 -0
  58. package/build/lib/express/websocket.d.ts +64 -0
  59. package/build/lib/express/websocket.d.ts.map +1 -0
  60. package/build/lib/helpers/capabilities.d.ts +13 -0
  61. package/build/lib/helpers/capabilities.d.ts.map +1 -0
  62. package/build/lib/helpers/capabilities.js +41 -1
  63. package/build/lib/index.d.ts +183 -0
  64. package/build/lib/index.d.ts.map +1 -0
  65. package/build/lib/index.js +40 -20
  66. package/build/lib/jsonwp-proxy/protocol-converter.d.ts +48 -0
  67. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -0
  68. package/build/lib/jsonwp-proxy/proxy.d.ts +41 -0
  69. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -0
  70. package/build/lib/jsonwp-status/status.d.ts +159 -0
  71. package/build/lib/jsonwp-status/status.d.ts.map +1 -0
  72. package/build/lib/protocol/errors.d.ts +310 -0
  73. package/build/lib/protocol/errors.d.ts.map +1 -0
  74. package/build/lib/protocol/errors.js +81 -2
  75. package/build/lib/protocol/helpers.d.ts +22 -0
  76. package/build/lib/protocol/helpers.d.ts.map +1 -0
  77. package/build/lib/protocol/index.d.ts +16 -0
  78. package/build/lib/protocol/index.d.ts.map +1 -0
  79. package/build/lib/protocol/index.js +7 -7
  80. package/build/lib/protocol/protocol.d.ts +11 -0
  81. package/build/lib/protocol/protocol.d.ts.map +1 -0
  82. package/build/lib/protocol/protocol.js +2 -6
  83. package/build/lib/protocol/routes.d.ts +6 -0
  84. package/build/lib/protocol/routes.d.ts.map +1 -0
  85. package/build/lib/protocol/routes.js +17 -1
  86. package/build/lib/protocol/validators.d.ts +8 -0
  87. package/build/lib/protocol/validators.d.ts.map +1 -0
  88. package/build/test/basedriver/driver-tests.js +30 -3
  89. package/build/test/unit/basedriver/commands/event.spec.js +7 -7
  90. package/build/test/unit/basedriver/commands/log.spec.js +31 -24
  91. package/build/test/unit/basedriver/timeout.spec.js +1 -1
  92. package/build/test/unit/protocol/routes.spec.js +2 -2
  93. package/build/tsconfig.tsbuildinfo +1 -0
  94. package/lib/basedriver/capabilities.js +49 -10
  95. package/lib/basedriver/commands/event.js +49 -31
  96. package/lib/basedriver/commands/find.js +108 -43
  97. package/lib/basedriver/commands/index.js +25 -19
  98. package/lib/basedriver/commands/log.js +60 -33
  99. package/lib/basedriver/commands/session.js +39 -141
  100. package/lib/basedriver/commands/settings.js +33 -13
  101. package/lib/basedriver/commands/timeout.js +153 -153
  102. package/lib/basedriver/core.js +497 -0
  103. package/lib/basedriver/desired-caps.js +1 -1
  104. package/lib/basedriver/device-settings.js +47 -12
  105. package/lib/basedriver/driver.js +273 -395
  106. package/lib/basedriver/helpers.js +18 -2
  107. package/lib/helpers/capabilities.js +60 -1
  108. package/lib/index.js +16 -12
  109. package/lib/protocol/errors.js +42 -42
  110. package/lib/protocol/index.js +4 -4
  111. package/lib/protocol/protocol.js +1 -3
  112. package/lib/protocol/routes.js +9 -0
  113. package/package.json +10 -9
  114. package/test/basedriver/driver-tests.js +31 -2
  115. package/index.d.ts +0 -386
@@ -1,27 +1,21 @@
1
- import {
2
- Protocol, errors, determineProtocol, DELETE_SESSION_COMMAND,
3
- } from '../protocol';
4
- import { fs, logger, node } from '@appium/support';
5
- import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
6
- import os from 'os';
7
- import commands from './commands';
8
- import * as helpers from './helpers';
9
- import DeviceSettings from './device-settings';
10
- import { desiredCapabilityConstraints } from './desired-caps';
11
- import { validateCaps } from './capabilities';
1
+ // @ts-check
2
+ /* eslint-disable require-await */
3
+ /* eslint-disable no-unused-vars */
4
+
5
+ import { DriverCore } from './core';
6
+ import { util } from '@appium/support';
12
7
  import B from 'bluebird';
13
8
  import _ from 'lodash';
14
- import AsyncLock from 'async-lock';
15
- import { EventEmitter } from 'events';
16
-
17
- // for compat with running tests transpiled and in-place
18
- const {version: BASEDRIVER_VER} = fs.readPackageJsonFrom(__dirname);
19
-
20
- B.config({
21
- cancellation: true,
22
- });
23
-
24
- const NEW_COMMAND_TIMEOUT_MS = 60 * 1000;
9
+ import { fixCaps, isW3cCaps } from '../helpers/capabilities';
10
+ import { DELETE_SESSION_COMMAND, determineProtocol, errors } from '../protocol';
11
+ import {
12
+ APPIUM_OPTS_CAP,
13
+ PREFIXED_APPIUM_OPTS_CAP,
14
+ processCapabilities,
15
+ promoteAppiumOptions
16
+ } from './capabilities';
17
+ import { createBaseDriverClass } from './commands';
18
+ import helpers from './helpers';
25
19
 
26
20
  const EVENT_SESSION_INIT = 'newSessionRequested';
27
21
  const EVENT_SESSION_START = 'newSessionStarted';
@@ -30,303 +24,23 @@ const EVENT_SESSION_QUIT_DONE = 'quitSessionFinished';
30
24
  const ON_UNEXPECTED_SHUTDOWN_EVENT = 'onUnexpectedShutdown';
31
25
 
32
26
 
33
- class BaseDriver extends Protocol {
34
-
35
- /**
36
- * Make the basedriver version available so for any driver which inherits from this package, we
37
- * know which version of basedriver it inherited from
38
- */
39
- static baseVersion = BASEDRIVER_VER;
40
-
41
- constructor (opts = {}, shouldValidateCaps = true) {
42
- super();
43
-
44
- // setup state
45
- this.sessionId = null;
46
- this.opts = opts;
47
- this.caps = null;
48
- this.originalCaps = null; // To give the original capabilities to reset
49
- this.helpers = helpers;
50
-
51
- this._log = null;
52
-
53
- // basePath is used for several purposes, for example in setting up
54
- // proxying to other drivers, since we need to know what the base path
55
- // of any incoming request might look like. We set it to the default
56
- // initially but it is automatically updated during any actual program
57
- // execution by the routeConfiguringFunction, which is necessarily run as
58
- // the entrypoint for any Appium server
59
- this.basePath = DEFAULT_BASE_PATH;
60
-
61
- // initialize security modes
62
- this.relaxedSecurityEnabled = false;
63
- this.allowInsecure = [];
64
- this.denyInsecure = [];
65
-
66
- // timeout initialization
67
- this.newCommandTimeoutMs = NEW_COMMAND_TIMEOUT_MS;
68
- this.implicitWaitMs = 0;
69
-
70
- this._constraints = _.cloneDeep(desiredCapabilityConstraints);
71
- this.locatorStrategies = [];
72
- this.webLocatorStrategies = [];
73
-
74
- // use a custom tmp dir to avoid losing data and app when computer is
75
- // restarted
76
- this.opts.tmpDir = this.opts.tmpDir ||
77
- process.env.APPIUM_TMP_DIR ||
78
- os.tmpdir();
79
-
80
- // base-driver internals
81
- this.shutdownUnexpectedly = false;
82
- this.noCommandTimer = null;
83
- this.shouldValidateCaps = shouldValidateCaps;
84
- this.commandsQueueGuard = new AsyncLock();
85
-
86
- // settings should be instantiated by drivers which extend BaseDriver, but
87
- // we set it to an empty DeviceSettings instance here to make sure that the
88
- // default settings are applied even if an extending driver doesn't utilize
89
- // the settings functionality itself
90
- this.settings = new DeviceSettings({}, _.noop);
91
-
92
- // keeping track of initial opts
93
- this.initialOpts = _.cloneDeep(this.opts);
94
-
95
- // allow subclasses to have internal drivers
96
- this.managedDrivers = [];
97
-
98
- // store event timings
99
- this._eventHistory = {
100
- commands: [] // commands get a special place
101
- };
102
-
103
- // used to handle driver events
104
- this.eventEmitter = new EventEmitter();
105
-
106
- this.protocol = null;
107
- }
108
-
109
- get log () {
110
- if (!this._log) {
111
- const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`;
112
- // We don't want the self reference to be captured inside the below closure
113
- // to avoid possible memory leaks,
114
- // but NodeJS started supporting WeakRef only since v. 14.6
115
- const self = global.WeakRef ? new global.WeakRef(this) : this;
116
- this._log = logger.getLogger(() => {
117
- let sessionId = self?.sessionId;
118
- if (!sessionId && _.isFunction(self.deref)) {
119
- const ref = self.deref();
120
- if (ref?.sessionId) {
121
- sessionId = ref.sessionId;
122
- }
123
- }
124
- return sessionId ? `${instanceName} (${sessionId.substring(0, 8)})` : instanceName;
125
- });
126
- }
127
-
128
- return this._log;
129
- }
130
-
131
- /**
132
- * Set a callback handler if needed to execute a custom piece of code
133
- * when the driver is shut down unexpectedly. Multiple calls to this method
134
- * will cause the handler to be executed mutiple times
135
- *
136
- * @param {Function} handler The code to be executed on unexpected shutdown.
137
- * The function may accept one argument, which is the actual error instance, which
138
- * caused the driver to shut down.
139
- */
140
- onUnexpectedShutdown (handler) {
141
- this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler);
142
- }
143
-
144
- /**
145
- * This property is used by AppiumDriver to store the data of the
146
- * specific driver sessions. This data can be later used to adjust
147
- * properties for driver instances running in parallel.
148
- * Override it in inherited driver classes if necessary.
149
- *
150
- * @return {object} Driver properties mapping
151
- */
152
- get driverData () {
153
- return {};
154
- }
155
-
156
- /**
157
- * This property controls the way {#executeCommand} method
158
- * handles new driver commands received from the client.
159
- * Override it for inherited classes only in special cases.
160
- *
161
- * @return {boolean} If the returned value is true (default) then all the commands
162
- * received by the particular driver instance are going to be put into the queue,
163
- * so each following command will not be executed until the previous command
164
- * execution is completed. False value disables that queue, so each driver command
165
- * is executed independently and does not wait for anything.
166
- */
167
- get isCommandsQueueEnabled () {
168
- return true;
169
- }
170
-
171
- /*
172
- * make eventHistory a property and return a cloned object so a consumer can't
173
- * inadvertently change data outside of logEvent
174
- */
175
- get eventHistory () {
176
- return _.cloneDeep(this._eventHistory);
177
- }
27
+ /**
28
+ * @implements {SessionHandler}
29
+ */
30
+ export class BaseDriverCore extends DriverCore {
178
31
 
179
- /*
180
- * API method for driver developers to log timings for important events
181
- */
182
- logEvent (eventName) {
183
- if (eventName === 'commands') {
184
- throw new Error('Cannot log commands directly');
185
- }
186
- if (typeof eventName !== 'string') {
187
- throw new Error(`Invalid eventName ${eventName}`);
188
- }
189
- if (!this._eventHistory[eventName]) {
190
- this._eventHistory[eventName] = [];
191
- }
192
- const ts = Date.now();
193
- const logTime = (new Date(ts)).toTimeString();
194
- this._eventHistory[eventName].push(ts);
195
- this.log.debug(`Event '${eventName}' logged at ${ts} (${logTime})`);
196
- }
197
-
198
- /*
199
- * Overridden in appium driver, but here so that individual drivers can be
200
- * tested with clients that poll
201
- */
202
- async getStatus () { // eslint-disable-line require-await
203
- return {};
204
- }
205
-
206
- // we only want subclasses to ever extend the contraints
207
- set desiredCapConstraints (constraints) {
208
- this._constraints = Object.assign(this._constraints, constraints);
209
- // 'presence' means different things in different versions of the validator,
210
- // when we say 'true' we mean that it should not be able to be empty
211
- for (const [, value] of _.toPairs(this._constraints)) {
212
- if (value && value.presence === true) {
213
- value.presence = {
214
- allowEmpty: false,
215
- };
216
- }
217
- }
218
- }
219
-
220
- get desiredCapConstraints () {
221
- return this._constraints;
222
- }
223
-
224
- // method required by MJSONWP in order to determine whether it should
225
- // respond with an invalid session response
226
- sessionExists (sessionId) {
227
- if (!sessionId) return false; // eslint-disable-line curly
228
- return sessionId === this.sessionId;
229
- }
230
-
231
- // method required by MJSONWP in order to determine if the command should
232
- // be proxied directly to the driver
233
- driverForSession (/*sessionId*/) {
234
- return this;
235
- }
236
-
237
- logExtraCaps (caps) {
238
- let extraCaps = _.difference(_.keys(caps),
239
- _.keys(this._constraints));
240
- if (extraCaps.length) {
241
- this.log.warn(`The following capabilities were provided, but are not ` +
242
- `recognized by Appium:`);
243
- for (const cap of extraCaps) {
244
- this.log.warn(` ${cap}`);
245
- }
246
- }
247
- }
248
-
249
- validateDesiredCaps (caps) {
250
- if (!this.shouldValidateCaps) {
251
- return true;
252
- }
253
-
254
- try {
255
- validateCaps(caps, this._constraints);
256
- } catch (e) {
257
- this.log.errorAndThrow(new errors.SessionNotCreatedError(`The desiredCapabilities object was not valid for the ` +
258
- `following reason(s): ${e.message}`));
259
- }
260
-
261
- this.logExtraCaps(caps);
262
-
263
- return true;
264
- }
265
-
266
- isMjsonwpProtocol () {
267
- return this.protocol === PROTOCOLS.MJSONWP;
268
- }
269
-
270
- isW3CProtocol () {
271
- return this.protocol === PROTOCOLS.W3C;
272
- }
273
-
274
- setProtocolMJSONWP () {
275
- this.protocol = PROTOCOLS.MJSONWP;
276
- }
277
-
278
- setProtocolW3C () {
279
- this.protocol = PROTOCOLS.W3C;
280
- }
281
-
282
- /**
283
- * Check whether a given feature is enabled via its name
284
- *
285
- * @param {string} name - name of feature/command
286
- *
287
- * @returns {Boolean}
288
- */
289
- isFeatureEnabled (name) {
290
- // if we have explicitly denied this feature, return false immediately
291
- if (this.denyInsecure && _.includes(this.denyInsecure, name)) {
292
- return false;
293
- }
294
-
295
- // if we specifically have allowed the feature, return true
296
- if (this.allowInsecure && _.includes(this.allowInsecure, name)) {
297
- return true;
298
- }
299
-
300
- // otherwise, if we've globally allowed insecure features and not denied
301
- // this one, return true
302
- if (this.relaxedSecurityEnabled) {
303
- return true;
304
- }
305
-
306
- // if we haven't allowed anything insecure, then reject
307
- return false;
308
- }
309
-
310
- /**
311
- * Assert that a given feature is enabled and throw a helpful error if it's
312
- * not
313
- *
314
- * @param {string} name - name of feature/command
315
- */
316
- ensureFeatureEnabled (name) {
317
- if (!this.isFeatureEnabled(name)) {
318
- throw new Error(`Potentially insecure feature '${name}' has not been ` +
319
- `enabled. If you want to enable this feature and accept ` +
320
- `the security ramifications, please do so by following ` +
321
- `the documented instructions at https://github.com/appium` +
322
- `/appium/blob/master/docs/en/writing-running-appium/security.md`);
323
- }
324
- }
32
+ /** @type {Record<string,any>|undefined} */
33
+ cliArgs;
325
34
 
326
35
  // This is the main command handler for the driver. It wraps command
327
36
  // execution with timeout logic, checking that we have a valid session,
328
37
  // and ensuring that we execute commands one at a time. This method is called
329
38
  // by MJSONWP's express router.
39
+ /**
40
+ * @param {string} cmd
41
+ * @param {...any[]} args
42
+ * @returns {Promise<any>}
43
+ */
330
44
  async executeCommand (cmd, ...args) {
331
45
  // get start time for this command, and log in special cases
332
46
  let startTime = Date.now();
@@ -341,10 +55,12 @@ class BaseDriver extends Protocol {
341
55
 
342
56
  // if we had a command timer running, clear it now that we're starting
343
57
  // a new command and so don't want to time out
344
- this.clearNewCommandTimeout();
58
+ await this.clearNewCommandTimeout();
345
59
 
346
60
  if (this.shutdownUnexpectedly) {
347
- throw new errors.NoSuchDriverError('The driver was unexpectedly shut down!');
61
+ throw new errors.NoSuchDriverError(
62
+ 'The driver was unexpectedly shut down!',
63
+ );
348
64
  }
349
65
 
350
66
  // If we don't have this command, it must not be implemented
@@ -353,19 +69,26 @@ class BaseDriver extends Protocol {
353
69
  }
354
70
 
355
71
  let unexpectedShutdownListener;
356
- const commandExecutor = async () => await B.race([
357
- this[cmd](...args),
358
- new B((resolve, reject) => {
359
- unexpectedShutdownListener = reject;
360
- this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, unexpectedShutdownListener);
361
- })
362
- ]).finally(() => {
363
- if (unexpectedShutdownListener) {
364
- // This is needed to prevent memory leaks
365
- this.eventEmitter.removeListener(ON_UNEXPECTED_SHUTDOWN_EVENT, unexpectedShutdownListener);
366
- unexpectedShutdownListener = null;
367
- }
368
- });
72
+ const commandExecutor = async () =>
73
+ await B.race([
74
+ this[cmd](...args),
75
+ new B((resolve, reject) => {
76
+ unexpectedShutdownListener = reject;
77
+ this.eventEmitter.on(
78
+ ON_UNEXPECTED_SHUTDOWN_EVENT,
79
+ unexpectedShutdownListener,
80
+ );
81
+ }),
82
+ ]).finally(() => {
83
+ if (unexpectedShutdownListener) {
84
+ // This is needed to prevent memory leaks
85
+ this.eventEmitter.removeListener(
86
+ ON_UNEXPECTED_SHUTDOWN_EVENT,
87
+ unexpectedShutdownListener,
88
+ );
89
+ unexpectedShutdownListener = null;
90
+ }
91
+ });
369
92
  const res = this.isCommandsQueueEnabled
370
93
  ? await this.commandsQueueGuard.acquire(BaseDriver.name, commandExecutor)
371
94
  : await commandExecutor();
@@ -378,7 +101,7 @@ class BaseDriver extends Protocol {
378
101
  // intentionally
379
102
  if (this.isCommandsQueueEnabled && cmd !== DELETE_SESSION_COMMAND) {
380
103
  // resetting existing timeout
381
- this.startNewCommandTimeout();
104
+ await this.startNewCommandTimeout();
382
105
  }
383
106
 
384
107
  // log timing information about this command
@@ -393,40 +116,77 @@ class BaseDriver extends Protocol {
393
116
  return res;
394
117
  }
395
118
 
396
- async startUnexpectedShutdown (err = new errors.NoSuchDriverError('The driver was unexpectedly shut down!')) {
119
+ /**
120
+ *
121
+ * @param {Error} err
122
+ */
123
+ async startUnexpectedShutdown (
124
+ err = new errors.NoSuchDriverError(
125
+ 'The driver was unexpectedly shut down!',
126
+ ),
127
+ ) {
397
128
  this.eventEmitter.emit(ON_UNEXPECTED_SHUTDOWN_EVENT, err); // allow others to listen for this
398
129
  this.shutdownUnexpectedly = true;
399
130
  try {
400
- await this.deleteSession(this.sessionId);
131
+ if (this.sessionId !== null) {
132
+ await this.deleteSession(this.sessionId);
133
+ }
401
134
  } finally {
402
135
  this.shutdownUnexpectedly = false;
403
136
  }
404
137
  }
405
138
 
406
- validateLocatorStrategy (strategy, webContext = false) {
407
- let validStrategies = this.locatorStrategies;
408
- this.log.debug(`Valid locator strategies for this request: ${validStrategies.join(', ')}`);
409
-
410
- if (webContext) {
411
- validStrategies = validStrategies.concat(this.webLocatorStrategies);
412
- }
139
+ async startNewCommandTimeout () {
140
+ // make sure there are no rogue timeouts
141
+ await this.clearNewCommandTimeout();
142
+
143
+ // if command timeout is 0, it is disabled
144
+ if (!this.newCommandTimeoutMs) return; // eslint-disable-line curly
145
+
146
+ this.noCommandTimer = setTimeout(async () => {
147
+ this.log.warn(
148
+ `Shutting down because we waited ` +
149
+ `${this.newCommandTimeoutMs / 1000.0} seconds for a command`,
150
+ );
151
+ const errorMessage =
152
+ `New Command Timeout of ` +
153
+ `${this.newCommandTimeoutMs / 1000.0} seconds ` +
154
+ `expired. Try customizing the timeout using the ` +
155
+ `'newCommandTimeout' desired capability`;
156
+ await this.startUnexpectedShutdown(new Error(errorMessage));
157
+ }, this.newCommandTimeoutMs);
158
+ }
413
159
 
414
- if (!_.includes(validStrategies, strategy)) {
415
- throw new errors.InvalidSelectorError(`Locator Strategy '${strategy}' is not supported for this session`);
416
- }
160
+ /**
161
+ *
162
+ * @param {import('@appium/types').AppiumServer} server
163
+ * @param {string} host
164
+ * @param {number} port
165
+ * @param {string} path
166
+ */
167
+ assignServer (server, host, port, path) {
168
+ this.server = server;
169
+ this.serverHost = host;
170
+ this.serverPort = port;
171
+ this.serverPath = path;
417
172
  }
418
173
 
419
174
  /*
420
- * Restart the session with the original caps,
421
- * preserving the timeout config.
422
- */
175
+ * Restart the session with the original caps,
176
+ * preserving the timeout config.
177
+ */
423
178
  async reset () {
424
179
  this.log.debug('Resetting app mid-session');
425
180
  this.log.debug('Running generic full reset');
426
181
 
427
182
  // preserving state
428
183
  let currentConfig = {};
429
- for (let property of ['implicitWaitMs', 'newCommandTimeoutMs', 'sessionId', 'resetOnUnexpectedShutdown']) {
184
+ for (let property of [
185
+ 'implicitWaitMs',
186
+ 'newCommandTimeoutMs',
187
+ 'sessionId',
188
+ 'resetOnUnexpectedShutdown',
189
+ ]) {
430
190
  currentConfig[property] = this[property];
431
191
  }
432
192
 
@@ -434,75 +194,193 @@ class BaseDriver extends Protocol {
434
194
  this.resetOnUnexpectedShutdown = () => {};
435
195
 
436
196
  try {
437
- await this.deleteSession(this.sessionId);
197
+ if (this.sessionId !== null) {
198
+ await this.deleteSession(this.sessionId);
199
+ }
438
200
  this.log.debug('Restarting app');
439
- await this.createSession(undefined, undefined, this.originalCaps);
201
+ await this.createSession(this.originalCaps);
440
202
  } finally {
441
203
  // always restore state.
442
204
  for (let [key, value] of _.toPairs(currentConfig)) {
443
205
  this[key] = value;
444
206
  }
445
207
  }
446
- this.clearNewCommandTimeout();
208
+ await this.clearNewCommandTimeout();
447
209
  }
448
210
 
449
- proxyActive (/* sessionId */) {
450
- return false;
451
- }
211
+ /**
212
+ *
213
+ * Historically the first two arguments were reserved for JSONWP capabilities.
214
+ * Appium 2 has dropped the support of these, so now we only accept capability
215
+ * objects in W3C format and thus allow any of the three arguments to represent
216
+ * the latter.
217
+ * @param {W3CCapabilities} w3cCapabilities1
218
+ * @param {W3CCapabilities} [w3cCapabilities2]
219
+ * @param {W3CCapabilities} [w3cCapabilities]
220
+ * @param {DriverData[]} [driverData]
221
+ * @returns {Promise<[string,object]>}
222
+ */
223
+ async createSession (
224
+ w3cCapabilities1,
225
+ w3cCapabilities2,
226
+ w3cCapabilities,
227
+ driverData,
228
+ ) {
229
+ if (this.sessionId !== null) {
230
+ throw new errors.SessionNotCreatedError(
231
+ 'Cannot create a new session while one is in progress',
232
+ );
233
+ }
452
234
 
453
- getProxyAvoidList (/* sessionId */) {
454
- return [];
455
- }
235
+ this.log.debug();
236
+
237
+ const originalCaps = _.cloneDeep([
238
+ w3cCapabilities,
239
+ w3cCapabilities1,
240
+ w3cCapabilities2,
241
+ ].find(isW3cCaps));
242
+ if (!originalCaps) {
243
+ throw new errors.SessionNotCreatedError(
244
+ 'Appium only supports W3C-style capability objects. ' +
245
+ 'Your client is sending an older capabilities format. Please update your client library.',
246
+ );
247
+ }
456
248
 
457
- canProxy (/* sessionId */) {
458
- return false;
459
- }
249
+ this.setProtocolW3C();
460
250
 
461
- /**
462
- * Whether a given command route (expressed as method and url) should not be
463
- * proxied according to this driver
464
- *
465
- * @param {string} sessionId - the current sessionId (in case the driver runs
466
- * multiple session ids and requires it). This is not used in this method but
467
- * should be made available to overridden methods.
468
- * @param {string} method - HTTP method of the route
469
- * @param {string} url - url of the route
470
- * @param {?*} body - webdriver request body
471
- *
472
- * @returns {boolean} - whether the route should be avoided
473
- */
474
- proxyRouteIsAvoided (sessionId, method, url/*, body*/) {
475
- for (let avoidSchema of this.getProxyAvoidList(sessionId)) {
476
- if (!_.isArray(avoidSchema) || avoidSchema.length !== 2) {
477
- throw new Error('Proxy avoidance must be a list of pairs');
478
- }
479
- let [avoidMethod, avoidPathRegex] = avoidSchema;
480
- if (!_.includes(['GET', 'POST', 'DELETE'], avoidMethod)) {
481
- throw new Error(`Unrecognized proxy avoidance method '${avoidMethod}'`);
482
- }
483
- if (!_.isRegExp(avoidPathRegex)) {
484
- throw new Error('Proxy avoidance path must be a regular expression');
485
- }
486
- let normalizedUrl = url.replace(new RegExp(`^${_.escapeRegExp(this.basePath)}`), '');
487
- if (avoidMethod === method && avoidPathRegex.test(normalizedUrl)) {
488
- return true;
251
+ this.originalCaps = _.cloneDeep(originalCaps);
252
+ this.log.debug(
253
+ `Creating session with W3C capabilities: ${JSON.stringify(
254
+ originalCaps,
255
+ null,
256
+ 2,
257
+ )}`,
258
+ );
259
+
260
+ let caps;
261
+ try {
262
+ caps = processCapabilities(
263
+ originalCaps,
264
+ this.desiredCapConstraints,
265
+ this.shouldValidateCaps,
266
+ );
267
+ if (caps[APPIUM_OPTS_CAP]) {
268
+ this.log.debug(
269
+ `Found ${PREFIXED_APPIUM_OPTS_CAP} capability present; will promote items inside to caps`,
270
+ );
271
+ caps = promoteAppiumOptions(caps);
489
272
  }
273
+ caps = fixCaps(caps, this.desiredCapConstraints, this.log);
274
+ } catch (e) {
275
+ throw new errors.SessionNotCreatedError(e.message);
490
276
  }
491
- return false;
492
- }
493
277
 
494
- addManagedDriver (driver) {
495
- this.managedDrivers.push(driver);
496
- }
278
+ this.validateDesiredCaps(caps);
279
+
280
+ this.sessionId = util.uuidV4();
281
+ this.caps = caps;
282
+ this.opts = _.cloneDeep(this.initialOpts);
497
283
 
498
- getManagedDrivers () {
499
- return this.managedDrivers;
284
+ // merge caps onto opts so we don't need to worry about what's where
285
+ Object.assign(this.opts, this.caps);
286
+
287
+ // deal with resets
288
+ // some people like to do weird things by setting noReset and fullReset
289
+ // both to true, but this is misguided and strange, so error here instead
290
+ if (this.opts.noReset && this.opts.fullReset) {
291
+ throw new Error(
292
+ "The 'noReset' and 'fullReset' capabilities are mutually " +
293
+ 'exclusive and should not both be set to true. You ' +
294
+ "probably meant to just use 'fullReset' on its own",
295
+ );
296
+ }
297
+ if (this.opts.noReset === true) {
298
+ this.opts.fullReset = false;
299
+ }
300
+ if (this.opts.fullReset === true) {
301
+ this.opts.noReset = false;
302
+ }
303
+ this.opts.fastReset = !this.opts.fullReset && !this.opts.noReset;
304
+ this.opts.skipUninstall = this.opts.fastReset || this.opts.noReset;
305
+
306
+ // Prevents empty string caps so we don't need to test it everywhere
307
+ if (typeof this.opts.app === 'string' && this.opts.app.trim() === '') {
308
+ delete this.opts.app;
309
+ }
310
+
311
+ if (!_.isUndefined(this.caps.newCommandTimeout)) {
312
+ this.newCommandTimeoutMs = this.caps.newCommandTimeout * 1000;
313
+ }
314
+
315
+ this._log.prefix = helpers.generateDriverLogPrefix(this, this.sessionId);
316
+
317
+ this.log.info(`Session created with session id: ${this.sessionId}`);
318
+
319
+ return [this.sessionId, caps];
500
320
  }
501
- }
502
321
 
503
- for (let [cmd, fn] of _.toPairs(commands)) {
504
- BaseDriver.prototype[cmd] = fn;
322
+ /**
323
+ *
324
+ * @param {string} [sessionId]
325
+ * @param {DriverData[]} [driverData]
326
+ * @returns {Promise<void>}
327
+ */
328
+ async deleteSession (sessionId, driverData) {
329
+ await this.clearNewCommandTimeout();
330
+ if (this.isCommandsQueueEnabled && this.commandsQueueGuard.isBusy()) {
331
+ // simple hack to release pending commands if they exist
332
+ // @ts-ignore
333
+ for (const key of _.keys(this.commandsQueueGuard.queues)) {
334
+ // @ts-ignore
335
+ this.commandsQueueGuard.queues[key] = [];
336
+ }
337
+ }
338
+ this.sessionId = null;
339
+ this._log.prefix = helpers.generateDriverLogPrefix(this);
340
+ }
505
341
  }
506
342
 
343
+ /**
344
+ * This ensures that all of the mixins correctly implement the interface described in {@linkcode Driver}.
345
+ * @implements {Driver}
346
+ */
347
+ class BaseDriver extends createBaseDriverClass(BaseDriverCore) {}
507
348
  export { BaseDriver };
508
349
  export default BaseDriver;
350
+
351
+ /**
352
+ * @typedef {import('@appium/types').HTTPMethod} HTTPMethod
353
+ * @typedef {import('@appium/types').Driver} Driver
354
+ * @typedef {import('@appium/types').ExternalDriver} ExternalDriver
355
+ * @typedef {import('@appium/types').Capabilities} Capabilities
356
+ * @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
357
+ * @typedef {import('@appium/types').DriverData} DriverData
358
+ */
359
+
360
+
361
+ /**
362
+ * @callback UpdateServerCallback
363
+ * @param {import('express').Express} app - Express app
364
+ * @param {import('@appium/types').AppiumServer} httpServer - HTTP server
365
+ * @returns {import('type-fest').Promisable<void>}
366
+ */
367
+
368
+ /**
369
+ * This is used to extend {@linkcode BaseDriverCore} by the mixins and also external drivers.
370
+ * @template [Proto={}]
371
+ * @template [Static={}]
372
+ * @typedef {import('@appium/types').Class<BaseDriverCore & Proto,BaseDriverStatic & Static>} BaseDriverBase
373
+ */
374
+
375
+ /**
376
+ * Static properties of `BaseDriver` and optional properties for subclasses.
377
+ * @template {ExternalDriver} [T=ExternalDriver]
378
+ * @typedef BaseDriverStatic
379
+ * @property {string} baseVersion
380
+ * @property {UpdateServerCallback} [updateServer]
381
+ * @property {import('@appium/types').MethodMap<T>} [newMethodMap]
382
+ */
383
+
384
+ /**
385
+ * @typedef {import('@appium/types').SessionHandler<[string, object],void>} SessionHandler
386
+ */