appium 2.0.0-beta.2 → 2.0.0-beta.23
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/check-npm-pack-files.js +23 -0
- package/build/commands-yml/parse.js +319 -0
- package/build/commands-yml/validator.js +130 -0
- package/build/index.js +19 -0
- package/build/lib/appium-config.schema.json +0 -0
- package/build/lib/appium.js +160 -53
- 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 +43 -29
- package/build/lib/cli/parser.js +156 -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 +53 -65
- 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 +21 -11
- package/build/lib/plugins.js +4 -2
- package/build/lib/schema/appium-config-schema.js +253 -0
- package/build/lib/schema/arg-spec.js +120 -0
- package/build/lib/schema/cli-args.js +188 -0
- package/build/lib/schema/cli-transformers.js +76 -0
- package/build/lib/schema/index.js +36 -0
- package/build/lib/schema/keywords.js +72 -0
- package/build/lib/schema/schema.js +357 -0
- package/build/lib/utils.js +44 -99
- package/build/postinstall.js +90 -0
- package/build/test/cli/cli-e2e-specs.js +221 -0
- package/build/test/cli/cli-helpers.js +86 -0
- package/build/test/cli/cli-specs.js +71 -0
- package/build/test/cli/fixtures/test-driver/package.json +27 -0
- package/build/test/cli/schema-args-specs.js +48 -0
- package/build/test/cli/schema-e2e-specs.js +47 -0
- package/build/test/config-e2e-specs.js +112 -0
- package/build/test/config-file-e2e-specs.js +209 -0
- package/build/test/config-file-specs.js +281 -0
- package/build/test/config-specs.js +159 -0
- package/build/test/driver-e2e-specs.js +435 -0
- package/build/test/driver-specs.js +321 -0
- package/build/test/ext-config-io-specs.js +181 -0
- package/build/test/extension-config-specs.js +365 -0
- package/build/test/fixtures/allow-feat.txt +5 -0
- package/build/test/fixtures/caps.json +3 -0
- package/build/test/fixtures/config/allow-insecure.txt +3 -0
- package/build/test/fixtures/config/appium.config.bad-nodeconfig.json +5 -0
- package/build/test/fixtures/config/appium.config.bad.json +32 -0
- package/build/test/fixtures/config/appium.config.ext-good.json +9 -0
- package/build/test/fixtures/config/appium.config.ext-unknown-props.json +10 -0
- package/build/test/fixtures/config/appium.config.good.js +40 -0
- package/build/test/fixtures/config/appium.config.good.json +33 -0
- package/build/test/fixtures/config/appium.config.good.yaml +30 -0
- package/build/test/fixtures/config/appium.config.invalid.json +31 -0
- package/build/test/fixtures/config/appium.config.security-array.json +5 -0
- package/build/test/fixtures/config/appium.config.security-delimited.json +5 -0
- package/build/test/fixtures/config/appium.config.security-path.json +5 -0
- package/build/test/fixtures/config/driver-fake.config.json +8 -0
- package/build/test/fixtures/config/nodeconfig.json +3 -0
- package/build/test/fixtures/config/plugin-fake.config.json +0 -0
- package/build/test/fixtures/default-args.js +35 -0
- package/build/test/fixtures/deny-feat.txt +5 -0
- package/build/test/fixtures/driver.schema.js +20 -0
- package/build/test/fixtures/extensions.yaml +27 -0
- package/build/test/fixtures/flattened-schema.js +504 -0
- package/build/test/fixtures/plugin.schema.js +20 -0
- package/build/test/fixtures/schema-with-extensions.js +28 -0
- package/build/test/grid-register-specs.js +74 -0
- package/build/test/helpers.js +75 -0
- package/build/test/logger-specs.js +76 -0
- package/build/test/npm-specs.js +20 -0
- package/build/test/parser-specs.js +314 -0
- package/build/test/plugin-e2e-specs.js +316 -0
- package/build/test/schema/arg-spec-specs.js +70 -0
- package/build/test/schema/cli-args-specs.js +431 -0
- package/build/test/schema/schema-specs.js +389 -0
- package/build/test/utils-specs.js +266 -0
- package/index.js +11 -0
- package/lib/appium-config.schema.json +278 -0
- package/lib/appium.js +207 -65
- 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 +86 -18
- package/lib/cli/parser.js +257 -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 +84 -63
- 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 +214 -77
- package/lib/plugin-config.js +35 -6
- package/lib/plugins.js +1 -0
- package/lib/schema/appium-config-schema.js +287 -0
- package/lib/schema/arg-spec.js +222 -0
- package/lib/schema/cli-args.js +285 -0
- package/lib/schema/cli-transformers.js +123 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +135 -0
- package/lib/schema/schema.js +577 -0
- package/lib/utils.js +42 -88
- package/package.json +55 -84
- 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,34 @@ 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;
|
|
219
|
+
|
|
220
|
+
// Drivers/plugins might also want to know where they are hosted
|
|
221
|
+
driverInstance.serverHost = this.args.address;
|
|
222
|
+
driverInstance.serverPort = this.args.port;
|
|
223
|
+
driverInstance.serverPath = this.args.basePath;
|
|
224
|
+
|
|
183
225
|
try {
|
|
184
226
|
runningDriversData = await this.curSessionDataForDriver(InnerDriver);
|
|
185
227
|
} catch (e) {
|
|
@@ -188,43 +230,43 @@ class AppiumDriver extends BaseDriver {
|
|
|
188
230
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
189
231
|
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
|
|
190
232
|
otherPendingDriversData = this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData);
|
|
191
|
-
this.pendingDrivers[InnerDriver.name].push(
|
|
233
|
+
this.pendingDrivers[InnerDriver.name].push(driverInstance);
|
|
192
234
|
});
|
|
193
235
|
|
|
194
236
|
try {
|
|
195
|
-
[innerSessionId, dCaps] = await
|
|
237
|
+
[innerSessionId, dCaps] = await driverInstance.createSession(
|
|
196
238
|
processedJsonwpCapabilities,
|
|
197
239
|
reqCaps,
|
|
198
240
|
processedW3CCapabilities,
|
|
199
241
|
[...runningDriversData, ...otherPendingDriversData]
|
|
200
242
|
);
|
|
201
|
-
protocol =
|
|
243
|
+
protocol = driverInstance.protocol;
|
|
202
244
|
await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
|
203
|
-
this.sessions[innerSessionId] =
|
|
245
|
+
this.sessions[innerSessionId] = driverInstance;
|
|
204
246
|
});
|
|
205
247
|
} finally {
|
|
206
248
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
207
|
-
_.pull(this.pendingDrivers[InnerDriver.name],
|
|
249
|
+
_.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
|
|
208
250
|
});
|
|
209
251
|
}
|
|
210
252
|
|
|
211
|
-
this.attachUnexpectedShutdownHandler(
|
|
253
|
+
this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
|
|
212
254
|
|
|
213
255
|
log.info(`New ${InnerDriver.name} session created successfully, session ` +
|
|
214
256
|
`${innerSessionId} added to master session list`);
|
|
215
257
|
|
|
216
258
|
// set the New Command Timeout for the inner driver
|
|
217
|
-
|
|
259
|
+
driverInstance.startNewCommandTimeout();
|
|
218
260
|
|
|
219
261
|
// apply initial values to Appium settings (if provided)
|
|
220
|
-
if (
|
|
262
|
+
if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
|
|
221
263
|
log.info(`Applying the initial values to Appium settings parsed from W3C caps: ` +
|
|
222
264
|
JSON.stringify(w3cSettings));
|
|
223
|
-
await
|
|
224
|
-
} else if (
|
|
265
|
+
await driverInstance.updateSettings(w3cSettings);
|
|
266
|
+
} else if (driverInstance.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
|
|
225
267
|
log.info(`Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
|
|
226
268
|
JSON.stringify(jwpSettings));
|
|
227
|
-
await
|
|
269
|
+
await driverInstance.updateSettings(jwpSettings);
|
|
228
270
|
}
|
|
229
271
|
} catch (error) {
|
|
230
272
|
return {
|
|
@@ -240,33 +282,31 @@ class AppiumDriver extends BaseDriver {
|
|
|
240
282
|
}
|
|
241
283
|
|
|
242
284
|
attachUnexpectedShutdownHandler (driver, innerSessionId) {
|
|
243
|
-
const
|
|
244
|
-
log.warn(`
|
|
285
|
+
const onShutdown = (cause = new Error('Unknown error')) => {
|
|
286
|
+
log.warn(`Ending session, cause was '${cause.message}'`);
|
|
287
|
+
|
|
288
|
+
if (this.sessionPlugins[innerSessionId]) {
|
|
289
|
+
for (const plugin of this.sessionPlugins[innerSessionId]) {
|
|
290
|
+
if (_.isFunction(plugin.onUnexpectedShutdown)) {
|
|
291
|
+
log.debug(`Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`);
|
|
292
|
+
try {
|
|
293
|
+
plugin.onUnexpectedShutdown(driver, cause);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
log.warn(`Got an error when running plugin ${plugin.name} shutdown handler: ${e}`);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
245
303
|
log.info(`Removing session '${innerSessionId}' from our master session list`);
|
|
246
304
|
delete this.sessions[innerSessionId];
|
|
305
|
+
delete this.sessionPlugins[innerSessionId];
|
|
247
306
|
};
|
|
248
307
|
|
|
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);
|
|
308
|
+
if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
309
|
+
driver.onUnexpectedShutdown(onShutdown);
|
|
270
310
|
} else {
|
|
271
311
|
log.warn(`Failed to attach the unexpected shutdown listener. ` +
|
|
272
312
|
`Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`);
|
|
@@ -308,6 +348,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
308
348
|
// make the session unavailable, because who knows what state it might
|
|
309
349
|
// be in otherwise
|
|
310
350
|
delete this.sessions[sessionId];
|
|
351
|
+
delete this.sessionPlugins[sessionId];
|
|
311
352
|
});
|
|
312
353
|
return {
|
|
313
354
|
protocol,
|
|
@@ -346,11 +387,50 @@ class AppiumDriver extends BaseDriver {
|
|
|
346
387
|
}
|
|
347
388
|
}
|
|
348
389
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
390
|
+
/**
|
|
391
|
+
* Get the appropriate plugins for a session (or sessionless plugins)
|
|
392
|
+
*
|
|
393
|
+
* @param {?string} sessionId - the sessionId (or null) to use to find plugins
|
|
394
|
+
* @returns {Array} - array of plugin instances
|
|
395
|
+
*/
|
|
396
|
+
pluginsForSession (sessionId = null) {
|
|
397
|
+
if (sessionId) {
|
|
398
|
+
if (!this.sessionPlugins[sessionId]) {
|
|
399
|
+
this.sessionPlugins[sessionId] = this.createPluginInstances();
|
|
400
|
+
}
|
|
401
|
+
return this.sessionPlugins[sessionId];
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
405
|
+
this.sessionlessPlugins = this.createPluginInstances();
|
|
406
|
+
}
|
|
407
|
+
return this.sessionlessPlugins;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* To get plugins for a command, we either get the plugin instances associated with the
|
|
412
|
+
* particular command's session, or in the case of sessionless plugins, pull from the set of
|
|
413
|
+
* plugin instances reserved for sessionless commands (and we lazily create plugin instances on
|
|
414
|
+
* first use)
|
|
415
|
+
*
|
|
416
|
+
* @param {string} cmd - the name of the command to find a plugin to handle
|
|
417
|
+
* @param {?string} sessionId - the particular session for which to find a plugin, or null if
|
|
418
|
+
* sessionless
|
|
419
|
+
*/
|
|
420
|
+
pluginsToHandleCmd (cmd, sessionId = null) {
|
|
421
|
+
// to handle a given command, a plugin should either implement that command as a plugin
|
|
422
|
+
// instance method or it should implement a generic 'handle' method
|
|
423
|
+
return this.pluginsForSession(sessionId)
|
|
424
|
+
.filter((p) => _.isFunction(p[cmd]) || _.isFunction(p.handle));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
createPluginInstances () {
|
|
428
|
+
return this.pluginClasses.map((PluginClass) => {
|
|
429
|
+
const name = PluginClass.pluginName;
|
|
430
|
+
const plugin = new PluginClass(name);
|
|
431
|
+
this.assignCliArgsToExtension('plugin', name, plugin);
|
|
432
|
+
return plugin;
|
|
433
|
+
});
|
|
354
434
|
}
|
|
355
435
|
|
|
356
436
|
async executeCommand (cmd, ...args) {
|
|
@@ -367,14 +447,21 @@ class AppiumDriver extends BaseDriver {
|
|
|
367
447
|
const isUmbrellaCmd = !isGetStatus && isAppiumDriverCommand(cmd);
|
|
368
448
|
const isSessionCmd = !isGetStatus && !isUmbrellaCmd;
|
|
369
449
|
|
|
370
|
-
//
|
|
371
|
-
|
|
450
|
+
// if a plugin override proxying for this command and that is why we are here instead of just
|
|
451
|
+
// letting the protocol proxy the command entirely, determine that, get the request object for
|
|
452
|
+
// use later on, then clean up the args
|
|
453
|
+
const reqForProxy = _.last(args)?.reqForProxy;
|
|
454
|
+
if (reqForProxy) {
|
|
455
|
+
args.pop();
|
|
456
|
+
}
|
|
457
|
+
|
|
372
458
|
|
|
373
459
|
// first do some error checking. If we're requesting a session command execution, then make
|
|
374
460
|
// sure that session actually exists on the session driver, and set the session driver itself
|
|
375
461
|
let sessionId = null;
|
|
376
462
|
let dstSession = null;
|
|
377
463
|
let protocol = null;
|
|
464
|
+
let driver = this;
|
|
378
465
|
if (isSessionCmd) {
|
|
379
466
|
sessionId = _.last(args);
|
|
380
467
|
dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => this.sessions[sessionId]);
|
|
@@ -383,8 +470,12 @@ class AppiumDriver extends BaseDriver {
|
|
|
383
470
|
}
|
|
384
471
|
// now save the response protocol given that the session driver's protocol might differ
|
|
385
472
|
protocol = dstSession.protocol;
|
|
473
|
+
driver = dstSession;
|
|
386
474
|
}
|
|
387
475
|
|
|
476
|
+
// get any plugins which are registered as handling this command
|
|
477
|
+
const plugins = this.pluginsToHandleCmd(cmd, sessionId);
|
|
478
|
+
|
|
388
479
|
// now we define a 'cmdHandledBy' object which will keep track of which plugins have handled this
|
|
389
480
|
// command. we care about this because (a) multiple plugins can handle the same command, and
|
|
390
481
|
// (b) there's no guarantee that a plugin will actually call the next() method which runs the
|
|
@@ -406,6 +497,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
406
497
|
// if we make it here, we know that the default behavior is handled
|
|
407
498
|
cmdHandledBy.default = true;
|
|
408
499
|
|
|
500
|
+
if (reqForProxy) {
|
|
501
|
+
// we would have proxied this command had a plugin not handled it, so the default behavior
|
|
502
|
+
// is to do the proxy and retrieve the result internally so it can be passed to the plugin
|
|
503
|
+
// in case it calls 'await next()'. This requires that the driver have defined
|
|
504
|
+
// 'proxyCommand' and not just 'proxyReqRes'.
|
|
505
|
+
if (!dstSession.proxyCommand) {
|
|
506
|
+
throw new NoDriverProxyCommandError();
|
|
507
|
+
}
|
|
508
|
+
return await dstSession.proxyCommand(reqForProxy.originalUrl, reqForProxy.method,
|
|
509
|
+
reqForProxy.body);
|
|
510
|
+
}
|
|
511
|
+
|
|
409
512
|
if (isGetStatus) {
|
|
410
513
|
return await this.getStatus();
|
|
411
514
|
}
|
|
@@ -421,17 +524,30 @@ class AppiumDriver extends BaseDriver {
|
|
|
421
524
|
};
|
|
422
525
|
|
|
423
526
|
// now take our default behavior, wrap it with any number of plugin behaviors, and run it
|
|
424
|
-
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
527
|
+
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
528
|
+
driver, cmd, args, plugins, cmdHandledBy, next: defaultBehavior
|
|
529
|
+
});
|
|
425
530
|
const res = await this.executeWrappedCommand({wrappedCmd, protocol});
|
|
426
531
|
|
|
427
532
|
// if we had plugins, make sure to log out the helpful report about which plugins ended up
|
|
428
533
|
// handling the command and which didn't
|
|
429
|
-
|
|
534
|
+
this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
|
|
535
|
+
|
|
536
|
+
// And finally, if the command was createSession, we want to migrate any plugins which were
|
|
537
|
+
// previously sessionless to use the new sessionId, so that plugins can share state between
|
|
538
|
+
// their createSession method and other instance methods
|
|
539
|
+
if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
|
|
540
|
+
const sessionId = _.first(res.value);
|
|
541
|
+
log.info(`Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
|
|
542
|
+
`to session ID ${sessionId}`);
|
|
543
|
+
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
|
|
544
|
+
this.sessionlessPlugins = [];
|
|
545
|
+
}
|
|
430
546
|
|
|
431
547
|
return res;
|
|
432
548
|
}
|
|
433
549
|
|
|
434
|
-
wrapCommandWithPlugins ({cmd, args, next, cmdHandledBy, plugins}) {
|
|
550
|
+
wrapCommandWithPlugins ({driver, cmd, args, next, cmdHandledBy, plugins}) {
|
|
435
551
|
plugins.length && log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
|
|
436
552
|
|
|
437
553
|
// now we can go through each plugin and wrap `next` around its own handler, passing the *old*
|
|
@@ -444,14 +560,23 @@ class AppiumDriver extends BaseDriver {
|
|
|
444
560
|
next = ((_next) => async () => {
|
|
445
561
|
log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
|
|
446
562
|
cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
|
|
447
|
-
|
|
563
|
+
// first attempt to handle the command via a command-specific handler on the plugin
|
|
564
|
+
if (plugin[cmd]) {
|
|
565
|
+
return await plugin[cmd](_next, driver, ...args);
|
|
566
|
+
}
|
|
567
|
+
// otherwise, call the generic 'handle' method
|
|
568
|
+
return await plugin.handle(_next, driver, cmd, ...args);
|
|
448
569
|
})(next);
|
|
449
570
|
}
|
|
450
571
|
|
|
451
572
|
return next;
|
|
452
573
|
}
|
|
453
574
|
|
|
454
|
-
logPluginHandlerReport ({cmd, cmdHandledBy}) {
|
|
575
|
+
logPluginHandlerReport (plugins, {cmd, cmdHandledBy}) {
|
|
576
|
+
if (!plugins.length) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
455
580
|
// at the end of the day, we have an object representing which plugins ended up getting
|
|
456
581
|
// their code run as part of handling this command. Because plugins can choose *not* to
|
|
457
582
|
// pass control to other plugins or to the default driver behavior, this is information
|
|
@@ -461,7 +586,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
461
586
|
const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]);
|
|
462
587
|
const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]);
|
|
463
588
|
if (didntHandle.length > 0) {
|
|
464
|
-
log.info(`Command '${cmd}' was not handled by the following
|
|
589
|
+
log.info(`Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` +
|
|
465
590
|
`though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
|
|
466
591
|
`command *was* handled by these: ${JSON.stringify(didHandle)}.`);
|
|
467
592
|
}
|
|
@@ -491,7 +616,6 @@ class AppiumDriver extends BaseDriver {
|
|
|
491
616
|
return res;
|
|
492
617
|
}
|
|
493
618
|
|
|
494
|
-
|
|
495
619
|
proxyActive (sessionId) {
|
|
496
620
|
const dstSession = this.sessions[sessionId];
|
|
497
621
|
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
|
@@ -514,4 +638,22 @@ function isAppiumDriverCommand (cmd) {
|
|
|
514
638
|
return !isSessionCommand(cmd) || cmd === 'deleteSession';
|
|
515
639
|
}
|
|
516
640
|
|
|
641
|
+
/**
|
|
642
|
+
* Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
|
|
643
|
+
* method did not exist
|
|
644
|
+
*/
|
|
645
|
+
export class NoDriverProxyCommandError extends Error {
|
|
646
|
+
/**
|
|
647
|
+
* @type {Readonly<string>}
|
|
648
|
+
*/
|
|
649
|
+
code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
650
|
+
|
|
651
|
+
constructor () {
|
|
652
|
+
super(`The default behavior for this command was to proxy, but the driver ` +
|
|
653
|
+
`did not have the 'proxyCommand' method defined. To fully support ` +
|
|
654
|
+
`plugins, drivers should have 'proxyCommand' set to a jwpProxy object's ` +
|
|
655
|
+
`'command()' method, in addition to the normal 'proxyReqRes'`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
517
659
|
export { AppiumDriver };
|