@appium/base-driver 8.2.3 → 8.3.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 (128) hide show
  1. package/build/lib/basedriver/capabilities.js +2 -4
  2. package/build/lib/basedriver/commands/event.js +2 -4
  3. package/build/lib/basedriver/commands/find.js +5 -14
  4. package/build/lib/basedriver/commands/index.js +2 -4
  5. package/build/lib/basedriver/commands/log.js +4 -9
  6. package/build/lib/basedriver/commands/session.js +19 -30
  7. package/build/lib/basedriver/commands/settings.js +5 -11
  8. package/build/lib/basedriver/commands/timeout.js +11 -18
  9. package/build/lib/basedriver/desired-caps.js +2 -4
  10. package/build/lib/basedriver/device-settings.js +15 -5
  11. package/build/lib/basedriver/driver.js +36 -23
  12. package/build/lib/basedriver/helpers.js +10 -12
  13. package/build/lib/basedriver/logger.js +2 -4
  14. package/build/lib/constants.js +2 -4
  15. package/build/lib/express/crash.js +2 -4
  16. package/build/lib/express/express-logging.js +3 -5
  17. package/build/lib/express/idempotency.js +3 -5
  18. package/build/lib/express/logger.js +2 -4
  19. package/build/lib/express/middleware.js +2 -4
  20. package/build/lib/express/server.js +2 -4
  21. package/build/lib/express/static.js +2 -4
  22. package/build/lib/express/websocket.js +2 -4
  23. package/build/lib/helpers/capabilities.js +37 -0
  24. package/build/lib/index.js +4 -8
  25. package/build/lib/jsonwp-proxy/protocol-converter.js +19 -15
  26. package/build/lib/jsonwp-proxy/proxy.js +20 -15
  27. package/build/lib/jsonwp-status/status.js +2 -4
  28. package/build/lib/protocol/errors.js +2 -4
  29. package/build/lib/protocol/helpers.js +2 -4
  30. package/build/lib/protocol/index.js +2 -4
  31. package/build/lib/protocol/protocol.js +37 -30
  32. package/build/lib/protocol/routes.js +68 -4
  33. package/build/lib/protocol/validators.js +2 -4
  34. package/build/test/basedriver/README.md +5 -0
  35. package/build/test/basedriver/driver-e2e-tests.js +2 -4
  36. package/build/test/basedriver/driver-tests.js +12 -17
  37. package/build/test/basedriver/index.js +2 -4
  38. package/build/test/e2e/basedriver/driver.e2e.spec.js +15 -0
  39. package/build/test/e2e/basedriver/helpers.e2e.spec.js +192 -0
  40. package/build/test/e2e/basedriver/websockets.e2e.spec.js +82 -0
  41. package/build/test/e2e/express/server.e2e.spec.js +159 -0
  42. package/build/test/e2e/jsonwp-proxy/proxy.e2e.spec.js +59 -0
  43. package/build/test/e2e/protocol/fake-driver.js +163 -0
  44. package/build/test/e2e/protocol/helpers.js +25 -0
  45. package/build/test/e2e/protocol/protocol.e2e.spec.js +1186 -0
  46. package/build/test/helpers.js +2 -4
  47. package/build/test/unit/basedriver/capabilities.spec.js +672 -0
  48. package/build/test/unit/basedriver/capability.spec.js +353 -0
  49. package/build/test/unit/basedriver/commands/event.spec.js +110 -0
  50. package/build/test/unit/basedriver/commands/log.spec.js +85 -0
  51. package/build/test/unit/basedriver/driver.spec.js +15 -0
  52. package/build/test/unit/basedriver/helpers.spec.js +151 -0
  53. package/build/test/unit/basedriver/timeout.spec.js +135 -0
  54. package/build/test/unit/express/server.spec.js +155 -0
  55. package/build/test/unit/express/static.spec.js +26 -0
  56. package/build/test/unit/jsonwp-proxy/mock-request.js +91 -0
  57. package/build/test/unit/jsonwp-proxy/protocol-converter.spec.js +171 -0
  58. package/build/test/unit/jsonwp-proxy/proxy.spec.js +292 -0
  59. package/build/test/unit/jsonwp-proxy/url.spec.js +165 -0
  60. package/build/test/unit/jsonwp-status/status.spec.js +34 -0
  61. package/build/test/unit/protocol/errors.spec.js +390 -0
  62. package/build/test/unit/protocol/routes.spec.js +80 -0
  63. package/build/test/unit/protocol/validator.spec.js +149 -0
  64. package/lib/basedriver/commands/find.js +3 -6
  65. package/lib/basedriver/commands/log.js +2 -4
  66. package/lib/basedriver/commands/session.js +21 -22
  67. package/lib/basedriver/commands/settings.js +3 -5
  68. package/lib/basedriver/commands/timeout.js +9 -10
  69. package/lib/basedriver/device-settings.js +10 -1
  70. package/lib/basedriver/driver.js +36 -12
  71. package/lib/basedriver/helpers.js +13 -11
  72. package/lib/express/express-logging.js +1 -1
  73. package/lib/express/idempotency.js +1 -1
  74. package/lib/helpers/capabilities.js +25 -0
  75. package/lib/index.js +2 -2
  76. package/lib/jsonwp-proxy/protocol-converter.js +14 -13
  77. package/lib/jsonwp-proxy/proxy.js +16 -12
  78. package/lib/protocol/protocol.js +34 -29
  79. package/lib/protocol/routes.js +60 -1
  80. package/package.json +36 -24
  81. package/test/basedriver/README.md +5 -0
  82. package/test/basedriver/driver-e2e-tests.js +1 -1
  83. package/test/basedriver/driver-tests.js +12 -7
  84. package/build/lib/protocol/sessions-cache.js +0 -88
  85. package/build/test/basedriver/capabilities-specs.js +0 -632
  86. package/build/test/basedriver/capability-specs.js +0 -396
  87. package/build/test/basedriver/commands/event-specs.js +0 -112
  88. package/build/test/basedriver/commands/log-specs.js +0 -80
  89. package/build/test/basedriver/driver-e2e-specs.js +0 -17
  90. package/build/test/basedriver/driver-specs.js +0 -17
  91. package/build/test/basedriver/helpers-e2e-specs.js +0 -194
  92. package/build/test/basedriver/helpers-specs.js +0 -153
  93. package/build/test/basedriver/timeout-specs.js +0 -139
  94. package/build/test/basedriver/websockets-e2e-specs.js +0 -84
  95. package/build/test/express/server-e2e-specs.js +0 -156
  96. package/build/test/express/server-specs.js +0 -151
  97. package/build/test/express/static-specs.js +0 -23
  98. package/build/test/jsonwp-proxy/mock-request.js +0 -93
  99. package/build/test/jsonwp-proxy/protocol-converter-specs.js +0 -173
  100. package/build/test/jsonwp-proxy/proxy-e2e-specs.js +0 -61
  101. package/build/test/jsonwp-proxy/proxy-specs.js +0 -294
  102. package/build/test/jsonwp-proxy/url-specs.js +0 -167
  103. package/build/test/jsonwp-status/status-specs.js +0 -36
  104. package/build/test/protocol/errors-specs.js +0 -388
  105. package/build/test/protocol/fake-driver.js +0 -168
  106. package/build/test/protocol/helpers.js +0 -27
  107. package/build/test/protocol/protocol-e2e-specs.js +0 -1182
  108. package/build/test/protocol/routes-specs.js +0 -82
  109. package/build/test/protocol/validator-specs.js +0 -151
  110. package/lib/protocol/sessions-cache.js +0 -74
  111. package/test/basedriver/capabilities-specs.js +0 -505
  112. package/test/basedriver/capability-specs.js +0 -409
  113. package/test/basedriver/commands/event-specs.js +0 -74
  114. package/test/basedriver/commands/log-specs.js +0 -70
  115. package/test/basedriver/driver-e2e-specs.js +0 -8
  116. package/test/basedriver/driver-specs.js +0 -8
  117. package/test/basedriver/fixtures/BadZippedApp.zip +0 -1
  118. package/test/basedriver/fixtures/FakeAndroidApp.apk +0 -1
  119. package/test/basedriver/fixtures/FakeAndroidApp.asd +0 -0
  120. package/test/basedriver/fixtures/FakeIOSApp.app +0 -1
  121. package/test/basedriver/fixtures/FakeIOSApp.app.zip +0 -0
  122. package/test/basedriver/fixtures/FakeIOSApp.ipa +0 -0
  123. package/test/basedriver/fixtures/custom-element-finder-bad.js +0 -5
  124. package/test/basedriver/fixtures/custom-element-finder.js +0 -29
  125. package/test/basedriver/helpers-e2e-specs.js +0 -187
  126. package/test/basedriver/helpers-specs.js +0 -137
  127. package/test/basedriver/timeout-specs.js +0 -133
  128. package/test/basedriver/websockets-e2e-specs.js +0 -75
package/lib/index.js CHANGED
@@ -5,9 +5,9 @@ import * as driver from './basedriver/driver';
5
5
  import * as deviceSettings from './basedriver/device-settings';
6
6
 
7
7
  const { BaseDriver } = driver;
8
- const { DeviceSettings, BASEDRIVER_HANDLED_SETTINGS } = deviceSettings;
8
+ const { DeviceSettings } = deviceSettings;
9
9
 
10
- export { BaseDriver, DeviceSettings, BASEDRIVER_HANDLED_SETTINGS };
10
+ export { BaseDriver, DeviceSettings };
11
11
  export default BaseDriver;
12
12
 
13
13
 
@@ -5,9 +5,6 @@ import {
5
5
  MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS
6
6
  } from '../constants';
7
7
 
8
- const log = logger.getLogger('Protocol Converter');
9
-
10
-
11
8
  export const COMMAND_URLS_CONFLICTS = [
12
9
  {
13
10
  commandNames: ['execute', 'executeAsync'],
@@ -41,20 +38,24 @@ export const COMMAND_URLS_CONFLICTS = [
41
38
  jsonwpConverter: (w3cUrl) => {
42
39
  const w3cPropertyRegex = /\/element\/([^/]+)\/property\/([^/]+)/;
43
40
  const jsonwpUrl = w3cUrl.replace(w3cPropertyRegex, '/element/$1/attribute/$2');
44
- log.info(`Converting W3C '${w3cUrl}' to '${jsonwpUrl}'`);
45
41
  return jsonwpUrl;
46
42
  },
47
43
  w3cConverter: (jsonwpUrl) => jsonwpUrl // Don't convert JSONWP URL to W3C. W3C accepts /attribute and /property
48
44
  }
49
45
  ];
50
-
51
46
  const {MJSONWP, W3C} = PROTOCOLS;
47
+ const DEFAULT_LOG = logger.getLogger('Protocol Converter');
52
48
 
53
49
 
54
50
  class ProtocolConverter {
55
- constructor (proxyFunc) {
51
+ constructor (proxyFunc, log = null) {
56
52
  this.proxyFunc = proxyFunc;
57
53
  this._downstreamProtocol = null;
54
+ this._log = log;
55
+ }
56
+
57
+ get log () {
58
+ return this._log ?? DEFAULT_LOG;
58
59
  }
59
60
 
60
61
  set downstreamProtocol (value) {
@@ -107,7 +108,7 @@ class ProtocolConverter {
107
108
  let response, resBody;
108
109
 
109
110
  const timeoutRequestObjects = this.getTimeoutRequestObjects(body);
110
- log.debug(`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`);
111
+ this.log.debug(`Will send the following request bodies to /timeouts: ${JSON.stringify(timeoutRequestObjects)}`);
111
112
  for (const timeoutObj of timeoutRequestObjects) {
112
113
  [response, resBody] = await this.proxyFunc(url, method, timeoutObj);
113
114
 
@@ -130,14 +131,14 @@ class ProtocolConverter {
130
131
  const bodyObj = util.safeJsonParse(body);
131
132
  if (_.isPlainObject(bodyObj)) {
132
133
  if (this.downstreamProtocol === W3C && _.has(bodyObj, 'name') && !_.has(bodyObj, 'handle')) {
133
- log.debug(`Copied 'name' value '${bodyObj.name}' to 'handle' as per W3C spec`);
134
+ this.log.debug(`Copied 'name' value '${bodyObj.name}' to 'handle' as per W3C spec`);
134
135
  return await this.proxyFunc(url, method, {
135
136
  ...bodyObj,
136
137
  handle: bodyObj.name,
137
138
  });
138
139
  }
139
140
  if (this.downstreamProtocol === MJSONWP && _.has(bodyObj, 'handle') && !_.has(bodyObj, 'name')) {
140
- log.debug(`Copied 'handle' value '${bodyObj.handle}' to 'name' as per JSONWP spec`);
141
+ this.log.debug(`Copied 'handle' value '${bodyObj.handle}' to 'name' as per JSONWP spec`);
141
142
  return await this.proxyFunc(url, method, {
142
143
  ...bodyObj,
143
144
  name: bodyObj.handle,
@@ -156,12 +157,12 @@ class ProtocolConverter {
156
157
  value = _.isString(text)
157
158
  ? [...text]
158
159
  : (_.isArray(text) ? text : []);
159
- log.debug(`Added 'value' property ${JSON.stringify(value)} to 'setValue' request body`);
160
+ this.log.debug(`Added 'value' property ${JSON.stringify(value)} to 'setValue' request body`);
160
161
  } else if (!util.hasValue(text) && util.hasValue(value)) {
161
162
  text = _.isArray(value)
162
163
  ? value.join('')
163
164
  : (_.isString(value) ? value : '');
164
- log.debug(`Added 'text' property ${JSON.stringify(text)} to 'setValue' request body`);
165
+ this.log.debug(`Added 'text' property ${JSON.stringify(text)} to 'setValue' request body`);
165
166
  }
166
167
  return await this.proxyFunc(url, method, Object.assign({}, bodyObj, {
167
168
  text,
@@ -236,11 +237,11 @@ class ProtocolConverter {
236
237
  ? jsonwpConverter(url)
237
238
  : w3cConverter(url);
238
239
  if (rewrittenUrl === url) {
239
- log.debug(`Did not know how to rewrite the original URL '${url}' ` +
240
+ this.log.debug(`Did not know how to rewrite the original URL '${url}' ` +
240
241
  `for ${this.downstreamProtocol} protocol`);
241
242
  break;
242
243
  }
243
- log.info(`Rewrote the original URL '${url}' to '${rewrittenUrl}' ` +
244
+ this.log.info(`Rewrote the original URL '${url}' to '${rewrittenUrl}' ` +
244
245
  `for ${this.downstreamProtocol} protocol`);
245
246
  return await this.proxyFunc(rewrittenUrl, method, body);
246
247
  }
@@ -12,8 +12,7 @@ import { formatResponseValue, formatStatus } from '../protocol/helpers';
12
12
  import http from 'http';
13
13
  import https from 'https';
14
14
 
15
-
16
- const log = logger.getLogger('WD Proxy');
15
+ const DEFAULT_LOG = logger.getLogger('WD Proxy');
17
16
  const DEFAULT_REQUEST_TIMEOUT = 240000;
18
17
  const COMPACT_ERROR_PATTERNS = [
19
18
  /\bECONNREFUSED\b/,
@@ -43,7 +42,12 @@ class JWProxy {
43
42
  };
44
43
  this.httpAgent = new http.Agent(agentOpts);
45
44
  this.httpsAgent = new https.Agent(agentOpts);
46
- this.protocolConverter = new ProtocolConverter(this.proxy.bind(this));
45
+ this.protocolConverter = new ProtocolConverter(this.proxy.bind(this), opts.log);
46
+ this._log = opts.log;
47
+ }
48
+
49
+ get log () {
50
+ return this._log ?? DEFAULT_LOG;
47
51
  }
48
52
 
49
53
  /**
@@ -166,7 +170,7 @@ class JWProxy {
166
170
  }
167
171
  }
168
172
 
169
- log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
173
+ this.log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
170
174
  (reqOpts.data ? `with body: ${truncateBody(reqOpts.data)}` : 'with no body'));
171
175
 
172
176
  const throwProxyError = (error) => {
@@ -187,7 +191,7 @@ class JWProxy {
187
191
  // If it cannot be coerced to an object then the response is wrong
188
192
  throwProxyError(data);
189
193
  }
190
- log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
194
+ this.log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
191
195
  isResponseLogged = true;
192
196
  const isSessionCreationRequest = /\/session$/.test(url) && method === 'POST';
193
197
  if (isSessionCreationRequest) {
@@ -195,7 +199,7 @@ class JWProxy {
195
199
  this.sessionId = data.sessionId || (data.value || {}).sessionId;
196
200
  }
197
201
  this.downstreamProtocol = this.getProtocolFromResBody(data);
198
- log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
202
+ this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
199
203
  }
200
204
  if (_.has(data, 'status') && parseInt(data.status, 10) !== 0) {
201
205
  // Some servers, like chromedriver may return response code 200 for non-zero JSONWP statuses
@@ -211,16 +215,16 @@ class JWProxy {
211
215
  if (util.hasValue(e.response)) {
212
216
  if (!isResponseLogged) {
213
217
  const error = truncateBody(e.response.data);
214
- log.info(util.hasValue(e.response.status)
218
+ this.log.info(util.hasValue(e.response.status)
215
219
  ? `Got response with status ${e.response.status}: ${error}`
216
220
  : `Got response with unknown status: ${error}`);
217
221
  }
218
222
  } else {
219
223
  proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${e.message}`;
220
224
  if (COMPACT_ERROR_PATTERNS.some((p) => p.test(e.message))) {
221
- log.info(e.message);
225
+ this.log.info(e.message);
222
226
  } else {
223
- log.info(e.stack);
227
+ this.log.info(e.stack);
224
228
  }
225
229
  }
226
230
  throw new errors.ProxyRequestError(proxyErrorMsg, e.response?.data, e.response?.status);
@@ -256,7 +260,7 @@ class JWProxy {
256
260
  if (!commandName) {
257
261
  return await this.proxy(url, method, body);
258
262
  }
259
- log.debug(`Matched '${url}' to command name '${commandName}'`);
263
+ this.log.debug(`Matched '${url}' to command name '${commandName}'`);
260
264
 
261
265
  return await this.protocolConverter.convertAndProxy(commandName, url, method, body);
262
266
  }
@@ -318,10 +322,10 @@ class JWProxy {
318
322
  const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
319
323
  if (_.has(resBodyObj, 'sessionId')) {
320
324
  if (reqSessionId) {
321
- log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${reqSessionId}`);
325
+ this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${reqSessionId}`);
322
326
  resBodyObj.sessionId = reqSessionId;
323
327
  } else if (this.sessionId) {
324
- log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${this.sessionId}`);
328
+ this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${this.sessionId}`);
325
329
  resBodyObj.sessionId = this.sessionId;
326
330
  }
327
331
  }
@@ -1,5 +1,5 @@
1
1
  import _ from 'lodash';
2
- import { util } from '@appium/support';
2
+ import { util, logger, node } from '@appium/support';
3
3
  import { validators } from './validators';
4
4
  import {
5
5
  errors, isErrorType, getResponseForW3CError,
@@ -7,11 +7,9 @@ import {
7
7
  } from './errors';
8
8
  import { METHOD_MAP, NO_SESSION_ID_COMMANDS } from './routes';
9
9
  import B from 'bluebird';
10
- import {
11
- formatResponseValue, formatStatus,
12
- } from './helpers';
10
+ import { formatResponseValue, formatStatus } from './helpers';
13
11
  import { MAX_LOG_BODY_LENGTH, PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
14
- import SESSIONS_CACHE from './sessions-cache';
12
+ import { isW3cCaps } from '../helpers/capabilities';
15
13
 
16
14
 
17
15
  const CREATE_SESSION_COMMAND = 'createSession';
@@ -20,10 +18,8 @@ const GET_STATUS_COMMAND = 'getStatus';
20
18
 
21
19
  class Protocol {}
22
20
 
23
- function determineProtocol (desiredCapabilities, requiredCapabilities, capabilities) {
24
- return _.isPlainObject(capabilities) ?
25
- PROTOCOLS.W3C :
26
- PROTOCOLS.MJSONWP;
21
+ function determineProtocol (createSessionArgs) {
22
+ return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
27
23
  }
28
24
 
29
25
  function extractProtocol (driver, sessionId = null) {
@@ -38,13 +34,30 @@ function extractProtocol (driver, sessionId = null) {
38
34
  }
39
35
 
40
36
  // Extract the protocol for the current session if the given driver is the umbrella one
41
- return dstDriver ? dstDriver.protocol : SESSIONS_CACHE.getProtocol(sessionId);
37
+ return dstDriver?.protocol ?? PROTOCOLS.W3C;
42
38
  }
43
39
 
44
40
  function isSessionCommand (command) {
45
41
  return !_.includes(NO_SESSION_ID_COMMANDS, command);
46
42
  }
47
43
 
44
+ function getLogger (driver, sessionId = null) {
45
+ const dstDriver = sessionId && _.isFunction(driver.driverForSession)
46
+ ? (driver.driverForSession(sessionId) ?? driver)
47
+ : driver;
48
+ if (_.isFunction(dstDriver.log?.info)) {
49
+ return dstDriver.log;
50
+ }
51
+
52
+ let logPrefix = dstDriver.constructor
53
+ ? `${dstDriver.constructor.name}@${node.getObjectId(dstDriver).substring(0, 8)}`
54
+ : 'AppiumDriver';
55
+ if (sessionId) {
56
+ logPrefix += ` (${sessionId.substring(0, 8)})`;
57
+ }
58
+ return logger.getLogger(logPrefix);
59
+ }
60
+
48
61
  function wrapParams (paramSets, jsonObj) {
49
62
  /* There are commands like performTouch which take a single parameter (primitive type or array).
50
63
  * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
@@ -192,11 +205,11 @@ function makeArgs (requestParams, jsonObj, payloadParams, protocol) {
192
205
 
193
206
  function routeConfiguringFunction (driver) {
194
207
  if (!driver.sessionExists) {
195
- throw new Error('Drivers used with MJSONWP must implement `sessionExists`');
208
+ throw new Error('Drivers must implement `sessionExists` property');
196
209
  }
197
210
 
198
211
  if (!(driver.executeCommand || driver.execute)) {
199
- throw new Error('Drivers used with MJSONWP must implement `executeCommand` or `execute`');
212
+ throw new Error('Drivers must implement `executeCommand` or `execute` method');
200
213
  }
201
214
 
202
215
  // return a function which will add all the routes to the driver. Here extraMethods might be
@@ -247,7 +260,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
247
260
  await doJwpProxy(driver, req, res);
248
261
  return;
249
262
  }
250
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol).debug(`Would have proxied ` +
263
+ getLogger(driver, req.params.sessionId).debug(`Would have proxied ` +
251
264
  `command directly, but a plugin exists which might require its value, so will let ` +
252
265
  `its value be collected internally and made part of plugin chain`);
253
266
  didPluginOverrideProxy = true;
@@ -272,7 +285,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
272
285
  if (spec.command === CREATE_SESSION_COMMAND) {
273
286
  // try to determine protocol by session creation args, so we can throw a
274
287
  // properly formatted error if arguments validation fails
275
- currentProtocol = determineProtocol(...makeArgs(req.params, jsonObj, spec.payloadParams || {}));
288
+ currentProtocol = determineProtocol(makeArgs(req.params, jsonObj, spec.payloadParams || {}));
276
289
  }
277
290
 
278
291
  // ensure that the json payload conforms to the spec
@@ -288,7 +301,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
288
301
  }
289
302
 
290
303
  // run the driver command wrapped inside the argument validators
291
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol).debug(`Calling ` +
304
+ getLogger(driver, req.params.sessionId).debug(`Calling ` +
292
305
  `${driver.constructor.name}.${spec.command}() with args: ` +
293
306
  _.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}));
294
307
 
@@ -317,8 +330,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
317
330
  // unpack createSession response
318
331
  if (spec.command === CREATE_SESSION_COMMAND) {
319
332
  newSessionId = driverRes[0];
320
- SESSIONS_CACHE.putSession(newSessionId, currentProtocol);
321
- SESSIONS_CACHE.getLogger(newSessionId, currentProtocol)
333
+ getLogger(driver, newSessionId)
322
334
  .debug(`Cached the protocol value '${currentProtocol}' for the new session ${newSessionId}`);
323
335
  if (currentProtocol === PROTOCOLS.MJSONWP) {
324
336
  driverRes = driverRes[1];
@@ -333,9 +345,9 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
333
345
 
334
346
  // delete should not return anything even if successful
335
347
  if (spec.command === DELETE_SESSION_COMMAND) {
336
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol)
348
+ getLogger(driver, req.params.sessionId)
337
349
  .debug(`Received response: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`);
338
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol).debug('But deleting session, so not returning');
350
+ getLogger(driver, req.params.sessionId).debug('But deleting session, so not returning');
339
351
  driverRes = null;
340
352
  }
341
353
 
@@ -349,15 +361,8 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
349
361
  }
350
362
 
351
363
  httpResBody.value = driverRes;
352
- SESSIONS_CACHE.getLogger(req.params.sessionId || newSessionId, currentProtocol).debug(`Responding ` +
364
+ getLogger(driver, req.params.sessionId || newSessionId).debug(`Responding ` +
353
365
  `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`);
354
-
355
- if (spec.command === DELETE_SESSION_COMMAND) {
356
- // We don't want to keep the logger instance in the cache
357
- // after the session is deleted, because it contains the logging history
358
- // and consumes the memory
359
- SESSIONS_CACHE.resetLogger(req.params.sessionId);
360
- }
361
366
  } catch (err) {
362
367
  // if anything goes wrong, figure out what our response should be
363
368
  // based on the type of error that we encountered
@@ -374,7 +379,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
374
379
  if (isErrorType(err, errors.ProxyRequestError)) {
375
380
  actualErr = err.getActualError();
376
381
  } else {
377
- SESSIONS_CACHE.getLogger(req.params.sessionId || newSessionId, currentProtocol)
382
+ getLogger(driver, req.params.sessionId || newSessionId)
378
383
  .debug(`Encountered internal error running command: ${errMsg}`);
379
384
  }
380
385
 
@@ -431,7 +436,7 @@ function driverShouldDoJwpProxy (driver, req, command) {
431
436
  }
432
437
 
433
438
  async function doJwpProxy (driver, req, res) {
434
- SESSIONS_CACHE.getLogger(req.params.sessionId, extractProtocol(driver, req.params.sessionId))
439
+ getLogger(driver, req.params.sessionId)
435
440
  .info('Driver proxy active, passing request on via HTTP proxy');
436
441
 
437
442
  // check that the inner driver has a proxy function
@@ -1,6 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import { util } from '@appium/support';
3
- import { DEFAULT_BASE_PATH } from '../constants';
3
+ import { PROTOCOLS, DEFAULT_BASE_PATH } from '../constants';
4
4
 
5
5
 
6
6
  const SET_ALERT_TEXT_PAYLOAD_PARAMS = {
@@ -33,6 +33,18 @@ const METHOD_MAP = {
33
33
  '/session/:sessionId/timeouts': {
34
34
  GET: {command: 'getTimeouts'}, // W3C route
35
35
  POST: {command: 'timeouts', payloadParams: {
36
+ validate: (jsonObj, protocolName) => {
37
+ if (protocolName === PROTOCOLS.W3C) {
38
+ if (!util.hasValue(jsonObj.script) && !util.hasValue(jsonObj.pageLoad) && !util.hasValue(jsonObj.implicit)) {
39
+ return 'W3C protocol expects any of script, pageLoad or implicit to be set';
40
+ }
41
+ } else {
42
+ // MJSONWP
43
+ if (!util.hasValue(jsonObj.type) || !util.hasValue(jsonObj.ms)) {
44
+ return 'MJSONWP protocol requires type and ms';
45
+ }
46
+ }
47
+ },
36
48
  optional: ['type', 'ms', 'script', 'pageLoad', 'implicit'],
37
49
  }}
38
50
  },
@@ -42,9 +54,19 @@ const METHOD_MAP = {
42
54
  '/session/:sessionId/timeouts/implicit_wait': {
43
55
  POST: {command: 'implicitWait', payloadParams: {required: ['ms']}}
44
56
  },
57
+ // JSONWP
58
+ '/session/:sessionId/window_handle': {
59
+ GET: {command: 'getWindowHandle'}
60
+ },
61
+ // W3C
45
62
  '/session/:sessionId/window/handle': {
46
63
  GET: {command: 'getWindowHandle'}
47
64
  },
65
+ // JSONWP
66
+ '/session/:sessionId/window_handles': {
67
+ GET: {command: 'getWindowHandles'}
68
+ },
69
+ // W3C
48
70
  '/session/:sessionId/window/handles': {
49
71
  GET: {command: 'getWindowHandles'}
50
72
  },
@@ -61,6 +83,14 @@ const METHOD_MAP = {
61
83
  '/session/:sessionId/refresh': {
62
84
  POST: {command: 'refresh'}
63
85
  },
86
+ // MJSONWP
87
+ '/session/:sessionId/execute': {
88
+ POST: {command: 'execute', payloadParams: {required: ['script', 'args']}}
89
+ },
90
+ // MJSONWP
91
+ '/session/:sessionId/execute_async': {
92
+ POST: {command: 'executeAsync', payloadParams: {required: ['script', 'args']}}
93
+ },
64
94
  '/session/:sessionId/screenshot': {
65
95
  GET: {command: 'getScreenshot'}
66
96
  },
@@ -207,6 +237,9 @@ const METHOD_MAP = {
207
237
  '/session/:sessionId/element/:elementId/size': {
208
238
  GET: {command: 'getSize'}
209
239
  },
240
+ '/session/:sessionId/element/:elementId/shadow': {
241
+ GET: {command: 'elementShadowRoot'}
242
+ },
210
243
  '/session/:sessionId/element/:elementId/css/:propertyName': {
211
244
  GET: {command: 'getCssProperty'}
212
245
  },
@@ -582,6 +615,28 @@ const METHOD_MAP = {
582
615
  POST: {command: 'logCustomEvent', payloadParams: {required: ['vendor', 'event']}}
583
616
  },
584
617
 
618
+ /*
619
+ * The W3C spec has some changes to the wire protocol.
620
+ * https://w3c.github.io/webdriver/webdriver-spec.html
621
+ * Begin to add those changes here, keeping the old version
622
+ * since clients still implement them.
623
+ */
624
+ // MJSONWP
625
+ '/session/:sessionId/alert_text': {
626
+ GET: {command: 'getAlertText'},
627
+ POST: {
628
+ command: 'setAlertText',
629
+ payloadParams: SET_ALERT_TEXT_PAYLOAD_PARAMS,
630
+ }
631
+ },
632
+ // MJSONWP
633
+ '/session/:sessionId/accept_alert': {
634
+ POST: {command: 'postAcceptAlert'}
635
+ },
636
+ // MJSONWP
637
+ '/session/:sessionId/dismiss_alert': {
638
+ POST: {command: 'postDismissAlert'}
639
+ },
585
640
  // https://w3c.github.io/webdriver/webdriver-spec.html#user-prompts
586
641
  '/session/:sessionId/alert/text': {
587
642
  GET: {command: 'getAlertText'},
@@ -606,6 +661,10 @@ const METHOD_MAP = {
606
661
  '/session/:sessionId/execute/async': {
607
662
  POST: {command: 'executeAsync', payloadParams: {required: ['script', 'args']}}
608
663
  },
664
+ // Pre-W3C endpoint for element screenshot
665
+ '/session/:sessionId/screenshot/:elementId': {
666
+ GET: {command: 'getElementScreenshot'}
667
+ },
609
668
  '/session/:sessionId/element/:elementId/screenshot': {
610
669
  GET: {command: 'getElementScreenshot'}
611
670
  },
package/package.json CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "name": "@appium/base-driver",
3
+ "version": "8.3.1",
3
4
  "description": "Base driver class for Appium drivers",
4
5
  "keywords": [
5
6
  "automation",
@@ -11,20 +12,17 @@
11
12
  "firefoxos",
12
13
  "testing"
13
14
  ],
14
- "version": "8.2.3",
15
- "author": "https://github.com/appium",
16
- "license": "Apache-2.0",
17
- "repository": {
18
- "type": "git",
19
- "url": "https://github.com/appium/appium.git"
20
- },
15
+ "homepage": "https://appium.io",
21
16
  "bugs": {
22
17
  "url": "https://github.com/appium/appium/issues"
23
18
  },
24
- "engines": {
25
- "node": ">=12",
26
- "npm": ">=6"
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/appium/appium.git",
22
+ "directory": "packages/base-driver"
27
23
  },
24
+ "license": "Apache-2.0",
25
+ "author": "https://github.com/appium",
28
26
  "directories": {
29
27
  "lib": "lib"
30
28
  },
@@ -34,34 +32,48 @@
34
32
  "lib",
35
33
  "static",
36
34
  "test/basedriver",
37
- "!test/basedriver/fixtures",
35
+ "!test/e2e/fixtures",
38
36
  "build",
39
- "!build/test/basedriver/fixtures"
37
+ "!build/test/e2e/fixtures"
40
38
  ],
39
+ "scripts": {
40
+ "build": "npm run build:sources && npm run build:test",
41
+ "build:sources": "babel lib --root-mode=upward --out-dir=build/lib",
42
+ "build:test": "babel test --root-mode=upward --out-dir=build/test --copy-files",
43
+ "dev": "npm run build -- --watch",
44
+ "fix": "npm run lint -- --fix",
45
+ "lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
46
+ "test": "npm run test:unit",
47
+ "test:e2e": "mocha --require ../../test/setup-babel.js --timeout 20s --slow 10s \"./test/e2e/**/*.spec.js\"",
48
+ "test:unit": "mocha --require ../../test/setup-babel.js \"./test/unit/**/*.spec.js\""
49
+ },
41
50
  "dependencies": {
42
- "@appium/support": "^2.55.3",
43
- "@babel/runtime": "7.16.3",
44
- "@dabh/colors": "1.4.0",
45
- "async-lock": "1.3.0",
51
+ "@appium/support": "^2.56.1",
52
+ "@babel/runtime": "7.17.8",
53
+ "@colors/colors": "1.5.0",
54
+ "async-lock": "1.3.1",
46
55
  "asyncbox": "2.9.2",
47
- "axios": "0.24.0",
56
+ "axios": "0.26.1",
48
57
  "bluebird": "3.7.2",
49
- "body-parser": "1.19.0",
58
+ "body-parser": "1.19.2",
50
59
  "es6-error": "4.1.1",
51
- "express": "4.17.1",
52
- "http-status-codes": "2.1.4",
60
+ "express": "4.17.3",
61
+ "http-status-codes": "2.2.0",
53
62
  "lodash": "4.17.21",
54
- "lru-cache": "6.0.0",
63
+ "lru-cache": "7.7.1",
55
64
  "method-override": "3.0.0",
56
65
  "morgan": "1.10.0",
57
66
  "serve-favicon": "2.5.0",
58
67
  "source-map-support": "0.5.21",
59
68
  "validate.js": "0.13.1",
60
- "ws": "7.5.6"
69
+ "ws": "7.5.7"
70
+ },
71
+ "engines": {
72
+ "node": ">=12",
73
+ "npm": ">=6"
61
74
  },
62
75
  "publishConfig": {
63
76
  "access": "public"
64
77
  },
65
- "homepage": "https://appium.io",
66
- "gitHead": "ca90a11813546ab4851e5b1f0406f420a53227e6"
78
+ "gitHead": "6d35def9ed754121fee691cdaf7b30e3a7ac3e8b"
67
79
  }
@@ -0,0 +1,5 @@
1
+ # Driver Test Helpers
2
+
3
+ This directory contains modules which expose test suites that an external driver can use to test against the base implementation. They are published in the `@appium/base-driver` package.
4
+
5
+ Drivers wanting to leverage these suites will want to add `@appium/base-driver` to their `devDependencies`.
@@ -2,7 +2,7 @@ import _ from 'lodash';
2
2
  import { BaseDriver, server, routeConfiguringFunction, DeviceSettings } from '../../lib';
3
3
  import axios from 'axios';
4
4
  import B from 'bluebird';
5
- import {TEST_HOST, getTestPort, createAppiumURL, METHODS} from '../helpers';
5
+ import { TEST_HOST, getTestPort, createAppiumURL, METHODS } from '../helpers';
6
6
  import { PREFIXED_APPIUM_OPTS_CAP } from '../../lib/basedriver/capabilities';
7
7
  const {POST, DELETE} = METHODS;
8
8
 
@@ -1,7 +1,7 @@
1
1
  import _ from 'lodash';
2
2
  import B from 'bluebird';
3
3
  import { DeviceSettings } from '../../lib';
4
- import sinon from 'sinon';
4
+ import { createSandbox } from 'sinon';
5
5
 
6
6
 
7
7
  // wrap these tests in a function so we can export the tests and re-use them
@@ -10,10 +10,14 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
10
10
  // to display the driver under test in report
11
11
  const className = DriverClass.name || '(unknown driver)';
12
12
 
13
+
13
14
  describe(`BaseDriver (as ${className})`, function () {
14
15
  let d, w3cCaps;
15
16
 
17
+ let sandbox;
18
+
16
19
  beforeEach(function () {
20
+ sandbox = createSandbox();
17
21
  d = new DriverClass();
18
22
  w3cCaps = {
19
23
  alwaysMatch: Object.assign({}, defaultCaps, {
@@ -25,6 +29,7 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
25
29
  });
26
30
  afterEach(async function () {
27
31
  await d.deleteSession();
32
+ sandbox.restore();
28
33
  });
29
34
 
30
35
  it('should report the version of BaseDriver used', function () {
@@ -345,7 +350,7 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
345
350
 
346
351
  describe('#proxyRouteIsAvoided', function () {
347
352
  it('should validate form of avoidance list', function () {
348
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
353
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
349
354
  avoidStub.returns([['POST', /\/foo/], ['GET']]);
350
355
  (() => { d.proxyRouteIsAvoided(); }).should.throw;
351
356
  avoidStub.returns([['POST', /\/foo/], ['GET', /^foo/, 'bar']]);
@@ -353,31 +358,31 @@ function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
353
358
  avoidStub.restore();
354
359
  });
355
360
  it('should reject bad http methods', function () {
356
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
361
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
357
362
  avoidStub.returns([['POST', /^foo/], ['BAZETE', /^bar/]]);
358
363
  (() => { d.proxyRouteIsAvoided(); }).should.throw;
359
364
  avoidStub.restore();
360
365
  });
361
366
  it('should reject non-regex routes', function () {
362
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
367
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
363
368
  avoidStub.returns([['POST', /^foo/], ['GET', '/bar']]);
364
369
  (() => { d.proxyRouteIsAvoided(); }).should.throw;
365
370
  avoidStub.restore();
366
371
  });
367
372
  it('should return true for routes in the avoid list', function () {
368
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
373
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
369
374
  avoidStub.returns([['POST', /^\/foo/]]);
370
375
  d.proxyRouteIsAvoided(null, 'POST', '/foo/bar').should.be.true;
371
376
  avoidStub.restore();
372
377
  });
373
378
  it('should strip away any wd/hub prefix', function () {
374
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
379
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
375
380
  avoidStub.returns([['POST', /^\/foo/]]);
376
381
  d.proxyRouteIsAvoided(null, 'POST', '/foo/bar').should.be.true;
377
382
  avoidStub.restore();
378
383
  });
379
384
  it('should return false for routes not in the avoid list', function () {
380
- const avoidStub = sinon.stub(d, 'getProxyAvoidList');
385
+ const avoidStub = sandbox.stub(d, 'getProxyAvoidList');
381
386
  avoidStub.returns([['POST', /^\/foo/]]);
382
387
  d.proxyRouteIsAvoided(null, 'GET', '/foo/bar').should.be.false;
383
388
  d.proxyRouteIsAvoided(null, 'POST', '/boo').should.be.false;