@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.
- package/build/lib/basedriver/capabilities.js +3 -1
- package/build/lib/basedriver/commands/find.js +4 -11
- package/build/lib/basedriver/commands/log.js +3 -6
- package/build/lib/basedriver/commands/session.js +18 -27
- package/build/lib/basedriver/commands/settings.js +4 -8
- package/build/lib/basedriver/commands/timeout.js +10 -15
- package/build/lib/basedriver/device-settings.js +14 -2
- package/build/lib/basedriver/driver.js +25 -23
- package/build/lib/basedriver/helpers.js +140 -84
- package/build/lib/express/express-logging.js +2 -2
- package/build/lib/express/idempotency.js +2 -2
- package/build/lib/helpers/capabilities.js +39 -0
- package/build/lib/index.js +7 -7
- package/build/lib/jsonwp-proxy/protocol-converter.js +19 -16
- package/build/lib/jsonwp-proxy/proxy.js +20 -16
- package/build/lib/protocol/errors.js +4 -2
- package/build/lib/protocol/helpers.js +3 -20
- package/build/lib/protocol/protocol.js +44 -45
- package/build/lib/protocol/routes.js +67 -1
- package/build/test/basedriver/capabilities-specs.js +43 -1
- package/build/test/basedriver/capability-specs.js +126 -167
- package/build/test/basedriver/commands/log-specs.js +12 -5
- package/build/test/basedriver/driver-tests.js +11 -14
- package/build/test/basedriver/helpers-specs.js +5 -1
- package/build/test/basedriver/timeout-specs.js +7 -9
- package/build/test/express/server-e2e-specs.js +10 -5
- package/build/test/express/server-specs.js +22 -16
- package/build/test/express/static-specs.js +10 -5
- package/build/test/jsonwp-proxy/proxy-e2e-specs.js +1 -2
- package/build/test/jsonwp-proxy/proxy-specs.js +1 -6
- package/build/test/protocol/fake-driver.js +12 -15
- package/build/test/protocol/protocol-e2e-specs.js +49 -103
- package/build/test/protocol/routes-specs.js +2 -2
- package/lib/basedriver/capabilities.js +3 -0
- package/lib/basedriver/commands/find.js +3 -6
- package/lib/basedriver/commands/log.js +2 -4
- package/lib/basedriver/commands/session.js +21 -22
- package/lib/basedriver/commands/settings.js +3 -5
- package/lib/basedriver/commands/timeout.js +9 -10
- package/lib/basedriver/device-settings.js +10 -1
- package/lib/basedriver/driver.js +29 -16
- package/lib/basedriver/helpers.js +201 -83
- package/lib/express/express-logging.js +1 -1
- package/lib/express/idempotency.js +1 -1
- package/lib/helpers/capabilities.js +25 -0
- package/lib/index.js +6 -4
- package/lib/jsonwp-proxy/protocol-converter.js +15 -18
- package/lib/jsonwp-proxy/proxy.js +17 -15
- package/lib/protocol/errors.js +1 -1
- package/lib/protocol/helpers.js +5 -25
- package/lib/protocol/protocol.js +43 -54
- package/lib/protocol/routes.js +60 -1
- package/package.json +29 -22
- package/test/basedriver/capabilities-specs.js +34 -2
- package/test/basedriver/capability-specs.js +120 -146
- package/test/basedriver/commands/log-specs.js +12 -3
- package/test/basedriver/driver-tests.js +12 -7
- package/test/basedriver/helpers-specs.js +4 -0
- package/test/basedriver/timeout-specs.js +6 -11
- package/build/lib/protocol/sessions-cache.js +0 -88
- 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
|
-
|
|
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
|
|
package/lib/protocol/errors.js
CHANGED
|
@@ -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
|
|
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);
|
package/lib/protocol/helpers.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
2
|
import { duplicateKeys } from '../basedriver/helpers';
|
|
3
|
-
import { MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY
|
|
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
|
|
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
|
|
39
|
-
|
|
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
|
};
|
package/lib/protocol/protocol.js
CHANGED
|
@@ -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,
|
|
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
|
|
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 (
|
|
23
|
-
return _.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
348
|
+
getLogger(driver, req.params.sessionId)
|
|
336
349
|
.debug(`Received response: ${_.truncate(JSON.stringify(driverRes), {length: MAX_LOG_BODY_LENGTH})}`);
|
|
337
|
-
|
|
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
|
-
|
|
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
|
-
|
|
382
|
+
getLogger(driver, req.params.sessionId || newSessionId)
|
|
377
383
|
.debug(`Encountered internal error running command: ${errMsg}`);
|
|
378
384
|
}
|
|
379
385
|
|
|
380
|
-
|
|
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
|
|
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 ===
|
|
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
|
-
|
|
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
|
|
444
|
+
throw new Error('Trying to proxy to a server but the driver is unable to proxy');
|
|
456
445
|
}
|
|
457
446
|
try {
|
|
458
|
-
|
|
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,
|
|
460
|
+
driverShouldDoJwpProxy, determineProtocol, CREATE_SESSION_COMMAND,
|
|
461
|
+
DELETE_SESSION_COMMAND, GET_STATUS_COMMAND,
|
|
473
462
|
};
|
package/lib/protocol/routes.js
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
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.
|
|
43
|
-
"@babel/runtime": "7.
|
|
44
|
-
"
|
|
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.
|
|
51
|
+
"axios": "0.26.1",
|
|
47
52
|
"bluebird": "3.7.2",
|
|
48
|
-
"body-parser": "1.19.
|
|
49
|
-
"colors": "1.4.0",
|
|
53
|
+
"body-parser": "1.19.2",
|
|
50
54
|
"es6-error": "4.1.1",
|
|
51
|
-
"express": "4.17.
|
|
52
|
-
"http-status-codes": "2.
|
|
55
|
+
"express": "4.17.3",
|
|
56
|
+
"http-status-codes": "2.2.0",
|
|
53
57
|
"lodash": "4.17.21",
|
|
54
|
-
"lru-cache": "
|
|
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.
|
|
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
|
-
"
|
|
66
|
-
"gitHead": "280d409df6c02d36b4ffc4d02a95ab3f4649b08c"
|
|
73
|
+
"gitHead": "f5cce0f29d31699decea63ed94c4506f7af469df"
|
|
67
74
|
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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
|
});
|