appium 2.0.0-beta.4 → 2.0.0-beta.42
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 +10 -11
- package/build/lib/appium.d.ts +204 -0
- package/build/lib/appium.d.ts.map +1 -0
- package/build/lib/appium.js +256 -131
- package/build/lib/cli/args.d.ts +17 -0
- package/build/lib/cli/args.d.ts.map +1 -0
- package/build/lib/cli/args.js +96 -282
- package/build/lib/cli/driver-command.d.ts +37 -0
- package/build/lib/cli/driver-command.d.ts.map +1 -0
- package/build/lib/cli/driver-command.js +27 -18
- package/build/lib/cli/extension-command.d.ts +376 -0
- package/build/lib/cli/extension-command.d.ts.map +1 -0
- package/build/lib/cli/extension-command.js +287 -156
- package/build/lib/cli/extension.d.ts +22 -0
- package/build/lib/cli/extension.d.ts.map +1 -0
- package/build/lib/cli/extension.js +31 -17
- package/build/lib/cli/parser.d.ts +84 -0
- package/build/lib/cli/parser.d.ts.map +1 -0
- package/build/lib/cli/parser.js +164 -94
- package/build/lib/cli/plugin-command.d.ts +34 -0
- package/build/lib/cli/plugin-command.d.ts.map +1 -0
- package/build/lib/cli/plugin-command.js +26 -19
- package/build/lib/cli/utils.d.ts +29 -0
- package/build/lib/cli/utils.d.ts.map +1 -0
- package/build/lib/cli/utils.js +27 -3
- package/build/lib/config-file.d.ts +100 -0
- package/build/lib/config-file.d.ts.map +1 -0
- package/build/lib/config-file.js +136 -0
- package/build/lib/config.d.ts +49 -0
- package/build/lib/config.d.ts.map +1 -0
- package/build/lib/config.js +119 -96
- package/build/lib/constants.d.ts +48 -0
- package/build/lib/constants.d.ts.map +1 -0
- package/build/lib/constants.js +60 -0
- package/build/lib/extension/driver-config.d.ts +81 -0
- package/build/lib/extension/driver-config.d.ts.map +1 -0
- package/build/lib/extension/driver-config.js +177 -0
- package/build/lib/extension/extension-config.d.ts +242 -0
- package/build/lib/extension/extension-config.d.ts.map +1 -0
- package/build/lib/extension/extension-config.js +436 -0
- package/build/lib/extension/index.d.ts +48 -0
- package/build/lib/extension/index.d.ts.map +1 -0
- package/build/lib/extension/index.js +75 -0
- package/build/lib/extension/manifest.d.ts +174 -0
- package/build/lib/extension/manifest.d.ts.map +1 -0
- package/build/lib/extension/manifest.js +256 -0
- package/build/lib/extension/package-changed.d.ts +11 -0
- package/build/lib/extension/package-changed.d.ts.map +1 -0
- package/build/lib/extension/package-changed.js +66 -0
- package/build/lib/extension/plugin-config.d.ts +57 -0
- package/build/lib/extension/plugin-config.d.ts.map +1 -0
- package/build/lib/extension/plugin-config.js +78 -0
- package/build/lib/grid-register.d.ts +10 -0
- package/build/lib/grid-register.d.ts.map +1 -0
- package/build/lib/grid-register.js +21 -25
- package/build/lib/logger.d.ts +3 -0
- package/build/lib/logger.d.ts.map +1 -0
- package/build/lib/logger.js +4 -6
- package/build/lib/logsink.d.ts +4 -0
- package/build/lib/logsink.d.ts.map +1 -0
- package/build/lib/logsink.js +14 -17
- package/build/lib/main.d.ts +55 -0
- package/build/lib/main.d.ts.map +1 -0
- package/build/lib/main.js +183 -91
- package/build/lib/schema/arg-spec.d.ts +143 -0
- package/build/lib/schema/arg-spec.d.ts.map +1 -0
- package/build/lib/schema/arg-spec.js +119 -0
- package/build/lib/schema/cli-args.d.ts +19 -0
- package/build/lib/schema/cli-args.d.ts.map +1 -0
- package/build/lib/schema/cli-args.js +178 -0
- package/build/lib/schema/cli-transformers.d.ts +5 -0
- package/build/lib/schema/cli-transformers.d.ts.map +1 -0
- package/build/lib/schema/cli-transformers.js +74 -0
- package/build/lib/schema/index.d.ts +3 -0
- package/build/lib/schema/index.d.ts.map +1 -0
- package/build/lib/schema/index.js +34 -0
- package/build/lib/schema/keywords.d.ts +24 -0
- package/build/lib/schema/keywords.d.ts.map +1 -0
- package/build/lib/schema/keywords.js +70 -0
- package/build/lib/schema/schema.d.ts +259 -0
- package/build/lib/schema/schema.d.ts.map +1 -0
- package/build/lib/schema/schema.js +450 -0
- package/build/lib/utils.d.ts +66 -0
- package/build/lib/utils.d.ts.map +1 -0
- package/build/lib/utils.js +35 -139
- package/build/tsconfig.tsbuildinfo +1 -0
- package/build/types/appium-manifest.d.ts +59 -0
- package/build/types/appium-manifest.d.ts.map +1 -0
- package/build/types/cli.d.ts +123 -0
- package/build/types/cli.d.ts.map +1 -0
- package/build/types/extension-manifest.d.ts +55 -0
- package/build/types/extension-manifest.d.ts.map +1 -0
- package/build/types/index.d.ts +16 -0
- package/build/types/index.d.ts.map +1 -0
- package/driver.d.ts +1 -0
- package/driver.js +14 -0
- package/index.js +11 -0
- package/lib/appium.js +520 -186
- package/lib/cli/args.js +267 -422
- package/lib/cli/driver-command.js +58 -23
- package/lib/cli/extension-command.js +613 -260
- package/lib/cli/extension.js +47 -17
- package/lib/cli/parser.js +263 -83
- package/lib/cli/plugin-command.js +48 -20
- package/lib/cli/utils.js +24 -10
- package/lib/config-file.js +219 -0
- package/lib/config.js +243 -110
- package/lib/constants.js +69 -0
- package/lib/extension/driver-config.js +249 -0
- package/lib/extension/extension-config.js +677 -0
- package/lib/extension/index.js +116 -0
- package/lib/extension/manifest.js +475 -0
- package/lib/extension/package-changed.js +64 -0
- package/lib/extension/plugin-config.js +113 -0
- package/lib/grid-register.js +49 -35
- package/lib/logger.js +1 -2
- package/lib/logsink.js +38 -33
- package/lib/main.js +308 -100
- package/lib/schema/arg-spec.js +229 -0
- package/lib/schema/cli-args.js +238 -0
- package/lib/schema/cli-transformers.js +115 -0
- package/lib/schema/index.js +2 -0
- package/lib/schema/keywords.js +136 -0
- package/lib/schema/schema.js +717 -0
- package/lib/utils.js +121 -140
- package/package.json +85 -85
- package/plugin.d.ts +1 -0
- package/plugin.js +13 -0
- package/scripts/autoinstall-extensions.js +185 -0
- package/support.d.ts +1 -0
- package/support.js +13 -0
- package/test.d.ts +7 -0
- package/test.js +13 -0
- package/types/appium-manifest.ts +73 -0
- package/types/cli.ts +150 -0
- package/types/extension-manifest.ts +64 -0
- package/types/index.ts +21 -0
- package/CHANGELOG.md +0 -3515
- package/bin/ios-webkit-debug-proxy-launcher.js +0 -71
- package/build/lib/cli/npm.js +0 -206
- package/build/lib/cli/parser-helpers.js +0 -82
- package/build/lib/driver-config.js +0 -77
- package/build/lib/drivers.js +0 -96
- package/build/lib/extension-config.js +0 -253
- package/build/lib/plugin-config.js +0 -59
- package/build/lib/plugins.js +0 -14
- package/lib/cli/npm.js +0 -183
- package/lib/cli/parser-helpers.js +0 -79
- package/lib/driver-config.js +0 -46
- package/lib/drivers.js +0 -81
- package/lib/extension-config.js +0 -209
- package/lib/plugin-config.js +0 -34
- package/lib/plugins.js +0 -10
package/lib/appium.js
CHANGED
|
@@ -1,14 +1,26 @@
|
|
|
1
|
+
/* eslint-disable no-unused-vars */
|
|
1
2
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {getBuildInfo, updateBuildInfo, APPIUM_VER} from './config';
|
|
4
|
+
import {
|
|
5
|
+
BaseDriver,
|
|
6
|
+
DriverCore,
|
|
7
|
+
errors,
|
|
8
|
+
isSessionCommand,
|
|
9
|
+
CREATE_SESSION_COMMAND,
|
|
10
|
+
DELETE_SESSION_COMMAND,
|
|
11
|
+
GET_STATUS_COMMAND,
|
|
12
|
+
} from '@appium/base-driver';
|
|
7
13
|
import AsyncLock from 'async-lock';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
import {parseCapsForInnerDriver, pullSettings} from './utils';
|
|
15
|
+
import {util, node, logger} from '@appium/support';
|
|
16
|
+
import {getDefaultsForExtension} from './schema';
|
|
17
|
+
import {DRIVER_TYPE, PLUGIN_TYPE} from './constants';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Invariant set of base constraints
|
|
21
|
+
* @type {Readonly<Constraints>}
|
|
22
|
+
*/
|
|
23
|
+
const desiredCapabilityConstraints = Object.freeze({
|
|
12
24
|
automationName: {
|
|
13
25
|
presence: true,
|
|
14
26
|
isString: true,
|
|
@@ -17,109 +29,211 @@ const desiredCapabilityConstraints = {
|
|
|
17
29
|
presence: true,
|
|
18
30
|
isString: true,
|
|
19
31
|
},
|
|
20
|
-
};
|
|
32
|
+
});
|
|
21
33
|
|
|
22
34
|
const sessionsListGuard = new AsyncLock();
|
|
23
35
|
const pendingDriversGuard = new AsyncLock();
|
|
24
36
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
/**
|
|
38
|
+
* @implements {SessionHandler}
|
|
39
|
+
*/
|
|
40
|
+
class AppiumDriver extends DriverCore {
|
|
41
|
+
/**
|
|
42
|
+
* Access to sessions list must be guarded with a Semaphore, because
|
|
43
|
+
* it might be changed by other async calls at any time
|
|
44
|
+
* It is not recommended to access this property directly from the outside
|
|
45
|
+
* @type {Record<string,ExternalDriver>}
|
|
46
|
+
*/
|
|
47
|
+
sessions = {};
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Access to pending drivers list must be guarded with a Semaphore, because
|
|
51
|
+
* it might be changed by other async calls at any time
|
|
52
|
+
* It is not recommended to access this property directly from the outside
|
|
53
|
+
* @type {Record<string,ExternalDriver[]>}
|
|
54
|
+
*/
|
|
55
|
+
pendingDrivers = {};
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Note that {@linkcode AppiumDriver} has no `newCommandTimeout` method.
|
|
59
|
+
* `AppiumDriver` does not set and observe its own timeouts; individual
|
|
60
|
+
* sessions (managed drivers) do instead.
|
|
61
|
+
*/
|
|
62
|
+
newCommandTimeoutMs = 0;
|
|
38
63
|
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
/**
|
|
65
|
+
* List of active plugins
|
|
66
|
+
* @type {Map<PluginClass,string>}
|
|
67
|
+
*/
|
|
68
|
+
pluginClasses;
|
|
41
69
|
|
|
42
|
-
|
|
70
|
+
/**
|
|
71
|
+
* map of sessions to actual plugin instances per session
|
|
72
|
+
* @type {Record<string,InstanceType<PluginClass>[]>}
|
|
73
|
+
*/
|
|
74
|
+
sessionPlugins = {};
|
|
43
75
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
/**
|
|
77
|
+
* some commands are sessionless, so we need a set of plugins for them
|
|
78
|
+
* @type {InstanceType<PluginClass>[]}
|
|
79
|
+
*/
|
|
80
|
+
sessionlessPlugins = [];
|
|
81
|
+
|
|
82
|
+
/** @type {DriverConfig} */
|
|
83
|
+
driverConfig;
|
|
84
|
+
|
|
85
|
+
/** @type {AppiumServer} */
|
|
86
|
+
server;
|
|
87
|
+
|
|
88
|
+
desiredCapConstraints = desiredCapabilityConstraints;
|
|
89
|
+
|
|
90
|
+
/** @type {DriverOpts} */
|
|
91
|
+
args;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {DriverOpts} opts
|
|
95
|
+
*/
|
|
96
|
+
constructor(opts) {
|
|
97
|
+
// It is necessary to set `--tmp` here since it should be set to
|
|
98
|
+
// process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
|
|
99
|
+
// The process argument will be referenced by BaseDriver.
|
|
100
|
+
// Please call @appium/support.tempDir module to apply this benefit.
|
|
101
|
+
if (opts.tmpDir) {
|
|
102
|
+
process.env.APPIUM_TMP_DIR = opts.tmpDir;
|
|
103
|
+
}
|
|
48
104
|
|
|
49
|
-
|
|
50
|
-
// it might be changed by other async calls at any time
|
|
51
|
-
// It is not recommended to access this property directly from the outside
|
|
52
|
-
this.pendingDrivers = {};
|
|
105
|
+
super(opts);
|
|
53
106
|
|
|
54
|
-
this.
|
|
107
|
+
this.args = {...opts};
|
|
55
108
|
|
|
56
109
|
// allow this to happen in the background, so no `await`
|
|
57
|
-
|
|
110
|
+
(async () => {
|
|
111
|
+
try {
|
|
112
|
+
await updateBuildInfo();
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// make sure we catch any possible errors to avoid unhandled rejections
|
|
115
|
+
this.log.debug(`Cannot fetch Appium build info: ${e.message}`);
|
|
116
|
+
}
|
|
117
|
+
})();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Retrieves logger instance for the current umbrella driver instance
|
|
122
|
+
*/
|
|
123
|
+
get log() {
|
|
124
|
+
if (!this._log) {
|
|
125
|
+
const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`;
|
|
126
|
+
this._log = logger.getLogger(instanceName);
|
|
127
|
+
}
|
|
128
|
+
return this._log;
|
|
58
129
|
}
|
|
59
130
|
|
|
60
131
|
/**
|
|
61
132
|
* Cancel commands queueing for the umbrella Appium driver
|
|
62
133
|
*/
|
|
63
|
-
get isCommandsQueueEnabled
|
|
134
|
+
get isCommandsQueueEnabled() {
|
|
64
135
|
return false;
|
|
65
136
|
}
|
|
66
137
|
|
|
67
|
-
sessionExists
|
|
138
|
+
sessionExists(sessionId) {
|
|
68
139
|
const dstSession = this.sessions[sessionId];
|
|
69
140
|
return dstSession && dstSession.sessionId !== null;
|
|
70
141
|
}
|
|
71
142
|
|
|
72
|
-
driverForSession
|
|
143
|
+
driverForSession(sessionId) {
|
|
73
144
|
return this.sessions[sessionId];
|
|
74
145
|
}
|
|
75
146
|
|
|
76
|
-
|
|
147
|
+
// eslint-disable-next-line require-await
|
|
148
|
+
async getStatus() {
|
|
77
149
|
return {
|
|
78
150
|
build: _.clone(getBuildInfo()),
|
|
79
151
|
};
|
|
80
152
|
}
|
|
81
153
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return _.toPairs(sessions)
|
|
85
|
-
|
|
154
|
+
// eslint-disable-next-line require-await
|
|
155
|
+
async getSessions() {
|
|
156
|
+
return _.toPairs(this.sessions).map(([id, driver]) => ({
|
|
157
|
+
id,
|
|
158
|
+
capabilities: driver.caps,
|
|
159
|
+
}));
|
|
86
160
|
}
|
|
87
161
|
|
|
88
|
-
printNewSessionAnnouncement
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
162
|
+
printNewSessionAnnouncement(driverName, driverVersion, driverBaseVersion) {
|
|
163
|
+
this.log.info(
|
|
164
|
+
driverVersion
|
|
165
|
+
? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
|
|
166
|
+
: `Appium v${APPIUM_VER} creating new ${driverName} session`
|
|
167
|
+
);
|
|
168
|
+
this.log.info(`Checking BaseDriver versions for Appium and ${driverName}`);
|
|
169
|
+
this.log.info(
|
|
170
|
+
AppiumDriver.baseVersion
|
|
171
|
+
? `Appium's BaseDriver version is ${AppiumDriver.baseVersion}`
|
|
172
|
+
: `Could not determine Appium's BaseDriver version`
|
|
173
|
+
);
|
|
174
|
+
this.log.info(
|
|
175
|
+
driverBaseVersion
|
|
176
|
+
? `${driverName}'s BaseDriver version is ${driverBaseVersion}`
|
|
177
|
+
: `Could not determine ${driverName}'s BaseDriver version`
|
|
178
|
+
);
|
|
93
179
|
}
|
|
94
180
|
|
|
95
181
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
182
|
+
* Retrieves all CLI arguments for a specific plugin.
|
|
183
|
+
* @param {string} extName - Plugin name
|
|
184
|
+
* @returns {Record<string,unknown>} Arguments object. If none, an empty object.
|
|
98
185
|
*/
|
|
99
|
-
|
|
100
|
-
return
|
|
186
|
+
getCliArgsForPlugin(extName) {
|
|
187
|
+
return /** @type {Record<string,unknown>} */ (this.args.plugin?.[extName] ?? {});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Retrieves CLI args for a specific driver.
|
|
192
|
+
*
|
|
193
|
+
* _Any arg which is equal to its default value will not be present in the returned object._
|
|
194
|
+
*
|
|
195
|
+
* _Note that this behavior currently (May 18 2022) differs from how plugins are handled_ (see {@linkcode AppiumDriver.getCliArgsForPlugin}).
|
|
196
|
+
* @param {string} extName - Driver name
|
|
197
|
+
* @returns {Record<string,unknown>|undefined} Arguments object. If none, `undefined`
|
|
198
|
+
*/
|
|
199
|
+
getCliArgsForDriver(extName) {
|
|
200
|
+
const allCliArgsForExt = /** @type {Record<string,unknown>|undefined} */ (
|
|
201
|
+
this.args.driver?.[extName]
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
if (!_.isEmpty(allCliArgsForExt)) {
|
|
205
|
+
const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
|
|
206
|
+
const cliArgs = _.isEmpty(defaults)
|
|
207
|
+
? allCliArgsForExt
|
|
208
|
+
: _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
|
|
209
|
+
if (!_.isEmpty(cliArgs)) {
|
|
210
|
+
return cliArgs;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
101
213
|
}
|
|
102
214
|
|
|
103
215
|
/**
|
|
104
216
|
* Create a new session
|
|
105
|
-
* @param {
|
|
106
|
-
* @param {
|
|
107
|
-
* @param {
|
|
108
|
-
* @
|
|
217
|
+
* @param {W3CCapabilities} jsonwpCaps JSONWP formatted desired capabilities
|
|
218
|
+
* @param {W3CCapabilities} reqCaps Required capabilities (JSONWP standard)
|
|
219
|
+
* @param {W3CCapabilities} w3cCapabilities W3C capabilities
|
|
220
|
+
* @param {DriverData[]} [driverData]
|
|
109
221
|
*/
|
|
110
|
-
async createSession
|
|
222
|
+
async createSession(jsonwpCaps, reqCaps, w3cCapabilities, driverData) {
|
|
111
223
|
const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
|
|
112
224
|
const defaultSettings = pullSettings(defaultCapabilities);
|
|
113
225
|
jsonwpCaps = _.cloneDeep(jsonwpCaps);
|
|
114
|
-
const jwpSettings =
|
|
226
|
+
const jwpSettings = {...defaultSettings, ...pullSettings(jsonwpCaps)};
|
|
115
227
|
w3cCapabilities = _.cloneDeep(w3cCapabilities);
|
|
116
228
|
// It is possible that the client only provides caps using JSONWP standard,
|
|
117
229
|
// although firstMatch/alwaysMatch properties are still present.
|
|
118
230
|
// In such case we assume the client understands W3C protocol and merge the given
|
|
119
231
|
// JSONWP caps to W3C caps
|
|
120
|
-
const w3cSettings =
|
|
121
|
-
|
|
122
|
-
|
|
232
|
+
const w3cSettings = {
|
|
233
|
+
...jwpSettings,
|
|
234
|
+
...pullSettings((w3cCapabilities ?? {}).alwaysMatch ?? {}),
|
|
235
|
+
};
|
|
236
|
+
for (const firstMatchEntry of (w3cCapabilities ?? {}).firstMatch ?? []) {
|
|
123
237
|
Object.assign(w3cSettings, pullSettings(firstMatchEntry));
|
|
124
238
|
}
|
|
125
239
|
|
|
@@ -134,9 +248,10 @@ class AppiumDriver extends BaseDriver {
|
|
|
134
248
|
defaultCapabilities
|
|
135
249
|
);
|
|
136
250
|
|
|
137
|
-
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities
|
|
251
|
+
const {desiredCaps, processedJsonwpCapabilities, processedW3CCapabilities} =
|
|
252
|
+
/** @type {import('./utils').ParsedDriverCaps} */ (parsedCaps);
|
|
138
253
|
protocol = parsedCaps.protocol;
|
|
139
|
-
|
|
254
|
+
const error = /** @type {import('./utils').InvalidCaps} */ (parsedCaps).error;
|
|
140
255
|
// If the parsing of the caps produced an error, throw it in here
|
|
141
256
|
if (error) {
|
|
142
257
|
throw error;
|
|
@@ -144,87 +259,118 @@ class AppiumDriver extends BaseDriver {
|
|
|
144
259
|
|
|
145
260
|
const {
|
|
146
261
|
driver: InnerDriver,
|
|
147
|
-
version: driverVersion
|
|
148
|
-
|
|
149
|
-
this.
|
|
262
|
+
version: driverVersion,
|
|
263
|
+
driverName,
|
|
264
|
+
} = this.driverConfig.findMatchingDriver(desiredCaps);
|
|
265
|
+
this.printNewSessionAnnouncement(InnerDriver.name, driverVersion, InnerDriver.baseVersion);
|
|
150
266
|
|
|
151
267
|
if (this.args.sessionOverride) {
|
|
152
268
|
await this.deleteAllSessions();
|
|
153
269
|
}
|
|
154
270
|
|
|
155
|
-
|
|
156
|
-
|
|
271
|
+
/**
|
|
272
|
+
* @type {DriverData[]}
|
|
273
|
+
*/
|
|
274
|
+
let runningDriversData = [];
|
|
275
|
+
/**
|
|
276
|
+
* @type {DriverData[]}
|
|
277
|
+
*/
|
|
278
|
+
let otherPendingDriversData = [];
|
|
279
|
+
|
|
280
|
+
const driverInstance = new InnerDriver(this.args, true);
|
|
157
281
|
|
|
158
282
|
// We want to assign security values directly on the driver. The driver
|
|
159
283
|
// should not read security values from `this.opts` because those values
|
|
160
284
|
// could have been set by a malicious user via capabilities, whereas we
|
|
161
285
|
// want a guarantee the values were set by the appium server admin
|
|
162
286
|
if (this.args.relaxedSecurityEnabled) {
|
|
163
|
-
log.info(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
287
|
+
this.log.info(
|
|
288
|
+
`Applying relaxed security to '${InnerDriver.name}' as per ` +
|
|
289
|
+
`server command line argument. All insecure features will be ` +
|
|
290
|
+
`enabled unless explicitly disabled by --deny-insecure`
|
|
291
|
+
);
|
|
292
|
+
driverInstance.relaxedSecurityEnabled = true;
|
|
167
293
|
}
|
|
168
294
|
|
|
169
295
|
if (!_.isEmpty(this.args.denyInsecure)) {
|
|
170
|
-
log.info('Explicitly preventing use of insecure features:');
|
|
171
|
-
this.args.denyInsecure.map((a) => log.info(` ${a}`));
|
|
172
|
-
|
|
296
|
+
this.log.info('Explicitly preventing use of insecure features:');
|
|
297
|
+
this.args.denyInsecure.map((a) => this.log.info(` ${a}`));
|
|
298
|
+
driverInstance.denyInsecure = this.args.denyInsecure;
|
|
173
299
|
}
|
|
174
300
|
|
|
175
301
|
if (!_.isEmpty(this.args.allowInsecure)) {
|
|
176
|
-
log.info('Explicitly enabling use of insecure features:');
|
|
177
|
-
this.args.allowInsecure.map((a) => log.info(` ${a}`));
|
|
178
|
-
|
|
302
|
+
this.log.info('Explicitly enabling use of insecure features:');
|
|
303
|
+
this.args.allowInsecure.map((a) => this.log.info(` ${a}`));
|
|
304
|
+
driverInstance.allowInsecure = this.args.allowInsecure;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Likewise, any driver-specific CLI args that were passed in should be assigned directly to
|
|
308
|
+
// the driver so that they cannot be mimicked by a malicious user sending in capabilities
|
|
309
|
+
const cliArgs = this.getCliArgsForDriver(driverName);
|
|
310
|
+
if (!_.isEmpty(cliArgs)) {
|
|
311
|
+
driverInstance.cliArgs = cliArgs;
|
|
179
312
|
}
|
|
180
313
|
|
|
181
314
|
// This assignment is required for correct web sockets functionality inside the driver
|
|
182
|
-
|
|
315
|
+
// Drivers/plugins might also want to know where they are hosted
|
|
316
|
+
|
|
317
|
+
// XXX: temporary hack to work around #16747
|
|
318
|
+
driverInstance.server = this.server;
|
|
319
|
+
driverInstance.serverHost = this.args.address;
|
|
320
|
+
driverInstance.serverPort = this.args.port;
|
|
321
|
+
driverInstance.serverPath = this.args.basePath;
|
|
322
|
+
|
|
183
323
|
try {
|
|
184
|
-
runningDriversData = await this.curSessionDataForDriver(InnerDriver);
|
|
324
|
+
runningDriversData = (await this.curSessionDataForDriver(InnerDriver)) ?? [];
|
|
185
325
|
} catch (e) {
|
|
186
326
|
throw new errors.SessionNotCreatedError(e.message);
|
|
187
327
|
}
|
|
188
328
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
189
329
|
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
|
|
190
|
-
otherPendingDriversData =
|
|
191
|
-
|
|
330
|
+
otherPendingDriversData = _.compact(
|
|
331
|
+
this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData)
|
|
332
|
+
);
|
|
333
|
+
this.pendingDrivers[InnerDriver.name].push(driverInstance);
|
|
192
334
|
});
|
|
193
335
|
|
|
194
336
|
try {
|
|
195
|
-
[innerSessionId, dCaps] = await
|
|
337
|
+
[innerSessionId, dCaps] = await driverInstance.createSession(
|
|
196
338
|
processedJsonwpCapabilities,
|
|
197
339
|
reqCaps,
|
|
198
340
|
processedW3CCapabilities,
|
|
199
341
|
[...runningDriversData, ...otherPendingDriversData]
|
|
200
342
|
);
|
|
201
|
-
protocol =
|
|
202
|
-
|
|
203
|
-
this.sessions[innerSessionId] = d;
|
|
204
|
-
});
|
|
343
|
+
protocol = driverInstance.protocol;
|
|
344
|
+
this.sessions[innerSessionId] = driverInstance;
|
|
205
345
|
} finally {
|
|
206
346
|
await pendingDriversGuard.acquire(AppiumDriver.name, () => {
|
|
207
|
-
_.pull(this.pendingDrivers[InnerDriver.name],
|
|
347
|
+
_.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
|
|
208
348
|
});
|
|
209
349
|
}
|
|
210
350
|
|
|
211
|
-
this.attachUnexpectedShutdownHandler(
|
|
351
|
+
this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
|
|
212
352
|
|
|
213
|
-
log.info(
|
|
214
|
-
|
|
353
|
+
this.log.info(
|
|
354
|
+
`New ${InnerDriver.name} session created successfully, session ` +
|
|
355
|
+
`${innerSessionId} added to master session list`
|
|
356
|
+
);
|
|
215
357
|
|
|
216
358
|
// set the New Command Timeout for the inner driver
|
|
217
|
-
|
|
359
|
+
driverInstance.startNewCommandTimeout();
|
|
218
360
|
|
|
219
361
|
// apply initial values to Appium settings (if provided)
|
|
220
|
-
if (
|
|
221
|
-
log.info(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
362
|
+
if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
|
|
363
|
+
this.log.info(
|
|
364
|
+
`Applying the initial values to Appium settings parsed from W3C caps: ` +
|
|
365
|
+
JSON.stringify(w3cSettings)
|
|
366
|
+
);
|
|
367
|
+
await driverInstance.updateSettings(w3cSettings);
|
|
368
|
+
} else if (driverInstance.isMjsonwpProtocol() && !_.isEmpty(jwpSettings)) {
|
|
369
|
+
this.log.info(
|
|
370
|
+
`Applying the initial values to Appium settings parsed from MJSONWP caps: ` +
|
|
371
|
+
JSON.stringify(jwpSettings)
|
|
372
|
+
);
|
|
373
|
+
await driverInstance.updateSettings(jwpSettings);
|
|
228
374
|
}
|
|
229
375
|
} catch (error) {
|
|
230
376
|
return {
|
|
@@ -235,86 +381,114 @@ class AppiumDriver extends BaseDriver {
|
|
|
235
381
|
|
|
236
382
|
return {
|
|
237
383
|
protocol,
|
|
238
|
-
value: [innerSessionId, dCaps, protocol]
|
|
384
|
+
value: [innerSessionId, dCaps, protocol],
|
|
239
385
|
};
|
|
240
386
|
}
|
|
241
387
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
388
|
+
/**
|
|
389
|
+
*
|
|
390
|
+
* @param {Driver} driver
|
|
391
|
+
* @param {string} innerSessionId
|
|
392
|
+
*/
|
|
393
|
+
attachUnexpectedShutdownHandler(driver, innerSessionId) {
|
|
394
|
+
const onShutdown = (cause = new Error('Unknown error')) => {
|
|
395
|
+
this.log.warn(`Ending session, cause was '${cause.message}'`);
|
|
396
|
+
|
|
397
|
+
if (this.sessionPlugins[innerSessionId]) {
|
|
398
|
+
for (const plugin of this.sessionPlugins[innerSessionId]) {
|
|
399
|
+
if (_.isFunction(plugin.onUnexpectedShutdown)) {
|
|
400
|
+
this.log.debug(
|
|
401
|
+
`Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`
|
|
402
|
+
);
|
|
403
|
+
try {
|
|
404
|
+
plugin.onUnexpectedShutdown(driver, cause);
|
|
405
|
+
} catch (e) {
|
|
406
|
+
this.log.warn(
|
|
407
|
+
`Got an error when running plugin ${plugin.name} shutdown handler: ${e}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
this.log.debug(`Plugin ${plugin.name} does not define an unexpected shutdown handler`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
this.log.info(`Removing session '${innerSessionId}' from our master session list`);
|
|
246
417
|
delete this.sessions[innerSessionId];
|
|
418
|
+
delete this.sessionPlugins[innerSessionId];
|
|
247
419
|
};
|
|
248
420
|
|
|
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);
|
|
421
|
+
if (_.isFunction(driver.onUnexpectedShutdown)) {
|
|
422
|
+
driver.onUnexpectedShutdown(onShutdown);
|
|
270
423
|
} else {
|
|
271
|
-
log.warn(
|
|
272
|
-
`
|
|
424
|
+
this.log.warn(
|
|
425
|
+
`Failed to attach the unexpected shutdown listener. ` +
|
|
426
|
+
`Is 'onUnexpectedShutdown' method available for '${driver.constructor.name}'?`
|
|
427
|
+
);
|
|
273
428
|
}
|
|
274
429
|
}
|
|
275
430
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
431
|
+
/**
|
|
432
|
+
*
|
|
433
|
+
* @param {DriverClass} InnerDriver
|
|
434
|
+
* @returns {Promise<DriverData[]>}}
|
|
435
|
+
*/
|
|
436
|
+
// eslint-disable-next-line require-await
|
|
437
|
+
async curSessionDataForDriver(InnerDriver) {
|
|
438
|
+
const data = _.compact(
|
|
439
|
+
_.values(this.sessions)
|
|
440
|
+
.filter((s) => s.constructor.name === InnerDriver.name)
|
|
441
|
+
.map((s) => s.driverData)
|
|
442
|
+
);
|
|
443
|
+
for (const datum of data) {
|
|
282
444
|
if (!datum) {
|
|
283
|
-
throw new Error(
|
|
284
|
-
|
|
285
|
-
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Problem getting session data for driver type ` +
|
|
447
|
+
`${InnerDriver.name}; does it implement 'get driverData'?`
|
|
448
|
+
);
|
|
286
449
|
}
|
|
287
450
|
}
|
|
288
451
|
return data;
|
|
289
452
|
}
|
|
290
453
|
|
|
291
|
-
|
|
454
|
+
/**
|
|
455
|
+
* @param {string} sessionId
|
|
456
|
+
*/
|
|
457
|
+
async deleteSession(sessionId) {
|
|
292
458
|
let protocol;
|
|
293
459
|
try {
|
|
294
|
-
let otherSessionsData
|
|
295
|
-
|
|
296
|
-
await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
|
460
|
+
let otherSessionsData;
|
|
461
|
+
const dstSession = await sessionsListGuard.acquire(AppiumDriver.name, () => {
|
|
297
462
|
if (!this.sessions[sessionId]) {
|
|
298
463
|
return;
|
|
299
464
|
}
|
|
300
465
|
const curConstructorName = this.sessions[sessionId].constructor.name;
|
|
301
466
|
otherSessionsData = _.toPairs(this.sessions)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
467
|
+
.filter(
|
|
468
|
+
([key, value]) => value.constructor.name === curConstructorName && key !== sessionId
|
|
469
|
+
)
|
|
470
|
+
.map(([, value]) => value.driverData);
|
|
471
|
+
const dstSession = this.sessions[sessionId];
|
|
305
472
|
protocol = dstSession.protocol;
|
|
306
|
-
log.info(`Removing session ${sessionId} from our master session list`);
|
|
473
|
+
this.log.info(`Removing session ${sessionId} from our master session list`);
|
|
307
474
|
// regardless of whether the deleteSession completes successfully or not
|
|
308
475
|
// make the session unavailable, because who knows what state it might
|
|
309
476
|
// be in otherwise
|
|
310
477
|
delete this.sessions[sessionId];
|
|
478
|
+
delete this.sessionPlugins[sessionId];
|
|
479
|
+
return dstSession;
|
|
311
480
|
});
|
|
481
|
+
// this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would
|
|
482
|
+
// throw anyway.
|
|
483
|
+
if (!dstSession) {
|
|
484
|
+
throw new Error('Session not found');
|
|
485
|
+
}
|
|
312
486
|
return {
|
|
313
487
|
protocol,
|
|
314
488
|
value: await dstSession.deleteSession(sessionId, otherSessionsData),
|
|
315
489
|
};
|
|
316
490
|
} catch (e) {
|
|
317
|
-
log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
|
|
491
|
+
this.log.error(`Had trouble ending session ${sessionId}: ${e.message}`);
|
|
318
492
|
return {
|
|
319
493
|
protocol,
|
|
320
494
|
error: e,
|
|
@@ -322,38 +496,89 @@ class AppiumDriver extends BaseDriver {
|
|
|
322
496
|
}
|
|
323
497
|
}
|
|
324
498
|
|
|
325
|
-
async deleteAllSessions
|
|
499
|
+
async deleteAllSessions(opts = {}) {
|
|
326
500
|
const sessionsCount = _.size(this.sessions);
|
|
327
501
|
if (0 === sessionsCount) {
|
|
328
|
-
log.debug('There are no active sessions for cleanup');
|
|
502
|
+
this.log.debug('There are no active sessions for cleanup');
|
|
329
503
|
return;
|
|
330
504
|
}
|
|
331
505
|
|
|
332
|
-
const {
|
|
333
|
-
|
|
334
|
-
reason,
|
|
335
|
-
} = opts;
|
|
336
|
-
log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
|
|
506
|
+
const {force = false, reason} = opts;
|
|
507
|
+
this.log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
|
|
337
508
|
const cleanupPromises = force
|
|
338
|
-
? _.values(this.sessions).map((drv) =>
|
|
509
|
+
? _.values(this.sessions).map((drv) =>
|
|
510
|
+
drv.startUnexpectedShutdown(reason && new Error(reason))
|
|
511
|
+
)
|
|
339
512
|
: _.keys(this.sessions).map((id) => this.deleteSession(id));
|
|
340
513
|
for (const cleanupPromise of cleanupPromises) {
|
|
341
514
|
try {
|
|
342
515
|
await cleanupPromise;
|
|
343
516
|
} catch (e) {
|
|
344
|
-
log.debug(e);
|
|
517
|
+
this.log.debug(e);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Get the appropriate plugins for a session (or sessionless plugins)
|
|
524
|
+
*
|
|
525
|
+
* @param {?string} sessionId - the sessionId (or null) to use to find plugins
|
|
526
|
+
* @returns {Array} - array of plugin instances
|
|
527
|
+
*/
|
|
528
|
+
pluginsForSession(sessionId = null) {
|
|
529
|
+
if (sessionId) {
|
|
530
|
+
if (!this.sessionPlugins[sessionId]) {
|
|
531
|
+
this.sessionPlugins[sessionId] = this.createPluginInstances();
|
|
345
532
|
}
|
|
533
|
+
return this.sessionPlugins[sessionId];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
537
|
+
this.sessionlessPlugins = this.createPluginInstances();
|
|
346
538
|
}
|
|
539
|
+
return this.sessionlessPlugins;
|
|
347
540
|
}
|
|
348
541
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
542
|
+
/**
|
|
543
|
+
* To get plugins for a command, we either get the plugin instances associated with the
|
|
544
|
+
* particular command's session, or in the case of sessionless plugins, pull from the set of
|
|
545
|
+
* plugin instances reserved for sessionless commands (and we lazily create plugin instances on
|
|
546
|
+
* first use)
|
|
547
|
+
*
|
|
548
|
+
* @param {string} cmd - the name of the command to find a plugin to handle
|
|
549
|
+
* @param {?string} sessionId - the particular session for which to find a plugin, or null if
|
|
550
|
+
* sessionless
|
|
551
|
+
*/
|
|
552
|
+
pluginsToHandleCmd(cmd, sessionId = null) {
|
|
553
|
+
// to handle a given command, a plugin should either implement that command as a plugin
|
|
554
|
+
// instance method or it should implement a generic 'handle' method
|
|
555
|
+
return this.pluginsForSession(sessionId).filter(
|
|
556
|
+
(p) => _.isFunction(p[cmd]) || _.isFunction(p.handle)
|
|
353
557
|
);
|
|
354
558
|
}
|
|
355
559
|
|
|
356
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Creates instances of all of the enabled Plugin classes
|
|
562
|
+
* @returns {Plugin[]}
|
|
563
|
+
*/
|
|
564
|
+
createPluginInstances() {
|
|
565
|
+
/** @type {Plugin[]} */
|
|
566
|
+
const pluginInstances = [];
|
|
567
|
+
for (const [PluginClass, name] of this.pluginClasses.entries()) {
|
|
568
|
+
const cliArgs = this.getCliArgsForPlugin(name);
|
|
569
|
+
const plugin = new PluginClass(name, cliArgs);
|
|
570
|
+
pluginInstances.push(plugin);
|
|
571
|
+
}
|
|
572
|
+
return pluginInstances;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
*
|
|
577
|
+
* @param {string} cmd
|
|
578
|
+
* @param {...any} args
|
|
579
|
+
* @returns {Promise<{value: any, error?: Error, protocol: string} | import('type-fest').AsyncReturnType<Driver['executeCommand']>>}
|
|
580
|
+
*/
|
|
581
|
+
async executeCommand(cmd, ...args) {
|
|
357
582
|
// We have basically three cases for how to handle commands:
|
|
358
583
|
// 1. handle getStatus (we do this as a special out of band case so it doesn't get added to an
|
|
359
584
|
// execution queue, and can be called while e.g. createSession is in progress)
|
|
@@ -363,28 +588,41 @@ class AppiumDriver extends BaseDriver {
|
|
|
363
588
|
// The tricky part is that because we support command plugins, we need to wrap any of these
|
|
364
589
|
// cases with plugin handling.
|
|
365
590
|
|
|
366
|
-
const isGetStatus = cmd ===
|
|
367
|
-
const isUmbrellaCmd =
|
|
368
|
-
const isSessionCmd =
|
|
591
|
+
const isGetStatus = cmd === GET_STATUS_COMMAND;
|
|
592
|
+
const isUmbrellaCmd = isAppiumDriverCommand(cmd);
|
|
593
|
+
const isSessionCmd = isSessionCommand(cmd);
|
|
369
594
|
|
|
370
|
-
//
|
|
371
|
-
|
|
595
|
+
// if a plugin override proxying for this command and that is why we are here instead of just
|
|
596
|
+
// letting the protocol proxy the command entirely, determine that, get the request object for
|
|
597
|
+
// use later on, then clean up the args
|
|
598
|
+
const reqForProxy = _.last(args)?.reqForProxy;
|
|
599
|
+
if (reqForProxy) {
|
|
600
|
+
args.pop();
|
|
601
|
+
}
|
|
372
602
|
|
|
373
603
|
// first do some error checking. If we're requesting a session command execution, then make
|
|
374
604
|
// sure that session actually exists on the session driver, and set the session driver itself
|
|
375
605
|
let sessionId = null;
|
|
376
606
|
let dstSession = null;
|
|
377
607
|
let protocol = null;
|
|
608
|
+
/** @type {this | ExternalDriver} */
|
|
609
|
+
let driver = this;
|
|
378
610
|
if (isSessionCmd) {
|
|
379
611
|
sessionId = _.last(args);
|
|
380
|
-
dstSession =
|
|
612
|
+
dstSession = this.sessions[sessionId];
|
|
381
613
|
if (!dstSession) {
|
|
382
614
|
throw new Error(`The session with id '${sessionId}' does not exist`);
|
|
383
615
|
}
|
|
384
616
|
// now save the response protocol given that the session driver's protocol might differ
|
|
385
617
|
protocol = dstSession.protocol;
|
|
618
|
+
if (!isUmbrellaCmd) {
|
|
619
|
+
driver = dstSession;
|
|
620
|
+
}
|
|
386
621
|
}
|
|
387
622
|
|
|
623
|
+
// get any plugins which are registered as handling this command
|
|
624
|
+
const plugins = this.pluginsToHandleCmd(cmd, sessionId);
|
|
625
|
+
|
|
388
626
|
// now we define a 'cmdHandledBy' object which will keep track of which plugins have handled this
|
|
389
627
|
// command. we care about this because (a) multiple plugins can handle the same command, and
|
|
390
628
|
// (b) there's no guarantee that a plugin will actually call the next() method which runs the
|
|
@@ -401,11 +639,26 @@ class AppiumDriver extends BaseDriver {
|
|
|
401
639
|
// if we're running with plugins, make sure we log that the default behavior is actually
|
|
402
640
|
// happening so we can tell when the plugin call chain is unwrapping to the default behavior
|
|
403
641
|
// if that's what happens
|
|
404
|
-
plugins.length && log.info(`Executing default handling behavior for command '${cmd}'`);
|
|
642
|
+
plugins.length && this.log.info(`Executing default handling behavior for command '${cmd}'`);
|
|
405
643
|
|
|
406
644
|
// if we make it here, we know that the default behavior is handled
|
|
407
645
|
cmdHandledBy.default = true;
|
|
408
646
|
|
|
647
|
+
if (reqForProxy) {
|
|
648
|
+
// we would have proxied this command had a plugin not handled it, so the default behavior
|
|
649
|
+
// is to do the proxy and retrieve the result internally so it can be passed to the plugin
|
|
650
|
+
// in case it calls 'await next()'. This requires that the driver have defined
|
|
651
|
+
// 'proxyCommand' and not just 'proxyReqRes'.
|
|
652
|
+
if (!dstSession.proxyCommand) {
|
|
653
|
+
throw new NoDriverProxyCommandError();
|
|
654
|
+
}
|
|
655
|
+
return await dstSession.proxyCommand(
|
|
656
|
+
reqForProxy.originalUrl,
|
|
657
|
+
reqForProxy.method,
|
|
658
|
+
reqForProxy.body
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
409
662
|
if (isGetStatus) {
|
|
410
663
|
return await this.getStatus();
|
|
411
664
|
}
|
|
@@ -413,7 +666,7 @@ class AppiumDriver extends BaseDriver {
|
|
|
413
666
|
if (isUmbrellaCmd) {
|
|
414
667
|
// some commands, like deleteSession, we want to make sure to handle on *this* driver,
|
|
415
668
|
// not the platform driver
|
|
416
|
-
return await
|
|
669
|
+
return await BaseDriver.prototype.executeCommand.call(this, cmd, ...args);
|
|
417
670
|
}
|
|
418
671
|
|
|
419
672
|
// here we know that we are executing a session command, and have a valid session driver
|
|
@@ -421,18 +674,39 @@ class AppiumDriver extends BaseDriver {
|
|
|
421
674
|
};
|
|
422
675
|
|
|
423
676
|
// now take our default behavior, wrap it with any number of plugin behaviors, and run it
|
|
424
|
-
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
677
|
+
const wrappedCmd = this.wrapCommandWithPlugins({
|
|
678
|
+
driver,
|
|
679
|
+
cmd,
|
|
680
|
+
args,
|
|
681
|
+
plugins,
|
|
682
|
+
cmdHandledBy,
|
|
683
|
+
next: defaultBehavior,
|
|
684
|
+
});
|
|
425
685
|
const res = await this.executeWrappedCommand({wrappedCmd, protocol});
|
|
426
686
|
|
|
427
687
|
// if we had plugins, make sure to log out the helpful report about which plugins ended up
|
|
428
688
|
// handling the command and which didn't
|
|
429
|
-
|
|
689
|
+
this.logPluginHandlerReport(plugins, {cmd, cmdHandledBy});
|
|
690
|
+
|
|
691
|
+
// And finally, if the command was createSession, we want to migrate any plugins which were
|
|
692
|
+
// previously sessionless to use the new sessionId, so that plugins can share state between
|
|
693
|
+
// their createSession method and other instance methods
|
|
694
|
+
if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
|
|
695
|
+
const sessionId = _.first(res.value);
|
|
696
|
+
this.log.info(
|
|
697
|
+
`Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
|
|
698
|
+
`to session ID ${sessionId}`
|
|
699
|
+
);
|
|
700
|
+
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
|
|
701
|
+
this.sessionlessPlugins = [];
|
|
702
|
+
}
|
|
430
703
|
|
|
431
704
|
return res;
|
|
432
705
|
}
|
|
433
706
|
|
|
434
|
-
wrapCommandWithPlugins
|
|
435
|
-
plugins.length &&
|
|
707
|
+
wrapCommandWithPlugins({driver, cmd, args, next, cmdHandledBy, plugins}) {
|
|
708
|
+
plugins.length &&
|
|
709
|
+
this.log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
|
|
436
710
|
|
|
437
711
|
// now we can go through each plugin and wrap `next` around its own handler, passing the *old*
|
|
438
712
|
// next in so that it can call it if it wants to
|
|
@@ -442,16 +716,25 @@ class AppiumDriver extends BaseDriver {
|
|
|
442
716
|
// evaluated, otherwise we end up with infinite recursion of the last `next` to be defined.
|
|
443
717
|
cmdHandledBy[plugin.name] = false; // we see a new plugin, so add it to the 'cmdHandledBy' object
|
|
444
718
|
next = ((_next) => async () => {
|
|
445
|
-
log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
|
|
719
|
+
this.log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
|
|
446
720
|
cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
|
|
447
|
-
|
|
721
|
+
// first attempt to handle the command via a command-specific handler on the plugin
|
|
722
|
+
if (plugin[cmd]) {
|
|
723
|
+
return await plugin[cmd](_next, driver, ...args);
|
|
724
|
+
}
|
|
725
|
+
// otherwise, call the generic 'handle' method
|
|
726
|
+
return await plugin.handle(_next, driver, cmd, ...args);
|
|
448
727
|
})(next);
|
|
449
728
|
}
|
|
450
729
|
|
|
451
730
|
return next;
|
|
452
731
|
}
|
|
453
732
|
|
|
454
|
-
logPluginHandlerReport
|
|
733
|
+
logPluginHandlerReport(plugins, {cmd, cmdHandledBy}) {
|
|
734
|
+
if (!plugins.length) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
|
|
455
738
|
// at the end of the day, we have an object representing which plugins ended up getting
|
|
456
739
|
// their code run as part of handling this command. Because plugins can choose *not* to
|
|
457
740
|
// pass control to other plugins or to the default driver behavior, this is information
|
|
@@ -461,14 +744,18 @@ class AppiumDriver extends BaseDriver {
|
|
|
461
744
|
const didHandle = Object.keys(cmdHandledBy).filter((k) => cmdHandledBy[k]);
|
|
462
745
|
const didntHandle = Object.keys(cmdHandledBy).filter((k) => !cmdHandledBy[k]);
|
|
463
746
|
if (didntHandle.length > 0) {
|
|
464
|
-
log.info(
|
|
465
|
-
|
|
466
|
-
|
|
747
|
+
this.log.info(
|
|
748
|
+
`Command '${cmd}' was *not* handled by the following behaviours or plugins, even ` +
|
|
749
|
+
`though they were registered to handle it: ${JSON.stringify(didntHandle)}. The ` +
|
|
750
|
+
`command *was* handled by these: ${JSON.stringify(didHandle)}.`
|
|
751
|
+
);
|
|
467
752
|
}
|
|
468
753
|
}
|
|
469
754
|
|
|
470
|
-
async executeWrappedCommand
|
|
471
|
-
let cmdRes,
|
|
755
|
+
async executeWrappedCommand({wrappedCmd, protocol}) {
|
|
756
|
+
let cmdRes,
|
|
757
|
+
cmdErr,
|
|
758
|
+
res = {};
|
|
472
759
|
try {
|
|
473
760
|
// At this point, `wrappedCmd` defines a whole sequence of plugin handlers, culminating in
|
|
474
761
|
// our default handler. Whatever it returns is what we're going to want to send back to the
|
|
@@ -491,18 +778,17 @@ class AppiumDriver extends BaseDriver {
|
|
|
491
778
|
return res;
|
|
492
779
|
}
|
|
493
780
|
|
|
494
|
-
|
|
495
|
-
proxyActive (sessionId) {
|
|
781
|
+
proxyActive(sessionId) {
|
|
496
782
|
const dstSession = this.sessions[sessionId];
|
|
497
783
|
return dstSession && _.isFunction(dstSession.proxyActive) && dstSession.proxyActive(sessionId);
|
|
498
784
|
}
|
|
499
785
|
|
|
500
|
-
getProxyAvoidList
|
|
786
|
+
getProxyAvoidList(sessionId) {
|
|
501
787
|
const dstSession = this.sessions[sessionId];
|
|
502
788
|
return dstSession ? dstSession.getProxyAvoidList() : [];
|
|
503
789
|
}
|
|
504
790
|
|
|
505
|
-
canProxy
|
|
791
|
+
canProxy(sessionId) {
|
|
506
792
|
const dstSession = this.sessions[sessionId];
|
|
507
793
|
return dstSession && dstSession.canProxy(sessionId);
|
|
508
794
|
}
|
|
@@ -510,8 +796,56 @@ class AppiumDriver extends BaseDriver {
|
|
|
510
796
|
|
|
511
797
|
// help decide which commands should be proxied to sub-drivers and which
|
|
512
798
|
// should be handled by this, our umbrella driver
|
|
513
|
-
function isAppiumDriverCommand
|
|
514
|
-
return !isSessionCommand(cmd) || cmd ===
|
|
799
|
+
function isAppiumDriverCommand(cmd) {
|
|
800
|
+
return !isSessionCommand(cmd) || cmd === DELETE_SESSION_COMMAND;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Thrown when Appium tried to proxy a command using a driver's `proxyCommand` method but the
|
|
805
|
+
* method did not exist
|
|
806
|
+
*/
|
|
807
|
+
export class NoDriverProxyCommandError extends Error {
|
|
808
|
+
/**
|
|
809
|
+
* @type {Readonly<string>}
|
|
810
|
+
*/
|
|
811
|
+
code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
812
|
+
|
|
813
|
+
constructor() {
|
|
814
|
+
super(
|
|
815
|
+
`The default behavior for this command was to proxy, but the driver ` +
|
|
816
|
+
`did not have the 'proxyCommand' method defined. To fully support ` +
|
|
817
|
+
`plugins, drivers should have 'proxyCommand' set to a jwpProxy object's ` +
|
|
818
|
+
`'command()' method, in addition to the normal 'proxyReqRes'`
|
|
819
|
+
);
|
|
820
|
+
}
|
|
515
821
|
}
|
|
516
822
|
|
|
517
|
-
export {
|
|
823
|
+
export {AppiumDriver};
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
|
827
|
+
* @typedef {import('@appium/types').Driver} Driver
|
|
828
|
+
* @typedef {import('@appium/types').DriverClass} DriverClass
|
|
829
|
+
* @typedef {import('@appium/types').W3CCapabilities} W3CCapabilities
|
|
830
|
+
* @typedef {import('@appium/types').DriverData} DriverData
|
|
831
|
+
* @typedef {import('@appium/types').ServerArgs} DriverOpts
|
|
832
|
+
* @typedef {import('@appium/types').Constraints} Constraints
|
|
833
|
+
* @typedef {import('@appium/types').AppiumServer} AppiumServer
|
|
834
|
+
* @typedef {import('@appium/types').ExtensionType} ExtensionType
|
|
835
|
+
* @typedef {import('./extension/driver-config').DriverConfig} DriverConfig
|
|
836
|
+
* @typedef {import('@appium/types').Plugin} Plugin
|
|
837
|
+
* @typedef {import('@appium/types').PluginClass} PluginClass
|
|
838
|
+
* @typedef {import('@appium/types').PluginType} PluginType
|
|
839
|
+
* @typedef {import('@appium/types').DriverType} DriverType
|
|
840
|
+
* @typedef {import('@appium/types').SessionHandler<SessionHandlerResult<any[]>,SessionHandlerResult<void>>} SessionHandler
|
|
841
|
+
*/
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Used by {@linkcode AppiumDriver.createSession} and {@linkcode AppiumDriver.deleteSession} to describe
|
|
845
|
+
* result.
|
|
846
|
+
* @template V
|
|
847
|
+
* @typedef SessionHandlerResult
|
|
848
|
+
* @property {V} [value]
|
|
849
|
+
* @property {Error} [error]
|
|
850
|
+
* @property {string} [protocol]
|
|
851
|
+
*/
|