appium 2.14.1 → 2.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -5
- package/build/lib/appium.d.ts +15 -12
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +49 -56
- package/build/lib/appium.js.map +1 -1
- package/build/lib/{bidi.d.ts → bidi-commands.d.ts} +12 -3
- package/build/lib/bidi-commands.d.ts.map +1 -0
- package/build/lib/{bidi.js → bidi-commands.js} +140 -49
- package/build/lib/bidi-commands.js.map +1 -0
- package/build/lib/inspector-commands.d.ts +5 -0
- package/build/lib/inspector-commands.d.ts.map +1 -0
- package/build/lib/inspector-commands.js +159 -0
- package/build/lib/inspector-commands.js.map +1 -0
- package/build/lib/main.d.ts.map +1 -1
- package/build/lib/main.js +4 -2
- package/build/lib/main.js.map +1 -1
- package/lib/appium.js +55 -57
- package/lib/{bidi.ts → bidi-commands.ts} +157 -50
- package/lib/inspector-commands.ts +190 -0
- package/lib/main.js +4 -2
- package/package.json +10 -16
- package/build/lib/bidi.d.ts.map +0 -1
- package/build/lib/bidi.js.map +0 -1
package/lib/appium.js
CHANGED
|
@@ -8,8 +8,11 @@ import {
|
|
|
8
8
|
CREATE_SESSION_COMMAND,
|
|
9
9
|
DELETE_SESSION_COMMAND,
|
|
10
10
|
GET_STATUS_COMMAND,
|
|
11
|
+
LIST_DRIVER_COMMANDS_COMMAND,
|
|
12
|
+
LIST_DRIVER_EXTENSIONS_COMMAND,
|
|
11
13
|
promoteAppiumOptions,
|
|
12
14
|
promoteAppiumOptionsForObject,
|
|
15
|
+
generateDriverLogPrefix,
|
|
13
16
|
} from '@appium/base-driver';
|
|
14
17
|
import AsyncLock from 'async-lock';
|
|
15
18
|
import {
|
|
@@ -18,10 +21,11 @@ import {
|
|
|
18
21
|
makeNonW3cCapsError,
|
|
19
22
|
validateFeatures,
|
|
20
23
|
} from './utils';
|
|
21
|
-
import {util
|
|
24
|
+
import {util} from '@appium/support';
|
|
22
25
|
import {getDefaultsForExtension} from './schema';
|
|
23
26
|
import {DRIVER_TYPE, BIDI_BASE_PATH} from './constants';
|
|
24
|
-
import * as
|
|
27
|
+
import * as bidiCommands from './bidi-commands';
|
|
28
|
+
import * as inspectorCommands from './inspector-commands';
|
|
25
29
|
|
|
26
30
|
const desiredCapabilityConstraints = /** @type {const} */ ({
|
|
27
31
|
automationName: {
|
|
@@ -140,17 +144,6 @@ class AppiumDriver extends DriverCore {
|
|
|
140
144
|
})();
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
/**
|
|
144
|
-
* Retrieves logger instance for the current umbrella driver instance
|
|
145
|
-
*/
|
|
146
|
-
get log() {
|
|
147
|
-
if (!this._log) {
|
|
148
|
-
const instanceName = `${this.constructor.name}@${node.getObjectId(this).substring(0, 4)}`;
|
|
149
|
-
this._log = logger.getLogger(instanceName);
|
|
150
|
-
}
|
|
151
|
-
return this._log;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
147
|
/**
|
|
155
148
|
* Cancel commands queueing for the umbrella Appium driver
|
|
156
149
|
*/
|
|
@@ -202,6 +195,11 @@ class AppiumDriver extends DriverCore {
|
|
|
202
195
|
}));
|
|
203
196
|
}
|
|
204
197
|
|
|
198
|
+
async getAppiumSessions () {
|
|
199
|
+
throw new errors.NotImplementedError('Not implemented yet. ' +
|
|
200
|
+
'Please check https://github.com/appium/appium/issues/20880 for more details.');
|
|
201
|
+
}
|
|
202
|
+
|
|
205
203
|
printNewSessionAnnouncement(driverName, driverVersion, driverBaseVersion) {
|
|
206
204
|
this.log.info(
|
|
207
205
|
driverVersion
|
|
@@ -343,7 +341,8 @@ class AppiumDriver extends DriverCore {
|
|
|
343
341
|
}
|
|
344
342
|
|
|
345
343
|
// We also want to assign any new Bidi Commands that the driver has specified, including all
|
|
346
|
-
// the standard bidi commands
|
|
344
|
+
// the standard bidi commands. But add a method existence guard since some old driver class
|
|
345
|
+
// instances might not have this method
|
|
347
346
|
if (_.isFunction(driverInstance.updateBidiCommands)) {
|
|
348
347
|
driverInstance.updateBidiCommands(InnerDriver.newBidiCommands ?? {});
|
|
349
348
|
}
|
|
@@ -412,7 +411,7 @@ class AppiumDriver extends DriverCore {
|
|
|
412
411
|
);
|
|
413
412
|
|
|
414
413
|
// set the New Command Timeout for the inner driver
|
|
415
|
-
driverInstance.startNewCommandTimeout();
|
|
414
|
+
await driverInstance.startNewCommandTimeout();
|
|
416
415
|
|
|
417
416
|
// apply initial values to Appium settings (if provided)
|
|
418
417
|
if (driverInstance.isW3CProtocol() && !_.isEmpty(w3cSettings)) {
|
|
@@ -429,14 +428,13 @@ class AppiumDriver extends DriverCore {
|
|
|
429
428
|
await driverInstance.updateSettings(jwpSettings);
|
|
430
429
|
}
|
|
431
430
|
|
|
432
|
-
// if the user has asked for bidi support,
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
if (dCaps.webSocketUrl && driverInstance.doesSupportBidi) {
|
|
431
|
+
// if the user has asked for bidi support, send our bidi url back to the user. The inner
|
|
432
|
+
// driver will need to have already saved any internal bidi urls it might want to proxy to,
|
|
433
|
+
// cause we are going to overwrite that information here!
|
|
434
|
+
if (dCaps.webSocketUrl) {
|
|
437
435
|
const {address, port, basePath} = this.args;
|
|
438
436
|
const scheme = `ws${this.server.isSecure() ? 's' : ''}`;
|
|
439
|
-
const host =
|
|
437
|
+
const host = bidiCommands.determineBiDiHost(address);
|
|
440
438
|
const bidiUrl = `${scheme}://${host}:${port}${basePath}${BIDI_BASE_PATH}/${innerSessionId}`;
|
|
441
439
|
this.log.info(
|
|
442
440
|
`Upstream driver responded with webSocketUrl ${dCaps.webSocketUrl}, will rewrite to ` +
|
|
@@ -575,32 +573,6 @@ class AppiumDriver extends DriverCore {
|
|
|
575
573
|
}
|
|
576
574
|
}
|
|
577
575
|
|
|
578
|
-
/**
|
|
579
|
-
* @param {string} sessionId
|
|
580
|
-
*/
|
|
581
|
-
cleanupBidiSockets(sessionId) {
|
|
582
|
-
// clean up any bidi sockets associated with session
|
|
583
|
-
if (this.bidiSockets[sessionId]) {
|
|
584
|
-
try {
|
|
585
|
-
this.log.debug(`Closing bidi socket(s) associated with session ${sessionId}`);
|
|
586
|
-
for (const ws of this.bidiSockets[sessionId]) {
|
|
587
|
-
// 1001 means server is going away
|
|
588
|
-
ws.close(1001, 'Appium session is closing');
|
|
589
|
-
}
|
|
590
|
-
} catch {}
|
|
591
|
-
delete this.bidiSockets[sessionId];
|
|
592
|
-
const proxyClient = this.bidiProxyClients[sessionId];
|
|
593
|
-
if (proxyClient) {
|
|
594
|
-
this.log.debug(`Also closing proxy connection to upstream bidi server`);
|
|
595
|
-
try {
|
|
596
|
-
// 1000 means normal closure, which seems correct when Appium is acting as the client
|
|
597
|
-
proxyClient.close(1000);
|
|
598
|
-
} catch {}
|
|
599
|
-
delete this.bidiProxyClients[sessionId];
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
576
|
async deleteAllSessions(opts = {}) {
|
|
605
577
|
const sessionsCount = _.size(this.sessions);
|
|
606
578
|
if (0 === sessionsCount) {
|
|
@@ -628,12 +600,13 @@ class AppiumDriver extends DriverCore {
|
|
|
628
600
|
* Get the appropriate plugins for a session (or sessionless plugins)
|
|
629
601
|
*
|
|
630
602
|
* @param {?string} sessionId - the sessionId (or null) to use to find plugins
|
|
631
|
-
* @returns {Array} - array of plugin instances
|
|
603
|
+
* @returns {Array<import('@appium/types').Plugin>} - array of plugin instances
|
|
632
604
|
*/
|
|
633
605
|
pluginsForSession(sessionId = null) {
|
|
634
606
|
if (sessionId) {
|
|
635
607
|
if (!this.sessionPlugins[sessionId]) {
|
|
636
|
-
this.
|
|
608
|
+
const driverId = generateDriverLogPrefix(this.sessions[sessionId]);
|
|
609
|
+
this.sessionPlugins[sessionId] = this.createPluginInstances(driverId || null);
|
|
637
610
|
}
|
|
638
611
|
return this.sessionPlugins[sessionId];
|
|
639
612
|
}
|
|
@@ -664,14 +637,19 @@ class AppiumDriver extends DriverCore {
|
|
|
664
637
|
|
|
665
638
|
/**
|
|
666
639
|
* Creates instances of all of the enabled Plugin classes
|
|
640
|
+
* @param {string|null} driverId - ID to use for linking a driver to a plugin in logs
|
|
667
641
|
* @returns {Plugin[]}
|
|
668
642
|
*/
|
|
669
|
-
createPluginInstances() {
|
|
643
|
+
createPluginInstances(driverId = null) {
|
|
670
644
|
/** @type {Plugin[]} */
|
|
671
645
|
const pluginInstances = [];
|
|
672
646
|
for (const [PluginClass, name] of this.pluginClasses.entries()) {
|
|
673
647
|
const cliArgs = this.getCliArgsForPlugin(name);
|
|
674
|
-
const plugin = new PluginClass(name, cliArgs);
|
|
648
|
+
const plugin = new PluginClass(name, cliArgs, driverId);
|
|
649
|
+
if (_.isFunction(/** @type {Plugin & ExtensionCore} */(plugin).updateBidiCommands)) {
|
|
650
|
+
// some old plugin classes don't have `updateBidiCommands`
|
|
651
|
+
/** @type {Plugin & ExtensionCore} */(plugin).updateBidiCommands(PluginClass.newBidiCommands ?? {});
|
|
652
|
+
}
|
|
675
653
|
pluginInstances.push(plugin);
|
|
676
654
|
}
|
|
677
655
|
return pluginInstances;
|
|
@@ -832,6 +810,12 @@ class AppiumDriver extends DriverCore {
|
|
|
832
810
|
`to session ID ${sessionId}`,
|
|
833
811
|
);
|
|
834
812
|
this.sessionPlugins[sessionId] = this.sessionlessPlugins;
|
|
813
|
+
for (const p of /** @type {(Plugin & ExtensionCore)[]} */(this.sessionPlugins[sessionId])) {
|
|
814
|
+
if (_.isFunction(p.updateLogPrefix)) {
|
|
815
|
+
// some old plugin classes don't have `updateLogPrefix` yet
|
|
816
|
+
p.updateLogPrefix(`${generateDriverLogPrefix(p)} <${generateDriverLogPrefix(this.sessions[sessionId])}>`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
835
819
|
this.sessionlessPlugins = [];
|
|
836
820
|
}
|
|
837
821
|
|
|
@@ -933,15 +917,28 @@ class AppiumDriver extends DriverCore {
|
|
|
933
917
|
return dstSession && dstSession.canProxy(sessionId);
|
|
934
918
|
}
|
|
935
919
|
|
|
936
|
-
onBidiConnection =
|
|
937
|
-
onBidiMessage =
|
|
938
|
-
onBidiServerError =
|
|
920
|
+
onBidiConnection = bidiCommands.onBidiConnection;
|
|
921
|
+
onBidiMessage = bidiCommands.onBidiMessage;
|
|
922
|
+
onBidiServerError = bidiCommands.onBidiServerError;
|
|
923
|
+
cleanupBidiSockets = bidiCommands.cleanupBidiSockets;
|
|
924
|
+
|
|
925
|
+
listCommands = inspectorCommands.listCommands;
|
|
926
|
+
listExtensions = inspectorCommands.listExtensions;
|
|
939
927
|
}
|
|
940
928
|
|
|
941
|
-
|
|
942
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Help decide which commands should be proxied to sub-drivers and which
|
|
931
|
+
* should be handled by this, our umbrella driver
|
|
932
|
+
* @param {string} cmd
|
|
933
|
+
* @returns {boolean}
|
|
934
|
+
*/
|
|
943
935
|
function isAppiumDriverCommand(cmd) {
|
|
944
|
-
return !isSessionCommand(cmd)
|
|
936
|
+
return !isSessionCommand(cmd)
|
|
937
|
+
|| _.includes([
|
|
938
|
+
DELETE_SESSION_COMMAND,
|
|
939
|
+
LIST_DRIVER_COMMANDS_COMMAND,
|
|
940
|
+
LIST_DRIVER_EXTENSIONS_COMMAND,
|
|
941
|
+
], cmd);
|
|
945
942
|
}
|
|
946
943
|
|
|
947
944
|
/**
|
|
@@ -979,6 +976,7 @@ export {AppiumDriver};
|
|
|
979
976
|
* @typedef {import('@appium/types').ExternalDriver} ExternalDriver
|
|
980
977
|
* @typedef {import('@appium/types').PluginClass} PluginClass
|
|
981
978
|
* @typedef {import('@appium/types').Plugin} Plugin
|
|
979
|
+
* @typedef {import('@appium/base-driver').ExtensionCore} ExtensionCore
|
|
982
980
|
* @typedef {import('@appium/types').DriverClass<import('@appium/types').Driver>} DriverClass
|
|
983
981
|
*/
|
|
984
982
|
|
|
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
|
|
2
2
|
import B from 'bluebird';
|
|
3
3
|
import {
|
|
4
4
|
errors,
|
|
5
|
+
ExtensionCore,
|
|
5
6
|
} from '@appium/base-driver';
|
|
6
7
|
import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
|
|
7
8
|
import WebSocket from 'ws';
|
|
@@ -17,14 +18,18 @@ import type {
|
|
|
17
18
|
ErrorBiDiCommandResponse,
|
|
18
19
|
SuccessBiDiCommandResponse,
|
|
19
20
|
ExternalDriver,
|
|
20
|
-
StringRecord
|
|
21
|
+
StringRecord,
|
|
22
|
+
Plugin,
|
|
23
|
+
BiDiResultData
|
|
21
24
|
} from '@appium/types';
|
|
22
25
|
|
|
26
|
+
type ExtensionPlugin = Plugin & ExtensionCore
|
|
23
27
|
type AnyDriver = ExternalDriver | AppiumDriver;
|
|
24
28
|
type SendData = (data: string | Buffer) => Promise<void>;
|
|
25
29
|
type LogSocketError = (err: Error) => void;
|
|
26
30
|
interface InitBiDiSocketResult {
|
|
27
31
|
bidiHandlerDriver: AnyDriver;
|
|
32
|
+
bidiHandlerPlugins: ExtensionPlugin[];
|
|
28
33
|
proxyClient: WebSocket | null;
|
|
29
34
|
send: SendData;
|
|
30
35
|
sendToProxy: SendData | null;
|
|
@@ -64,7 +69,7 @@ export function determineBiDiHost(address: string): string {
|
|
|
64
69
|
export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): void {
|
|
65
70
|
try {
|
|
66
71
|
const initBiDiSocketFunc: OmitThisParameter<typeof initBidiSocket> = initBidiSocket.bind(this);
|
|
67
|
-
const {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
|
|
72
|
+
const {bidiHandlerDriver, bidiHandlerPlugins, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
|
|
68
73
|
ws,
|
|
69
74
|
req,
|
|
70
75
|
);
|
|
@@ -77,6 +82,7 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
|
|
|
77
82
|
send,
|
|
78
83
|
sendToProxy,
|
|
79
84
|
bidiHandlerDriver,
|
|
85
|
+
bidiHandlerPlugins,
|
|
80
86
|
logSocketErr,
|
|
81
87
|
);
|
|
82
88
|
if (proxyClient) {
|
|
@@ -86,7 +92,7 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
|
|
|
86
92
|
}
|
|
87
93
|
const initBidiEventListenersFunc: OmitThisParameter<typeof initBidiEventListeners> = initBidiEventListeners
|
|
88
94
|
.bind(this);
|
|
89
|
-
initBidiEventListenersFunc(ws, bidiHandlerDriver, send);
|
|
95
|
+
initBidiEventListenersFunc(ws, bidiHandlerDriver, bidiHandlerPlugins, send);
|
|
90
96
|
} catch (err) {
|
|
91
97
|
this.log.error(err);
|
|
92
98
|
try {
|
|
@@ -95,14 +101,25 @@ export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: Incomin
|
|
|
95
101
|
}
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
function wrapCommandWithPlugins(driver: ExtensionCore, plugins: ExtensionCore[], method: string, params: StringRecord): () => Promise<BiDiResultData> {
|
|
105
|
+
const [moduleName, methodName] = method.split('.');
|
|
106
|
+
let next = async () => await driver.executeBidiCommand(method, params);
|
|
107
|
+
for (const plugin of plugins.filter((p) => p.doesBidiCommandExist(moduleName, methodName))) {
|
|
108
|
+
next = ((_next) => async () => await plugin.executeBidiCommand(method, params, _next, driver))(next);
|
|
109
|
+
}
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
/**
|
|
99
114
|
* @param data
|
|
100
115
|
* @param driver
|
|
116
|
+
* @param plugins
|
|
101
117
|
*/
|
|
102
118
|
export async function onBidiMessage(
|
|
103
119
|
this: AppiumDriver,
|
|
104
120
|
data: Buffer,
|
|
105
|
-
driver: AnyDriver
|
|
121
|
+
driver: AnyDriver,
|
|
122
|
+
plugins: ExtensionPlugin[]
|
|
106
123
|
): Promise<SuccessBiDiCommandResponse | ErrorBiDiCommandResponse> {
|
|
107
124
|
let resMessage: SuccessBiDiCommandResponse | ErrorBiDiCommandResponse;
|
|
108
125
|
let id: number = 0;
|
|
@@ -129,7 +146,8 @@ export async function onBidiMessage(
|
|
|
129
146
|
`Missing params for BiDi operation in '${dataTruncated}`,
|
|
130
147
|
);
|
|
131
148
|
}
|
|
132
|
-
const
|
|
149
|
+
const executeWrappedCommand = wrapCommandWithPlugins(driver as ExtensionCore, plugins, method, params);
|
|
150
|
+
const result = await executeWrappedCommand();
|
|
133
151
|
resMessage = {
|
|
134
152
|
id,
|
|
135
153
|
type: 'success',
|
|
@@ -158,6 +176,38 @@ export function onBidiServerError(this: AppiumDriver, err: Error): void {
|
|
|
158
176
|
this.log.warn(`Error from bidi websocket server: ${err}`);
|
|
159
177
|
}
|
|
160
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Clean up any bidi sockets associated with session
|
|
181
|
+
*
|
|
182
|
+
* @param sessionId
|
|
183
|
+
*/
|
|
184
|
+
export function cleanupBidiSockets(this: AppiumDriver, sessionId: string): void {
|
|
185
|
+
if (!this.bidiSockets[sessionId]) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
this.log.debug(`Closing bidi socket(s) associated with session ${sessionId}`);
|
|
190
|
+
for (const ws of this.bidiSockets[sessionId]) {
|
|
191
|
+
// 1001 means server is going away
|
|
192
|
+
ws.close(1001, 'Appium session is closing');
|
|
193
|
+
}
|
|
194
|
+
} catch {}
|
|
195
|
+
delete this.bidiSockets[sessionId];
|
|
196
|
+
|
|
197
|
+
const proxyClient = this.bidiProxyClients[sessionId];
|
|
198
|
+
if (!proxyClient) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.log.debug(`Also closing proxy connection to upstream bidi server`);
|
|
202
|
+
try {
|
|
203
|
+
// 1000 means normal closure, which seems correct when Appium is acting as the client
|
|
204
|
+
proxyClient.close(1000);
|
|
205
|
+
} catch {}
|
|
206
|
+
delete this.bidiProxyClients[sessionId];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// #region Private functions
|
|
210
|
+
|
|
161
211
|
/**
|
|
162
212
|
* Initialize a new bidi connection
|
|
163
213
|
* @param ws The websocket connection object
|
|
@@ -186,6 +236,7 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
|
|
|
186
236
|
|
|
187
237
|
let bidiHandlerDriver: AnyDriver;
|
|
188
238
|
let proxyClient: WebSocket | null = null;
|
|
239
|
+
const bidiHandlerPlugins: ExtensionPlugin[] = [];
|
|
189
240
|
if (sessionMatch) {
|
|
190
241
|
// If we found a session id, see if it matches an active session
|
|
191
242
|
const sessionId = sessionMatch[1];
|
|
@@ -217,15 +268,19 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
|
|
|
217
268
|
`url ${bidiProxyUrl}, but this was not a valid url`,
|
|
218
269
|
);
|
|
219
270
|
}
|
|
220
|
-
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}`
|
|
271
|
+
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}. ` +
|
|
272
|
+
`Plugins will not handle bidi commands`);
|
|
221
273
|
proxyClient = new WebSocket(bidiProxyUrl);
|
|
222
274
|
this.bidiProxyClients[sessionId] = proxyClient;
|
|
275
|
+
} else {
|
|
276
|
+
bidiHandlerPlugins.push(...this.pluginsForSession(sessionId) as ExtensionPlugin[]);
|
|
223
277
|
}
|
|
224
278
|
} else {
|
|
225
279
|
this.log.info('Bidi websocket connection made to main server');
|
|
226
280
|
// no need to store the socket connection if it's to the main server since it will just
|
|
227
281
|
// stay open as long as the server itself is and will close when the server closes.
|
|
228
282
|
bidiHandlerDriver = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
283
|
+
bidiHandlerPlugins.push(...this.pluginsForSession() as ExtensionPlugin[]);
|
|
229
284
|
}
|
|
230
285
|
|
|
231
286
|
const driverLog = bidiHandlerDriver.log;
|
|
@@ -240,6 +295,7 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
|
|
|
240
295
|
const socketSend = B.promisify(socket.send, {context: socket});
|
|
241
296
|
return async (data: string | Buffer) => {
|
|
242
297
|
try {
|
|
298
|
+
await assertIsOpen(socket);
|
|
243
299
|
await socketSend(data);
|
|
244
300
|
} catch (err) {
|
|
245
301
|
logSocketErr(err);
|
|
@@ -254,7 +310,7 @@ function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage)
|
|
|
254
310
|
// bidi socket server (e.g. on a browser)
|
|
255
311
|
const sendToProxy: SendData | null = proxyClient ? sendFactory(proxyClient) : null;
|
|
256
312
|
|
|
257
|
-
return {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr};
|
|
313
|
+
return {bidiHandlerDriver, bidiHandlerPlugins, proxyClient, send, sendToProxy, logSocketErr};
|
|
258
314
|
}
|
|
259
315
|
|
|
260
316
|
/**
|
|
@@ -315,6 +371,7 @@ function initBidiProxyHandlers(
|
|
|
315
371
|
* upstream socket
|
|
316
372
|
* @param bidiHandlerDriver - the driver
|
|
317
373
|
* handling the bidi commands
|
|
374
|
+
* @param bidiHandlerPlugins - plugins that might also handle bidi commands
|
|
318
375
|
* @param logSocketErr - a special prefixed logger
|
|
319
376
|
*/
|
|
320
377
|
function initBidiSocketHandlers(
|
|
@@ -324,6 +381,7 @@ function initBidiSocketHandlers(
|
|
|
324
381
|
send: SendData,
|
|
325
382
|
sendToProxy: SendData | null,
|
|
326
383
|
bidiHandlerDriver: AnyDriver,
|
|
384
|
+
bidiHandlerPlugins: ExtensionPlugin[],
|
|
327
385
|
logSocketErr: LogSocketError,
|
|
328
386
|
): void {
|
|
329
387
|
const driverLog = bidiHandlerDriver.log;
|
|
@@ -340,9 +398,11 @@ function initBidiSocketHandlers(
|
|
|
340
398
|
ws.on('message', async (data: Buffer) => {
|
|
341
399
|
if (proxyClient && sendToProxy) {
|
|
342
400
|
// if we're meant to proxy to an upstream bidi socket, just do that
|
|
401
|
+
// TODO trying to determine how this proxying behaviour would interface with plugins is too
|
|
402
|
+
// complex for now, so just ignore plugins in this case
|
|
343
403
|
await sendToProxy(data.toString('utf8'));
|
|
344
404
|
} else {
|
|
345
|
-
const res = await this.onBidiMessage(data, bidiHandlerDriver);
|
|
405
|
+
const res = await this.onBidiMessage(data, bidiHandlerDriver, bidiHandlerPlugins);
|
|
346
406
|
await send(JSON.stringify(res));
|
|
347
407
|
}
|
|
348
408
|
});
|
|
@@ -381,56 +441,103 @@ function initBidiEventListeners(
|
|
|
381
441
|
this: AppiumDriver,
|
|
382
442
|
ws: WebSocket,
|
|
383
443
|
bidiHandlerDriver: AnyDriver,
|
|
444
|
+
bidiHandlerPlugins: ExtensionPlugin[],
|
|
384
445
|
send: SendData,
|
|
385
446
|
): void {
|
|
386
447
|
// If the driver emits a bidi event that should maybe get sent to the client, check to make
|
|
387
448
|
// sure the client is subscribed and then pass it on
|
|
388
|
-
const driverLog = bidiHandlerDriver.log;
|
|
389
|
-
const driverEe = bidiHandlerDriver.eventEmitter;
|
|
390
449
|
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
|
|
391
450
|
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
context
|
|
396
|
-
|
|
397
|
-
if (!method || !params) {
|
|
398
|
-
driverLog.warn(
|
|
399
|
-
`Driver emitted a bidi event that was malformed. Require method and params keys ` +
|
|
400
|
-
`(with optional context). But instead received: ${_.truncate(JSON.stringify({
|
|
401
|
-
context,
|
|
402
|
-
method,
|
|
403
|
-
params,
|
|
404
|
-
}), {length: MAX_LOGGED_DATA_LENGTH})}`,
|
|
405
|
-
);
|
|
406
|
-
return;
|
|
407
|
-
}
|
|
408
|
-
if (ws.readyState !== WebSocket.OPEN) {
|
|
409
|
-
// if the websocket is not still 'open', then we can ignore sending these events
|
|
410
|
-
if (ws.readyState > WebSocket.OPEN) {
|
|
411
|
-
// if the websocket is closed or closing, we can remove this listener as well to avoid
|
|
412
|
-
// leaks
|
|
413
|
-
driverEe.removeListener(BIDI_EVENT_NAME, eventListener);
|
|
451
|
+
const eventListenerFactory = (extType: 'driver'|'plugin', ext: ExtensionCore) => {
|
|
452
|
+
const eventListener = async ({context, method, params = {}}) => {
|
|
453
|
+
// if the driver didn't specify a context, use the empty context
|
|
454
|
+
if (!context) {
|
|
455
|
+
context = '';
|
|
414
456
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
driverLog.info(
|
|
424
|
-
`<-- BIDI EVENT ${method} (context: '${context}', ` +
|
|
425
|
-
`params: ${_.truncate(JSON.stringify(params), {length: MAX_LOGGED_DATA_LENGTH})}). ` +
|
|
426
|
-
`All further similar events won't be logged.`,
|
|
457
|
+
if (!method || !params) {
|
|
458
|
+
ext.log?.warn( // some old plugins might not have the `log` property
|
|
459
|
+
`${_.capitalize(extType)} emitted a bidi event that was malformed. Require method and params keys ` +
|
|
460
|
+
`(with optional context). But instead received: ${_.truncate(JSON.stringify({
|
|
461
|
+
context,
|
|
462
|
+
method,
|
|
463
|
+
params,
|
|
464
|
+
}), {length: MAX_LOGGED_DATA_LENGTH})}`,
|
|
427
465
|
);
|
|
428
|
-
|
|
466
|
+
return;
|
|
429
467
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
468
|
+
if (ws.readyState !== WebSocket.OPEN) {
|
|
469
|
+
// if the websocket is not still 'open', then we can ignore sending these events
|
|
470
|
+
if (ws.readyState > WebSocket.OPEN) {
|
|
471
|
+
// if the websocket is closed or closing, we can remove this listener as well to avoid
|
|
472
|
+
// leaks. Some old plugin classes might not have the `eventEmitter` property, so use an
|
|
473
|
+
// existence guard for now.
|
|
474
|
+
ext.eventEmitter?.removeListener(BIDI_EVENT_NAME, eventListener);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
const eventSubs = bidiHandlerDriver.bidiEventSubs[method];
|
|
480
|
+
if (_.isArray(eventSubs) && eventSubs.includes(context)) {
|
|
481
|
+
if (method in eventLogCounts) {
|
|
482
|
+
++eventLogCounts[method];
|
|
483
|
+
} else {
|
|
484
|
+
ext.log?.info( // some old plugins might not have the `log` property
|
|
485
|
+
`<-- BIDI EVENT ${method} (context: '${context}', ` +
|
|
486
|
+
`params: ${_.truncate(JSON.stringify(params), {length: MAX_LOGGED_DATA_LENGTH})}). ` +
|
|
487
|
+
`All further similar events won't be logged.`,
|
|
488
|
+
);
|
|
489
|
+
eventLogCounts[method] = 1;
|
|
490
|
+
}
|
|
491
|
+
// now we can send the event onto the socket
|
|
492
|
+
const ev = {type: 'event', context, method, params};
|
|
493
|
+
await send(JSON.stringify(ev));
|
|
494
|
+
}
|
|
495
|
+
};
|
|
496
|
+
return eventListener;
|
|
434
497
|
};
|
|
435
|
-
|
|
498
|
+
bidiHandlerDriver.eventEmitter.on(BIDI_EVENT_NAME, eventListenerFactory('driver', bidiHandlerDriver as ExtensionCore));
|
|
499
|
+
for (const plugin of bidiHandlerPlugins) {
|
|
500
|
+
// some old plugins might not have the eventEmitter property
|
|
501
|
+
plugin.eventEmitter?.on(BIDI_EVENT_NAME, eventListenerFactory('plugin', plugin));
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async function assertIsOpen(
|
|
506
|
+
ws: WebSocket,
|
|
507
|
+
timeoutMs: number = 5000,
|
|
508
|
+
): Promise<WebSocket> {
|
|
509
|
+
if (ws.readyState === ws.OPEN) {
|
|
510
|
+
return ws;
|
|
511
|
+
}
|
|
512
|
+
if (ws.readyState > ws.OPEN) {
|
|
513
|
+
throw new Error(`The BiDi web socket at ${ws.url} is not open`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
let errorListener;
|
|
517
|
+
let openListener;
|
|
518
|
+
// The socket is in CONNECTING state. Wait up to `timeoutMs` until it is open
|
|
519
|
+
try {
|
|
520
|
+
await new Promise((resolve, reject) => {
|
|
521
|
+
setTimeout(() => reject(
|
|
522
|
+
new Error(
|
|
523
|
+
`The BiDi web socket at ${ws.url} did not ` +
|
|
524
|
+
`open after ${timeoutMs}ms timeout`
|
|
525
|
+
)
|
|
526
|
+
), timeoutMs);
|
|
527
|
+
ws.once('error', reject);
|
|
528
|
+
errorListener = reject;
|
|
529
|
+
ws.once('open', resolve);
|
|
530
|
+
openListener = resolve;
|
|
531
|
+
});
|
|
532
|
+
} finally {
|
|
533
|
+
if (errorListener) {
|
|
534
|
+
ws.off('error', errorListener);
|
|
535
|
+
}
|
|
536
|
+
if (openListener) {
|
|
537
|
+
ws.off('open', openListener);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return ws;
|
|
436
541
|
}
|
|
542
|
+
|
|
543
|
+
// #endregion
|