appium 3.3.0 → 3.3.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/build/lib/appium.d.ts +147 -205
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +169 -282
- package/build/lib/appium.js.map +1 -1
- package/build/lib/bidi-commands.d.ts.map +1 -1
- package/build/lib/bidi-commands.js +11 -11
- package/build/lib/bidi-commands.js.map +1 -1
- package/build/lib/bootstrap/appium-initializer.d.ts +21 -0
- package/build/lib/bootstrap/appium-initializer.d.ts.map +1 -0
- package/build/lib/bootstrap/appium-initializer.js +146 -0
- package/build/lib/bootstrap/appium-initializer.js.map +1 -0
- package/build/lib/bootstrap/appium-main-runner.d.ts +22 -0
- package/build/lib/bootstrap/appium-main-runner.d.ts.map +1 -0
- package/build/lib/bootstrap/appium-main-runner.js +109 -0
- package/build/lib/bootstrap/appium-main-runner.js.map +1 -0
- package/build/lib/bootstrap/config-file.d.ts +37 -0
- package/build/lib/bootstrap/config-file.d.ts.map +1 -0
- package/build/lib/{config-file.js → bootstrap/config-file.js} +9 -26
- package/build/lib/bootstrap/config-file.js.map +1 -0
- package/build/lib/bootstrap/grid-v3-register.d.ts +20 -0
- package/build/lib/bootstrap/grid-v3-register.d.ts.map +1 -0
- package/build/lib/{grid-register.js → bootstrap/grid-v3-register.js} +28 -13
- package/build/lib/bootstrap/grid-v3-register.js.map +1 -0
- package/build/lib/bootstrap/init-types.d.ts +16 -0
- package/build/lib/bootstrap/init-types.d.ts.map +1 -0
- package/build/lib/bootstrap/init-types.js +3 -0
- package/build/lib/bootstrap/init-types.js.map +1 -0
- package/build/lib/bootstrap/main-helpers.d.ts +55 -0
- package/build/lib/bootstrap/main-helpers.d.ts.map +1 -0
- package/build/lib/bootstrap/main-helpers.js +187 -0
- package/build/lib/bootstrap/main-helpers.js.map +1 -0
- package/build/lib/bootstrap/node-helpers.d.ts +32 -0
- package/build/lib/bootstrap/node-helpers.d.ts.map +1 -0
- package/build/lib/bootstrap/node-helpers.js +201 -0
- package/build/lib/bootstrap/node-helpers.js.map +1 -0
- package/build/lib/bootstrap/startup-config.d.ts +22 -0
- package/build/lib/bootstrap/startup-config.d.ts.map +1 -0
- package/build/lib/bootstrap/startup-config.js +111 -0
- package/build/lib/bootstrap/startup-config.js.map +1 -0
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +9 -9
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts +95 -95
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +18 -18
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/extension.d.ts +1 -1
- package/build/lib/cli/extension.d.ts.map +1 -1
- package/build/lib/cli/extension.js +5 -5
- package/build/lib/cli/extension.js.map +1 -1
- package/build/lib/cli/parser.d.ts +8 -8
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +49 -49
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/setup-command.js +6 -6
- package/build/lib/cli/setup-command.js.map +1 -1
- package/build/lib/cli/utils.d.ts +17 -17
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +29 -29
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/doctor/doctor.d.ts +2 -2
- package/build/lib/doctor/doctor.d.ts.map +1 -1
- package/build/lib/doctor/doctor.js +6 -6
- package/build/lib/doctor/doctor.js.map +1 -1
- package/build/lib/extension/driver-config.d.ts +18 -77
- package/build/lib/extension/driver-config.d.ts.map +1 -1
- package/build/lib/extension/driver-config.js +37 -125
- package/build/lib/extension/driver-config.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts +103 -210
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +180 -342
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/index.d.ts +12 -29
- package/build/lib/extension/index.d.ts.map +1 -1
- package/build/lib/extension/index.js +33 -75
- package/build/lib/extension/index.js.map +1 -1
- package/build/lib/extension/manifest-migrations.d.ts +3 -20
- package/build/lib/extension/manifest-migrations.d.ts.map +1 -1
- package/build/lib/extension/manifest-migrations.js +20 -101
- package/build/lib/extension/manifest-migrations.js.map +1 -1
- package/build/lib/extension/manifest.d.ts +61 -107
- package/build/lib/extension/manifest.d.ts.map +1 -1
- package/build/lib/extension/manifest.js +181 -356
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/extension/package-changed.d.ts +1 -3
- package/build/lib/extension/package-changed.d.ts.map +1 -1
- package/build/lib/extension/package-changed.js +8 -15
- package/build/lib/extension/package-changed.js.map +1 -1
- package/build/lib/extension/plugin-config.d.ts +10 -52
- package/build/lib/extension/plugin-config.d.ts.map +1 -1
- package/build/lib/extension/plugin-config.js +11 -63
- package/build/lib/extension/plugin-config.js.map +1 -1
- package/build/lib/helpers/build.d.ts +22 -0
- package/build/lib/helpers/build.d.ts.map +1 -0
- package/build/lib/helpers/build.js +109 -0
- package/build/lib/helpers/build.js.map +1 -0
- package/build/lib/helpers/capability.d.ts +38 -0
- package/build/lib/helpers/capability.d.ts.map +1 -0
- package/build/lib/helpers/capability.js +128 -0
- package/build/lib/helpers/capability.js.map +1 -0
- package/build/lib/helpers/network.d.ts +14 -0
- package/build/lib/helpers/network.d.ts.map +1 -0
- package/build/lib/helpers/network.js +35 -0
- package/build/lib/helpers/network.js.map +1 -0
- package/build/lib/insecure-features.js +6 -6
- package/build/lib/insecure-features.js.map +1 -1
- package/build/lib/inspector-commands.d.ts +6 -0
- package/build/lib/inspector-commands.d.ts.map +1 -1
- package/build/lib/inspector-commands.js +6 -0
- package/build/lib/inspector-commands.js.map +1 -1
- package/build/lib/logger.d.ts +2 -3
- package/build/lib/logger.d.ts.map +1 -1
- package/build/lib/logger.js +2 -3
- package/build/lib/logger.js.map +1 -1
- package/build/lib/main.d.ts +15 -58
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +25 -425
- package/build/lib/main.js.map +1 -1
- package/build/lib/schema/cli-args-guards.d.ts +34 -0
- package/build/lib/schema/cli-args-guards.d.ts.map +1 -0
- package/build/lib/schema/cli-args-guards.js +49 -0
- package/build/lib/schema/cli-args-guards.js.map +1 -0
- package/build/lib/schema/cli-args.js +2 -2
- package/build/lib/schema/cli-args.js.map +1 -1
- package/build/lib/schema/format-errors.d.ts +28 -0
- package/build/lib/schema/format-errors.d.ts.map +1 -0
- package/build/lib/schema/format-errors.js +29 -0
- package/build/lib/schema/format-errors.js.map +1 -0
- package/build/lib/schema/index.d.ts +2 -0
- package/build/lib/schema/index.d.ts.map +1 -1
- package/build/lib/schema/index.js +2 -0
- package/build/lib/schema/index.js.map +1 -1
- package/build/lib/schema/schema.d.ts +15 -15
- package/build/lib/schema/schema.d.ts.map +1 -1
- package/build/lib/schema/schema.js +37 -37
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.d.ts +0 -81
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +1 -248
- package/build/lib/utils.js.map +1 -1
- package/lib/{appium.js → appium.ts} +297 -341
- package/lib/bidi-commands.ts +10 -14
- package/lib/bootstrap/appium-initializer.ts +212 -0
- package/lib/bootstrap/appium-main-runner.ts +172 -0
- package/lib/{config-file.ts → bootstrap/config-file.ts} +29 -63
- package/lib/{grid-register.ts → bootstrap/grid-v3-register.ts} +35 -35
- package/lib/bootstrap/init-types.ts +31 -0
- package/lib/bootstrap/main-helpers.ts +223 -0
- package/lib/bootstrap/node-helpers.ts +180 -0
- package/lib/bootstrap/startup-config.ts +143 -0
- package/lib/cli/args.ts +10 -10
- package/lib/cli/extension-command.ts +132 -132
- package/lib/cli/extension.ts +7 -7
- package/lib/cli/parser.ts +50 -50
- package/lib/cli/setup-command.ts +2 -2
- package/lib/cli/utils.ts +33 -33
- package/lib/doctor/doctor.ts +8 -8
- package/lib/extension/driver-config.ts +165 -0
- package/lib/extension/{extension-config.js → extension-config.ts} +291 -405
- package/lib/extension/index.ts +143 -0
- package/lib/extension/manifest-migrations.ts +57 -0
- package/lib/extension/manifest.ts +369 -0
- package/lib/extension/{package-changed.js → package-changed.ts} +9 -18
- package/lib/extension/plugin-config.ts +62 -0
- package/lib/helpers/build.ts +111 -0
- package/lib/helpers/capability.ts +171 -0
- package/lib/helpers/network.ts +30 -0
- package/lib/insecure-features.ts +1 -1
- package/lib/inspector-commands.ts +6 -1
- package/lib/{logger.js → logger.ts} +1 -2
- package/lib/main.ts +60 -0
- package/lib/schema/cli-args-guards.ts +67 -0
- package/lib/schema/cli-args.ts +1 -1
- package/lib/schema/format-errors.ts +43 -0
- package/lib/schema/index.ts +2 -0
- package/lib/schema/schema.ts +51 -52
- package/lib/utils.ts +0 -331
- package/package.json +12 -13
- package/scripts/autoinstall-extensions.js +3 -0
- package/build/lib/config-file.d.ts +0 -57
- package/build/lib/config-file.d.ts.map +0 -1
- package/build/lib/config-file.js.map +0 -1
- package/build/lib/config.d.ts +0 -68
- package/build/lib/config.d.ts.map +0 -1
- package/build/lib/config.js +0 -358
- package/build/lib/config.js.map +0 -1
- package/build/lib/grid-register.d.ts +0 -35
- package/build/lib/grid-register.d.ts.map +0 -1
- package/build/lib/grid-register.js.map +0 -1
- package/lib/config.ts +0 -377
- package/lib/extension/driver-config.js +0 -245
- package/lib/extension/index.js +0 -169
- package/lib/extension/manifest-migrations.js +0 -136
- package/lib/extension/manifest.js +0 -550
- package/lib/extension/plugin-config.js +0 -112
- package/lib/main.js +0 -545
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
2
|
+
import type WebSocket from 'ws';
|
|
3
|
+
import type {
|
|
4
|
+
AppiumServer,
|
|
5
|
+
DriverCaps,
|
|
6
|
+
DriverData,
|
|
7
|
+
DriverOpts,
|
|
8
|
+
ExternalDriver,
|
|
9
|
+
Plugin,
|
|
10
|
+
PluginClass,
|
|
11
|
+
PluginCommand,
|
|
12
|
+
Protocol,
|
|
13
|
+
RouteMatcher,
|
|
14
|
+
StringRecord,
|
|
15
|
+
TimestampedMultiSessionData,
|
|
16
|
+
W3CDriverCaps,
|
|
17
|
+
} from '@appium/types';
|
|
3
18
|
import {
|
|
4
19
|
BaseDriver,
|
|
5
20
|
DriverCore,
|
|
21
|
+
type ExtensionCore,
|
|
6
22
|
errors,
|
|
7
23
|
isSessionCommand,
|
|
8
24
|
PROTOCOLS,
|
|
@@ -16,16 +32,22 @@ import {
|
|
|
16
32
|
generateDriverLogPrefix,
|
|
17
33
|
isW3cCaps,
|
|
18
34
|
} from '@appium/base-driver';
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
35
|
+
import {APPIUM_VER, getBuildInfo, updateBuildInfo} from './helpers/build';
|
|
36
|
+
import {
|
|
37
|
+
makeNonW3cCapsError,
|
|
38
|
+
parseCapsForInnerDriver,
|
|
39
|
+
pullSettings,
|
|
40
|
+
type ParsedDriverCaps,
|
|
41
|
+
} from './helpers/capability';
|
|
21
42
|
import {util} from '@appium/support';
|
|
22
43
|
import {getDefaultsForExtension} from './schema';
|
|
23
44
|
import {DRIVER_TYPE, BIDI_BASE_PATH, SESSION_DISCOVERY_FEATURE} from './constants';
|
|
24
45
|
import * as bidiCommands from './bidi-commands';
|
|
25
46
|
import * as insecureFeatures from './insecure-features';
|
|
26
47
|
import * as inspectorCommands from './inspector-commands';
|
|
48
|
+
import type {DriverConfig} from './extension/driver-config';
|
|
27
49
|
|
|
28
|
-
const desiredCapabilityConstraints =
|
|
50
|
+
const desiredCapabilityConstraints = {
|
|
29
51
|
automationName: {
|
|
30
52
|
presence: true,
|
|
31
53
|
isString: true,
|
|
@@ -34,81 +56,76 @@ const desiredCapabilityConstraints = /** @type {const} */ ({
|
|
|
34
56
|
presence: true,
|
|
35
57
|
isString: true,
|
|
36
58
|
},
|
|
37
|
-
}
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
export type AppiumDriverConstraints = typeof desiredCapabilityConstraints;
|
|
62
|
+
export type W3CAppiumDriverCaps = W3CDriverCaps<AppiumDriverConstraints>;
|
|
63
|
+
|
|
64
|
+
/** Result shape for umbrella {@link AppiumDriver.createSession} / {@link AppiumDriver.deleteSession}. */
|
|
65
|
+
interface SessionHandlerResult<V = unknown> {
|
|
66
|
+
value?: V;
|
|
67
|
+
error?: Error;
|
|
68
|
+
protocol?: string;
|
|
69
|
+
}
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
|
|
71
|
+
type SessionHandlerCreateResult = SessionHandlerResult<
|
|
72
|
+
[string, DriverCaps<AppiumDriverConstraints>, string | undefined]
|
|
73
|
+
>;
|
|
74
|
+
|
|
75
|
+
type SessionHandlerDeleteResult = SessionHandlerResult<void>;
|
|
41
76
|
|
|
42
77
|
/**
|
|
43
|
-
*
|
|
78
|
+
* Umbrella driver: owns the session table, loads platform drivers and plugins, and routes
|
|
79
|
+
* commands to the right session driver or plugin chain.
|
|
44
80
|
*/
|
|
45
|
-
class AppiumDriver extends DriverCore {
|
|
46
|
-
|
|
47
|
-
* Access to sessions list must be guarded with a Semaphore, because
|
|
48
|
-
* it might be changed by other async calls at any time
|
|
49
|
-
* It is not recommended to access this property directly from the outside
|
|
50
|
-
* @type {Record<string,ExternalDriver>}
|
|
51
|
-
*/
|
|
52
|
-
sessions;
|
|
81
|
+
export class AppiumDriver extends DriverCore<AppiumDriverConstraints> {
|
|
82
|
+
readonly sessions: Record<string, ExternalDriver> = {};
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
* Access to pending drivers list must be guarded with a Semaphore, because
|
|
56
|
-
* it might be changed by other async calls at any time
|
|
57
|
-
* It is not recommended to access this property directly from the outside
|
|
58
|
-
* @type {Record<string,ExternalDriver[]>}
|
|
59
|
-
*/
|
|
60
|
-
pendingDrivers;
|
|
84
|
+
readonly pendingDrivers: Record<string, ExternalDriver[]> = {};
|
|
61
85
|
|
|
62
86
|
/**
|
|
63
|
-
*
|
|
64
|
-
* `AppiumDriver` does not set and observe its own timeouts; individual
|
|
65
|
-
* sessions (managed drivers) do instead.
|
|
87
|
+
* The umbrella driver does not observe its own command timeout; inner session drivers do.
|
|
66
88
|
*/
|
|
67
|
-
newCommandTimeoutMs;
|
|
89
|
+
override newCommandTimeoutMs = 0;
|
|
68
90
|
|
|
69
|
-
/**
|
|
70
|
-
|
|
71
|
-
* @type {Map<PluginClass,string>}
|
|
72
|
-
*/
|
|
73
|
-
pluginClasses;
|
|
91
|
+
/** Filled during server bootstrap; keep this map instance (do not reassign). */
|
|
92
|
+
readonly pluginClasses = new Map<PluginClass, string>();
|
|
74
93
|
|
|
75
|
-
|
|
76
|
-
* map of sessions to actual plugin instances per session
|
|
77
|
-
* @type {Record<string,InstanceType<PluginClass>[]>}
|
|
78
|
-
*/
|
|
79
|
-
sessionPlugins;
|
|
94
|
+
readonly sessionPlugins: Record<string, Plugin[]> = {};
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
* some commands are sessionless, so we need a set of plugins for them
|
|
83
|
-
* @type {InstanceType<PluginClass>[]}
|
|
84
|
-
*/
|
|
85
|
-
sessionlessPlugins;
|
|
96
|
+
sessionlessPlugins: Plugin[] = [];
|
|
86
97
|
|
|
87
|
-
/**
|
|
88
|
-
driverConfig;
|
|
98
|
+
/** Set during server init before listen; tests may assign a mock after construction. */
|
|
99
|
+
driverConfig!: DriverConfig;
|
|
89
100
|
|
|
90
|
-
/**
|
|
91
|
-
server;
|
|
101
|
+
/** Set when the HTTP server is created in server bootstrap. */
|
|
102
|
+
server!: AppiumServer;
|
|
92
103
|
|
|
93
|
-
|
|
94
|
-
bidiSockets;
|
|
104
|
+
readonly bidiSockets: Record<string, WebSocket[]> = {};
|
|
95
105
|
|
|
96
|
-
|
|
97
|
-
bidiProxyClients;
|
|
106
|
+
readonly bidiProxyClients: Record<string, WebSocket> = {};
|
|
98
107
|
|
|
99
|
-
|
|
100
|
-
* @type {AppiumDriverConstraints}
|
|
101
|
-
* @readonly
|
|
102
|
-
*/
|
|
103
|
-
desiredCapConstraints;
|
|
108
|
+
readonly desiredCapConstraints = desiredCapabilityConstraints;
|
|
104
109
|
|
|
105
|
-
|
|
106
|
-
|
|
110
|
+
readonly args!: DriverOpts<AppiumDriverConstraints>;
|
|
111
|
+
|
|
112
|
+
onBidiConnection = bidiCommands.onBidiConnection;
|
|
113
|
+
onBidiMessage = bidiCommands.onBidiMessage;
|
|
114
|
+
onBidiServerError = bidiCommands.onBidiServerError;
|
|
115
|
+
cleanupBidiSockets = bidiCommands.cleanupBidiSockets;
|
|
116
|
+
|
|
117
|
+
configureGlobalFeatures = insecureFeatures.configureGlobalFeatures;
|
|
118
|
+
configureDriverFeatures = insecureFeatures.configureDriverFeatures;
|
|
119
|
+
|
|
120
|
+
listCommands = inspectorCommands.listCommands;
|
|
121
|
+
listExtensions = inspectorCommands.listExtensions;
|
|
122
|
+
|
|
123
|
+
private _isShuttingDown = false;
|
|
107
124
|
|
|
108
125
|
/**
|
|
109
|
-
* @param
|
|
126
|
+
* @param opts - CLI/server options (address, port, security, default capabilities, etc.)
|
|
110
127
|
*/
|
|
111
|
-
constructor(opts) {
|
|
128
|
+
constructor(opts: DriverOpts<AppiumDriverConstraints>) {
|
|
112
129
|
// It is necessary to set `--tmp` here since it should be set to
|
|
113
130
|
// process.env.APPIUM_TMP_DIR once at an initial point in the Appium lifecycle.
|
|
114
131
|
// The process argument will be referenced by BaseDriver.
|
|
@@ -120,46 +137,40 @@ class AppiumDriver extends DriverCore {
|
|
|
120
137
|
super(opts);
|
|
121
138
|
|
|
122
139
|
this.args = {...opts};
|
|
123
|
-
this.sessions = {};
|
|
124
|
-
this.pendingDrivers = {};
|
|
125
|
-
this.newCommandTimeoutMs = 0;
|
|
126
|
-
this.pluginClasses = new Map();
|
|
127
|
-
this.sessionPlugins = {};
|
|
128
|
-
this.sessionlessPlugins = [];
|
|
129
|
-
this.bidiSockets = {};
|
|
130
|
-
this.bidiProxyClients = {};
|
|
131
|
-
this.desiredCapConstraints = desiredCapabilityConstraints;
|
|
132
|
-
this._isShuttingDown = false;
|
|
133
140
|
|
|
134
141
|
// allow this to happen in the background, so no `await`
|
|
135
|
-
(async () => {
|
|
142
|
+
void (async () => {
|
|
136
143
|
try {
|
|
137
144
|
await updateBuildInfo();
|
|
138
|
-
} catch (e) {
|
|
145
|
+
} catch (e: unknown) {
|
|
139
146
|
// make sure we catch any possible errors to avoid unhandled rejections
|
|
140
|
-
|
|
147
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
148
|
+
this.log.debug(`Cannot fetch Appium build info: ${msg}`);
|
|
141
149
|
}
|
|
142
150
|
})();
|
|
143
151
|
}
|
|
144
152
|
|
|
145
|
-
/**
|
|
146
|
-
|
|
147
|
-
*/
|
|
148
|
-
get isCommandsQueueEnabled() {
|
|
153
|
+
/** The umbrella driver does not queue commands; inner session drivers may. */
|
|
154
|
+
get isCommandsQueueEnabled(): boolean {
|
|
149
155
|
return false;
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
|
|
158
|
+
/** Whether a non-null session id is registered on this server. */
|
|
159
|
+
override sessionExists(sessionId: string): boolean {
|
|
153
160
|
const dstSession = this.sessions[sessionId];
|
|
154
|
-
return dstSession && dstSession.sessionId !== null;
|
|
161
|
+
return Boolean(dstSession && dstSession.sessionId !== null);
|
|
155
162
|
}
|
|
156
163
|
|
|
157
|
-
|
|
158
|
-
|
|
164
|
+
/** Active automation driver for the session, or `null` if unknown. */
|
|
165
|
+
override driverForSession(sessionId: string): ExternalDriver | null {
|
|
166
|
+
return this.sessions[sessionId] ?? null;
|
|
159
167
|
}
|
|
160
168
|
|
|
161
|
-
|
|
162
|
-
|
|
169
|
+
/**
|
|
170
|
+
* WebDriver status payload (readiness message and build metadata).
|
|
171
|
+
* @see https://www.w3.org/TR/webdriver/#dfn-status
|
|
172
|
+
*/
|
|
173
|
+
override async getStatus() {
|
|
163
174
|
const statusObj = this._isShuttingDown
|
|
164
175
|
? {
|
|
165
176
|
ready: false,
|
|
@@ -175,32 +186,34 @@ class AppiumDriver extends DriverCore {
|
|
|
175
186
|
};
|
|
176
187
|
}
|
|
177
188
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
180
|
-
*/
|
|
181
|
-
async shutdown(reason = null) {
|
|
189
|
+
/** Marks the server as shutting down and ends all sessions (forced unexpected shutdown). */
|
|
190
|
+
async shutdown(reason: string | null = null): Promise<void> {
|
|
182
191
|
this._isShuttingDown = true;
|
|
183
192
|
await this.deleteAllSessions({
|
|
184
193
|
force: true,
|
|
185
|
-
reason,
|
|
194
|
+
reason: reason ?? undefined,
|
|
186
195
|
});
|
|
187
196
|
}
|
|
188
197
|
|
|
189
198
|
/**
|
|
190
199
|
* Retrieve information about all active sessions.
|
|
191
200
|
* Results are returned only if the `session_discovery` insecure feature is enabled.
|
|
192
|
-
* @returns {Promise<import('@appium/types').TimestampedMultiSessionData[]>}
|
|
193
201
|
*/
|
|
194
|
-
async getAppiumSessions
|
|
202
|
+
async getAppiumSessions(): Promise<TimestampedMultiSessionData[]> {
|
|
195
203
|
this.assertFeatureEnabled(SESSION_DISCOVERY_FEATURE);
|
|
196
204
|
return _.toPairs(this.sessions).map(([id, driver]) => ({
|
|
197
205
|
id,
|
|
198
206
|
created: driver.sessionCreationTimestampMs,
|
|
199
|
-
capabilities:
|
|
207
|
+
capabilities: driver.caps as DriverCaps<AppiumDriverConstraints>,
|
|
200
208
|
}));
|
|
201
209
|
}
|
|
202
210
|
|
|
203
|
-
|
|
211
|
+
/** Logs BaseDriver version lines when starting a new inner-driver session. */
|
|
212
|
+
printNewSessionAnnouncement(
|
|
213
|
+
driverName: string,
|
|
214
|
+
driverVersion?: string,
|
|
215
|
+
driverBaseVersion?: string
|
|
216
|
+
): void {
|
|
204
217
|
this.log.info(
|
|
205
218
|
driverVersion
|
|
206
219
|
? `Appium v${APPIUM_VER} creating new ${driverName} (v${driverVersion}) session`
|
|
@@ -220,48 +233,42 @@ class AppiumDriver extends DriverCore {
|
|
|
220
233
|
}
|
|
221
234
|
|
|
222
235
|
/**
|
|
223
|
-
*
|
|
224
|
-
* @param {string} extName - Plugin name
|
|
225
|
-
* @returns {StringRecord} Arguments object. If none, an empty object.
|
|
236
|
+
* CLI arguments object for a plugin (from server config). Empty object if none were passed.
|
|
226
237
|
*/
|
|
227
|
-
getCliArgsForPlugin(extName) {
|
|
228
|
-
return
|
|
238
|
+
getCliArgsForPlugin(extName: string): StringRecord {
|
|
239
|
+
return (this.args.plugin?.[extName] ?? {}) as StringRecord;
|
|
229
240
|
}
|
|
230
241
|
|
|
231
242
|
/**
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
* _Note that this behavior currently (May 18 2022) differs from how plugins are handled_ (see {@linkcode AppiumDriver.getCliArgsForPlugin}).
|
|
237
|
-
* @param {string} extName - Driver name
|
|
238
|
-
* @returns {StringRecord|undefined} Arguments object. If none, `undefined`
|
|
243
|
+
* CLI arguments for a driver, omitting keys that match schema defaults (unlike
|
|
244
|
+
* {@link AppiumDriver.getCliArgsForPlugin}, which returns defaults explicitly). `undefined` if
|
|
245
|
+
* there is nothing to pass after omitting defaults.
|
|
239
246
|
*/
|
|
240
|
-
getCliArgsForDriver(extName) {
|
|
241
|
-
const allCliArgsForExt =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
|
|
245
|
-
const cliArgs = _.isEmpty(defaults)
|
|
246
|
-
? allCliArgsForExt
|
|
247
|
-
: _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
|
|
248
|
-
if (!_.isEmpty(cliArgs)) {
|
|
249
|
-
return cliArgs;
|
|
250
|
-
}
|
|
247
|
+
getCliArgsForDriver(extName: string): StringRecord | undefined {
|
|
248
|
+
const allCliArgsForExt = this.args.driver?.[extName] as StringRecord | undefined;
|
|
249
|
+
if (_.isEmpty(allCliArgsForExt)) {
|
|
250
|
+
return undefined;
|
|
251
251
|
}
|
|
252
|
+
|
|
253
|
+
const defaults = getDefaultsForExtension(DRIVER_TYPE, extName);
|
|
254
|
+
const cliArgs = _.isEmpty(defaults)
|
|
255
|
+
? allCliArgsForExt
|
|
256
|
+
: _.omitBy(allCliArgsForExt, (value, key) => _.isEqual(defaults[key], value));
|
|
257
|
+
return _.isEmpty(cliArgs) ? undefined : cliArgs;
|
|
252
258
|
}
|
|
253
259
|
|
|
254
260
|
/**
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* @param {W3CAppiumDriverCaps} [w3cCapabilities2] W3C capabilities (legacy)
|
|
259
|
-
* @param {W3CAppiumDriverCaps} [w3cCapabilities3] W3C capabilities (legacy)
|
|
260
|
-
* @returns {Promise<SessionHandlerCreateResult>}
|
|
261
|
+
* Creates a session: picks an inner driver from caps, runs plugin hooks, and returns a protocol
|
|
262
|
+
* envelope with either `[sessionId, caps, protocol]` or an error. Legacy call sites may pass the
|
|
263
|
+
* same W3C caps in up to three positions; the first W3C-shaped value wins.
|
|
261
264
|
*/
|
|
262
|
-
async createSession(
|
|
265
|
+
async createSession(
|
|
266
|
+
w3cCapabilities1: W3CAppiumDriverCaps,
|
|
267
|
+
w3cCapabilities2?: W3CAppiumDriverCaps,
|
|
268
|
+
w3cCapabilities3?: W3CAppiumDriverCaps
|
|
269
|
+
): Promise<SessionHandlerCreateResult> {
|
|
263
270
|
const defaultCapabilities = _.cloneDeep(this.args.defaultCapabilities);
|
|
264
|
-
const defaultSettings = pullSettings(defaultCapabilities);
|
|
271
|
+
const defaultSettings = pullSettings((defaultCapabilities ?? {}) as StringRecord);
|
|
265
272
|
const w3cCapabilities = _.cloneDeep(
|
|
266
273
|
[w3cCapabilities3, w3cCapabilities2, w3cCapabilities1].find(isW3cCaps)
|
|
267
274
|
);
|
|
@@ -277,24 +284,21 @@ class AppiumDriver extends DriverCore {
|
|
|
277
284
|
}
|
|
278
285
|
|
|
279
286
|
const protocol = PROTOCOLS.W3C;
|
|
280
|
-
let innerSessionId
|
|
287
|
+
let innerSessionId: string;
|
|
288
|
+
let dCaps: DriverCaps<AppiumDriverConstraints> & {webSocketUrl?: string | boolean};
|
|
281
289
|
try {
|
|
282
290
|
// Parse the caps into a format that the InnerDriver will accept
|
|
283
|
-
const parsedCaps = parseCapsForInnerDriver(
|
|
284
|
-
promoteAppiumOptions(
|
|
291
|
+
const parsedCaps = parseCapsForInnerDriver<AppiumDriverConstraints>(
|
|
292
|
+
promoteAppiumOptions(w3cCapabilities),
|
|
285
293
|
this.desiredCapConstraints,
|
|
286
|
-
defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
|
|
294
|
+
defaultCapabilities ? promoteAppiumOptionsForObject(defaultCapabilities) : undefined
|
|
287
295
|
);
|
|
288
296
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const error = /** @type {import('./utils').InvalidCaps<AppiumDriverConstraints>} */ (
|
|
292
|
-
parsedCaps
|
|
293
|
-
).error;
|
|
294
|
-
// If the parsing of the caps produced an error, throw it in here
|
|
295
|
-
if (error) {
|
|
296
|
-
throw error;
|
|
297
|
+
if ('error' in parsedCaps && parsedCaps.error) {
|
|
298
|
+
throw parsedCaps.error;
|
|
297
299
|
}
|
|
300
|
+
const {desiredCaps, processedW3CCapabilities} =
|
|
301
|
+
parsedCaps as ParsedDriverCaps<AppiumDriverConstraints>;
|
|
298
302
|
|
|
299
303
|
const {
|
|
300
304
|
driver: InnerDriver,
|
|
@@ -307,16 +311,10 @@ class AppiumDriver extends DriverCore {
|
|
|
307
311
|
await this.deleteAllSessions();
|
|
308
312
|
}
|
|
309
313
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
*/
|
|
313
|
-
let runningDriversData = [];
|
|
314
|
-
/**
|
|
315
|
-
* @type {DriverData[]}
|
|
316
|
-
*/
|
|
317
|
-
let otherPendingDriversData = [];
|
|
314
|
+
let runningDriversData: DriverData[] = [];
|
|
315
|
+
let otherPendingDriversData: DriverData[] = [];
|
|
318
316
|
|
|
319
|
-
const driverInstance =
|
|
317
|
+
const driverInstance = new InnerDriver(this.args, true) as unknown as ExternalDriver;
|
|
320
318
|
|
|
321
319
|
this.configureDriverFeatures(driverInstance, driverName);
|
|
322
320
|
|
|
@@ -331,7 +329,7 @@ class AppiumDriver extends DriverCore {
|
|
|
331
329
|
// the driver so that they cannot be mimicked by a malicious user sending in capabilities
|
|
332
330
|
const cliArgs = this.getCliArgsForDriver(driverName);
|
|
333
331
|
if (!_.isUndefined(cliArgs)) {
|
|
334
|
-
driverInstance.cliArgs = cliArgs;
|
|
332
|
+
(driverInstance as ExternalDriver & {cliArgs?: StringRecord}).cliArgs = cliArgs;
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
// This assignment is required for correct web sockets functionality inside the driver
|
|
@@ -345,29 +343,26 @@ class AppiumDriver extends DriverCore {
|
|
|
345
343
|
|
|
346
344
|
try {
|
|
347
345
|
runningDriversData = (await this.curSessionDataForDriver(InnerDriver)) ?? [];
|
|
348
|
-
} catch (e) {
|
|
349
|
-
|
|
346
|
+
} catch (e: unknown) {
|
|
347
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
348
|
+
throw new errors.SessionNotCreatedError(msg);
|
|
350
349
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
this.pendingDrivers[InnerDriver.name].push(driverInstance);
|
|
357
|
-
});
|
|
350
|
+
this.pendingDrivers[InnerDriver.name] = this.pendingDrivers[InnerDriver.name] || [];
|
|
351
|
+
otherPendingDriversData = _.compact(
|
|
352
|
+
this.pendingDrivers[InnerDriver.name].map((drv) => drv.driverData),
|
|
353
|
+
);
|
|
354
|
+
this.pendingDrivers[InnerDriver.name].push(driverInstance);
|
|
358
355
|
|
|
359
356
|
try {
|
|
360
|
-
[innerSessionId, dCaps] = await driverInstance.createSession(
|
|
361
|
-
|
|
357
|
+
[innerSessionId, dCaps] = (await driverInstance.createSession(
|
|
358
|
+
processedW3CCapabilities as never,
|
|
362
359
|
processedW3CCapabilities,
|
|
363
360
|
processedW3CCapabilities,
|
|
364
|
-
[...runningDriversData, ...otherPendingDriversData]
|
|
365
|
-
);
|
|
361
|
+
[...runningDriversData, ...otherPendingDriversData]
|
|
362
|
+
)) as [string, DriverCaps<AppiumDriverConstraints> & {webSocketUrl?: string | boolean}];
|
|
366
363
|
this.sessions[innerSessionId] = driverInstance;
|
|
367
364
|
} finally {
|
|
368
|
-
|
|
369
|
-
_.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
|
|
370
|
-
});
|
|
365
|
+
_.pull(this.pendingDrivers[InnerDriver.name], driverInstance);
|
|
371
366
|
}
|
|
372
367
|
|
|
373
368
|
this.attachUnexpectedShutdownHandler(driverInstance, innerSessionId);
|
|
@@ -406,10 +401,10 @@ class AppiumDriver extends DriverCore {
|
|
|
406
401
|
// capability constraint system
|
|
407
402
|
dCaps.webSocketUrl = bidiUrl;
|
|
408
403
|
}
|
|
409
|
-
} catch (error) {
|
|
404
|
+
} catch (error: unknown) {
|
|
410
405
|
return {
|
|
411
406
|
protocol,
|
|
412
|
-
error,
|
|
407
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
413
408
|
};
|
|
414
409
|
}
|
|
415
410
|
|
|
@@ -420,12 +415,11 @@ class AppiumDriver extends DriverCore {
|
|
|
420
415
|
}
|
|
421
416
|
|
|
422
417
|
/**
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
* @param {string} innerSessionId
|
|
418
|
+
* Subscribes to the inner driver’s unexpected-shutdown hook so Appium can drop the session and
|
|
419
|
+
* notify plugins.
|
|
426
420
|
*/
|
|
427
|
-
attachUnexpectedShutdownHandler(driver, innerSessionId) {
|
|
428
|
-
const onShutdown = (cause = new Error('Unknown error')) => {
|
|
421
|
+
attachUnexpectedShutdownHandler(driver: ExternalDriver, innerSessionId: string): void {
|
|
422
|
+
const onShutdown = async (cause: Error = new Error('Unknown error')) => {
|
|
429
423
|
this.log.warn(`Ending session, cause was '${cause.message}'`);
|
|
430
424
|
|
|
431
425
|
if (this.sessionPlugins[innerSessionId]) {
|
|
@@ -435,7 +429,7 @@ class AppiumDriver extends DriverCore {
|
|
|
435
429
|
`Plugin ${plugin.name} defines an unexpected shutdown handler; calling it now`,
|
|
436
430
|
);
|
|
437
431
|
try {
|
|
438
|
-
plugin.onUnexpectedShutdown(driver, cause);
|
|
432
|
+
await plugin.onUnexpectedShutdown(driver, cause);
|
|
439
433
|
} catch (e) {
|
|
440
434
|
this.log.warn(
|
|
441
435
|
`Got an error when running plugin ${plugin.name} shutdown handler: ${e}`,
|
|
@@ -463,13 +457,11 @@ class AppiumDriver extends DriverCore {
|
|
|
463
457
|
}
|
|
464
458
|
|
|
465
459
|
/**
|
|
466
|
-
*
|
|
467
|
-
*
|
|
468
|
-
* @
|
|
469
|
-
* @privateRemarks The _intent_ is that `InnerDriver` is the class of a driver, but it only really
|
|
470
|
-
* needs to be a function or constructor.
|
|
460
|
+
* Collects `driverData` for every active session whose driver class matches `InnerDriver.name`
|
|
461
|
+
* (used when creating another session of the same driver type).
|
|
462
|
+
* @remarks `InnerDriver` is expected to be the driver class; only `.name` is read.
|
|
471
463
|
*/
|
|
472
|
-
async curSessionDataForDriver(InnerDriver) {
|
|
464
|
+
async curSessionDataForDriver(InnerDriver: {name: string}): Promise<DriverData[]> {
|
|
473
465
|
const data = _.compact(
|
|
474
466
|
_.values(this.sessions)
|
|
475
467
|
.filter((s) => s.constructor.name === InnerDriver.name)
|
|
@@ -487,23 +479,22 @@ class AppiumDriver extends DriverCore {
|
|
|
487
479
|
}
|
|
488
480
|
|
|
489
481
|
/**
|
|
490
|
-
*
|
|
482
|
+
* Ends one session: removes it from the master list immediately, then delegates to the inner
|
|
483
|
+
* driver’s `deleteSession` with sibling-session metadata.
|
|
491
484
|
*/
|
|
492
|
-
async deleteSession(sessionId) {
|
|
493
|
-
let protocol;
|
|
485
|
+
async deleteSession(sessionId: string): Promise<SessionHandlerDeleteResult> {
|
|
486
|
+
let protocol: Protocol | undefined;
|
|
494
487
|
try {
|
|
495
|
-
let otherSessionsData;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
488
|
+
let otherSessionsData: DriverData[] | undefined;
|
|
489
|
+
let dstSession: ExternalDriver | undefined;
|
|
490
|
+
if (this.sessions[sessionId]) {
|
|
500
491
|
const curConstructorName = this.sessions[sessionId].constructor.name;
|
|
501
492
|
otherSessionsData = _.toPairs(this.sessions)
|
|
502
493
|
.filter(
|
|
503
494
|
([key, value]) => value.constructor.name === curConstructorName && key !== sessionId,
|
|
504
495
|
)
|
|
505
496
|
.map(([, value]) => value.driverData);
|
|
506
|
-
|
|
497
|
+
dstSession = this.sessions[sessionId];
|
|
507
498
|
protocol = dstSession.protocol;
|
|
508
499
|
this.log.info(`Removing session ${sessionId} from our master session list`);
|
|
509
500
|
// regardless of whether the deleteSession completes successfully or not
|
|
@@ -513,9 +504,7 @@ class AppiumDriver extends DriverCore {
|
|
|
513
504
|
delete this.sessionPlugins[sessionId];
|
|
514
505
|
|
|
515
506
|
this.cleanupBidiSockets(sessionId);
|
|
516
|
-
|
|
517
|
-
return dstSession;
|
|
518
|
-
});
|
|
507
|
+
}
|
|
519
508
|
// this may not be correct, but if `dstSession` was falsy, the call to `deleteSession()` would
|
|
520
509
|
// throw anyway.
|
|
521
510
|
if (!dstSession) {
|
|
@@ -525,16 +514,21 @@ class AppiumDriver extends DriverCore {
|
|
|
525
514
|
protocol,
|
|
526
515
|
value: await dstSession.deleteSession(sessionId, otherSessionsData),
|
|
527
516
|
};
|
|
528
|
-
} catch (e) {
|
|
529
|
-
|
|
517
|
+
} catch (e: unknown) {
|
|
518
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
519
|
+
this.log.error(`Had trouble ending session ${sessionId}: ${msg}`);
|
|
530
520
|
return {
|
|
531
521
|
protocol,
|
|
532
|
-
error: e,
|
|
522
|
+
error: e instanceof Error ? e : new Error(msg),
|
|
533
523
|
};
|
|
534
524
|
}
|
|
535
525
|
}
|
|
536
526
|
|
|
537
|
-
|
|
527
|
+
/**
|
|
528
|
+
* Ends every active session, either by normal `deleteSession` or by `startUnexpectedShutdown`
|
|
529
|
+
* when `force` is true.
|
|
530
|
+
*/
|
|
531
|
+
async deleteAllSessions(opts: {force?: boolean; reason?: string} = {}): Promise<void> {
|
|
538
532
|
const sessionsCount = _.size(this.sessions);
|
|
539
533
|
if (0 === sessionsCount) {
|
|
540
534
|
this.log.debug('There are no active sessions for cleanup');
|
|
@@ -545,7 +539,7 @@ class AppiumDriver extends DriverCore {
|
|
|
545
539
|
this.log.debug(`Cleaning up ${util.pluralize('active session', sessionsCount, true)}`);
|
|
546
540
|
const cleanupPromises = force
|
|
547
541
|
? _.values(this.sessions).map((drv) =>
|
|
548
|
-
drv.startUnexpectedShutdown(reason
|
|
542
|
+
drv.startUnexpectedShutdown(reason ? new Error(reason) : undefined),
|
|
549
543
|
)
|
|
550
544
|
: _.keys(this.sessions).map((id) => this.deleteSession(id));
|
|
551
545
|
for (const cleanupPromise of cleanupPromises) {
|
|
@@ -558,16 +552,22 @@ class AppiumDriver extends DriverCore {
|
|
|
558
552
|
}
|
|
559
553
|
|
|
560
554
|
/**
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
* @param {string|null} [sessionId=null] - the sessionId (or null) to use to find plugins
|
|
564
|
-
* @returns {Array<import('@appium/types').Plugin>} - array of plugin instances
|
|
555
|
+
* Plugin instances for a session id, or the shared sessionless list when `sessionId` is null.
|
|
556
|
+
* Lazily builds sessionless instances on first use.
|
|
565
557
|
*/
|
|
566
|
-
pluginsForSession(sessionId = null) {
|
|
558
|
+
pluginsForSession(sessionId: string | null = null): Plugin[] {
|
|
567
559
|
if (sessionId) {
|
|
560
|
+
const existingPlugins = this.sessionPlugins[sessionId];
|
|
561
|
+
if (existingPlugins) {
|
|
562
|
+
return existingPlugins;
|
|
563
|
+
}
|
|
568
564
|
const driver = this.sessions[sessionId];
|
|
569
|
-
|
|
570
|
-
|
|
565
|
+
if (!driver) {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
return (this.sessionPlugins[sessionId] = this.createPluginInstances(
|
|
569
|
+
generateDriverLogPrefix(driver),
|
|
570
|
+
));
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
if (_.isEmpty(this.sessionlessPlugins)) {
|
|
@@ -577,16 +577,10 @@ class AppiumDriver extends DriverCore {
|
|
|
577
577
|
}
|
|
578
578
|
|
|
579
579
|
/**
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* plugin instances reserved for sessionless commands (and we lazily create plugin instances on
|
|
583
|
-
* first use)
|
|
584
|
-
*
|
|
585
|
-
* @param {string} cmd - the name of the command to find a plugin to handle
|
|
586
|
-
* @param {?string} sessionId - the particular session for which to find a plugin, or null if
|
|
587
|
-
* sessionless
|
|
580
|
+
* Plugins that declare a method named `cmd` or a generic `handle` method, scoped to the given
|
|
581
|
+
* session (or sessionless when `sessionId` is null).
|
|
588
582
|
*/
|
|
589
|
-
pluginsToHandleCmd(cmd, sessionId = null) {
|
|
583
|
+
pluginsToHandleCmd(cmd: string, sessionId: string | null = null): Plugin[] {
|
|
590
584
|
// to handle a given command, a plugin should either implement that command as a plugin
|
|
591
585
|
// instance method or it should implement a generic 'handle' method
|
|
592
586
|
return this.pluginsForSession(sessionId).filter(
|
|
@@ -595,19 +589,17 @@ class AppiumDriver extends DriverCore {
|
|
|
595
589
|
}
|
|
596
590
|
|
|
597
591
|
/**
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
* @returns {Plugin[]}
|
|
592
|
+
* One instance per registered plugin class. `driverId` becomes the plugin log prefix segment
|
|
593
|
+
* when tied to a session driver; use `null` for sessionless plugins.
|
|
601
594
|
*/
|
|
602
|
-
createPluginInstances(driverId = null) {
|
|
603
|
-
|
|
604
|
-
const pluginInstances = [];
|
|
595
|
+
createPluginInstances(driverId: string | null = null): Plugin[] {
|
|
596
|
+
const pluginInstances: Plugin[] = [];
|
|
605
597
|
for (const [PluginClass, name] of this.pluginClasses.entries()) {
|
|
606
598
|
const cliArgs = this.getCliArgsForPlugin(name);
|
|
607
599
|
const plugin = new PluginClass(name, cliArgs, driverId);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
600
|
+
const extPlugin = plugin as Plugin & ExtensionCore;
|
|
601
|
+
if (_.isFunction(extPlugin.updateBidiCommands)) {
|
|
602
|
+
extPlugin.updateBidiCommands(PluginClass.newBidiCommands ?? {});
|
|
611
603
|
}
|
|
612
604
|
pluginInstances.push(plugin);
|
|
613
605
|
}
|
|
@@ -615,12 +607,10 @@ class AppiumDriver extends DriverCore {
|
|
|
615
607
|
}
|
|
616
608
|
|
|
617
609
|
/**
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
* @param {...any} args
|
|
621
|
-
* @returns {Promise<{value: any, error?: Error, protocol: string} | import('type-fest').AsyncReturnType<ExternalDriver['executeCommand']>>}
|
|
610
|
+
* Dispatches a WebDriver/Appium command: may run on this driver, a session’s inner driver, or
|
|
611
|
+
* through a plugin chain, and normalizes the return value into a protocol-shaped result.
|
|
622
612
|
*/
|
|
623
|
-
async executeCommand(cmd, ...args) {
|
|
613
|
+
async executeCommand(cmd: string, ...args: any[]): Promise<SessionHandlerResult<unknown>> {
|
|
624
614
|
// We have basically three cases for how to handle commands:
|
|
625
615
|
// 1. handle getStatus (we do this as a special out of band case so it doesn't get added to an
|
|
626
616
|
// execution queue, and can be called while e.g. createSession is in progress)
|
|
@@ -644,15 +634,14 @@ class AppiumDriver extends DriverCore {
|
|
|
644
634
|
|
|
645
635
|
// first do some error checking. If we're requesting a session command execution, then make
|
|
646
636
|
// sure that session actually exists on the session driver, and set the session driver itself
|
|
647
|
-
let sessionId = null;
|
|
648
|
-
let dstSession = null;
|
|
649
|
-
let protocol = null;
|
|
650
|
-
/** @type {this | ExternalDriver} */
|
|
637
|
+
let sessionId: string | null = null;
|
|
638
|
+
let dstSession: ExternalDriver | null = null;
|
|
639
|
+
let protocol: string | null | undefined = null;
|
|
651
640
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
652
|
-
let driver = this;
|
|
641
|
+
let driver: this | ExternalDriver = this;
|
|
653
642
|
if (isSessionCmd) {
|
|
654
|
-
sessionId = _.last(args);
|
|
655
|
-
dstSession = this.sessions[sessionId];
|
|
643
|
+
sessionId = _.last(args) as string;
|
|
644
|
+
dstSession = this.sessions[sessionId] ?? null;
|
|
656
645
|
if (!dstSession) {
|
|
657
646
|
throw new Error(`The session with id '${sessionId}' does not exist`);
|
|
658
647
|
}
|
|
@@ -682,7 +671,7 @@ class AppiumDriver extends DriverCore {
|
|
|
682
671
|
// original command execution. This results in a situation where the command might be handled
|
|
683
672
|
// by some but not all plugins, or by plugin(s) but not by the default behavior. So start out
|
|
684
673
|
// this object declaring that the default handler has not been executed.
|
|
685
|
-
const cmdHandledBy = {default: false};
|
|
674
|
+
const cmdHandledBy: Record<string, boolean> = {default: false};
|
|
686
675
|
|
|
687
676
|
// now we define an async function which will be passed to plugins, and successively wrapped
|
|
688
677
|
// if there is more than one plugin that can handle the command. To start off with, the async
|
|
@@ -724,8 +713,10 @@ class AppiumDriver extends DriverCore {
|
|
|
724
713
|
return await BaseDriver.prototype.executeCommand.call(this, cmd, ...args);
|
|
725
714
|
}
|
|
726
715
|
|
|
727
|
-
|
|
728
|
-
|
|
716
|
+
if (!dstSession) {
|
|
717
|
+
throw new Error('Internal error: session command without a session driver');
|
|
718
|
+
}
|
|
719
|
+
return await dstSession.executeCommand(cmd, ...args);
|
|
729
720
|
};
|
|
730
721
|
|
|
731
722
|
// now take our default behavior, wrap it with any number of plugin behaviors, and run it
|
|
@@ -763,16 +754,18 @@ class AppiumDriver extends DriverCore {
|
|
|
763
754
|
// previously sessionless to use the new sessionId, so that plugins can share state between
|
|
764
755
|
// their createSession method and other instance methods
|
|
765
756
|
if (cmd === CREATE_SESSION_COMMAND && this.sessionlessPlugins.length && !res.error) {
|
|
766
|
-
const
|
|
757
|
+
const newSessionId = _.first(res.value as unknown[]) as string;
|
|
767
758
|
this.log.info(
|
|
768
759
|
`Promoting ${this.sessionlessPlugins.length} sessionless plugins to be attached ` +
|
|
769
|
-
`to session ID ${
|
|
760
|
+
`to session ID ${newSessionId}`,
|
|
770
761
|
);
|
|
771
|
-
this.sessionPlugins[
|
|
772
|
-
|
|
762
|
+
this.sessionPlugins[newSessionId] = this.sessionlessPlugins;
|
|
763
|
+
const promoted = this.sessionPlugins[newSessionId] as (Plugin & ExtensionCore)[];
|
|
764
|
+
for (const p of promoted) {
|
|
773
765
|
if (_.isFunction(p.updateLogPrefix)) {
|
|
774
|
-
|
|
775
|
-
|
|
766
|
+
p.updateLogPrefix(
|
|
767
|
+
`${generateDriverLogPrefix(p)} <${generateDriverLogPrefix(this.sessions[newSessionId])}>`
|
|
768
|
+
);
|
|
776
769
|
}
|
|
777
770
|
}
|
|
778
771
|
this.sessionlessPlugins = [];
|
|
@@ -781,7 +774,22 @@ class AppiumDriver extends DriverCore {
|
|
|
781
774
|
return res;
|
|
782
775
|
}
|
|
783
776
|
|
|
784
|
-
|
|
777
|
+
/** Builds an async chain: each plugin wraps `next` until the default driver behavior runs. */
|
|
778
|
+
wrapCommandWithPlugins({
|
|
779
|
+
driver,
|
|
780
|
+
cmd,
|
|
781
|
+
args,
|
|
782
|
+
next,
|
|
783
|
+
cmdHandledBy,
|
|
784
|
+
plugins,
|
|
785
|
+
}: {
|
|
786
|
+
driver: AppiumDriver | ExternalDriver;
|
|
787
|
+
cmd: string;
|
|
788
|
+
args: any[];
|
|
789
|
+
next: () => Promise<unknown>;
|
|
790
|
+
cmdHandledBy: Record<string, boolean>;
|
|
791
|
+
plugins: Plugin[];
|
|
792
|
+
}): () => Promise<unknown> {
|
|
785
793
|
if (plugins.length) {
|
|
786
794
|
this.log.info(`Plugins which can handle cmd '${cmd}': ${plugins.map((p) => p.name)}`);
|
|
787
795
|
}
|
|
@@ -797,18 +805,31 @@ class AppiumDriver extends DriverCore {
|
|
|
797
805
|
this.log.info(`Plugin ${plugin.name} is now handling cmd '${cmd}'`);
|
|
798
806
|
cmdHandledBy[plugin.name] = true; // if we make it here, this plugin has attempted to handle cmd
|
|
799
807
|
// first attempt to handle the command via a command-specific handler on the plugin
|
|
800
|
-
|
|
801
|
-
|
|
808
|
+
const cmdHandler = (plugin as Plugin & Record<string, unknown>)[cmd];
|
|
809
|
+
if (_.isFunction(cmdHandler)) {
|
|
810
|
+
// Command methods must run with plugin as `this` (detached property access drops binding).
|
|
811
|
+
return await (cmdHandler as PluginCommand).call(
|
|
812
|
+
plugin,
|
|
813
|
+
_next,
|
|
814
|
+
driver as ExternalDriver,
|
|
815
|
+
...args,
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
if (!_.isFunction(plugin.handle)) {
|
|
819
|
+
throw new Error(`Plugin ${plugin.name} cannot handle command '${cmd}'`);
|
|
802
820
|
}
|
|
803
|
-
|
|
804
|
-
return await plugin.handle(_next, driver, cmd, ...args);
|
|
821
|
+
return await plugin.handle(_next, driver as ExternalDriver, cmd, ...args);
|
|
805
822
|
})(next);
|
|
806
823
|
}
|
|
807
824
|
|
|
808
825
|
return next;
|
|
809
826
|
}
|
|
810
827
|
|
|
811
|
-
|
|
828
|
+
/** After a command with plugins, logs which handlers ran vs. were skipped (debugging aid). */
|
|
829
|
+
logPluginHandlerReport(
|
|
830
|
+
plugins: Plugin[],
|
|
831
|
+
{cmd, cmdHandledBy}: {cmd: string; cmdHandledBy: Record<string, boolean>}
|
|
832
|
+
): void {
|
|
812
833
|
if (!plugins.length) {
|
|
813
834
|
return;
|
|
814
835
|
}
|
|
@@ -830,10 +851,17 @@ class AppiumDriver extends DriverCore {
|
|
|
830
851
|
}
|
|
831
852
|
}
|
|
832
853
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
854
|
+
/** Runs the wrapped plugin chain and merges the result into a `SessionHandlerResult` shape. */
|
|
855
|
+
async executeWrappedCommand({
|
|
856
|
+
wrappedCmd,
|
|
857
|
+
protocol,
|
|
858
|
+
}: {
|
|
859
|
+
wrappedCmd: () => Promise<unknown>;
|
|
860
|
+
protocol: string | null | undefined;
|
|
861
|
+
}): Promise<SessionHandlerResult<unknown>> {
|
|
862
|
+
let cmdRes: unknown;
|
|
863
|
+
let cmdErr: unknown;
|
|
864
|
+
const res: SessionHandlerResult<unknown> = {};
|
|
837
865
|
try {
|
|
838
866
|
// At this point, `wrappedCmd` defines a whole sequence of plugin handlers, culminating in
|
|
839
867
|
// our default handler. Whatever it returns is what we're going to want to send back to the
|
|
@@ -847,60 +875,33 @@ class AppiumDriver extends DriverCore {
|
|
|
847
875
|
// object, or a protocol-aware object with protocol and error/value keys. So we need to sniff
|
|
848
876
|
// it and make sure we don't double-wrap it if it's the latter kind.
|
|
849
877
|
if (_.isPlainObject(cmdRes) && _.has(cmdRes, 'protocol')) {
|
|
850
|
-
res
|
|
878
|
+
Object.assign(res, cmdRes);
|
|
851
879
|
} else {
|
|
852
880
|
res.value = cmdRes;
|
|
853
|
-
res.error = cmdErr;
|
|
854
|
-
res.protocol = protocol;
|
|
881
|
+
res.error = cmdErr instanceof Error ? cmdErr : undefined;
|
|
882
|
+
res.protocol = protocol ?? undefined;
|
|
855
883
|
}
|
|
856
884
|
return res;
|
|
857
885
|
}
|
|
858
886
|
|
|
859
|
-
|
|
887
|
+
/** Whether the inner session driver is actively proxying for this session id. */
|
|
888
|
+
override proxyActive(sessionId: string): boolean {
|
|
860
889
|
const dstSession = this.sessions[sessionId];
|
|
861
|
-
return
|
|
890
|
+
return _.isFunction(dstSession?.proxyActive) && dstSession.proxyActive(sessionId);
|
|
862
891
|
}
|
|
863
892
|
|
|
864
|
-
/**
|
|
865
|
-
|
|
866
|
-
* @param {string} sessionId
|
|
867
|
-
* @returns {import('@appium/types').RouteMatcher[]}
|
|
868
|
-
*/
|
|
869
|
-
getProxyAvoidList(sessionId) {
|
|
893
|
+
/** URL patterns the session driver does not want proxied; empty if no session or no list. */
|
|
894
|
+
override getProxyAvoidList(sessionId: string): RouteMatcher[] {
|
|
870
895
|
const dstSession = this.sessions[sessionId];
|
|
871
|
-
return dstSession ? dstSession.getProxyAvoidList() : [];
|
|
896
|
+
return _.isFunction(dstSession?.getProxyAvoidList) ? dstSession.getProxyAvoidList() : [];
|
|
872
897
|
}
|
|
873
898
|
|
|
874
|
-
|
|
899
|
+
/** Whether the session driver supports proxying for this session. */
|
|
900
|
+
override canProxy(sessionId: string): boolean {
|
|
875
901
|
const dstSession = this.sessions[sessionId];
|
|
876
|
-
return dstSession && dstSession.canProxy(sessionId);
|
|
902
|
+
return _.isFunction(dstSession?.canProxy) && dstSession.canProxy(sessionId);
|
|
877
903
|
}
|
|
878
904
|
|
|
879
|
-
onBidiConnection = bidiCommands.onBidiConnection;
|
|
880
|
-
onBidiMessage = bidiCommands.onBidiMessage;
|
|
881
|
-
onBidiServerError = bidiCommands.onBidiServerError;
|
|
882
|
-
cleanupBidiSockets = bidiCommands.cleanupBidiSockets;
|
|
883
|
-
|
|
884
|
-
configureGlobalFeatures = insecureFeatures.configureGlobalFeatures;
|
|
885
|
-
configureDriverFeatures = insecureFeatures.configureDriverFeatures;
|
|
886
|
-
|
|
887
|
-
listCommands = inspectorCommands.listCommands;
|
|
888
|
-
listExtensions = inspectorCommands.listExtensions;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Help decide which commands should be proxied to sub-drivers and which
|
|
893
|
-
* should be handled by this, our umbrella driver
|
|
894
|
-
* @param {string} cmd
|
|
895
|
-
* @returns {boolean}
|
|
896
|
-
*/
|
|
897
|
-
function isAppiumDriverCommand(cmd) {
|
|
898
|
-
return !isSessionCommand(cmd)
|
|
899
|
-
|| _.includes([
|
|
900
|
-
DELETE_SESSION_COMMAND,
|
|
901
|
-
LIST_DRIVER_COMMANDS_COMMAND,
|
|
902
|
-
LIST_DRIVER_EXTENSIONS_COMMAND,
|
|
903
|
-
], cmd);
|
|
904
905
|
}
|
|
905
906
|
|
|
906
907
|
/**
|
|
@@ -908,10 +909,7 @@ function isAppiumDriverCommand(cmd) {
|
|
|
908
909
|
* method did not exist
|
|
909
910
|
*/
|
|
910
911
|
export class NoDriverProxyCommandError extends Error {
|
|
911
|
-
|
|
912
|
-
* @type {Readonly<string>}
|
|
913
|
-
*/
|
|
914
|
-
code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
912
|
+
readonly code = 'APPIUMERR_NO_DRIVER_PROXYCOMMAND';
|
|
915
913
|
|
|
916
914
|
constructor() {
|
|
917
915
|
super(
|
|
@@ -923,55 +921,13 @@ export class NoDriverProxyCommandError extends Error {
|
|
|
923
921
|
}
|
|
924
922
|
}
|
|
925
923
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
* @typedef {import('@appium/types').PluginType} PluginType
|
|
936
|
-
* @typedef {import('@appium/types').DriverType} DriverType
|
|
937
|
-
* @typedef {import('@appium/types').StringRecord} StringRecord
|
|
938
|
-
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
|
939
|
-
* @typedef {import('@appium/types').PluginClass} PluginClass
|
|
940
|
-
* @typedef {import('@appium/types').Plugin} Plugin
|
|
941
|
-
* @typedef {import('@appium/base-driver').ExtensionCore} ExtensionCore
|
|
942
|
-
* @typedef {import('@appium/types').DriverClass<import('@appium/types').Driver>} DriverClass
|
|
943
|
-
*/
|
|
944
|
-
|
|
945
|
-
/**
|
|
946
|
-
* @typedef {import('@appium/types').ISessionHandler<AppiumDriverConstraints,
|
|
947
|
-
* SessionHandlerCreateResult, SessionHandlerDeleteResult>} AppiumSessionHandler
|
|
948
|
-
*/
|
|
949
|
-
|
|
950
|
-
/**
|
|
951
|
-
* @typedef {SessionHandlerResult<[innerSessionId: string, caps:
|
|
952
|
-
* import('@appium/types').DriverCaps<Constraints>, protocol: string|undefined]>} SessionHandlerCreateResult
|
|
953
|
-
*/
|
|
954
|
-
|
|
955
|
-
/**
|
|
956
|
-
* @template {Constraints} C
|
|
957
|
-
* @typedef {import('@appium/types').Core<C>} Core
|
|
958
|
-
*/
|
|
959
|
-
|
|
960
|
-
/**
|
|
961
|
-
* @typedef {SessionHandlerResult<void>} SessionHandlerDeleteResult
|
|
962
|
-
*/
|
|
963
|
-
|
|
964
|
-
/**
|
|
965
|
-
* Used by {@linkcode AppiumDriver.createSession} and {@linkcode AppiumDriver.deleteSession} to describe
|
|
966
|
-
* result.
|
|
967
|
-
* @template V
|
|
968
|
-
* @typedef SessionHandlerResult
|
|
969
|
-
* @property {V} [value]
|
|
970
|
-
* @property {Error} [error]
|
|
971
|
-
* @property {string} [protocol]
|
|
972
|
-
*/
|
|
924
|
+
/** True if `cmd` should run on the umbrella driver instead of only on the session’s inner driver. */
|
|
925
|
+
function isAppiumDriverCommand(cmd: string): boolean {
|
|
926
|
+
return !isSessionCommand(cmd)
|
|
927
|
+
|| _.includes([
|
|
928
|
+
DELETE_SESSION_COMMAND,
|
|
929
|
+
LIST_DRIVER_COMMANDS_COMMAND,
|
|
930
|
+
LIST_DRIVER_EXTENSIONS_COMMAND,
|
|
931
|
+
], cmd);
|
|
932
|
+
}
|
|
973
933
|
|
|
974
|
-
/**
|
|
975
|
-
* @typedef {typeof desiredCapabilityConstraints} AppiumDriverConstraints
|
|
976
|
-
* @typedef {import('@appium/types').W3CDriverCaps<AppiumDriverConstraints>} W3CAppiumDriverCaps
|
|
977
|
-
*/
|