appium 2.0.0-beta.2 → 2.0.0-beta.20
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/README.md +9 -9
- package/build/lib/appium-config.schema.json +0 -0
- package/build/lib/appium.js +157 -53
- package/build/lib/cli/argparse-actions.js +104 -0
- package/build/lib/cli/args.js +115 -279
- package/build/lib/cli/driver-command.js +11 -1
- package/build/lib/cli/extension-command.js +60 -8
- package/build/lib/cli/extension.js +30 -7
- package/build/lib/cli/npm.js +17 -14
- package/build/lib/cli/parser.js +152 -89
- package/build/lib/cli/plugin-command.js +11 -1
- package/build/lib/cli/utils.js +29 -3
- package/build/lib/config-file.js +141 -0
- package/build/lib/config.js +76 -61
- package/build/lib/driver-config.js +42 -19
- package/build/lib/drivers.js +8 -4
- package/build/lib/ext-config-io.js +165 -0
- package/build/lib/extension-config.js +130 -61
- package/build/lib/grid-register.js +22 -24
- package/build/lib/logger.js +3 -3
- package/build/lib/logsink.js +11 -13
- package/build/lib/main.js +197 -77
- package/build/lib/plugin-config.js +20 -10
- package/build/lib/plugins.js +4 -2
- package/build/lib/schema/appium-config-schema.js +252 -0
- package/build/lib/schema/arg-spec.js +120 -0
- package/build/lib/schema/cli-args.js +173 -0
- package/build/lib/schema/cli-transformers.js +76 -0
- package/build/lib/schema/index.js +36 -0
- package/build/lib/schema/keywords.js +62 -0
- package/build/lib/schema/schema.js +357 -0
- package/build/lib/utils.js +44 -99
- package/lib/appium-config.schema.json +277 -0
- package/lib/appium.js +201 -65
- package/lib/cli/argparse-actions.js +77 -0
- package/lib/cli/args.js +174 -375
- package/lib/cli/driver-command.js +4 -0
- package/lib/cli/extension-command.js +70 -5
- package/lib/cli/extension.js +25 -5
- package/lib/cli/npm.js +18 -12
- package/lib/cli/parser.js +254 -79
- package/lib/cli/plugin-command.js +4 -0
- package/lib/cli/utils.js +21 -1
- package/lib/config-file.js +227 -0
- package/lib/config.js +109 -62
- package/lib/driver-config.js +66 -11
- package/lib/drivers.js +4 -1
- package/lib/ext-config-io.js +287 -0
- package/lib/extension-config.js +225 -67
- package/lib/grid-register.js +27 -24
- package/lib/logger.js +1 -1
- package/lib/logsink.js +10 -7
- package/lib/main.js +211 -77
- package/lib/plugin-config.js +34 -5
- package/lib/plugins.js +1 -0
- package/lib/schema/appium-config-schema.js +286 -0
- package/lib/schema/arg-spec.js +218 -0
- package/lib/schema/cli-args.js +273 -0
- package/lib/schema/cli-transformers.js +123 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +119 -0
- package/lib/schema/schema.js +577 -0
- package/lib/utils.js +42 -88
- package/package.json +55 -80
- package/postinstall.js +71 -0
- package/types/appium-config.d.ts +197 -0
- package/types/types.d.ts +201 -0
- package/CHANGELOG.md +0 -3515
- package/build/lib/cli/parser-helpers.js +0 -82
- package/lib/cli/parser-helpers.js +0 -79
package/lib/appium.js
CHANGED
|
@@ -2,11 +2,11 @@ import _ from 'lodash';
|
|
|
2
2
|
import log from './logger';
|
|
3
3
|
import { getBuildInfo, updateBuildInfo, APPIUM_VER } from './config';
|
|
4
4
|
import { findMatchingDriver } from './drivers';
|
|
5
|
-
import { BaseDriver, errors, isSessionCommand
|
|
6
|
-
|
|
5
|
+
import { BaseDriver, errors, isSessionCommand,
|
|
6
|
+
CREATE_SESSION_COMMAND } from '@appium/base-driver';
|
|
7
7
|
import AsyncLock from 'async-lock';
|
|
8
8
|
import { parseCapsForInnerDriver, pullSettings } from './utils';
|
|
9
|
-
import { util } from 'appium
|
|
9
|
+
import { util } from '@appium/support';
|
|
10
10
|
|
|
11
11
|
const desiredCapabilityConstraints = {
|
|
12
12
|
automationName: {
|
|
@@ -27,7 +27,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
27
27
|
// It is necessary to set `--tmp` here since it should be set to
|
|
28
28
|
// process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
|
|
29
29
|
// The process argument will be referenced by BaseDriver.
|
|
30
|
-
// Please call appium
|
|
30
|
+
// Please call @appium/support.tempDir module to apply this benefit.
|
|
31
31
|
if (args.tmpDir) {
|
|
32
32
|
process.env.APPIUM_TMP_DIR = args.tmpDir;
|
|
33
33
|
}
|
|
@@ -39,7 +39,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
39
39
|
// the main Appium Driver has no new command timeout
|
|
40
40
|
this.newCommandTimeoutMs = 0;
|
|
41
41
|
|
|
42
|
-
this.args =
|
|
42
|
+
this.args = {...args};
|
|
43
43
|
|
|
44
44
|
// Access to sessions list must be guarded with a Semaphore, because
|
|
45
45
|
// it might be changed by other async calls at any time
|
|
@@ -51,12 +51,17 @@ class AppiumDriver extends BaseDriver {
|
|
|
51
51
|
// It is not recommended to access this property directly from the outside
|
|
52
52
|
this.pendingDrivers = {};
|
|
53
53
|
|
|
54
|
-
this.
|
|
54
|
+
this.pluginClasses = []; // list of which plugins are active
|
|
55
|
+
this.sessionPlugins = {}; // map of sessions to actual plugin instances per session
|
|
56
|
+
this.sessionlessPlugins = []; // some commands are sessionless, so we need a set of plugins for them
|
|
55
57
|
|
|
56
58
|
// allow this to happen in the background, so no `await`
|
|
57
59
|
updateBuildInfo();
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/** @type {DriverConfig|undefined} */
|
|
63
|
+
driverConfig;
|
|
64
|
+
|
|
60
65
|
/**
|
|
61
66
|
* Cancel commands queueing for the umbrella Appium driver
|
|
62
67
|
*/
|
|
@@ -85,11 +90,20 @@ class AppiumDriver extends BaseDriver {
|
|
|
85
90
|
.map(([id, driver]) => ({id, capabilities: driver.caps}));
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
printNewSessionAnnouncement (driverName, driverVersion) {
|
|
89
|
-
|
|
93
|
+
printNewSessionAnnouncement (driverName, driverVersion, driverBaseVersion) {
|
|
94
|
+
log.info(driverVersion
|
|
90
95
|
? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
|
|
91
|
-
: `Appium v${APPIUM_VER} creating new ${driverName} session
|
|
92
|
-
|
|
96
|
+
: `Appium v${APPIUM_VER} creating new ${driverName} session`
|
|
97
|
+
);
|
|
98
|
+
log.info(`Checking BaseDriver versions for Appium and ${driverName}`);
|
|
99
|
+
log.info(AppiumDriver.baseVersion
|
|
100
|
+
? `Appium's BaseDriver version is ${AppiumDriver.baseVersion}`
|
|
101
|
+
: `Could not determine Appium's BaseDriver version`
|
|
102
|
+
);
|
|
103
|
+
log.info(driverBaseVersion
|
|
104
|
+
? `${driverName}'s BaseDriver version is ${driverBaseVersion}`
|
|
105
|
+
: `Could not determine ${driverName}'s BaseDriver version`
|
|
106
|
+
);
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
/**
|
|
@@ -100,6 +114,21 @@ class AppiumDriver extends BaseDriver {
|
|
|
100
114
|
return findMatchingDriver(...args);
|
|
101
115
|
}
|
|
102
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Validate and assign CLI args for a driver or plugin
|
|
119
|
+
*
|
|
120
|
+
* If the extension has provided a schema, validation has already happened.
|
|
121
|
+
* @param {import('./ext-config-io').ExtensionType} extType 'driver' or 'plugin'
|
|
122
|
+
* @param {string} extName the name of the extension
|
|
123
|
+
* @param {Object} extInstance the driver or plugin instance
|
|
124
|
+
*/
|
|
125
|
+
assignCliArgsToExtension (extType, extName, extInstance) {
|
|
126
|
+
const cliArgs = this.args[extType]?.[extName];
|
|
127
|
+
if (!_.isEmpty(cliArgs)) {
|
|
128
|
+
extInstance.cliArgs = cliArgs;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
103
132
|
/**
|
|
104
133
|
* Create a new session
|
|
105
134
|
* @param {Object} jsonwpCaps JSONWP formatted desired capabilities
|
|
@@ -144,16 +173,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
144
173
|
|
|
145
174
|
const {
|
|
146
175
|
driver: InnerDriver,
|
|
147
|
-
version: driverVersion
|
|
176
|
+
version: driverVersion,
|
|
177
|
+
driverName
|
|
148
178
|
} = this._findMatchingDriver(this.driverConfig, desiredCaps);
|
|
149
|
-
this.printNewSessionAnnouncement(InnerDriver.name, driverVersion);
|
|
179
|
+
this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion);
|
|
150
180
|
|
|
151
181
|
if (this.args.sessionOverride) {
|
|
152
182
|
await this.deleteAllSessions();
|
|
153
183
|
}
|
|
154
184
|
|
|
155
185
|
let runningDriversData, otherPendingDriversData;
|
|
156
|
-
|
|
186
|
+
|
|
187
|
+
const driverInstance = new InnerDriver(this.args, true);
|
|
157
188
|
|
|
158
189
|
// We want to assign security values directly on the driver. The driver
|
|
159
190
|
// should not read security values from `this.opts` because those values
|
|
@@ -163,23 +194,28 @@ class AppiumDriver extends BaseDriver {
|
|
|
163
194
|
log.info(`Applying relaxed security to '${InnerDriver.name}' as per ` +
|
|
164
195
|
`server command line argument. All insecure features will be ` +
|
|
165
196
|
`enabled unless explicitly disabled by --deny-insecure`);
|
|
166
|
-
|
|
197
|
+
driverInstance.relaxedSecurityEnabled = true;
|
|
167
198
|
}
|
|
168
199
|
|
|
169
200
|
if (!_.isEmpty(this.args.denyInsecure)) {
|
|
170
201
|
log.info('Explicitly preventing use of insecure features:');
|
|
171
202
|
this.args.denyInsecure.map((a) => log.info(` ${a}`));
|
|
172
|
-
|
|
203
|
+
driverInstance.denyInsecure = this.args.denyInsecure;
|
|
173
204
|
}
|
|
174
205
|
|
|
175
206
|
if (!_.isEmpty(this.args.allowInsecure)) {
|
|
176
207
|
log.info('Explicitly enabling use of insecure features:');
|
|
177
208
|
this.args.allowInsecure.map((a) => log.info(` ${a}`));
|
|
178
|
-
|
|
209
|
+
driverInstance.allowInsecure = this.args.allowInsecure;
|
|
179
210
|
}
|
|
180
211
|
|
|
212
|
+
// Likewise, any driver-specific CLI args that were passed in should be assigned directly to
|
|
213
|
+
// the driver so that they cannot be mimicked by a malicious user sending in capabilities
|
|
214
|
+
this.assignCliArgsToExtension('driver', driverName, driverInstance);
|
|
215
|
+
|
|
216
|
+
|
|
181
217
|
// This assignment is required for correct web sockets functionality inside the driver
|
|
182
|
-
|
|
218
|
+
driverInstance.server = this.server;
|
|
183
219
|
try {
|
|
184
220
|
runningDriversData = await this.curSessionDataForDriver(InnerDriver);
|
|
185
221
|
} catch (e) {
|
|
@@ -188,43 +224,43 @@ class AppiumDriver extends BaseDriver {
|
|
|
188
224
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
189
225
|
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
|
|
190
226
|
otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
|
|
191
|
-
this.pendingDrivers[InnerDriver.name].push(
|
|
227
|
+
this.pendingDrivers[InnerDriver.name].push(driverInstance);
|
|
192
228
|
});
|
|
193
229
|
|
|
194
230
|
try {
|
|
195
|
-
[innerSessionId, dCaps] = await
|
|
231
|
+
[innerSessionId, dCaps] = await driverInstance.createSession(
|
|
196
232
|
processedJsonwpCapabilities,
|
|
197
233
|
reqCaps,
|
|
198
234
|
processedW3CCapabilities,
|
|
199
235
|
[...runningDriversData, ...otherPendingDriversData]
|
|
200
236
|
);
|
|
201
|
-
protocol =
|
|
237
|
+
protocol = driverInstance.protocol;
|
|
202
238
|
await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
|
203
|
-
this.sessions[innerSessionId] =
|
|
239
|
+
this.sessions[innerSessionId] = driverInstance;
|
|
204
240
|
});
|
|
205
241
|
} finally {
|
|
206
242
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
207
|
-
_.pull(this.pendingDrivers[InnerDriver.name],
|
|
243
|
+
_.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
|
|
208
244
|
});
|
|
209
245
|
}
|
|
210
246
|
|
|
211
|
-
this.attachUnexpectedShutdownHandler(
|
|
247
|
+
this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
|
|
212
248
|
|
|
213
249
|
log.info(`New ${InnerDriver.name} session created successfully, session ` +
|
|
214
250
|
`${innerSessionId} added to master session list`);
|
|
215
251
|
|
|
216
252
|
// set the New Command Timeout for the inner driver
|
|
217
|
-
|
|
253
|
+
driverInstance.startNewCommandTimeout();
|
|
218
254
|
|
|
219
255
|
// apply initial values to Appium settings (if provided)
|
|
220
|
-
if (
|
|
256
|
+
if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
|
|
221
257
|
log.info(`Applying the initial values to Appium settings parsed from W3C caps: ` +
|
|
222
258
|
JSON.stringify(w3cSettings));
|
|
223
|
-
await
|
|
224
|
-
} else if (
|
|
259
|
+
await driverInstance.updateSettings(w3cSettings);
|
|
260
|
+
} else if (driverInstance.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
|
|
225
261
|
log.info(`Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
|
|
226
262
|
JSON.stringify(jwpSettings));
|
|
227
|
-
await
|
|
263
|
+
await driverInstance.updateSettings(jwpSettings);
|
|
228
264
|
}
|
|
229
265
|
} catch (error) {
|
|
230
266
|
return {
|
|
@@ -240,33 +276,31 @@ class AppiumDriver extends BaseDriver {
|
|
|
240
276
|
}
|
|
241
277
|
|
|
242
278
|
attachUnexpectedShutdownHandler (driver, innerSessionId) {
|
|
243
|
-
const
|
|
244
|
-
log.warn(`
|
|
279
|
+
const onShutdown = (cause = new Error('Unknown error')) => {
|
|
280
|
+
log.warn(`Ending session, cause was '${cause.message}'`);
|
|
281
|
+
|
|
282
|
+
if (this.sessionPlugins[innerSessionId]) {
|
|
283
|
+
for (const plugin of this.sessionPlugins[innerSessionId]) {
|
|
284
|
+
if (_.isFunction(plugin.onUnexpectedShutdown)) {
|
|
285
|
+
log.debug(`Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`);
|
|
286
|
+
try {
|
|
287
|
+
plugin.onUnexpectedShutdown(driver, cause);
|
|
288
|
+
} catch (e) {
|
|
289
|
+
log.warn(`Got an error when running plugin ${plugin.name} shutdown handler: ${e}`);
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
245
297
|
log.info(`Removing session '${innerSessionId}' from our master session list`);
|
|
246
298
|
delete this.sessions[innerSessionId];
|
|
299
|
+
delete this.sessionPlugins[innerSessionId];
|
|
247
300
|
};
|
|
248
301
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// TODO: Remove this block after all the drivers use base driver above v 5.0.0
|
|
252
|
-
// Remove the session on unexpected shutdown, so that we are in a position
|
|
253
|
-
// to open another session later on.
|
|
254
|
-
driver.onUnexpectedShutdown
|
|
255
|
-
// eslint-disable-next-line promise/prefer-await-to-then
|
|
256
|
-
.then(() => {
|
|
257
|
-
// if we get here, we've had an unexpected shutdown, so error
|
|
258
|
-
throw new Error('Unexpected shutdown');
|
|
259
|
-
})
|
|
260
|
-
.catch((e) => {
|
|
261
|
-
// if we cancelled the unexpected shutdown promise, that means we
|
|
262
|
-
// no longer care about it, and can safely ignore it
|
|
263
|
-
if (!(e instanceof B.CancellationError)) {
|
|
264
|
-
removeSessionFromMasterList(e);
|
|
265
|
-
}
|
|
266
|
-
}); // this is a cancellable promise
|
|
267
|
-
} else if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
268
|
-
// since base driver v 5.0.0
|
|
269
|
-
driver.onUnexpectedShutdown(removeSessionFromMasterList);
|
|
302
|
+
if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
303
|
+
driver.onUnexpectedShutdown(onShutdown);
|
|
270
304
|
} else {
|
|
271
305
|
log.warn(`Failed to attach the unexpected shutdown listener. ` +
|
|
272
306
|
`Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`);
|
|
@@ -308,6 +342,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
308
342
|
// make the session unavailable, because who knows what state it might
|
|
309
343
|
// be in otherwise
|
|
310
344
|
delete this.sessions[sessionId];
|
|
345
|
+
delete this.sessionPlugins[sessionId];
|
|
311
346
|
});
|
|
312
347
|
return {
|
|
313
348
|
protocol,
|
|
@@ -346,11 +381,50 @@ class AppiumDriver extends BaseDriver {
|
|
|
346
381
|
}
|
|
347
382
|
}
|
|
348
383
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Get the appropriate plugins for a session (or sessionless plugins)
|
|
386
|
+
*
|
|
387
|
+
* @param {?string} sessionId - the sessionId (or null) to use to find plugins
|
|
388
|
+
* @returns {Array} - array of plugin instances
|
|
389
|
+
*/
|
|
390
|
+
pluginsForSession (sessionId = null) {
|
|
391
|
+
if (sessionId) {
|
|
392
|
+
if (!this.sessionPlugins[sessionId]) {
|
|
393
|
+
this.sessionPlugins[sessionId] = this.createPluginInstances();
|
|
394
|
+
}
|
|
395
|
+
return this.sessionPlugins[sessionId];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
399
|
+
this.sessionlessPlugins = this.createPluginInstances();
|
|
400
|
+
}
|
|
401
|
+
return this.sessionlessPlugins;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* To get plugins for a command, we either get the plugin instances associated with the
|
|
406
|
+
* particular command's session, or in the case of sessionless plugins, pull from the set of
|
|
407
|
+
* plugin instances reserved for sessionless commands (and we lazily create plugin instances on
|
|
408
|
+
* first use)
|
|
409
|
+
*
|
|
410
|
+
* @param {string} cmd - the name of the command to find a plugin to handle
|
|
411
|
+
* @param {?string} sessionId - the particular session for which to find a plugin, or null if
|
|
412
|
+
* sessionless
|
|
413
|
+
*/
|
|
414
|
+
pluginsToHandleCmd (cmd, sessionId = null) {
|
|
415
|
+
// to handle a given command, a plugin should either implement that command as a plugin
|
|
416
|
+
// instance method or it should implement a generic 'handle' method
|
|
417
|
+
return this.pluginsForSession(sessionId)
|
|
418
|
+
.filter((p) => _.isFunction(p[cmd]) || _.isFunction(p.handle));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
createPluginInstances () {
|
|
422
|
+
return this.pluginClasses.map((PluginClass) => {
|
|
423
|
+
const name = PluginClass.pluginName;
|
|
424
|
+
const plugin = new PluginClass(name);
|
|
425
|
+
this.assignCliArgsToExtension('plugin', name, plugin);
|
|
426
|
+
return plugin;
|
|
427
|
+
});
|
|
354
428
|
}
|
|
355
429
|
|
|
356
430
|
async executeCommand (cmd, ...args) {
|
|
@@ -367,14 +441,21 @@ class AppiumDriver extends BaseDriver {
|
|
|
367
441
|
const isUmbrellaCmd = !isGetStatus && isAppiumDriverCommand(cmd);
|
|
368
442
|
const isSessionCmd = !isGetStatus && !isUmbrellaCmd;
|
|
369
443
|
|
|
370
|
-
//
|
|
371
|
-
|
|
444
|
+
// if a plugin override proxying for this command and that is why we are here instead of just
|
|
445
|
+
// letting the protocol proxy the command entirely, determine that, get the request object for
|
|
446
|
+
// use later on, then clean up the args
|
|
447
|
+
const reqForProxy = _.last(args)?.reqForProxy;
|
|
448
|
+
if (reqForProxy) {
|
|
449
|
+
args.pop();
|
|
450
|
+
}
|
|
451
|
+
|
|
372
452
|
|
|
373
453
|
// first do some error checking. If we're requesting a session command execution, then make
|
|
374
454
|
// sure that session actually exists on the session driver, and set the session driver itself
|
|
375
455
|
let sessionId = null;
|
|
376
456
|
let dstSession = null;
|
|
377
457
|
let protocol = null;
|
|
458
|
+
let driver = this;
|
|
378
459
|
if (isSessionCmd) {
|
|
379
460
|
sessionId = _.last(args);
|
|
380
461
|
dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
|
|
@@ -383,8 +464,12 @@ class AppiumDriver extends BaseDriver {
|
|
|
383
464
|
}
|
|
384
465
|
// now save the response protocol given that the session driver's protocol might differ
|
|
385
466
|
protocol = dstSession.protocol;
|
|
467
|
+
driver = dstSession;
|
|
386
468
|
}
|
|
387
469
|
|
|
470
|
+
// get any plugins which are registered as handling this command
|
|
471
|
+
const plugins = this.pluginsToHandleCmd(cmd, sessionId);
|
|
472
|
+
|
|
388
473
|
// now we define a 'cmdHandledBy' object which will keep track of which plugins have handled this
|
|
389
474
|
// command. we care about this because (a) multiple plugins can handle the same command, and
|
|
390
475
|
// (b) there's no guarantee that a plugin will actually call the next() method which runs the
|
|
@@ -406,6 +491,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
406
491
|
// if we make it here, we know that the default behavior is handled
|
|
407
492
|
cmdHandledBy.default = true;
|
|
408
493
|
|
|
494
|
+
if (reqForProxy) {
|
|
495
|
+
// we would have proxied this command had a plugin not handled it, so the default behavior
|
|
496
|
+
// is to do the proxy and retrieve the result internally so it can be passed to the plugin
|
|
497
|
+
// in case it calls 'await next()'. This requires that the driver have defined
|
|
498
|
+
// 'proxyCommand' and not just 'proxyReqRes'.
|
|
499
|
+
if (!dstSession.proxyCommand) {
|
|
500
|
+
throw new NoDriverProxyCommandError();
|
|
501
|
+
}
|
|
502
|
+
return await dstSession.proxyCommand(reqForProxy.originalUrl, reqForProxy.method,
|
|
503
|
+
reqForProxy.body);
|
|
504
|
+
}
|
|
505
|
+
|
|
409
506
|
if (isGetStatus) {
|
|
410
507
|
return await this.getStatus();
|
|
411
508
|
}
|
|
@@ -421,17 +518,30 @@ class AppiumDriver extends BaseDriver {
|
|
|
421
518
|
};
|
|
422
519
|
|
|
423
520
|
// now take our default behavior, wrap it with any number of plugin behaviors, and run it
|
|
424
|
-
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
521
|
+
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
522
|
+
driver, cmd, args, plugins, cmdHandledBy, next: defaultBehavior
|
|
523
|
+
});
|
|
425
524
|
const res = await this.executeWrappedCommand({wrappedCmd, protocol});
|
|
426
525
|
|
|
427
526
|
// if we had plugins, make sure to log out the helpful report about which plugins ended up
|
|
428
527
|
// handling the command and which didn't
|
|
429
|
-
|
|
528
|
+
this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
|
|
529
|
+
|
|
530
|
+
// And finally, if the command was createSession, we want to migrate any plugins which were
|
|
531
|
+
// previously sessionless to use the new sessionId, so that plugins can share state between
|
|
532
|
+
// their createSession method and other instance methods
|
|
533
|
+
if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
|
|
534
|
+
const sessionId = _.first(res.value);
|
|
535
|
+
log.info(`Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
|
|
536
|
+
`to session ID ${sessionId}`);
|
|
537
|
+
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
|
|
538
|
+
this.sessionlessPlugins = [];
|
|
539
|
+
}
|
|
430
540
|
|
|
431
541
|
return res;
|
|
432
542
|
}
|
|
433
543
|
|
|
434
|
-
wrapCommandWithPlugins ({cmd, args, next, cmdHandledBy, plugins}) {
|
|
544
|
+
wrapCommandWithPlugins ({driver, cmd, args, next, cmdHandledBy, plugins}) {
|
|
435
545
|
plugins.length && log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
|
|
436
546
|
|
|
437
547
|
// now we can go through each plugin and wrap `next` around its own handler, passing the *old*
|
|
@@ -444,14 +554,23 @@ class AppiumDriver extends BaseDriver {
|
|
|
444
554
|
next = ((_next) => async () => {
|
|
445
555
|
log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
|
|
446
556
|
cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
|
|
447
|
-
|
|
557
|
+
// first attempt to handle the command via a command-specific handler on the plugin
|
|
558
|
+
if (plugin[cmd]) {
|
|
559
|
+
return await plugin[cmd](_next, driver, ...args);
|
|
560
|
+
}
|
|
561
|
+
// otherwise, call the generic 'handle' method
|
|
562
|
+
return await plugin.handle(_next, driver, cmd, ...args);
|
|
448
563
|
})(next);
|
|
449
564
|
}
|
|
450
565
|
|
|
451
566
|
return next;
|
|
452
567
|
}
|
|
453
568
|
|
|
454
|
-
logPluginHandlerReport ({cmd, cmdHandledBy}) {
|
|
569
|
+
logPluginHandlerReport (plugins, {cmd, cmdHandledBy}) {
|
|
570
|
+
if (!plugins.length) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
|
|
455
574
|
// at the end of the day, we have an object representing which plugins ended up getting
|
|
456
575
|
// their code run as part of handling this command. Because plugins can choose *not* to
|
|
457
576
|
// pass control to other plugins or to the default driver behavior, this is information
|
|
@@ -461,7 +580,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
461
580
|
const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]);
|
|
462
581
|
const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]);
|
|
463
582
|
if (didntHandle.length > 0) {
|
|
464
|
-
log.info(`Command '${cmd}' was not handled by the following
|
|
583
|
+
log.info(`Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` +
|
|
465
584
|
`though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
|
|
466
585
|
`command *was* handled by these: ${JSON.stringify(didHandle)}.`);
|
|
467
586
|
}
|
|
@@ -491,7 +610,6 @@ class AppiumDriver extends BaseDriver {
|
|
|
491
610
|
return res;
|
|
492
611
|
}
|
|
493
612
|
|
|
494
|
-
|
|
495
613
|
proxyActive (sessionId) {
|
|
496
614
|
const dstSession = this.sessions[sessionId];
|
|
497
615
|
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
|
@@ -514,4 +632,22 @@ function isAppiumDriverCommand (cmd) {
|
|
|
514
632
|
return !isSessionCommand(cmd) || cmd === 'deleteSession';
|
|
515
633
|
}
|
|
516
634
|
|
|
635
|
+
/**
|
|
636
|
+
* Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
|
|
637
|
+
* method did not exist
|
|
638
|
+
*/
|
|
639
|
+
export class NoDriverProxyCommandError extends Error {
|
|
640
|
+
/**
|
|
641
|
+
* @type {Readonly<string>}
|
|
642
|
+
*/
|
|
643
|
+
code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
644
|
+
|
|
645
|
+
constructor () {
|
|
646
|
+
super(`The default behavior for this command was to proxy, but the driver ` +
|
|
647
|
+
`did not have the 'proxyCommand' method defined. To fully support ` +
|
|
648
|
+
`plugins, drivers should have 'proxyCommand' set to a jwpProxy object's ` +
|
|
649
|
+
`'command()' method, in addition to the normal 'proxyReqRes'`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
517
653
|
export { AppiumDriver };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Action } from 'argparse';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
const DEFAULT_CAPS_ARG = '--default-capabilities';
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StoreDeprecatedAction extends Action {
|
|
8
|
+
constructor (options = {}) {
|
|
9
|
+
const opts = Object.assign({}, options);
|
|
10
|
+
let helpPrefix = '[DEPRECATED]';
|
|
11
|
+
if (opts.deprecated_for) {
|
|
12
|
+
helpPrefix = `[DEPRECATED, use ${opts.deprecated_for} instead]`;
|
|
13
|
+
delete opts.deprecated_for;
|
|
14
|
+
}
|
|
15
|
+
if (opts.help) {
|
|
16
|
+
opts.help = `${helpPrefix} - ${opts.help}`;
|
|
17
|
+
} else {
|
|
18
|
+
opts.help = helpPrefix;
|
|
19
|
+
}
|
|
20
|
+
super(opts);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
call (parser, namespace, values) {
|
|
24
|
+
namespace[this.dest] = values;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class StoreDeprecatedTrueAction extends StoreDeprecatedAction {
|
|
30
|
+
constructor (options = {}) {
|
|
31
|
+
super(Object.assign({}, options, {const: true, nargs: 0}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
call (parser, namespace) {
|
|
35
|
+
namespace[this.dest] = this.const;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StoreDeprecatedDefaultCapabilityAction extends StoreDeprecatedAction {
|
|
41
|
+
constructor (options = {}) {
|
|
42
|
+
super(Object.assign({}, options, {deprecated_for: DEFAULT_CAPS_ARG}));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_writeDefaultCap (namespace, value) {
|
|
46
|
+
namespace[this.dest] = value;
|
|
47
|
+
if (value === this.default) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!namespace.defaultCapabilities) {
|
|
52
|
+
namespace.defaultCapabilities = {};
|
|
53
|
+
}
|
|
54
|
+
namespace.defaultCapabilities[this.dest] = value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
call (parser, namespace, values) {
|
|
58
|
+
this._writeDefaultCap(namespace, values);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class StoreDeprecatedDefaultCapabilityTrueAction extends StoreDeprecatedDefaultCapabilityAction {
|
|
64
|
+
constructor (options = {}) {
|
|
65
|
+
super(Object.assign({}, options, {const: true, nargs: 0}));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
call (parser, namespace) {
|
|
69
|
+
this._writeDefaultCap(namespace, this.const);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
StoreDeprecatedAction, StoreDeprecatedTrueAction,
|
|
75
|
+
StoreDeprecatedDefaultCapabilityAction, StoreDeprecatedDefaultCapabilityTrueAction,
|
|
76
|
+
DEFAULT_CAPS_ARG,
|
|
77
|
+
};
|