@appium/base-driver 8.2.2 → 8.3.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 (61) hide show
  1. package/build/lib/basedriver/capabilities.js +3 -1
  2. package/build/lib/basedriver/commands/find.js +4 -11
  3. package/build/lib/basedriver/commands/log.js +3 -6
  4. package/build/lib/basedriver/commands/session.js +18 -27
  5. package/build/lib/basedriver/commands/settings.js +4 -8
  6. package/build/lib/basedriver/commands/timeout.js +10 -15
  7. package/build/lib/basedriver/device-settings.js +14 -2
  8. package/build/lib/basedriver/driver.js +25 -23
  9. package/build/lib/basedriver/helpers.js +140 -84
  10. package/build/lib/express/express-logging.js +2 -2
  11. package/build/lib/express/idempotency.js +2 -2
  12. package/build/lib/helpers/capabilities.js +39 -0
  13. package/build/lib/index.js +7 -7
  14. package/build/lib/jsonwp-proxy/protocol-converter.js +19 -16
  15. package/build/lib/jsonwp-proxy/proxy.js +20 -16
  16. package/build/lib/protocol/errors.js +4 -2
  17. package/build/lib/protocol/helpers.js +3 -20
  18. package/build/lib/protocol/protocol.js +44 -45
  19. package/build/lib/protocol/routes.js +67 -1
  20. package/build/test/basedriver/capabilities-specs.js +43 -1
  21. package/build/test/basedriver/capability-specs.js +126 -167
  22. package/build/test/basedriver/commands/log-specs.js +12 -5
  23. package/build/test/basedriver/driver-tests.js +11 -14
  24. package/build/test/basedriver/helpers-specs.js +5 -1
  25. package/build/test/basedriver/timeout-specs.js +7 -9
  26. package/build/test/express/server-e2e-specs.js +10 -5
  27. package/build/test/express/server-specs.js +22 -16
  28. package/build/test/express/static-specs.js +10 -5
  29. package/build/test/jsonwp-proxy/proxy-e2e-specs.js +1 -2
  30. package/build/test/jsonwp-proxy/proxy-specs.js +1 -6
  31. package/build/test/protocol/fake-driver.js +12 -15
  32. package/build/test/protocol/protocol-e2e-specs.js +49 -103
  33. package/build/test/protocol/routes-specs.js +2 -2
  34. package/lib/basedriver/capabilities.js +3 -0
  35. package/lib/basedriver/commands/find.js +3 -6
  36. package/lib/basedriver/commands/log.js +2 -4
  37. package/lib/basedriver/commands/session.js +21 -22
  38. package/lib/basedriver/commands/settings.js +3 -5
  39. package/lib/basedriver/commands/timeout.js +9 -10
  40. package/lib/basedriver/device-settings.js +10 -1
  41. package/lib/basedriver/driver.js +29 -16
  42. package/lib/basedriver/helpers.js +201 -83
  43. package/lib/express/express-logging.js +1 -1
  44. package/lib/express/idempotency.js +1 -1
  45. package/lib/helpers/capabilities.js +25 -0
  46. package/lib/index.js +6 -4
  47. package/lib/jsonwp-proxy/protocol-converter.js +15 -18
  48. package/lib/jsonwp-proxy/proxy.js +17 -15
  49. package/lib/protocol/errors.js +1 -1
  50. package/lib/protocol/helpers.js +5 -25
  51. package/lib/protocol/protocol.js +43 -54
  52. package/lib/protocol/routes.js +60 -1
  53. package/package.json +29 -22
  54. package/test/basedriver/capabilities-specs.js +34 -2
  55. package/test/basedriver/capability-specs.js +120 -146
  56. package/test/basedriver/commands/log-specs.js +12 -3
  57. package/test/basedriver/driver-tests.js +12 -7
  58. package/test/basedriver/helpers-specs.js +4 -0
  59. package/test/basedriver/timeout-specs.js +6 -11
  60. package/build/lib/protocol/sessions-cache.js +0 -88
  61. package/lib/protocol/sessions-cache.js +0 -74
@@ -9,12 +9,10 @@ import { routeToCommandName } from '../protocol';
9
9
  import { MAX_LOG_BODY_LENGTH, DEFAULT_BASE_PATH, PROTOCOLS } from '../constants';
10
10
  import ProtocolConverter from './protocol-converter';
11
11
  import { formatResponseValue, formatStatus } from '../protocol/helpers';
12
- import SESSIONS_CACHE from '../protocol/sessions-cache';
13
12
  import http from 'http';
14
13
  import https from 'https';
15
14
 
16
-
17
- const log = logger.getLogger('WD Proxy');
15
+ const DEFAULT_LOG = logger.getLogger('WD Proxy');
18
16
  const DEFAULT_REQUEST_TIMEOUT = 240000;
19
17
  const COMPACT_ERROR_PATTERNS = [
20
18
  /\bECONNREFUSED\b/,
@@ -44,7 +42,12 @@ class JWProxy {
44
42
  };
45
43
  this.httpAgent = new http.Agent(agentOpts);
46
44
  this.httpsAgent = new https.Agent(agentOpts);
47
- 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;
48
51
  }
49
52
 
50
53
  /**
@@ -167,7 +170,7 @@ class JWProxy {
167
170
  }
168
171
  }
169
172
 
170
- log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
173
+ this.log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
171
174
  (reqOpts.data ? `with body: ${truncateBody(reqOpts.data)}` : 'with no body'));
172
175
 
173
176
  const throwProxyError = (error) => {
@@ -188,7 +191,7 @@ class JWProxy {
188
191
  // If it cannot be coerced to an object then the response is wrong
189
192
  throwProxyError(data);
190
193
  }
191
- log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
194
+ this.log.debug(`Got response with status ${status}: ${truncateBody(data)}`);
192
195
  isResponseLogged = true;
193
196
  const isSessionCreationRequest = /\/session$/.test(url) && method === 'POST';
194
197
  if (isSessionCreationRequest) {
@@ -196,7 +199,7 @@ class JWProxy {
196
199
  this.sessionId = data.sessionId || (data.value || {}).sessionId;
197
200
  }
198
201
  this.downstreamProtocol = this.getProtocolFromResBody(data);
199
- log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
202
+ this.log.info(`Determined the downstream protocol as '${this.downstreamProtocol}'`);
200
203
  }
201
204
  if (_.has(data, 'status') && parseInt(data.status, 10) !== 0) {
202
205
  // Some servers, like chromedriver may return response code 200 for non-zero JSONWP statuses
@@ -212,16 +215,16 @@ class JWProxy {
212
215
  if (util.hasValue(e.response)) {
213
216
  if (!isResponseLogged) {
214
217
  const error = truncateBody(e.response.data);
215
- log.info(util.hasValue(e.response.status)
218
+ this.log.info(util.hasValue(e.response.status)
216
219
  ? `Got response with status ${e.response.status}: ${error}`
217
220
  : `Got response with unknown status: ${error}`);
218
221
  }
219
222
  } else {
220
223
  proxyErrorMsg = `Could not proxy command to the remote server. Original error: ${e.message}`;
221
224
  if (COMPACT_ERROR_PATTERNS.some((p) => p.test(e.message))) {
222
- log.info(e.message);
225
+ this.log.info(e.message);
223
226
  } else {
224
- log.info(e.stack);
227
+ this.log.info(e.stack);
225
228
  }
226
229
  }
227
230
  throw new errors.ProxyRequestError(proxyErrorMsg, e.response?.data, e.response?.status);
@@ -257,7 +260,7 @@ class JWProxy {
257
260
  if (!commandName) {
258
261
  return await this.proxy(url, method, body);
259
262
  }
260
- log.debug(`Matched '${url}' to command name '${commandName}'`);
263
+ this.log.debug(`Matched '${url}' to command name '${commandName}'`);
261
264
 
262
265
  return await this.protocolConverter.convertAndProxy(commandName, url, method, body);
263
266
  }
@@ -319,16 +322,15 @@ class JWProxy {
319
322
  const reqSessionId = this.getSessionIdFromUrl(req.originalUrl);
320
323
  if (_.has(resBodyObj, 'sessionId')) {
321
324
  if (reqSessionId) {
322
- log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${reqSessionId}`);
325
+ this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${reqSessionId}`);
323
326
  resBodyObj.sessionId = reqSessionId;
324
327
  } else if (this.sessionId) {
325
- log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${this.sessionId}`);
328
+ this.log.info(`Replacing sessionId ${resBodyObj.sessionId} with ${this.sessionId}`);
326
329
  resBodyObj.sessionId = this.sessionId;
327
330
  }
328
331
  }
329
332
  resBodyObj.value = formatResponseValue(resBodyObj.value);
330
- formatStatus(resBodyObj, res.statusCode, SESSIONS_CACHE.getProtocol(reqSessionId));
331
- res.status(response.statusCode).send(JSON.stringify(resBodyObj));
333
+ res.status(response.statusCode).send(JSON.stringify(formatStatus(resBodyObj)));
332
334
  }
333
335
  }
334
336
 
@@ -657,7 +657,7 @@ class ProxyRequestError extends ES6Error {
657
657
 
658
658
  getActualError () {
659
659
  // If it's MJSONWP error, returns actual error cause for request failure based on `jsonwp.status`
660
- if (util.hasValue(this.jsonwp) && util.hasValue(this.jsonwp.status) && util.hasValue(this.jsonwp.value)) {
660
+ if (util.hasValue(this.jsonwp?.status) && util.hasValue(this.jsonwp?.value)) {
661
661
  return errorFromMJSONWPStatusCode(this.jsonwp.status, this.jsonwp.value);
662
662
  } else if (util.hasValue(this.w3c) && _.isNumber(this.w3cStatus) && this.w3cStatus >= 300) {
663
663
  return errorFromW3CJsonCode(this.w3c.error, this.w3c.message || this.message, this.w3c.stacktrace);
@@ -1,9 +1,6 @@
1
1
  import _ from 'lodash';
2
2
  import { duplicateKeys } from '../basedriver/helpers';
3
- import { MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, PROTOCOLS } from '../constants';
4
-
5
- const JSONWP_SUCCESS_STATUS_CODE = 0;
6
- const JSONWP_UNKNOWN_ERROR_STATUS_CODE = 13;
3
+ import { MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY } from '../constants';
7
4
 
8
5
  /**
9
6
  * Preprocesses the resulting value for API responses,
@@ -26,33 +23,16 @@ function formatResponseValue (resValue) {
26
23
 
27
24
  /**
28
25
  * Properly formats the status for API responses,
29
- * so they are correct for both W3C and JSONWP protocols.
30
- * This method DOES mutate the `responseBody` argument if needed
26
+ * so they are correct for the W3C protocol.
31
27
  *
32
28
  * @param {Object} responseBody
33
- * @param {number} responseCode the HTTP response code
34
- * @param {?string} protocol The name of the protocol, either
35
- * `PROTOCOLS.W3C` or `PROTOCOLS.MJSONWP`
36
29
  * @returns {Object} The fixed response body
37
30
  */
38
- function formatStatus (responseBody, responseCode = 200, protocol = null) {
39
- if (!_.isPlainObject(responseBody)) {
40
- return responseBody;
41
- }
42
- const isError = _.has(responseBody.value, 'error') || responseCode >= 400;
43
- if ((protocol === PROTOCOLS.MJSONWP && !_.isInteger(responseBody.status))
44
- || (!protocol && !_.has(responseBody, 'status'))) {
45
- responseBody.status = isError
46
- ? JSONWP_UNKNOWN_ERROR_STATUS_CODE
47
- : JSONWP_SUCCESS_STATUS_CODE;
48
- } else if (protocol === PROTOCOLS.W3C && _.has(responseBody, 'status')) {
49
- delete responseBody.status;
50
- }
51
- return responseBody;
31
+ function formatStatus (responseBody) {
32
+ return _.isPlainObject(responseBody) ? _.omit(responseBody, ['status']) : responseBody;
52
33
  }
53
34
 
54
35
 
55
36
  export {
56
- MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, formatResponseValue,
57
- JSONWP_SUCCESS_STATUS_CODE, formatStatus,
37
+ MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY, formatResponseValue, formatStatus
58
38
  };
@@ -1,28 +1,25 @@
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
- errors, isErrorType, getResponseForW3CError, getResponseForJsonwpError,
5
+ errors, isErrorType, getResponseForW3CError,
6
6
  errorFromMJSONWPStatusCode, errorFromW3CJsonCode,
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';
18
16
  const DELETE_SESSION_COMMAND = 'deleteSession';
17
+ const GET_STATUS_COMMAND = 'getStatus';
19
18
 
20
19
  class Protocol {}
21
20
 
22
- function determineProtocol (desiredCapabilities, requiredCapabilities, capabilities) {
23
- return _.isPlainObject(capabilities) ?
24
- PROTOCOLS.W3C :
25
- PROTOCOLS.MJSONWP;
21
+ function determineProtocol (createSessionArgs) {
22
+ return _.some(createSessionArgs, isW3cCaps) ? PROTOCOLS.W3C : PROTOCOLS.MJSONWP;
26
23
  }
27
24
 
28
25
  function extractProtocol (driver, sessionId = null) {
@@ -37,13 +34,30 @@ function extractProtocol (driver, sessionId = null) {
37
34
  }
38
35
 
39
36
  // Extract the protocol for the current session if the given driver is the umbrella one
40
- return dstDriver ? dstDriver.protocol : SESSIONS_CACHE.getProtocol(sessionId);
37
+ return dstDriver?.protocol ?? PROTOCOLS.W3C;
41
38
  }
42
39
 
43
40
  function isSessionCommand (command) {
44
41
  return !_.includes(NO_SESSION_ID_COMMANDS, command);
45
42
  }
46
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
+
47
61
  function wrapParams (paramSets, jsonObj) {
48
62
  /* There are commands like performTouch which take a single parameter (primitive type or array).
49
63
  * Some drivers choose to pass this parameter as a value (eg. [action1, action2...]) while others to
@@ -191,11 +205,11 @@ function makeArgs (requestParams, jsonObj, payloadParams, protocol) {
191
205
 
192
206
  function routeConfiguringFunction (driver) {
193
207
  if (!driver.sessionExists) {
194
- throw new Error('Drivers used with MJSONWP must implement `sessionExists`');
208
+ throw new Error('Drivers must implement `sessionExists` property');
195
209
  }
196
210
 
197
211
  if (!(driver.executeCommand || driver.execute)) {
198
- throw new Error('Drivers used with MJSONWP must implement `executeCommand` or `execute`');
212
+ throw new Error('Drivers must implement `executeCommand` or `execute` method');
199
213
  }
200
214
 
201
215
  // return a function which will add all the routes to the driver. Here extraMethods might be
@@ -246,7 +260,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
246
260
  await doJwpProxy(driver, req, res);
247
261
  return;
248
262
  }
249
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol).debug(`Would have proxied ` +
263
+ getLogger(driver, req.params.sessionId).debug(`Would have proxied ` +
250
264
  `command directly, but a plugin exists which might require its value, so will let ` +
251
265
  `its value be collected internally and made part of plugin chain`);
252
266
  didPluginOverrideProxy = true;
@@ -271,7 +285,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
271
285
  if (spec.command === CREATE_SESSION_COMMAND) {
272
286
  // try to determine protocol by session creation args, so we can throw a
273
287
  // properly formatted error if arguments validation fails
274
- currentProtocol = determineProtocol(...makeArgs(req.params, jsonObj, spec.payloadParams || {}));
288
+ currentProtocol = determineProtocol(makeArgs(req.params, jsonObj, spec.payloadParams || {}));
275
289
  }
276
290
 
277
291
  // ensure that the json payload conforms to the spec
@@ -287,7 +301,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
287
301
  }
288
302
 
289
303
  // run the driver command wrapped inside the argument validators
290
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol).debug(`Calling ` +
304
+ getLogger(driver, req.params.sessionId).debug(`Calling ` +
291
305
  `${driver.constructor.name}.${spec.command}() with args: ` +
292
306
  _.truncate(JSON.stringify(args), {length: MAX_LOG_BODY_LENGTH}));
293
307
 
@@ -316,8 +330,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
316
330
  // unpack createSession response
317
331
  if (spec.command === CREATE_SESSION_COMMAND) {
318
332
  newSessionId = driverRes[0];
319
- SESSIONS_CACHE.putSession(newSessionId, currentProtocol);
320
- SESSIONS_CACHE.getLogger(newSessionId, currentProtocol)
333
+ getLogger(driver, newSessionId)
321
334
  .debug(`Cached the protocol value '${currentProtocol}' for the new session ${newSessionId}`);
322
335
  if (currentProtocol === PROTOCOLS.MJSONWP) {
323
336
  driverRes = driverRes[1];
@@ -332,9 +345,9 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
332
345
 
333
346
  // delete should not return anything even if successful
334
347
  if (spec.command === DELETE_SESSION_COMMAND) {
335
- SESSIONS_CACHE.getLogger(req.params.sessionId, currentProtocol)
348
+ getLogger(driver, req.params.sessionId)
336
349
  .debug(`Received response: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`);
337
- 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');
338
351
  driverRes = null;
339
352
  }
340
353
 
@@ -348,15 +361,8 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
348
361
  }
349
362
 
350
363
  httpResBody.value = driverRes;
351
- SESSIONS_CACHE.getLogger(req.params.sessionId || newSessionId, currentProtocol).debug(`Responding ` +
364
+ getLogger(driver, req.params.sessionId || newSessionId).debug(`Responding ` +
352
365
  `to client with driver.${spec.command}() result: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`);
353
-
354
- if (spec.command === DELETE_SESSION_COMMAND) {
355
- // We don't want to keep the logger instance in the cache
356
- // after the session is deleted, because it contains the logging history
357
- // and consumes the memory
358
- SESSIONS_CACHE.resetLogger(req.params.sessionId);
359
- }
360
366
  } catch (err) {
361
367
  // if anything goes wrong, figure out what our response should be
362
368
  // based on the type of error that we encountered
@@ -373,28 +379,11 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
373
379
  if (isErrorType(err, errors.ProxyRequestError)) {
374
380
  actualErr = err.getActualError();
375
381
  } else {
376
- SESSIONS_CACHE.getLogger(req.params.sessionId || newSessionId, currentProtocol)
382
+ getLogger(driver, req.params.sessionId || newSessionId)
377
383
  .debug(`Encountered internal error running command: ${errMsg}`);
378
384
  }
379
385
 
380
- if (currentProtocol === PROTOCOLS.W3C) {
381
- [httpStatus, httpResBody] = getResponseForW3CError(actualErr);
382
- } else if (currentProtocol === PROTOCOLS.MJSONWP) {
383
- [httpStatus, httpResBody] = getResponseForJsonwpError(actualErr);
384
- } else {
385
- // If it's unknown what the protocol is (like if it's `getStatus` prior to `createSession`), merge the responses
386
- // together to be protocol-agnostic
387
- let jsonwpRes = getResponseForJsonwpError(actualErr);
388
- let w3cRes = getResponseForW3CError(actualErr);
389
-
390
- httpResBody = {
391
- ...jsonwpRes[1],
392
- ...w3cRes[1],
393
- };
394
-
395
- // Use the JSONWP status code (which is usually 500)
396
- httpStatus = jsonwpRes[0];
397
- }
386
+ [httpStatus, httpResBody] = getResponseForW3CError(actualErr);
398
387
  }
399
388
 
400
389
  // decode the response, which is either a string or json
@@ -415,7 +404,7 @@ function buildHandler (app, method, path, spec, driver, isSessCmd) {
415
404
  delete httpResBody.sessionId;
416
405
  }
417
406
 
418
- httpResBody = formatStatus(httpResBody, httpStatus, currentProtocol);
407
+ httpResBody = formatStatus(httpResBody);
419
408
  res.status(httpStatus).json(httpResBody);
420
409
  }
421
410
  };
@@ -433,7 +422,7 @@ function driverShouldDoJwpProxy (driver, req, command) {
433
422
 
434
423
  // we should never proxy deleteSession because we need to give the containing
435
424
  // driver an opportunity to clean itself up
436
- if (command === 'deleteSession') {
425
+ if (command === DELETE_SESSION_COMMAND) {
437
426
  return false;
438
427
  }
439
428
 
@@ -447,16 +436,15 @@ function driverShouldDoJwpProxy (driver, req, command) {
447
436
  }
448
437
 
449
438
  async function doJwpProxy (driver, req, res) {
450
- SESSIONS_CACHE.getLogger(req.params.sessionId, extractProtocol(driver, req.params.sessionId))
439
+ getLogger(driver, req.params.sessionId)
451
440
  .info('Driver proxy active, passing request on via HTTP proxy');
452
441
 
453
442
  // check that the inner driver has a proxy function
454
443
  if (!driver.canProxy(req.params.sessionId)) {
455
- throw new Error('Trying to proxy to a JSONWP server but driver is unable to proxy');
444
+ throw new Error('Trying to proxy to a server but the driver is unable to proxy');
456
445
  }
457
446
  try {
458
- const proxiedRes = await driver.executeCommand('proxyReqRes', req, res, req.params.sessionId);
459
- if (proxiedRes && proxiedRes.error) throw proxiedRes.error; // eslint-disable-line curly
447
+ await driver.executeCommand('proxyReqRes', req, res, req.params.sessionId);
460
448
  } catch (err) {
461
449
  if (isErrorType(err, errors.ProxyRequestError)) {
462
450
  throw err;
@@ -469,5 +457,6 @@ async function doJwpProxy (driver, req, res) {
469
457
 
470
458
  export {
471
459
  Protocol, routeConfiguringFunction, isSessionCommand,
472
- driverShouldDoJwpProxy, determineProtocol, CREATE_SESSION_COMMAND, DELETE_SESSION_COMMAND,
460
+ driverShouldDoJwpProxy, determineProtocol, CREATE_SESSION_COMMAND,
461
+ DELETE_SESSION_COMMAND, GET_STATUS_COMMAND,
473
462
  };
@@ -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.0",
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.2",
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
  },
@@ -38,30 +36,39 @@
38
36
  "build",
39
37
  "!build/test/basedriver/fixtures"
40
38
  ],
39
+ "scripts": {
40
+ "dev": "gulp dev --no-notif",
41
+ "build": "gulp transpile",
42
+ "test": "gulp unit-test:run",
43
+ "test:e2e": "gulp e2e-test:run"
44
+ },
41
45
  "dependencies": {
42
- "@appium/support": "^2.55.2",
43
- "@babel/runtime": "7.16.3",
44
- "async-lock": "1.3.0",
46
+ "@appium/support": "^2.56.0",
47
+ "@babel/runtime": "7.17.8",
48
+ "@colors/colors": "1.5.0",
49
+ "async-lock": "1.3.1",
45
50
  "asyncbox": "2.9.2",
46
- "axios": "0.24.0",
51
+ "axios": "0.26.1",
47
52
  "bluebird": "3.7.2",
48
- "body-parser": "1.19.0",
49
- "colors": "1.4.0",
53
+ "body-parser": "1.19.2",
50
54
  "es6-error": "4.1.1",
51
- "express": "4.17.1",
52
- "http-status-codes": "2.1.4",
55
+ "express": "4.17.3",
56
+ "http-status-codes": "2.2.0",
53
57
  "lodash": "4.17.21",
54
- "lru-cache": "6.0.0",
58
+ "lru-cache": "7.7.1",
55
59
  "method-override": "3.0.0",
56
60
  "morgan": "1.10.0",
57
61
  "serve-favicon": "2.5.0",
58
62
  "source-map-support": "0.5.21",
59
63
  "validate.js": "0.13.1",
60
- "ws": "7.5.5"
64
+ "ws": "7.5.7"
65
+ },
66
+ "engines": {
67
+ "node": ">=12",
68
+ "npm": ">=6"
61
69
  },
62
70
  "publishConfig": {
63
71
  "access": "public"
64
72
  },
65
- "homepage": "https://appium.io",
66
- "gitHead": "280d409df6c02d36b4ffc4d02a95ab3f4649b08c"
73
+ "gitHead": "f5cce0f29d31699decea63ed94c4506f7af469df"
67
74
  }
@@ -1,7 +1,10 @@
1
- import { parseCaps, validateCaps, mergeCaps, processCapabilities, findNonPrefixedCaps,
2
- promoteAppiumOptions, APPIUM_OPTS_CAP, stripAppiumPrefixes } from '../../lib/basedriver/capabilities';
1
+ import {
2
+ parseCaps, validateCaps, mergeCaps, processCapabilities, findNonPrefixedCaps,
3
+ promoteAppiumOptions, APPIUM_OPTS_CAP, stripAppiumPrefixes
4
+ } from '../../lib/basedriver/capabilities';
3
5
  import _ from 'lodash';
4
6
  import { desiredCapabilityConstraints } from '../../lib/basedriver/desired-caps';
7
+ import { isW3cCaps } from '../../lib/helpers/capabilities';
5
8
 
6
9
 
7
10
  describe('caps', function () {
@@ -502,4 +505,33 @@ describe('caps', function () {
502
505
  });
503
506
  });
504
507
  });
508
+
509
+ describe('#isW3cCaps', function () {
510
+ it('should drop invalid W3C capabilities', function () {
511
+ for (const invalidCaps of [
512
+ null, undefined, [], {},
513
+ {firstMatch: null},
514
+ {firtMatch: [{}]},
515
+ {alwaysMatch: null},
516
+ {firstMatch: [{}], alwaysMatch: null},
517
+ {firstMatch: [], alwaysMatch: {}},
518
+ {firstMatch: []},
519
+ {firstMatch: {}},
520
+ {alwaysMatch: []},
521
+ ]) {
522
+ isW3cCaps(invalidCaps).should.be.false;
523
+ }
524
+ });
525
+
526
+ it('should accept valid W3C capabilities', function () {
527
+ for (const validCaps of [
528
+ {firstMatch: [{}]},
529
+ {firstMatch: [{}], alaysMatch: {}},
530
+ {firtMatch: [{}], alwaysMatch: {}},
531
+ {alwaysMatch: {}},
532
+ ]) {
533
+ isW3cCaps(validCaps).should.be.true;
534
+ }
535
+ });
536
+ });
505
537
  });