appium 2.13.0 → 2.14.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/build/lib/appium.d.ts +4 -64
- package/build/lib/appium.d.ts.map +1 -1
- package/build/lib/appium.js +46 -316
- package/build/lib/appium.js.map +1 -1
- package/build/lib/bidi.d.ts +32 -0
- package/build/lib/bidi.d.ts.map +1 -0
- package/build/lib/bidi.js +348 -0
- package/build/lib/bidi.js.map +1 -0
- package/build/lib/cli/args.d.ts.map +1 -1
- package/build/lib/cli/args.js +21 -31
- package/build/lib/cli/args.js.map +1 -1
- package/build/lib/cli/extension-command.d.ts.map +1 -1
- package/build/lib/cli/extension-command.js +38 -5
- package/build/lib/cli/extension-command.js.map +1 -1
- package/build/lib/cli/parser.d.ts.map +1 -1
- package/build/lib/cli/parser.js +26 -15
- package/build/lib/cli/parser.js.map +1 -1
- package/build/lib/cli/utils.d.ts.map +1 -1
- package/build/lib/cli/utils.js +3 -1
- package/build/lib/cli/utils.js.map +1 -1
- package/build/lib/config.js +42 -9
- package/build/lib/config.js.map +1 -1
- package/build/lib/extension/extension-config.d.ts +7 -0
- package/build/lib/extension/extension-config.d.ts.map +1 -1
- package/build/lib/extension/extension-config.js +42 -11
- package/build/lib/extension/extension-config.js.map +1 -1
- package/build/lib/extension/manifest.js +2 -2
- package/build/lib/extension/manifest.js.map +1 -1
- package/build/lib/schema/schema.js +3 -3
- package/build/lib/schema/schema.js.map +1 -1
- package/build/lib/utils.js +1 -2
- package/build/lib/utils.js.map +1 -1
- package/lib/appium.js +16 -377
- package/lib/bidi.ts +436 -0
- package/lib/cli/args.js +22 -32
- package/lib/cli/extension-command.js +4 -4
- package/lib/cli/parser.js +33 -17
- package/lib/cli/utils.js +3 -1
- package/lib/config.js +7 -7
- package/lib/extension/extension-config.js +49 -11
- package/lib/extension/manifest.js +2 -2
- package/lib/schema/schema.js +2 -2
- package/lib/utils.js +2 -2
- package/package.json +13 -13
- package/scripts/autoinstall-extensions.js +5 -5
- package/LICENSE +0 -201
package/lib/bidi.ts
ADDED
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import B from 'bluebird';
|
|
3
|
+
import {
|
|
4
|
+
errors,
|
|
5
|
+
} from '@appium/base-driver';
|
|
6
|
+
import {BIDI_BASE_PATH, BIDI_EVENT_NAME} from './constants';
|
|
7
|
+
import WebSocket from 'ws';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import {
|
|
10
|
+
isBroadcastIp,
|
|
11
|
+
fetchInterfaces,
|
|
12
|
+
V4_BROADCAST_IP,
|
|
13
|
+
} from './utils';
|
|
14
|
+
import type {IncomingMessage} from 'node:http';
|
|
15
|
+
import type {AppiumDriver} from './appium';
|
|
16
|
+
import type {
|
|
17
|
+
ErrorBiDiCommandResponse,
|
|
18
|
+
SuccessBiDiCommandResponse,
|
|
19
|
+
ExternalDriver,
|
|
20
|
+
StringRecord
|
|
21
|
+
} from '@appium/types';
|
|
22
|
+
|
|
23
|
+
type AnyDriver = ExternalDriver | AppiumDriver;
|
|
24
|
+
type SendData = (data: string | Buffer) => Promise<void>;
|
|
25
|
+
type LogSocketError = (err: Error) => void;
|
|
26
|
+
interface InitBiDiSocketResult {
|
|
27
|
+
bidiHandlerDriver: AnyDriver;
|
|
28
|
+
proxyClient: WebSocket | null;
|
|
29
|
+
send: SendData;
|
|
30
|
+
sendToProxy: SendData | null;
|
|
31
|
+
logSocketErr: LogSocketError;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
const MIN_WS_CODE_VAL = 1000;
|
|
36
|
+
const MAX_WS_CODE_VAL = 1015;
|
|
37
|
+
const WS_FALLBACK_CODE = 1011; // server encountered an error while fulfilling request
|
|
38
|
+
const BIDI_EVENTS_MAP: WeakMap<AnyDriver, Record<string, number>> = new WeakMap();
|
|
39
|
+
const MAX_LOGGED_DATA_LENGTH = 300;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Clients cannot use broadcast addresses, like 0.0.0.0 or ::
|
|
43
|
+
* to create connections. Thus we prefer a hostname if such
|
|
44
|
+
* address is provided or the actual address of a non-local interface,
|
|
45
|
+
* in case the host only has one such interface.
|
|
46
|
+
*
|
|
47
|
+
* @param address
|
|
48
|
+
*/
|
|
49
|
+
export function determineBiDiHost(address: string): string {
|
|
50
|
+
if (!isBroadcastIp(address)) {
|
|
51
|
+
return address;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nonLocalInterfaces = fetchInterfaces(address === V4_BROADCAST_IP ? 4 : 6)
|
|
55
|
+
.filter((iface) => !iface.internal);
|
|
56
|
+
return nonLocalInterfaces.length === 1 ? nonLocalInterfaces[0].address : os.hostname();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Initialize a new bidi connection and set up handlers
|
|
61
|
+
* @param ws The websocket connection object
|
|
62
|
+
* @param req The connection pathname, which might include the session id
|
|
63
|
+
*/
|
|
64
|
+
export function onBidiConnection(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): void {
|
|
65
|
+
try {
|
|
66
|
+
const initBiDiSocketFunc: OmitThisParameter<typeof initBidiSocket> = initBidiSocket.bind(this);
|
|
67
|
+
const {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr} = initBiDiSocketFunc(
|
|
68
|
+
ws,
|
|
69
|
+
req,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const initBidiSocketHandlersFunc: OmitThisParameter<typeof initBidiSocketHandlers> = initBidiSocketHandlers
|
|
73
|
+
.bind(this);
|
|
74
|
+
initBidiSocketHandlersFunc(
|
|
75
|
+
ws,
|
|
76
|
+
proxyClient,
|
|
77
|
+
send,
|
|
78
|
+
sendToProxy,
|
|
79
|
+
bidiHandlerDriver,
|
|
80
|
+
logSocketErr,
|
|
81
|
+
);
|
|
82
|
+
if (proxyClient) {
|
|
83
|
+
const initBidiProxyHandlersFunc: OmitThisParameter<typeof initBidiProxyHandlers> = initBidiProxyHandlers
|
|
84
|
+
.bind(bidiHandlerDriver);
|
|
85
|
+
initBidiProxyHandlersFunc(proxyClient, ws, send);
|
|
86
|
+
}
|
|
87
|
+
const initBidiEventListenersFunc: OmitThisParameter<typeof initBidiEventListeners> = initBidiEventListeners
|
|
88
|
+
.bind(this);
|
|
89
|
+
initBidiEventListenersFunc(ws, bidiHandlerDriver, send);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
this.log.error(err);
|
|
92
|
+
try {
|
|
93
|
+
ws.close();
|
|
94
|
+
} catch {}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* @param data
|
|
100
|
+
* @param driver
|
|
101
|
+
*/
|
|
102
|
+
export async function onBidiMessage(
|
|
103
|
+
this: AppiumDriver,
|
|
104
|
+
data: Buffer,
|
|
105
|
+
driver: AnyDriver
|
|
106
|
+
): Promise<SuccessBiDiCommandResponse | ErrorBiDiCommandResponse> {
|
|
107
|
+
let resMessage: SuccessBiDiCommandResponse | ErrorBiDiCommandResponse;
|
|
108
|
+
let id: number = 0;
|
|
109
|
+
const driverLog = driver.log;
|
|
110
|
+
const dataTruncated = _.truncate(data.toString(), {length: MAX_LOGGED_DATA_LENGTH});
|
|
111
|
+
try {
|
|
112
|
+
let method: string;
|
|
113
|
+
let params: StringRecord;
|
|
114
|
+
try {
|
|
115
|
+
({id, method, params} = JSON.parse(data.toString('utf8')));
|
|
116
|
+
} catch (err) {
|
|
117
|
+
throw new errors.InvalidArgumentError(
|
|
118
|
+
`Could not parse Bidi command '${dataTruncated}': ${err.message}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
driverLog.info(`--> BIDI message #${id}`);
|
|
122
|
+
if (!method) {
|
|
123
|
+
throw new errors.InvalidArgumentError(
|
|
124
|
+
`Missing method for BiDi operation in '${dataTruncated}'`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
if (!params) {
|
|
128
|
+
throw new errors.InvalidArgumentError(
|
|
129
|
+
`Missing params for BiDi operation in '${dataTruncated}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const result = await driver.executeBidiCommand(method, params);
|
|
133
|
+
resMessage = {
|
|
134
|
+
id,
|
|
135
|
+
type: 'success',
|
|
136
|
+
result,
|
|
137
|
+
};
|
|
138
|
+
} catch (err) {
|
|
139
|
+
resMessage = _.has(err, 'bidiErrObject')
|
|
140
|
+
? err.bidiErrObject(id)
|
|
141
|
+
: {
|
|
142
|
+
id,
|
|
143
|
+
type: 'error',
|
|
144
|
+
error: errors.UnknownError.error(),
|
|
145
|
+
message: (err as Error).message,
|
|
146
|
+
stacktrace: (err as Error).stack,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
driverLog.info(`<-- BIDI message #${id}`);
|
|
150
|
+
return resMessage;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Log a bidi server error
|
|
155
|
+
* @param err
|
|
156
|
+
*/
|
|
157
|
+
export function onBidiServerError(this: AppiumDriver, err: Error): void {
|
|
158
|
+
this.log.warn(`Error from bidi websocket server: ${err}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Initialize a new bidi connection
|
|
163
|
+
* @param ws The websocket connection object
|
|
164
|
+
* @param req The connection pathname, which might include the session id
|
|
165
|
+
*/
|
|
166
|
+
function initBidiSocket(this: AppiumDriver, ws: WebSocket, req: IncomingMessage): InitBiDiSocketResult {
|
|
167
|
+
const pathname = req.url;
|
|
168
|
+
if (!pathname) {
|
|
169
|
+
throw new Error('Invalid connection request: pathname missing from request');
|
|
170
|
+
}
|
|
171
|
+
const bidiSessionRe = new RegExp(`${BIDI_BASE_PATH}/([^/]+)$`);
|
|
172
|
+
const bidiNoSessionRe = new RegExp(`${BIDI_BASE_PATH}/?$`);
|
|
173
|
+
const sessionMatch = bidiSessionRe.exec(pathname);
|
|
174
|
+
const noSessionMatch = bidiNoSessionRe.exec(pathname);
|
|
175
|
+
|
|
176
|
+
if (!sessionMatch && !noSessionMatch) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Got websocket connection for path ${pathname} but didn't know what to do with it. ` +
|
|
179
|
+
`Ignoring and will close the connection`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Let's figure out which driver is going to handle this socket connection. It's either going
|
|
184
|
+
// to be a driver matching a session id appended to the bidi base path, or this umbrella driver
|
|
185
|
+
// (if no session id is included in the bidi connection request)
|
|
186
|
+
|
|
187
|
+
let bidiHandlerDriver: AnyDriver;
|
|
188
|
+
let proxyClient: WebSocket | null = null;
|
|
189
|
+
if (sessionMatch) {
|
|
190
|
+
// If we found a session id, see if it matches an active session
|
|
191
|
+
const sessionId = sessionMatch[1];
|
|
192
|
+
bidiHandlerDriver = this.sessions[sessionId];
|
|
193
|
+
if (!bidiHandlerDriver) {
|
|
194
|
+
// The session ID sent in doesn't match an active session; just ignore this socket
|
|
195
|
+
// connection in that case
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Got bidi connection request for session with id ${sessionId} which is closed ` +
|
|
198
|
+
`or does not exist. Closing the socket connection.`,
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
const driverName = bidiHandlerDriver.constructor.name;
|
|
202
|
+
this.log.info(`Bidi websocket connection made for session ${sessionId}`);
|
|
203
|
+
// store this socket connection for later removal on session deletion. theoretically there
|
|
204
|
+
// can be multiple sockets per session
|
|
205
|
+
if (!this.bidiSockets[sessionId]) {
|
|
206
|
+
this.bidiSockets[sessionId] = [];
|
|
207
|
+
}
|
|
208
|
+
this.bidiSockets[sessionId].push(ws);
|
|
209
|
+
|
|
210
|
+
const bidiProxyUrl = bidiHandlerDriver.bidiProxyUrl;
|
|
211
|
+
if (bidiProxyUrl) {
|
|
212
|
+
try {
|
|
213
|
+
new URL(bidiProxyUrl);
|
|
214
|
+
} catch {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Got request for ${driverName} to proxy bidi connections to upstream socket with ` +
|
|
217
|
+
`url ${bidiProxyUrl}, but this was not a valid url`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
this.log.info(`Bidi connection for ${driverName} will be proxied to ${bidiProxyUrl}`);
|
|
221
|
+
proxyClient = new WebSocket(bidiProxyUrl);
|
|
222
|
+
this.bidiProxyClients[sessionId] = proxyClient;
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
this.log.info('Bidi websocket connection made to main server');
|
|
226
|
+
// no need to store the socket connection if it's to the main server since it will just
|
|
227
|
+
// stay open as long as the server itself is and will close when the server closes.
|
|
228
|
+
bidiHandlerDriver = this; // eslint-disable-line @typescript-eslint/no-this-alias
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const driverLog = bidiHandlerDriver.log;
|
|
232
|
+
const logSocketErr: LogSocketError = (err: Error) => {
|
|
233
|
+
driverLog.warn(err.message);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// This is a function which wraps the 'send' method on a web socket for two reasons:
|
|
237
|
+
// 1. Make it async-await friendly
|
|
238
|
+
// 2. Do some logging if there's a send error
|
|
239
|
+
const sendFactory = (socket: WebSocket) => {
|
|
240
|
+
const socketSend = B.promisify(socket.send, {context: socket});
|
|
241
|
+
return async (data: string | Buffer) => {
|
|
242
|
+
try {
|
|
243
|
+
await socketSend(data);
|
|
244
|
+
} catch (err) {
|
|
245
|
+
logSocketErr(err);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// Construct our send method for sending messages to the client
|
|
251
|
+
const send: SendData = sendFactory(ws);
|
|
252
|
+
|
|
253
|
+
// Construct a conditional send method for proxying messages from the client to an upstream
|
|
254
|
+
// bidi socket server (e.g. on a browser)
|
|
255
|
+
const sendToProxy: SendData | null = proxyClient ? sendFactory(proxyClient) : null;
|
|
256
|
+
|
|
257
|
+
return {bidiHandlerDriver, proxyClient, send, sendToProxy, logSocketErr};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Set up handlers on upstream bidi socket we are proxying to/from
|
|
262
|
+
*
|
|
263
|
+
* @param proxyClient - the websocket connection to/from the
|
|
264
|
+
* upstream socket (the one we're proxying to/from)
|
|
265
|
+
* @param ws - the websocket connection to/from the client
|
|
266
|
+
* @param send - a method used to send data to the
|
|
267
|
+
* client
|
|
268
|
+
*/
|
|
269
|
+
function initBidiProxyHandlers(
|
|
270
|
+
this: AnyDriver,
|
|
271
|
+
proxyClient: WebSocket,
|
|
272
|
+
ws: WebSocket,
|
|
273
|
+
send: SendData,
|
|
274
|
+
): void {
|
|
275
|
+
// Set up handlers for events that might come from the upstream bidi socket connection if
|
|
276
|
+
// we're in proxy mode
|
|
277
|
+
const driverLog = this.log;
|
|
278
|
+
|
|
279
|
+
// Here we're receiving a message from the upstream socket server. We want to pass it on to
|
|
280
|
+
// the client
|
|
281
|
+
proxyClient.on('message', send);
|
|
282
|
+
|
|
283
|
+
// If the upstream socket server closes the connection, should close the connection to the
|
|
284
|
+
// client as well
|
|
285
|
+
proxyClient.on('close', (code, reason) => {
|
|
286
|
+
driverLog.debug(
|
|
287
|
+
`Upstream bidi socket closed connection (code ${code}, reason: '${reason}'). ` +
|
|
288
|
+
`Closing proxy connection to client`,
|
|
289
|
+
);
|
|
290
|
+
const intCode: number = _.isNumber(code) ? (code as number) : parseInt(code, 10);
|
|
291
|
+
if (_.isNaN(intCode) || intCode < MIN_WS_CODE_VAL || intCode > MAX_WS_CODE_VAL) {
|
|
292
|
+
driverLog.warn(
|
|
293
|
+
`Received code ${code} from upstream socket, but this is not a valid ` +
|
|
294
|
+
`websocket code. Rewriting to ${WS_FALLBACK_CODE} for ws compatibility`,
|
|
295
|
+
);
|
|
296
|
+
code = WS_FALLBACK_CODE;
|
|
297
|
+
}
|
|
298
|
+
ws.close(code, reason);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
proxyClient.on('error', (err) => {
|
|
302
|
+
driverLog.warn(`Got error on upstream bidi socket connection: ${err.message}`);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Set up handlers on the bidi socket connection to the client
|
|
308
|
+
*
|
|
309
|
+
* @param ws - the websocket connection to/from the client
|
|
310
|
+
* @param proxyClient - the websocket connection to/from the
|
|
311
|
+
* upstream socket (the one we're proxying to/from, if we're proxying)
|
|
312
|
+
* @param send - a method used to send data to the
|
|
313
|
+
* client
|
|
314
|
+
* @param sendToProxy - a method used to send data to the
|
|
315
|
+
* upstream socket
|
|
316
|
+
* @param bidiHandlerDriver - the driver
|
|
317
|
+
* handling the bidi commands
|
|
318
|
+
* @param logSocketErr - a special prefixed logger
|
|
319
|
+
*/
|
|
320
|
+
function initBidiSocketHandlers(
|
|
321
|
+
this: AppiumDriver,
|
|
322
|
+
ws: WebSocket,
|
|
323
|
+
proxyClient: WebSocket | null,
|
|
324
|
+
send: SendData,
|
|
325
|
+
sendToProxy: SendData | null,
|
|
326
|
+
bidiHandlerDriver: AnyDriver,
|
|
327
|
+
logSocketErr: LogSocketError,
|
|
328
|
+
): void {
|
|
329
|
+
const driverLog = bidiHandlerDriver.log;
|
|
330
|
+
// Can't do much with random errors on the connection other than log them
|
|
331
|
+
ws.on('error', logSocketErr);
|
|
332
|
+
|
|
333
|
+
ws.on('open', () => {
|
|
334
|
+
driverLog.info('BiDi websocket connection is now open');
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// Now set up handlers for the various events that might happen on the websocket connection
|
|
338
|
+
// coming from the client
|
|
339
|
+
// First is incoming messages from the client
|
|
340
|
+
ws.on('message', async (data: Buffer) => {
|
|
341
|
+
if (proxyClient && sendToProxy) {
|
|
342
|
+
// if we're meant to proxy to an upstream bidi socket, just do that
|
|
343
|
+
await sendToProxy(data.toString('utf8'));
|
|
344
|
+
} else {
|
|
345
|
+
const res = await this.onBidiMessage(data, bidiHandlerDriver);
|
|
346
|
+
await send(JSON.stringify(res));
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Next consider if the client closes the socket connection on us
|
|
351
|
+
ws.on('close', (code, reason) => {
|
|
352
|
+
// Not sure if we need to do anything here if the client closes the websocket connection.
|
|
353
|
+
// Probably if a session was started via the socket, and the socket closes, we should end the
|
|
354
|
+
// associated session to free up resources. But otherwise, for sockets attached to existing
|
|
355
|
+
// sessions, doing nothing is probably right.
|
|
356
|
+
driverLog.debug(`BiDi socket connection closed (code ${code}, reason: '${reason}')`);
|
|
357
|
+
|
|
358
|
+
// If we're proxying, might as well close the upstream connection and clean it up
|
|
359
|
+
if (proxyClient) {
|
|
360
|
+
driverLog.debug('Also closing BiDi proxy socket connection');
|
|
361
|
+
proxyClient.close(code, reason);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const eventLogCounts = BIDI_EVENTS_MAP.get(bidiHandlerDriver);
|
|
365
|
+
if (!_.isEmpty(eventLogCounts)) {
|
|
366
|
+
driverLog.debug(`BiDi events statistics: ${JSON.stringify(eventLogCounts, null, 2)}`);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Set up bidi event listeners
|
|
373
|
+
*
|
|
374
|
+
* @param ws - the websocket connection to/from the client
|
|
375
|
+
* @param bidiHandlerDriver - the driver
|
|
376
|
+
* handling the bidi commands
|
|
377
|
+
* @param send - a method used to send data to the
|
|
378
|
+
* client
|
|
379
|
+
*/
|
|
380
|
+
function initBidiEventListeners(
|
|
381
|
+
this: AppiumDriver,
|
|
382
|
+
ws: WebSocket,
|
|
383
|
+
bidiHandlerDriver: AnyDriver,
|
|
384
|
+
send: SendData,
|
|
385
|
+
): void {
|
|
386
|
+
// If the driver emits a bidi event that should maybe get sent to the client, check to make
|
|
387
|
+
// sure the client is subscribed and then pass it on
|
|
388
|
+
const driverLog = bidiHandlerDriver.log;
|
|
389
|
+
const driverEe = bidiHandlerDriver.eventEmitter;
|
|
390
|
+
const eventLogCounts: Record<string, number> = BIDI_EVENTS_MAP.get(bidiHandlerDriver) ?? {};
|
|
391
|
+
BIDI_EVENTS_MAP.set(bidiHandlerDriver, eventLogCounts);
|
|
392
|
+
const eventListener = async ({context, method, params = {}}) => {
|
|
393
|
+
// if the driver didn't specify a context, use the empty context
|
|
394
|
+
if (!context) {
|
|
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);
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const eventSubs = bidiHandlerDriver.bidiEventSubs[method];
|
|
419
|
+
if (_.isArray(eventSubs) && eventSubs.includes(context)) {
|
|
420
|
+
if (method in eventLogCounts) {
|
|
421
|
+
++eventLogCounts[method];
|
|
422
|
+
} else {
|
|
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.`,
|
|
427
|
+
);
|
|
428
|
+
eventLogCounts[method] = 1;
|
|
429
|
+
}
|
|
430
|
+
// now we can send the event onto the socket
|
|
431
|
+
const ev = {type: 'event', context, method, params};
|
|
432
|
+
await send(JSON.stringify(ev));
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
driverEe.on(BIDI_EVENT_NAME, eventListener);
|
|
436
|
+
}
|
package/lib/cli/args.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import {INSTALL_TYPES} from '../extension/extension-config';
|
|
13
13
|
import {toParserArgs} from '../schema/cli-args';
|
|
14
14
|
const DRIVER_EXAMPLE = 'xcuitest';
|
|
15
|
-
const PLUGIN_EXAMPLE = '
|
|
15
|
+
const PLUGIN_EXAMPLE = 'images';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* This is necessary because we pass the array into `argparse`. `argparse` is bad and mutates things. We don't want that.
|
|
@@ -32,7 +32,7 @@ const globalExtensionArgs = new Map([
|
|
|
32
32
|
required: false,
|
|
33
33
|
default: false,
|
|
34
34
|
action: 'store_true',
|
|
35
|
-
help: '
|
|
35
|
+
help: 'Return output in JSON format',
|
|
36
36
|
dest: 'json',
|
|
37
37
|
},
|
|
38
38
|
],
|
|
@@ -82,7 +82,7 @@ function makeListArgs(type) {
|
|
|
82
82
|
required: false,
|
|
83
83
|
default: false,
|
|
84
84
|
action: 'store_true',
|
|
85
|
-
help:
|
|
85
|
+
help: `Show information about available ${type} updates`,
|
|
86
86
|
dest: 'showUpdates',
|
|
87
87
|
},
|
|
88
88
|
],
|
|
@@ -92,7 +92,7 @@ function makeListArgs(type) {
|
|
|
92
92
|
required: false,
|
|
93
93
|
default: false,
|
|
94
94
|
action: 'store_true',
|
|
95
|
-
help:
|
|
95
|
+
help: `Show more information about each ${type}`,
|
|
96
96
|
dest: 'verbose',
|
|
97
97
|
},
|
|
98
98
|
],
|
|
@@ -112,9 +112,8 @@ function makeInstallArgs(type) {
|
|
|
112
112
|
{
|
|
113
113
|
type: 'str',
|
|
114
114
|
help:
|
|
115
|
-
`Name of the ${type} to install, for example: ` +
|
|
116
|
-
|
|
117
|
-
: PLUGIN_EXAMPLE,
|
|
115
|
+
`Name of the ${type} to install, for example: ` +
|
|
116
|
+
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
|
|
118
117
|
},
|
|
119
118
|
],
|
|
120
119
|
[
|
|
@@ -124,7 +123,7 @@ function makeInstallArgs(type) {
|
|
|
124
123
|
default: null,
|
|
125
124
|
choices: INSTALL_TYPES_ARRAY,
|
|
126
125
|
help:
|
|
127
|
-
`Where to look for the ${type} if it is not one of Appium's
|
|
126
|
+
`Where to look for the ${type} if it is not one of Appium's official ` +
|
|
128
127
|
`${type}s. Possible values: ${INSTALL_TYPES_ARRAY.join(', ')}`,
|
|
129
128
|
dest: 'installType',
|
|
130
129
|
},
|
|
@@ -136,9 +135,8 @@ function makeInstallArgs(type) {
|
|
|
136
135
|
default: null,
|
|
137
136
|
type: 'str',
|
|
138
137
|
help:
|
|
139
|
-
`
|
|
140
|
-
`
|
|
141
|
-
`should be reported here, otherwise the install will probably fail.`,
|
|
138
|
+
`The Node.js package name of the ${type}. ` +
|
|
139
|
+
`Required if "source" is set to "git" or "github".`,
|
|
142
140
|
dest: 'packageName',
|
|
143
141
|
},
|
|
144
142
|
],
|
|
@@ -158,9 +156,8 @@ function makeUninstallArgs(type) {
|
|
|
158
156
|
{
|
|
159
157
|
type: 'str',
|
|
160
158
|
help:
|
|
161
|
-
`Name of the ${type} to uninstall, for example: ` +
|
|
162
|
-
|
|
163
|
-
: PLUGIN_EXAMPLE,
|
|
159
|
+
`Name of the ${type} to uninstall, for example: ` +
|
|
160
|
+
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
|
|
164
161
|
},
|
|
165
162
|
],
|
|
166
163
|
]);
|
|
@@ -179,9 +176,8 @@ function makeDoctorArgs(type) {
|
|
|
179
176
|
{
|
|
180
177
|
type: 'str',
|
|
181
178
|
help:
|
|
182
|
-
`Name of the ${type} to run doctor checks for, for example: ` +
|
|
183
|
-
|
|
184
|
-
: PLUGIN_EXAMPLE,
|
|
179
|
+
`Name of the ${type} to run doctor checks for, for example: ` +
|
|
180
|
+
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
|
|
185
181
|
},
|
|
186
182
|
],
|
|
187
183
|
]);
|
|
@@ -200,13 +196,10 @@ function makeUpdateArgs(type) {
|
|
|
200
196
|
{
|
|
201
197
|
type: 'str',
|
|
202
198
|
help:
|
|
203
|
-
`Name of the ${type} to update, or
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
DRIVER_TYPE
|
|
208
|
-
? DRIVER_EXAMPLE
|
|
209
|
-
: PLUGIN_EXAMPLE,
|
|
199
|
+
`Name of the ${type} to update, or "installed" to update all installed ${type}s. ` +
|
|
200
|
+
`To see available ${type} updates, run "appium ${type} list --installed --updates". ` +
|
|
201
|
+
'For example: ' +
|
|
202
|
+
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
|
|
210
203
|
},
|
|
211
204
|
],
|
|
212
205
|
[
|
|
@@ -216,8 +209,7 @@ function makeUpdateArgs(type) {
|
|
|
216
209
|
default: false,
|
|
217
210
|
action: 'store_true',
|
|
218
211
|
help:
|
|
219
|
-
|
|
220
|
-
`breaking changes`,
|
|
212
|
+
'Include any available major revision updates, which may have breaking changes',
|
|
221
213
|
},
|
|
222
214
|
],
|
|
223
215
|
]);
|
|
@@ -236,9 +228,8 @@ function makeRunArgs(type) {
|
|
|
236
228
|
{
|
|
237
229
|
type: 'str',
|
|
238
230
|
help:
|
|
239
|
-
`Name of the ${type} to run a script from, for example: ` +
|
|
240
|
-
|
|
241
|
-
: PLUGIN_EXAMPLE,
|
|
231
|
+
`Name of the ${type} to run a script from, for example: ` +
|
|
232
|
+
(type === DRIVER_TYPE ? DRIVER_EXAMPLE : PLUGIN_EXAMPLE),
|
|
242
233
|
},
|
|
243
234
|
],
|
|
244
235
|
[
|
|
@@ -248,9 +239,8 @@ function makeRunArgs(type) {
|
|
|
248
239
|
nargs: '?',
|
|
249
240
|
type: 'str',
|
|
250
241
|
help:
|
|
251
|
-
`Name of the script to run
|
|
252
|
-
`
|
|
253
|
-
`If not provided then all available script names are going to be listed.`,
|
|
242
|
+
`Name of the ${type} script to run. If not provided, return a list ` +
|
|
243
|
+
`of available scripts for this ${type}.`,
|
|
254
244
|
},
|
|
255
245
|
],
|
|
256
246
|
]);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
import B from 'bluebird';
|
|
3
2
|
import _ from 'lodash';
|
|
4
3
|
import path from 'path';
|
|
@@ -18,7 +17,7 @@ import {inspect} from 'node:util';
|
|
|
18
17
|
import {pathToFileURL} from 'url';
|
|
19
18
|
import {Doctor, EXIT_CODE as DOCTOR_EXIT_CODE} from '../doctor/doctor';
|
|
20
19
|
import {npmPackage} from '../utils';
|
|
21
|
-
import semver from 'semver';
|
|
20
|
+
import * as semver from 'semver';
|
|
22
21
|
|
|
23
22
|
const UPDATE_ALL = 'installed';
|
|
24
23
|
|
|
@@ -486,10 +485,11 @@ class ExtensionCliCommand {
|
|
|
486
485
|
getInstallationReceipt({pkg, installPath, installType, installSpec}) {
|
|
487
486
|
const {appium, name, version, peerDependencies} = pkg;
|
|
488
487
|
|
|
488
|
+
const strVersion = /** @type {string} */ (version);
|
|
489
489
|
/** @type {import('appium/types').InternalMetadata} */
|
|
490
490
|
const internal = {
|
|
491
|
-
pkgName: name,
|
|
492
|
-
version,
|
|
491
|
+
pkgName: /** @type {string} */ (name),
|
|
492
|
+
version: strVersion,
|
|
493
493
|
installType,
|
|
494
494
|
installSpec,
|
|
495
495
|
installPath,
|