@appium/base-driver 8.3.0 → 8.4.1

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 (202) 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 +12 -13
  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 -23
  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 +42 -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 -16
  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 +23 -27
  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 -149
  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 -19
  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 -139
  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 +2 -4
  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 +20 -14
  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 +101 -256
  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 +16 -2
  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/basedriver/logger.js +2 -4
  43. package/build/lib/constants.d.ts +9 -0
  44. package/build/lib/constants.d.ts.map +1 -0
  45. package/build/lib/constants.js +2 -4
  46. package/build/lib/express/crash.d.ts +3 -0
  47. package/build/lib/express/crash.d.ts.map +1 -0
  48. package/build/lib/express/crash.js +2 -4
  49. package/build/lib/express/express-logging.d.ts +3 -0
  50. package/build/lib/express/express-logging.d.ts.map +1 -0
  51. package/build/lib/express/express-logging.js +2 -4
  52. package/build/lib/express/idempotency.d.ts +2 -0
  53. package/build/lib/express/idempotency.d.ts.map +1 -0
  54. package/build/lib/express/idempotency.js +2 -4
  55. package/build/lib/express/logger.d.ts +3 -0
  56. package/build/lib/express/logger.d.ts.map +1 -0
  57. package/build/lib/express/logger.js +2 -4
  58. package/build/lib/express/middleware.d.ts +9 -0
  59. package/build/lib/express/middleware.d.ts.map +1 -0
  60. package/build/lib/express/middleware.js +2 -4
  61. package/build/lib/express/server.d.ts +10 -0
  62. package/build/lib/express/server.d.ts.map +1 -0
  63. package/build/lib/express/server.js +2 -4
  64. package/build/lib/express/static.d.ts +6 -0
  65. package/build/lib/express/static.d.ts.map +1 -0
  66. package/build/lib/express/static.js +2 -4
  67. package/build/lib/express/websocket.d.ts +64 -0
  68. package/build/lib/express/websocket.d.ts.map +1 -0
  69. package/build/lib/express/websocket.js +40 -41
  70. package/build/lib/helpers/capabilities.d.ts +13 -0
  71. package/build/lib/helpers/capabilities.d.ts.map +1 -0
  72. package/build/lib/helpers/capabilities.js +40 -2
  73. package/build/lib/index.d.ts +183 -0
  74. package/build/lib/index.d.ts.map +1 -0
  75. package/build/lib/index.js +41 -23
  76. package/build/lib/jsonwp-proxy/protocol-converter.d.ts +48 -0
  77. package/build/lib/jsonwp-proxy/protocol-converter.d.ts.map +1 -0
  78. package/build/lib/jsonwp-proxy/protocol-converter.js +2 -4
  79. package/build/lib/jsonwp-proxy/proxy.d.ts +41 -0
  80. package/build/lib/jsonwp-proxy/proxy.d.ts.map +1 -0
  81. package/build/lib/jsonwp-proxy/proxy.js +25 -9
  82. package/build/lib/jsonwp-status/status.d.ts +159 -0
  83. package/build/lib/jsonwp-status/status.d.ts.map +1 -0
  84. package/build/lib/jsonwp-status/status.js +2 -4
  85. package/build/lib/protocol/errors.d.ts +310 -0
  86. package/build/lib/protocol/errors.d.ts.map +1 -0
  87. package/build/lib/protocol/errors.js +82 -5
  88. package/build/lib/protocol/helpers.d.ts +22 -0
  89. package/build/lib/protocol/helpers.d.ts.map +1 -0
  90. package/build/lib/protocol/helpers.js +2 -4
  91. package/build/lib/protocol/index.d.ts +16 -0
  92. package/build/lib/protocol/index.d.ts.map +1 -0
  93. package/build/lib/protocol/index.js +8 -10
  94. package/build/lib/protocol/protocol.d.ts +11 -0
  95. package/build/lib/protocol/protocol.d.ts.map +1 -0
  96. package/build/lib/protocol/protocol.js +3 -9
  97. package/build/lib/protocol/routes.d.ts +6 -0
  98. package/build/lib/protocol/routes.d.ts.map +1 -0
  99. package/build/lib/protocol/routes.js +18 -4
  100. package/build/lib/protocol/validators.d.ts +8 -0
  101. package/build/lib/protocol/validators.d.ts.map +1 -0
  102. package/build/lib/protocol/validators.js +2 -4
  103. package/build/test/basedriver/README.md +5 -0
  104. package/build/test/basedriver/driver-e2e-tests.js +2 -4
  105. package/build/test/basedriver/driver-tests.js +31 -6
  106. package/build/test/basedriver/index.js +2 -4
  107. package/build/test/e2e/basedriver/driver.e2e.spec.js +15 -0
  108. package/build/test/e2e/basedriver/helpers.e2e.spec.js +192 -0
  109. package/build/test/e2e/basedriver/websockets.e2e.spec.js +87 -0
  110. package/build/test/e2e/express/server.e2e.spec.js +159 -0
  111. package/build/test/e2e/jsonwp-proxy/proxy.e2e.spec.js +59 -0
  112. package/build/test/e2e/protocol/fake-driver.js +163 -0
  113. package/build/test/e2e/protocol/helpers.js +25 -0
  114. package/build/test/e2e/protocol/protocol.e2e.spec.js +1186 -0
  115. package/build/test/helpers.js +2 -4
  116. package/build/test/unit/basedriver/capabilities.spec.js +672 -0
  117. package/build/test/unit/basedriver/capability.spec.js +353 -0
  118. package/build/test/unit/basedriver/commands/event.spec.js +110 -0
  119. package/build/test/unit/basedriver/commands/log.spec.js +92 -0
  120. package/build/test/unit/basedriver/driver.spec.js +15 -0
  121. package/build/test/unit/basedriver/helpers.spec.js +151 -0
  122. package/build/test/unit/basedriver/timeout.spec.js +135 -0
  123. package/build/test/unit/express/server.spec.js +155 -0
  124. package/build/test/unit/express/static.spec.js +26 -0
  125. package/build/test/unit/jsonwp-proxy/mock-request.js +91 -0
  126. package/build/test/unit/jsonwp-proxy/protocol-converter.spec.js +171 -0
  127. package/build/test/unit/jsonwp-proxy/proxy.spec.js +292 -0
  128. package/build/test/unit/jsonwp-proxy/url.spec.js +165 -0
  129. package/build/test/unit/jsonwp-status/status.spec.js +34 -0
  130. package/build/test/unit/protocol/errors.spec.js +390 -0
  131. package/build/test/unit/protocol/routes.spec.js +80 -0
  132. package/build/test/unit/protocol/validator.spec.js +149 -0
  133. package/build/tsconfig.tsbuildinfo +1 -0
  134. package/lib/basedriver/capabilities.js +49 -10
  135. package/lib/basedriver/commands/event.js +49 -31
  136. package/lib/basedriver/commands/find.js +108 -43
  137. package/lib/basedriver/commands/index.js +25 -19
  138. package/lib/basedriver/commands/log.js +60 -33
  139. package/lib/basedriver/commands/session.js +39 -141
  140. package/lib/basedriver/commands/settings.js +33 -13
  141. package/lib/basedriver/commands/timeout.js +153 -153
  142. package/lib/basedriver/core.js +497 -0
  143. package/lib/basedriver/desired-caps.js +1 -1
  144. package/lib/basedriver/device-settings.js +47 -12
  145. package/lib/basedriver/driver.js +272 -383
  146. package/lib/basedriver/helpers.js +18 -2
  147. package/lib/express/websocket.js +35 -32
  148. package/lib/helpers/capabilities.js +60 -1
  149. package/lib/index.js +16 -12
  150. package/lib/jsonwp-proxy/proxy.js +26 -6
  151. package/lib/protocol/errors.js +42 -42
  152. package/lib/protocol/index.js +4 -4
  153. package/lib/protocol/protocol.js +1 -3
  154. package/lib/protocol/routes.js +9 -0
  155. package/package.json +22 -14
  156. package/test/basedriver/README.md +5 -0
  157. package/test/basedriver/driver-e2e-tests.js +1 -1
  158. package/test/basedriver/driver-tests.js +31 -2
  159. package/build/test/basedriver/capabilities-specs.js +0 -674
  160. package/build/test/basedriver/capability-specs.js +0 -355
  161. package/build/test/basedriver/commands/event-specs.js +0 -112
  162. package/build/test/basedriver/commands/log-specs.js +0 -87
  163. package/build/test/basedriver/driver-e2e-specs.js +0 -17
  164. package/build/test/basedriver/driver-specs.js +0 -17
  165. package/build/test/basedriver/helpers-e2e-specs.js +0 -194
  166. package/build/test/basedriver/helpers-specs.js +0 -153
  167. package/build/test/basedriver/timeout-specs.js +0 -137
  168. package/build/test/basedriver/websockets-e2e-specs.js +0 -84
  169. package/build/test/express/server-e2e-specs.js +0 -161
  170. package/build/test/express/server-specs.js +0 -157
  171. package/build/test/express/static-specs.js +0 -28
  172. package/build/test/jsonwp-proxy/mock-request.js +0 -93
  173. package/build/test/jsonwp-proxy/protocol-converter-specs.js +0 -173
  174. package/build/test/jsonwp-proxy/proxy-e2e-specs.js +0 -61
  175. package/build/test/jsonwp-proxy/proxy-specs.js +0 -294
  176. package/build/test/jsonwp-proxy/url-specs.js +0 -167
  177. package/build/test/jsonwp-status/status-specs.js +0 -36
  178. package/build/test/protocol/errors-specs.js +0 -388
  179. package/build/test/protocol/fake-driver.js +0 -165
  180. package/build/test/protocol/helpers.js +0 -27
  181. package/build/test/protocol/protocol-e2e-specs.js +0 -1188
  182. package/build/test/protocol/routes-specs.js +0 -82
  183. package/build/test/protocol/validator-specs.js +0 -151
  184. package/index.d.ts +0 -386
  185. package/test/basedriver/capabilities-specs.js +0 -537
  186. package/test/basedriver/capability-specs.js +0 -383
  187. package/test/basedriver/commands/event-specs.js +0 -74
  188. package/test/basedriver/commands/log-specs.js +0 -79
  189. package/test/basedriver/driver-e2e-specs.js +0 -8
  190. package/test/basedriver/driver-specs.js +0 -8
  191. package/test/basedriver/fixtures/BadZippedApp.zip +0 -1
  192. package/test/basedriver/fixtures/FakeAndroidApp.apk +0 -1
  193. package/test/basedriver/fixtures/FakeAndroidApp.asd +0 -0
  194. package/test/basedriver/fixtures/FakeIOSApp.app +0 -1
  195. package/test/basedriver/fixtures/FakeIOSApp.app.zip +0 -0
  196. package/test/basedriver/fixtures/FakeIOSApp.ipa +0 -0
  197. package/test/basedriver/fixtures/custom-element-finder-bad.js +0 -5
  198. package/test/basedriver/fixtures/custom-element-finder.js +0 -29
  199. package/test/basedriver/helpers-e2e-specs.js +0 -187
  200. package/test/basedriver/helpers-specs.js +0 -137
  201. package/test/basedriver/timeout-specs.js +0 -128
  202. package/test/basedriver/websockets-e2e-specs.js +0 -75
@@ -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,292 +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, 8)}`;
112
- this._log = logger.getLogger(() =>
113
- this.sessionId ? `${instanceName} (${this.sessionId.substring(0, 8)})` : instanceName
114
- );
115
- }
116
-
117
- return this._log;
118
- }
119
-
120
- /**
121
- * Set a callback handler if needed to execute a custom piece of code
122
- * when the driver is shut down unexpectedly. Multiple calls to this method
123
- * will cause the handler to be executed mutiple times
124
- *
125
- * @param {Function} handler The code to be executed on unexpected shutdown.
126
- * The function may accept one argument, which is the actual error instance, which
127
- * caused the driver to shut down.
128
- */
129
- onUnexpectedShutdown (handler) {
130
- this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, handler);
131
- }
132
-
133
- /**
134
- * This property is used by AppiumDriver to store the data of the
135
- * specific driver sessions. This data can be later used to adjust
136
- * properties for driver instances running in parallel.
137
- * Override it in inherited driver classes if necessary.
138
- *
139
- * @return {object} Driver properties mapping
140
- */
141
- get driverData () {
142
- return {};
143
- }
144
-
145
- /**
146
- * This property controls the way {#executeCommand} method
147
- * handles new driver commands received from the client.
148
- * Override it for inherited classes only in special cases.
149
- *
150
- * @return {boolean} If the returned value is true (default) then all the commands
151
- * received by the particular driver instance are going to be put into the queue,
152
- * so each following command will not be executed until the previous command
153
- * execution is completed. False value disables that queue, so each driver command
154
- * is executed independently and does not wait for anything.
155
- */
156
- get isCommandsQueueEnabled () {
157
- return true;
158
- }
159
-
160
- /*
161
- * make eventHistory a property and return a cloned object so a consumer can't
162
- * inadvertently change data outside of logEvent
163
- */
164
- get eventHistory () {
165
- return _.cloneDeep(this._eventHistory);
166
- }
167
-
168
- /*
169
- * API method for driver developers to log timings for important events
170
- */
171
- logEvent (eventName) {
172
- if (eventName === 'commands') {
173
- throw new Error('Cannot log commands directly');
174
- }
175
- if (typeof eventName !== 'string') {
176
- throw new Error(`Invalid eventName ${eventName}`);
177
- }
178
- if (!this._eventHistory[eventName]) {
179
- this._eventHistory[eventName] = [];
180
- }
181
- const ts = Date.now();
182
- const logTime = (new Date(ts)).toTimeString();
183
- this._eventHistory[eventName].push(ts);
184
- this.log.debug(`Event '${eventName}' logged at ${ts} (${logTime})`);
185
- }
186
-
187
- /*
188
- * Overridden in appium driver, but here so that individual drivers can be
189
- * tested with clients that poll
190
- */
191
- async getStatus () { // eslint-disable-line require-await
192
- return {};
193
- }
194
-
195
- // we only want subclasses to ever extend the contraints
196
- set desiredCapConstraints (constraints) {
197
- this._constraints = Object.assign(this._constraints, constraints);
198
- // 'presence' means different things in different versions of the validator,
199
- // when we say 'true' we mean that it should not be able to be empty
200
- for (const [, value] of _.toPairs(this._constraints)) {
201
- if (value && value.presence === true) {
202
- value.presence = {
203
- allowEmpty: false,
204
- };
205
- }
206
- }
207
- }
208
-
209
- get desiredCapConstraints () {
210
- return this._constraints;
211
- }
212
-
213
- // method required by MJSONWP in order to determine whether it should
214
- // respond with an invalid session response
215
- sessionExists (sessionId) {
216
- if (!sessionId) return false; // eslint-disable-line curly
217
- return sessionId === this.sessionId;
218
- }
219
-
220
- // method required by MJSONWP in order to determine if the command should
221
- // be proxied directly to the driver
222
- driverForSession (/*sessionId*/) {
223
- return this;
224
- }
225
-
226
- logExtraCaps (caps) {
227
- let extraCaps = _.difference(_.keys(caps),
228
- _.keys(this._constraints));
229
- if (extraCaps.length) {
230
- this.log.warn(`The following capabilities were provided, but are not ` +
231
- `recognized by Appium:`);
232
- for (const cap of extraCaps) {
233
- this.log.warn(` ${cap}`);
234
- }
235
- }
236
- }
237
-
238
- validateDesiredCaps (caps) {
239
- if (!this.shouldValidateCaps) {
240
- return true;
241
- }
242
-
243
- try {
244
- validateCaps(caps, this._constraints);
245
- } catch (e) {
246
- this.log.errorAndThrow(new errors.SessionNotCreatedError(`The desiredCapabilities object was not valid for the ` +
247
- `following reason(s): ${e.message}`));
248
- }
249
-
250
- this.logExtraCaps(caps);
251
-
252
- return true;
253
- }
254
-
255
- isMjsonwpProtocol () {
256
- return this.protocol === PROTOCOLS.MJSONWP;
257
- }
258
-
259
- isW3CProtocol () {
260
- return this.protocol === PROTOCOLS.W3C;
261
- }
262
-
263
- setProtocolMJSONWP () {
264
- this.protocol = PROTOCOLS.MJSONWP;
265
- }
266
-
267
- setProtocolW3C () {
268
- this.protocol = PROTOCOLS.W3C;
269
- }
270
-
271
- /**
272
- * Check whether a given feature is enabled via its name
273
- *
274
- * @param {string} name - name of feature/command
275
- *
276
- * @returns {Boolean}
277
- */
278
- isFeatureEnabled (name) {
279
- // if we have explicitly denied this feature, return false immediately
280
- if (this.denyInsecure && _.includes(this.denyInsecure, name)) {
281
- return false;
282
- }
27
+ /**
28
+ * @implements {SessionHandler}
29
+ */
30
+ export class BaseDriverCore extends DriverCore {
283
31
 
284
- // if we specifically have allowed the feature, return true
285
- if (this.allowInsecure && _.includes(this.allowInsecure, name)) {
286
- return true;
287
- }
288
-
289
- // otherwise, if we've globally allowed insecure features and not denied
290
- // this one, return true
291
- if (this.relaxedSecurityEnabled) {
292
- return true;
293
- }
294
-
295
- // if we haven't allowed anything insecure, then reject
296
- return false;
297
- }
298
-
299
- /**
300
- * Assert that a given feature is enabled and throw a helpful error if it's
301
- * not
302
- *
303
- * @param {string} name - name of feature/command
304
- */
305
- ensureFeatureEnabled (name) {
306
- if (!this.isFeatureEnabled(name)) {
307
- throw new Error(`Potentially insecure feature '${name}' has not been ` +
308
- `enabled. If you want to enable this feature and accept ` +
309
- `the security ramifications, please do so by following ` +
310
- `the documented instructions at https://github.com/appium` +
311
- `/appium/blob/master/docs/en/writing-running-appium/security.md`);
312
- }
313
- }
32
+ /** @type {Record<string,any>|undefined} */
33
+ cliArgs;
314
34
 
315
35
  // This is the main command handler for the driver. It wraps command
316
36
  // execution with timeout logic, checking that we have a valid session,
317
37
  // and ensuring that we execute commands one at a time. This method is called
318
38
  // by MJSONWP's express router.
39
+ /**
40
+ * @param {string} cmd
41
+ * @param {...any[]} args
42
+ * @returns {Promise<any>}
43
+ */
319
44
  async executeCommand (cmd, ...args) {
320
45
  // get start time for this command, and log in special cases
321
46
  let startTime = Date.now();
@@ -330,10 +55,12 @@ class BaseDriver extends Protocol {
330
55
 
331
56
  // if we had a command timer running, clear it now that we're starting
332
57
  // a new command and so don't want to time out
333
- this.clearNewCommandTimeout();
58
+ await this.clearNewCommandTimeout();
334
59
 
335
60
  if (this.shutdownUnexpectedly) {
336
- throw new errors.NoSuchDriverError('The driver was unexpectedly shut down!');
61
+ throw new errors.NoSuchDriverError(
62
+ 'The driver was unexpectedly shut down!',
63
+ );
337
64
  }
338
65
 
339
66
  // If we don't have this command, it must not be implemented
@@ -342,19 +69,26 @@ class BaseDriver extends Protocol {
342
69
  }
343
70
 
344
71
  let unexpectedShutdownListener;
345
- const commandExecutor = async () => await B.race([
346
- this[cmd](...args),
347
- new B((resolve, reject) => {
348
- unexpectedShutdownListener = reject;
349
- this.eventEmitter.on(ON_UNEXPECTED_SHUTDOWN_EVENT, unexpectedShutdownListener);
350
- })
351
- ]).finally(() => {
352
- if (unexpectedShutdownListener) {
353
- // This is needed to prevent memory leaks
354
- this.eventEmitter.removeListener(ON_UNEXPECTED_SHUTDOWN_EVENT, unexpectedShutdownListener);
355
- unexpectedShutdownListener = null;
356
- }
357
- });
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
+ });
358
92
  const res = this.isCommandsQueueEnabled
359
93
  ? await this.commandsQueueGuard.acquire(BaseDriver.name, commandExecutor)
360
94
  : await commandExecutor();
@@ -367,7 +101,7 @@ class BaseDriver extends Protocol {
367
101
  // intentionally
368
102
  if (this.isCommandsQueueEnabled && cmd !== DELETE_SESSION_COMMAND) {
369
103
  // resetting existing timeout
370
- this.startNewCommandTimeout();
104
+ await this.startNewCommandTimeout();
371
105
  }
372
106
 
373
107
  // log timing information about this command
@@ -382,40 +116,77 @@ class BaseDriver extends Protocol {
382
116
  return res;
383
117
  }
384
118
 
385
- 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
+ ) {
386
128
  this.eventEmitter.emit(ON_UNEXPECTED_SHUTDOWN_EVENT, err); // allow others to listen for this
387
129
  this.shutdownUnexpectedly = true;
388
130
  try {
389
- await this.deleteSession(this.sessionId);
131
+ if (this.sessionId !== null) {
132
+ await this.deleteSession(this.sessionId);
133
+ }
390
134
  } finally {
391
135
  this.shutdownUnexpectedly = false;
392
136
  }
393
137
  }
394
138
 
395
- validateLocatorStrategy (strategy, webContext = false) {
396
- let validStrategies = this.locatorStrategies;
397
- this.log.debug(`Valid locator strategies for this request: ${validStrategies.join(', ')}`);
139
+ async startNewCommandTimeout () {
140
+ // make sure there are no rogue timeouts
141
+ await this.clearNewCommandTimeout();
398
142
 
399
- if (webContext) {
400
- validStrategies = validStrategies.concat(this.webLocatorStrategies);
401
- }
143
+ // if command timeout is 0, it is disabled
144
+ if (!this.newCommandTimeoutMs) return; // eslint-disable-line curly
402
145
 
403
- if (!_.includes(validStrategies, strategy)) {
404
- throw new errors.InvalidSelectorError(`Locator Strategy '${strategy}' is not supported for this session`);
405
- }
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);
406
158
  }
407
159
 
408
- /*
409
- * Restart the session with the original caps,
410
- * preserving the timeout config.
160
+ /**
161
+ *
162
+ * @param {import('@appium/types').AppiumServer} server
163
+ * @param {string} host
164
+ * @param {number} port
165
+ * @param {string} path
411
166
  */
167
+ assignServer (server, host, port, path) {
168
+ this.server = server;
169
+ this.serverHost = host;
170
+ this.serverPort = port;
171
+ this.serverPath = path;
172
+ }
173
+
174
+ /*
175
+ * Restart the session with the original caps,
176
+ * preserving the timeout config.
177
+ */
412
178
  async reset () {
413
179
  this.log.debug('Resetting app mid-session');
414
180
  this.log.debug('Running generic full reset');
415
181
 
416
182
  // preserving state
417
183
  let currentConfig = {};
418
- for (let property of ['implicitWaitMs', 'newCommandTimeoutMs', 'sessionId', 'resetOnUnexpectedShutdown']) {
184
+ for (let property of [
185
+ 'implicitWaitMs',
186
+ 'newCommandTimeoutMs',
187
+ 'sessionId',
188
+ 'resetOnUnexpectedShutdown',
189
+ ]) {
419
190
  currentConfig[property] = this[property];
420
191
  }
421
192
 
@@ -423,75 +194,193 @@ class BaseDriver extends Protocol {
423
194
  this.resetOnUnexpectedShutdown = () => {};
424
195
 
425
196
  try {
426
- await this.deleteSession(this.sessionId);
197
+ if (this.sessionId !== null) {
198
+ await this.deleteSession(this.sessionId);
199
+ }
427
200
  this.log.debug('Restarting app');
428
- await this.createSession(undefined, undefined, this.originalCaps);
201
+ await this.createSession(this.originalCaps);
429
202
  } finally {
430
203
  // always restore state.
431
204
  for (let [key, value] of _.toPairs(currentConfig)) {
432
205
  this[key] = value;
433
206
  }
434
207
  }
435
- this.clearNewCommandTimeout();
208
+ await this.clearNewCommandTimeout();
436
209
  }
437
210
 
438
- proxyActive (/* sessionId */) {
439
- return false;
440
- }
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
+ }
441
234
 
442
- getProxyAvoidList (/* sessionId */) {
443
- return [];
444
- }
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
+ }
445
248
 
446
- canProxy (/* sessionId */) {
447
- return false;
448
- }
249
+ this.setProtocolW3C();
449
250
 
450
- /**
451
- * Whether a given command route (expressed as method and url) should not be
452
- * proxied according to this driver
453
- *
454
- * @param {string} sessionId - the current sessionId (in case the driver runs
455
- * multiple session ids and requires it). This is not used in this method but
456
- * should be made available to overridden methods.
457
- * @param {string} method - HTTP method of the route
458
- * @param {string} url - url of the route
459
- * @param {?*} body - webdriver request body
460
- *
461
- * @returns {boolean} - whether the route should be avoided
462
- */
463
- proxyRouteIsAvoided (sessionId, method, url/*, body*/) {
464
- for (let avoidSchema of this.getProxyAvoidList(sessionId)) {
465
- if (!_.isArray(avoidSchema) || avoidSchema.length !== 2) {
466
- throw new Error('Proxy avoidance must be a list of pairs');
467
- }
468
- let [avoidMethod, avoidPathRegex] = avoidSchema;
469
- if (!_.includes(['GET', 'POST', 'DELETE'], avoidMethod)) {
470
- throw new Error(`Unrecognized proxy avoidance method '${avoidMethod}'`);
471
- }
472
- if (!_.isRegExp(avoidPathRegex)) {
473
- throw new Error('Proxy avoidance path must be a regular expression');
474
- }
475
- let normalizedUrl = url.replace(new RegExp(`^${_.escapeRegExp(this.basePath)}`), '');
476
- if (avoidMethod === method && avoidPathRegex.test(normalizedUrl)) {
477
- 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);
478
272
  }
273
+ caps = fixCaps(caps, this.desiredCapConstraints, this.log);
274
+ } catch (e) {
275
+ throw new errors.SessionNotCreatedError(e.message);
479
276
  }
480
- return false;
481
- }
482
277
 
483
- addManagedDriver (driver) {
484
- this.managedDrivers.push(driver);
485
- }
278
+ this.validateDesiredCaps(caps);
279
+
280
+ this.sessionId = util.uuidV4();
281
+ this.caps = caps;
282
+ this.opts = _.cloneDeep(this.initialOpts);
486
283
 
487
- getManagedDrivers () {
488
- 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];
489
320
  }
490
- }
491
321
 
492
- for (let [cmd, fn] of _.toPairs(commands)) {
493
- 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
+ }
494
341
  }
495
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) {}
496
348
  export { BaseDriver };
497
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
+ */