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