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