appium-chromedriver 8.0.31 → 8.2.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/CHANGELOG.md +12 -0
- package/build/lib/chromedriver.d.ts +88 -117
- package/build/lib/chromedriver.d.ts.map +1 -1
- package/build/lib/chromedriver.js +273 -284
- package/build/lib/chromedriver.js.map +1 -1
- package/build/lib/constants.d.ts +18 -18
- package/build/lib/constants.d.ts.map +1 -1
- package/build/lib/constants.js.map +1 -1
- package/build/lib/protocol-helpers.d.ts +14 -13
- package/build/lib/protocol-helpers.d.ts.map +1 -1
- package/build/lib/protocol-helpers.js +13 -12
- package/build/lib/protocol-helpers.js.map +1 -1
- package/build/lib/storage-client/chromelabs.d.ts +15 -15
- package/build/lib/storage-client/chromelabs.d.ts.map +1 -1
- package/build/lib/storage-client/chromelabs.js +15 -19
- package/build/lib/storage-client/chromelabs.js.map +1 -1
- package/build/lib/storage-client/googleapis.d.ts +14 -16
- package/build/lib/storage-client/googleapis.d.ts.map +1 -1
- package/build/lib/storage-client/googleapis.js +25 -41
- package/build/lib/storage-client/googleapis.js.map +1 -1
- package/build/lib/storage-client/storage-client.d.ts +41 -56
- package/build/lib/storage-client/storage-client.d.ts.map +1 -1
- package/build/lib/storage-client/storage-client.js +118 -137
- package/build/lib/storage-client/storage-client.js.map +1 -1
- package/build/lib/utils.d.ts +55 -41
- package/build/lib/utils.d.ts.map +1 -1
- package/build/lib/utils.js +51 -54
- package/build/lib/utils.js.map +1 -1
- package/lib/{chromedriver.js → chromedriver.ts} +344 -333
- package/lib/{constants.js → constants.ts} +5 -4
- package/lib/protocol-helpers.ts +44 -0
- package/lib/storage-client/{chromelabs.js → chromelabs.ts} +54 -26
- package/lib/storage-client/{googleapis.js → googleapis.ts} +55 -54
- package/lib/storage-client/{storage-client.js → storage-client.ts} +188 -182
- package/lib/utils.ts +185 -0
- package/package.json +2 -1
- package/lib/protocol-helpers.js +0 -44
- package/lib/utils.js +0 -189
|
@@ -3,7 +3,7 @@ import {JWProxy, PROTOCOLS} from '@appium/base-driver';
|
|
|
3
3
|
import cp from 'child_process';
|
|
4
4
|
import {system, fs, logger, util} from '@appium/support';
|
|
5
5
|
import {retryInterval, asyncmap} from 'asyncbox';
|
|
6
|
-
import {SubProcess, exec} from 'teen_process';
|
|
6
|
+
import {SubProcess, exec, type ExecError} from 'teen_process';
|
|
7
7
|
import B from 'bluebird';
|
|
8
8
|
import {
|
|
9
9
|
getChromeVersion,
|
|
@@ -18,6 +18,10 @@ import path from 'path';
|
|
|
18
18
|
import {compareVersions} from 'compare-versions';
|
|
19
19
|
import {ChromedriverStorageClient} from './storage-client/storage-client';
|
|
20
20
|
import {toW3cCapNames, getCapValue, toW3cCapName} from './protocol-helpers';
|
|
21
|
+
import type {ADB} from 'appium-adb';
|
|
22
|
+
import type {ProxyOptions, HTTPMethod, HTTPBody} from '@appium/types';
|
|
23
|
+
import type {Request, Response} from 'express';
|
|
24
|
+
import type {ChromedriverOpts, ChromedriverVersionMapping} from './types';
|
|
21
25
|
|
|
22
26
|
const NEW_CD_VERSION_FORMAT_MAJOR_VERSION = 73;
|
|
23
27
|
const DEFAULT_HOST = '127.0.0.1';
|
|
@@ -25,17 +29,60 @@ const MIN_CD_VERSION_WITH_W3C_SUPPORT = 75;
|
|
|
25
29
|
const DEFAULT_PORT = 9515;
|
|
26
30
|
const CHROME_BUNDLE_ID = 'com.android.chrome';
|
|
27
31
|
const WEBVIEW_SHELL_BUNDLE_ID = 'org.chromium.webview_shell';
|
|
28
|
-
const WEBVIEW_BUNDLE_IDS = ['com.google.android.webview', 'com.android.webview'];
|
|
32
|
+
const WEBVIEW_BUNDLE_IDS = ['com.google.android.webview', 'com.android.webview'] as const;
|
|
29
33
|
const VERSION_PATTERN = /([\d.]+)/;
|
|
30
34
|
|
|
31
35
|
const CD_VERSION_TIMEOUT = 5000;
|
|
32
36
|
|
|
37
|
+
interface ChromedriverInfo {
|
|
38
|
+
executable: string;
|
|
39
|
+
version: string;
|
|
40
|
+
minChromeVersion: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface NewSessionResponse {
|
|
44
|
+
capabilities?: Record<string, any>;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type SessionCapabilities = Record<string, any>;
|
|
49
|
+
|
|
33
50
|
export class Chromedriver extends events.EventEmitter {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
51
|
+
static readonly EVENT_ERROR = 'chromedriver_error';
|
|
52
|
+
static readonly EVENT_CHANGED = 'stateChanged';
|
|
53
|
+
static readonly STATE_STOPPED = 'stopped';
|
|
54
|
+
static readonly STATE_STARTING = 'starting';
|
|
55
|
+
static readonly STATE_ONLINE = 'online';
|
|
56
|
+
static readonly STATE_STOPPING = 'stopping';
|
|
57
|
+
static readonly STATE_RESTARTING = 'restarting';
|
|
58
|
+
|
|
59
|
+
private readonly _log: any;
|
|
60
|
+
private readonly proxyHost: string;
|
|
61
|
+
private readonly proxyPort: number;
|
|
62
|
+
private readonly adb?: ADB;
|
|
63
|
+
private readonly cmdArgs?: string[];
|
|
64
|
+
private proc: SubProcess | null;
|
|
65
|
+
private readonly useSystemExecutable: boolean;
|
|
66
|
+
private chromedriver?: string;
|
|
67
|
+
private readonly executableDir: string;
|
|
68
|
+
private readonly mappingPath?: string;
|
|
69
|
+
private bundleId?: string;
|
|
70
|
+
private executableVerified: boolean;
|
|
71
|
+
state: string;
|
|
72
|
+
private readonly _execFunc: typeof exec;
|
|
73
|
+
jwproxy: JWProxy;
|
|
74
|
+
private readonly isCustomExecutableDir: boolean;
|
|
75
|
+
private readonly verbose?: boolean;
|
|
76
|
+
private readonly logPath?: string;
|
|
77
|
+
private readonly disableBuildCheck: boolean;
|
|
78
|
+
private readonly storageClient: ChromedriverStorageClient | null;
|
|
79
|
+
private readonly details?: ChromedriverOpts['details'];
|
|
80
|
+
private capabilities: SessionCapabilities;
|
|
81
|
+
private _desiredProtocol: keyof typeof PROTOCOLS | null;
|
|
82
|
+
private _driverVersion: string | null;
|
|
83
|
+
private _onlineStatus: Record<string, any> | null;
|
|
84
|
+
|
|
85
|
+
constructor(args: ChromedriverOpts = {}) {
|
|
39
86
|
super();
|
|
40
87
|
|
|
41
88
|
const {
|
|
@@ -58,13 +105,12 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
58
105
|
this._log = logger.getLogger(generateLogPrefix(this));
|
|
59
106
|
|
|
60
107
|
this.proxyHost = host;
|
|
61
|
-
this.proxyPort = port;
|
|
108
|
+
this.proxyPort = parseInt(String(port), 10);
|
|
62
109
|
this.adb = adb;
|
|
63
110
|
this.cmdArgs = cmdArgs;
|
|
64
111
|
this.proc = null;
|
|
65
112
|
this.useSystemExecutable = useSystemExecutable;
|
|
66
113
|
this.chromedriver = executable;
|
|
67
|
-
this.executableDir = executableDir;
|
|
68
114
|
this.mappingPath = mappingPath;
|
|
69
115
|
this.bundleId = bundleId;
|
|
70
116
|
this.executableVerified = false;
|
|
@@ -73,8 +119,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
73
119
|
// to mock in unit test
|
|
74
120
|
this._execFunc = exec;
|
|
75
121
|
|
|
76
|
-
|
|
77
|
-
const proxyOpts = {
|
|
122
|
+
const proxyOpts: ProxyOptions = {
|
|
78
123
|
server: this.proxyHost,
|
|
79
124
|
port: this.proxyPort,
|
|
80
125
|
log: this._log,
|
|
@@ -83,12 +128,13 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
83
128
|
proxyOpts.reqBasePath = reqBasePath;
|
|
84
129
|
}
|
|
85
130
|
this.jwproxy = new JWProxy(proxyOpts);
|
|
86
|
-
if (
|
|
131
|
+
if (executableDir) {
|
|
87
132
|
// Expects the user set the executable directory explicitly
|
|
133
|
+
this.executableDir = executableDir;
|
|
88
134
|
this.isCustomExecutableDir = true;
|
|
89
135
|
} else {
|
|
90
|
-
this.isCustomExecutableDir = false;
|
|
91
136
|
this.executableDir = getChromedriverDir();
|
|
137
|
+
this.isCustomExecutableDir = false;
|
|
92
138
|
}
|
|
93
139
|
|
|
94
140
|
this.verbose = verbose;
|
|
@@ -98,30 +144,260 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
98
144
|
? new ChromedriverStorageClient({chromedriverDir: this.executableDir})
|
|
99
145
|
: null;
|
|
100
146
|
this.details = details;
|
|
101
|
-
/** @type {any} */
|
|
102
147
|
this.capabilities = {};
|
|
103
|
-
/** @type {keyof PROTOCOLS | null} */
|
|
104
148
|
this._desiredProtocol = null;
|
|
105
149
|
|
|
106
150
|
// Store the running driver version
|
|
107
|
-
/** @type {string|null} */
|
|
108
151
|
this._driverVersion = null;
|
|
109
|
-
/** @type {Record<string, any> | null} */
|
|
110
152
|
this._onlineStatus = null;
|
|
111
153
|
}
|
|
112
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Gets the logger instance for this Chromedriver instance.
|
|
157
|
+
* @returns The logger instance.
|
|
158
|
+
*/
|
|
113
159
|
get log() {
|
|
114
160
|
return this._log;
|
|
115
161
|
}
|
|
116
162
|
|
|
117
163
|
/**
|
|
118
|
-
*
|
|
164
|
+
* Gets the version of the currently running Chromedriver.
|
|
165
|
+
* @returns The driver version string, or null if not yet determined.
|
|
119
166
|
*/
|
|
120
|
-
get driverVersion() {
|
|
167
|
+
get driverVersion(): string | null {
|
|
121
168
|
return this._driverVersion;
|
|
122
169
|
}
|
|
123
170
|
|
|
124
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Starts a new Chromedriver session with the given capabilities.
|
|
173
|
+
* @param caps - The session capabilities to use.
|
|
174
|
+
* @param emitStartingState - Whether to emit the starting state event (default: true).
|
|
175
|
+
* @returns A promise that resolves to the session capabilities returned by Chromedriver.
|
|
176
|
+
* @throws {Error} If Chromedriver fails to start or crashes during startup.
|
|
177
|
+
*/
|
|
178
|
+
async start(caps: SessionCapabilities, emitStartingState = true): Promise<SessionCapabilities> {
|
|
179
|
+
this.capabilities = _.cloneDeep(caps);
|
|
180
|
+
|
|
181
|
+
// set the logging preferences to ALL the console logs
|
|
182
|
+
this.capabilities.loggingPrefs = _.cloneDeep(getCapValue(caps, 'loggingPrefs', {}));
|
|
183
|
+
if (_.isEmpty(this.capabilities.loggingPrefs.browser)) {
|
|
184
|
+
this.capabilities.loggingPrefs.browser = 'ALL';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (emitStartingState) {
|
|
188
|
+
this.changeState(Chromedriver.STATE_STARTING);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const args = this.buildChromedriverArgs();
|
|
192
|
+
// what are the process stdout/stderr conditions wherein we know that
|
|
193
|
+
// the process has started to our satisfaction?
|
|
194
|
+
const startDetector = (stdout: string) => stdout.startsWith('Starting ');
|
|
195
|
+
|
|
196
|
+
let processIsAlive = false;
|
|
197
|
+
let webviewVersion: string | undefined;
|
|
198
|
+
try {
|
|
199
|
+
const chromedriverPath = await this.initChromedriverPath();
|
|
200
|
+
await this.killAll();
|
|
201
|
+
|
|
202
|
+
// set up our subprocess object
|
|
203
|
+
this.proc = new SubProcess(chromedriverPath, args);
|
|
204
|
+
processIsAlive = true;
|
|
205
|
+
|
|
206
|
+
// handle log output
|
|
207
|
+
for (const streamName of ['stderr', 'stdout'] as const) {
|
|
208
|
+
this.proc.on(`line-${streamName}`, (line: string) => {
|
|
209
|
+
// if the cd output is not printed, find the chrome version and print
|
|
210
|
+
// will get a response like
|
|
211
|
+
// DevTools response: {
|
|
212
|
+
// "Android-Package": "io.appium.sampleapp",
|
|
213
|
+
// "Browser": "Chrome/55.0.2883.91",
|
|
214
|
+
// "Protocol-Version": "1.2",
|
|
215
|
+
// "User-Agent": "...",
|
|
216
|
+
// "WebKit-Version": "537.36"
|
|
217
|
+
// }
|
|
218
|
+
if (!webviewVersion) {
|
|
219
|
+
const match = /"Browser": "([^"]+)"/.exec(line);
|
|
220
|
+
if (match) {
|
|
221
|
+
webviewVersion = match[1];
|
|
222
|
+
this.log.debug(`Webview version: '${webviewVersion}'`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (this.verbose) {
|
|
227
|
+
// give the output if it is requested
|
|
228
|
+
this.log.debug(`[${streamName.toUpperCase()}] ${line}`);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// handle out-of-bound exit by simply emitting a stopped state
|
|
234
|
+
this.proc.once('exit', (code: number | null, signal: string | null) => {
|
|
235
|
+
this._driverVersion = null;
|
|
236
|
+
this._desiredProtocol = null;
|
|
237
|
+
this._onlineStatus = null;
|
|
238
|
+
processIsAlive = false;
|
|
239
|
+
if (
|
|
240
|
+
this.state !== Chromedriver.STATE_STOPPED &&
|
|
241
|
+
this.state !== Chromedriver.STATE_STOPPING &&
|
|
242
|
+
this.state !== Chromedriver.STATE_RESTARTING
|
|
243
|
+
) {
|
|
244
|
+
const msg = `Chromedriver exited unexpectedly with code ${code}, signal ${signal}`;
|
|
245
|
+
this.log.error(msg);
|
|
246
|
+
this.changeState(Chromedriver.STATE_STOPPED);
|
|
247
|
+
}
|
|
248
|
+
this.proc?.removeAllListeners();
|
|
249
|
+
this.proc = null;
|
|
250
|
+
});
|
|
251
|
+
this.log.info(`Spawning Chromedriver with: ${this.chromedriver} ${args.join(' ')}`);
|
|
252
|
+
// start subproc and wait for startDetector
|
|
253
|
+
await this.proc.start(startDetector);
|
|
254
|
+
await this.waitForOnline();
|
|
255
|
+
this.syncProtocol();
|
|
256
|
+
return await this.startSession();
|
|
257
|
+
} catch (e) {
|
|
258
|
+
const err = e as Error;
|
|
259
|
+
this.log.debug(err);
|
|
260
|
+
this.emit(Chromedriver.EVENT_ERROR, err);
|
|
261
|
+
// just because we had an error doesn't mean the chromedriver process
|
|
262
|
+
// finished; we should clean up if necessary
|
|
263
|
+
if (processIsAlive) {
|
|
264
|
+
await this.proc?.stop();
|
|
265
|
+
}
|
|
266
|
+
this.proc?.removeAllListeners();
|
|
267
|
+
this.proc = null;
|
|
268
|
+
|
|
269
|
+
let message = '';
|
|
270
|
+
// often the user's Chrome version is not supported by the version of Chromedriver
|
|
271
|
+
if (err.message.includes('Chrome version must be')) {
|
|
272
|
+
message +=
|
|
273
|
+
'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
|
|
274
|
+
if (webviewVersion) {
|
|
275
|
+
message += `Chrome version on the device: ${webviewVersion}\n`;
|
|
276
|
+
}
|
|
277
|
+
const versionsSupportedByDriver =
|
|
278
|
+
/Chrome version must be (.+)/.exec(err.message)?.[1] || '';
|
|
279
|
+
if (versionsSupportedByDriver) {
|
|
280
|
+
message += `Chromedriver supports Chrome version(s): ${versionsSupportedByDriver}\n`;
|
|
281
|
+
}
|
|
282
|
+
message += 'Check the driver tutorial for troubleshooting.\n';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
message += err.message;
|
|
286
|
+
throw this.log.errorWithException(message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Gets the current session ID if the driver is online.
|
|
292
|
+
* @returns The session ID string, or null if the driver is not online.
|
|
293
|
+
*/
|
|
294
|
+
sessionId(): string | null {
|
|
295
|
+
return this.state === Chromedriver.STATE_ONLINE ? this.jwproxy.sessionId : null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Restarts the Chromedriver session.
|
|
300
|
+
* The session will be stopped and then started again with the same capabilities.
|
|
301
|
+
* @returns A promise that resolves to the session capabilities returned by Chromedriver.
|
|
302
|
+
* @throws {Error} If the driver is not online or if restart fails.
|
|
303
|
+
*/
|
|
304
|
+
async restart(): Promise<SessionCapabilities> {
|
|
305
|
+
this.log.info('Restarting chromedriver');
|
|
306
|
+
if (this.state !== Chromedriver.STATE_ONLINE) {
|
|
307
|
+
throw new Error("Can't restart when we're not online");
|
|
308
|
+
}
|
|
309
|
+
this.changeState(Chromedriver.STATE_RESTARTING);
|
|
310
|
+
await this.stop(false);
|
|
311
|
+
return await this.start(this.capabilities, false);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Stops the Chromedriver session and terminates the process.
|
|
316
|
+
* @param emitStates - Whether to emit state change events during shutdown (default: true).
|
|
317
|
+
* @returns A promise that resolves when the session has been stopped.
|
|
318
|
+
*/
|
|
319
|
+
async stop(emitStates = true): Promise<void> {
|
|
320
|
+
if (emitStates) {
|
|
321
|
+
this.changeState(Chromedriver.STATE_STOPPING);
|
|
322
|
+
}
|
|
323
|
+
const runSafeStep = async (f: () => Promise<any> | any): Promise<void> => {
|
|
324
|
+
try {
|
|
325
|
+
return await f();
|
|
326
|
+
} catch (e) {
|
|
327
|
+
const err = e as Error;
|
|
328
|
+
this.log.warn(err.message);
|
|
329
|
+
this.log.debug(err.stack);
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
|
|
333
|
+
await runSafeStep(() => {
|
|
334
|
+
this.proc?.stop('SIGTERM', 20000);
|
|
335
|
+
this.proc?.removeAllListeners();
|
|
336
|
+
this.proc = null;
|
|
337
|
+
});
|
|
338
|
+
this.log.prefix = generateLogPrefix(this);
|
|
339
|
+
if (emitStates) {
|
|
340
|
+
this.changeState(Chromedriver.STATE_STOPPED);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Sends a command to the Chromedriver server.
|
|
346
|
+
* @param url - The endpoint URL (e.g., '/url', '/session').
|
|
347
|
+
* @param method - The HTTP method to use ('POST', 'GET', or 'DELETE').
|
|
348
|
+
* @param body - Optional request body for POST requests.
|
|
349
|
+
* @returns A promise that resolves to the response from Chromedriver.
|
|
350
|
+
*/
|
|
351
|
+
async sendCommand(url: string, method: HTTPMethod, body: HTTPBody = null): Promise<HTTPBody> {
|
|
352
|
+
return await this.jwproxy.command(url, method, body);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Proxies an HTTP request/response to the Chromedriver server.
|
|
357
|
+
* @param req - The incoming HTTP request object.
|
|
358
|
+
* @param res - The outgoing HTTP response object.
|
|
359
|
+
* @returns A promise that resolves when the proxying is complete.
|
|
360
|
+
*/
|
|
361
|
+
async proxyReq(req: Request, res: Response): Promise<void> {
|
|
362
|
+
await this.jwproxy.proxyReqRes(req, res);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Checks if Chromedriver is currently able to automate webviews.
|
|
367
|
+
* Sometimes Chromedriver stops automating webviews; this method runs a simple
|
|
368
|
+
* command to determine the current state.
|
|
369
|
+
* @returns A promise that resolves to true if webviews are working, false otherwise.
|
|
370
|
+
*/
|
|
371
|
+
async hasWorkingWebview(): Promise<boolean> {
|
|
372
|
+
try {
|
|
373
|
+
await this.jwproxy.command('/url', 'GET');
|
|
374
|
+
return true;
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Private methods at the tail of the class
|
|
381
|
+
|
|
382
|
+
private buildChromedriverArgs(): string[] {
|
|
383
|
+
const args = [`--port=${this.proxyPort}`];
|
|
384
|
+
if (this.adb?.adbPort) {
|
|
385
|
+
args.push(`--adb-port=${this.adb.adbPort}`);
|
|
386
|
+
}
|
|
387
|
+
if (_.isArray(this.cmdArgs)) {
|
|
388
|
+
args.push(...this.cmdArgs);
|
|
389
|
+
}
|
|
390
|
+
if (this.logPath) {
|
|
391
|
+
args.push(`--log-path=${this.logPath}`);
|
|
392
|
+
}
|
|
393
|
+
if (this.disableBuildCheck) {
|
|
394
|
+
args.push('--disable-build-check');
|
|
395
|
+
}
|
|
396
|
+
args.push('--verbose');
|
|
397
|
+
return args;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async getDriversMapping(): Promise<ChromedriverVersionMapping> {
|
|
125
401
|
let mapping = _.cloneDeep(CHROMEDRIVER_CHROME_MAPPING);
|
|
126
402
|
if (this.mappingPath) {
|
|
127
403
|
this.log.debug(`Attempting to use Chromedriver->Chrome mapping from '${this.mappingPath}'`);
|
|
@@ -132,7 +408,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
132
408
|
try {
|
|
133
409
|
mapping = JSON.parse(await fs.readFile(this.mappingPath, 'utf8'));
|
|
134
410
|
} catch (e) {
|
|
135
|
-
const err =
|
|
411
|
+
const err = e as Error;
|
|
136
412
|
this.log.warn(`Error parsing mapping from '${this.mappingPath}': ${err.message}`);
|
|
137
413
|
this.log.info('Defaulting to the static Chromedriver->Chrome mapping');
|
|
138
414
|
}
|
|
@@ -153,10 +429,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
153
429
|
return mapping;
|
|
154
430
|
}
|
|
155
431
|
|
|
156
|
-
|
|
157
|
-
* @param {ChromedriverVersionMapping} mapping
|
|
158
|
-
*/
|
|
159
|
-
async getChromedrivers(mapping) {
|
|
432
|
+
private async getChromedrivers(mapping: ChromedriverVersionMapping): Promise<ChromedriverInfo[]> {
|
|
160
433
|
// go through the versions available
|
|
161
434
|
const executables = await fs.glob('*', {
|
|
162
435
|
cwd: this.executableDir,
|
|
@@ -168,11 +441,16 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
168
441
|
`in '${this.executableDir}'`,
|
|
169
442
|
);
|
|
170
443
|
const cds = (
|
|
171
|
-
await asyncmap(executables, async (executable) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
444
|
+
await asyncmap(executables, async (executable: string) => {
|
|
445
|
+
const logError = ({
|
|
446
|
+
message,
|
|
447
|
+
stdout,
|
|
448
|
+
stderr,
|
|
449
|
+
}: {
|
|
450
|
+
message: string;
|
|
451
|
+
stdout?: string;
|
|
452
|
+
stderr?: string;
|
|
453
|
+
}): null => {
|
|
176
454
|
let errMsg =
|
|
177
455
|
`Cannot retrieve version number from '${path.basename(
|
|
178
456
|
executable,
|
|
@@ -188,14 +466,14 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
188
466
|
return null;
|
|
189
467
|
};
|
|
190
468
|
|
|
191
|
-
let stdout;
|
|
192
|
-
let stderr;
|
|
469
|
+
let stdout: string;
|
|
470
|
+
let stderr: string | undefined;
|
|
193
471
|
try {
|
|
194
472
|
({stdout, stderr} = await this._execFunc(executable, ['--version'], {
|
|
195
473
|
timeout: CD_VERSION_TIMEOUT,
|
|
196
474
|
}));
|
|
197
475
|
} catch (e) {
|
|
198
|
-
const err =
|
|
476
|
+
const err = e as ExecError;
|
|
199
477
|
if (
|
|
200
478
|
!(err.message || '').includes('timed out') &&
|
|
201
479
|
!(err.stdout || '').includes('Starting ChromeDriver')
|
|
@@ -213,15 +491,13 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
213
491
|
return logError({message: 'Cannot parse the version string', stdout, stderr});
|
|
214
492
|
}
|
|
215
493
|
let version = match[1];
|
|
216
|
-
let minChromeVersion = mapping[version];
|
|
494
|
+
let minChromeVersion = mapping[version] || null;
|
|
217
495
|
const coercedVersion = semver.coerce(version);
|
|
218
496
|
if (coercedVersion) {
|
|
219
497
|
// before 2019-03-06 versions were of the form major.minor
|
|
220
498
|
if (coercedVersion.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
|
|
221
|
-
version =
|
|
222
|
-
|
|
223
|
-
);
|
|
224
|
-
minChromeVersion = mapping[version];
|
|
499
|
+
version = `${coercedVersion.major}.${coercedVersion.minor}`;
|
|
500
|
+
minChromeVersion = mapping[version] || null;
|
|
225
501
|
}
|
|
226
502
|
if (!minChromeVersion && coercedVersion.major >= NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
|
|
227
503
|
// Assume the major Chrome version is the same as the corresponding driver major version
|
|
@@ -235,7 +511,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
235
511
|
};
|
|
236
512
|
})
|
|
237
513
|
)
|
|
238
|
-
.filter((cd) => !!cd)
|
|
514
|
+
.filter((cd): cd is ChromedriverInfo => !!cd)
|
|
239
515
|
.sort((a, b) => compareVersions(b.version, a.version));
|
|
240
516
|
if (_.isEmpty(cds)) {
|
|
241
517
|
this.log.info(`No Chromedrivers were found in '${this.executableDir}'`);
|
|
@@ -252,7 +528,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
252
528
|
return cds;
|
|
253
529
|
}
|
|
254
530
|
|
|
255
|
-
async getChromeVersion() {
|
|
531
|
+
private async getChromeVersion(): Promise<semver.SemVer | null> {
|
|
256
532
|
// Try to retrieve the version from `details` property if it is set
|
|
257
533
|
// The `info` item must contain the output of /json/version CDP command
|
|
258
534
|
// where `Browser` field looks like `Chrome/72.0.3601.0``
|
|
@@ -267,7 +543,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
267
543
|
}
|
|
268
544
|
}
|
|
269
545
|
|
|
270
|
-
let chromeVersion;
|
|
546
|
+
let chromeVersion: string | undefined;
|
|
271
547
|
|
|
272
548
|
// in case of WebView Browser Tester, simply try to find the underlying webview
|
|
273
549
|
if (this.bundleId === WEBVIEW_SHELL_BUNDLE_ID) {
|
|
@@ -321,12 +597,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
321
597
|
return chromeVersion ? semver.coerce(chromeVersion) : null;
|
|
322
598
|
}
|
|
323
599
|
|
|
324
|
-
|
|
325
|
-
*
|
|
326
|
-
* @param {ChromedriverVersionMapping} newMapping
|
|
327
|
-
* @returns {Promise<void>}
|
|
328
|
-
*/
|
|
329
|
-
async updateDriversMapping(newMapping) {
|
|
600
|
+
private async updateDriversMapping(newMapping: ChromedriverVersionMapping): Promise<void> {
|
|
330
601
|
let shouldUpdateStaticMapping = true;
|
|
331
602
|
if (!this.mappingPath) {
|
|
332
603
|
this.log.warn('No mapping path provided');
|
|
@@ -337,7 +608,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
337
608
|
await fs.writeFile(this.mappingPath, JSON.stringify(newMapping, null, 2), 'utf8');
|
|
338
609
|
shouldUpdateStaticMapping = false;
|
|
339
610
|
} catch (e) {
|
|
340
|
-
const err =
|
|
611
|
+
const err = e as Error;
|
|
341
612
|
this.log.warn(
|
|
342
613
|
`Cannot store the updated chromedrivers mapping into '${this.mappingPath}'. ` +
|
|
343
614
|
`This may reduce the performance of further executions. Original error: ${err.message}`,
|
|
@@ -349,12 +620,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
349
620
|
}
|
|
350
621
|
}
|
|
351
622
|
|
|
352
|
-
|
|
353
|
-
* When executableDir is given explicitly for non-adb environment,
|
|
354
|
-
* this method will respect the executableDir rather than the system installed binary.
|
|
355
|
-
* @returns {Promise<string>}
|
|
356
|
-
*/
|
|
357
|
-
async getCompatibleChromedriver() {
|
|
623
|
+
private async getCompatibleChromedriver(): Promise<string> {
|
|
358
624
|
if (!this.adb && !this.isCustomExecutableDir) {
|
|
359
625
|
return await getChromedriverBinaryPath();
|
|
360
626
|
}
|
|
@@ -365,11 +631,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
365
631
|
}
|
|
366
632
|
|
|
367
633
|
let didStorageSync = false;
|
|
368
|
-
|
|
369
|
-
*
|
|
370
|
-
* @param {import('semver').SemVer} chromeVersion
|
|
371
|
-
*/
|
|
372
|
-
const syncChromedrivers = async (chromeVersion) => {
|
|
634
|
+
const syncChromedrivers = async (chromeVersion: semver.SemVer): Promise<boolean> => {
|
|
373
635
|
didStorageSync = true;
|
|
374
636
|
if (!this.storageClient) {
|
|
375
637
|
return false;
|
|
@@ -389,17 +651,16 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
389
651
|
const {version, minBrowserVersion} = retrievedMapping[x];
|
|
390
652
|
acc[version] = minBrowserVersion;
|
|
391
653
|
return acc;
|
|
392
|
-
},
|
|
654
|
+
}, {} as ChromedriverVersionMapping);
|
|
393
655
|
Object.assign(mapping, synchronizedDriversMapping);
|
|
394
656
|
await this.updateDriversMapping(mapping);
|
|
395
657
|
return true;
|
|
396
658
|
};
|
|
397
659
|
|
|
398
|
-
|
|
660
|
+
while (true) {
|
|
399
661
|
const cds = await this.getChromedrivers(mapping);
|
|
400
662
|
|
|
401
|
-
|
|
402
|
-
const missingVersions = {};
|
|
663
|
+
const missingVersions: ChromedriverVersionMapping = {};
|
|
403
664
|
for (const {version, minChromeVersion} of cds) {
|
|
404
665
|
if (!minChromeVersion || mapping[version]) {
|
|
405
666
|
continue;
|
|
@@ -473,7 +734,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
473
734
|
continue;
|
|
474
735
|
}
|
|
475
736
|
} catch (e) {
|
|
476
|
-
const err =
|
|
737
|
+
const err = e as Error;
|
|
477
738
|
this.log.warn(
|
|
478
739
|
`Cannot synchronize local chromedrivers with the remote storage: ${err.message}`,
|
|
479
740
|
);
|
|
@@ -499,13 +760,12 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
499
760
|
` capability.`,
|
|
500
761
|
);
|
|
501
762
|
return binPath;
|
|
502
|
-
|
|
503
|
-
} while (true);
|
|
763
|
+
}
|
|
504
764
|
}
|
|
505
765
|
|
|
506
|
-
async initChromedriverPath() {
|
|
766
|
+
private async initChromedriverPath(): Promise<string> {
|
|
507
767
|
if (this.executableVerified && this.chromedriver) {
|
|
508
|
-
return
|
|
768
|
+
return this.chromedriver;
|
|
509
769
|
}
|
|
510
770
|
|
|
511
771
|
let chromedriver = this.chromedriver;
|
|
@@ -521,21 +781,15 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
521
781
|
if (!(await fs.exists(chromedriver))) {
|
|
522
782
|
throw new Error(
|
|
523
783
|
`Trying to use a chromedriver binary at the path ` +
|
|
524
|
-
`${
|
|
784
|
+
`${chromedriver}, but it doesn't exist!`,
|
|
525
785
|
);
|
|
526
786
|
}
|
|
527
787
|
this.executableVerified = true;
|
|
528
|
-
this.log.info(`Set chromedriver binary as: ${
|
|
529
|
-
return
|
|
788
|
+
this.log.info(`Set chromedriver binary as: ${chromedriver}`);
|
|
789
|
+
return chromedriver;
|
|
530
790
|
}
|
|
531
791
|
|
|
532
|
-
|
|
533
|
-
* Determines the driver communication protocol
|
|
534
|
-
* based on various validation rules.
|
|
535
|
-
*
|
|
536
|
-
* @returns {keyof PROTOCOLS}
|
|
537
|
-
*/
|
|
538
|
-
syncProtocol() {
|
|
792
|
+
private syncProtocol(): keyof typeof PROTOCOLS {
|
|
539
793
|
if (this.driverVersion) {
|
|
540
794
|
const coercedVersion = semver.coerce(this.driverVersion);
|
|
541
795
|
if (!coercedVersion || coercedVersion.major < MIN_CD_VERSION_WITH_W3C_SUPPORT) {
|
|
@@ -571,158 +825,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
571
825
|
return this._desiredProtocol;
|
|
572
826
|
}
|
|
573
827
|
|
|
574
|
-
|
|
575
|
-
*
|
|
576
|
-
* @param {SessionCapabilities} caps
|
|
577
|
-
* @param {boolean} emitStartingState
|
|
578
|
-
* @returns {Promise<SessionCapabilities>}
|
|
579
|
-
*/
|
|
580
|
-
async start(caps, emitStartingState = true) {
|
|
581
|
-
this.capabilities = _.cloneDeep(caps);
|
|
582
|
-
|
|
583
|
-
// set the logging preferences to ALL the console logs
|
|
584
|
-
this.capabilities.loggingPrefs = _.cloneDeep(getCapValue(caps, 'loggingPrefs', {}));
|
|
585
|
-
if (_.isEmpty(this.capabilities.loggingPrefs.browser)) {
|
|
586
|
-
this.capabilities.loggingPrefs.browser = 'ALL';
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
if (emitStartingState) {
|
|
590
|
-
this.changeState(Chromedriver.STATE_STARTING);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const args = [`--port=${this.proxyPort}`];
|
|
594
|
-
if (this.adb?.adbPort) {
|
|
595
|
-
args.push(`--adb-port=${this.adb.adbPort}`);
|
|
596
|
-
}
|
|
597
|
-
if (_.isArray(this.cmdArgs)) {
|
|
598
|
-
args.push(...this.cmdArgs);
|
|
599
|
-
}
|
|
600
|
-
if (this.logPath) {
|
|
601
|
-
args.push(`--log-path=${this.logPath}`);
|
|
602
|
-
}
|
|
603
|
-
if (this.disableBuildCheck) {
|
|
604
|
-
args.push('--disable-build-check');
|
|
605
|
-
}
|
|
606
|
-
args.push('--verbose');
|
|
607
|
-
// what are the process stdout/stderr conditions wherein we know that
|
|
608
|
-
// the process has started to our satisfaction?
|
|
609
|
-
const startDetector = /** @param {string} stdout */ (stdout) => stdout.startsWith('Starting ');
|
|
610
|
-
|
|
611
|
-
let processIsAlive = false;
|
|
612
|
-
/** @type {string|undefined} */
|
|
613
|
-
let webviewVersion;
|
|
614
|
-
try {
|
|
615
|
-
const chromedriverPath = await this.initChromedriverPath();
|
|
616
|
-
await this.killAll();
|
|
617
|
-
|
|
618
|
-
// set up our subprocess object
|
|
619
|
-
this.proc = new SubProcess(chromedriverPath, args);
|
|
620
|
-
processIsAlive = true;
|
|
621
|
-
|
|
622
|
-
// handle log output
|
|
623
|
-
for (const streamName of ['stderr', 'stdout']) {
|
|
624
|
-
this.proc.on(`line-${streamName}`, (line) => {
|
|
625
|
-
// if the cd output is not printed, find the chrome version and print
|
|
626
|
-
// will get a response like
|
|
627
|
-
// DevTools response: {
|
|
628
|
-
// "Android-Package": "io.appium.sampleapp",
|
|
629
|
-
// "Browser": "Chrome/55.0.2883.91",
|
|
630
|
-
// "Protocol-Version": "1.2",
|
|
631
|
-
// "User-Agent": "...",
|
|
632
|
-
// "WebKit-Version": "537.36"
|
|
633
|
-
// }
|
|
634
|
-
if (!webviewVersion) {
|
|
635
|
-
const match = /"Browser": "([^"]+)"/.exec(line);
|
|
636
|
-
if (match) {
|
|
637
|
-
webviewVersion = match[1];
|
|
638
|
-
this.log.debug(`Webview version: '${webviewVersion}'`);
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (this.verbose) {
|
|
643
|
-
// give the output if it is requested
|
|
644
|
-
this.log.debug(`[${streamName.toUpperCase()}] ${line}`);
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// handle out-of-bound exit by simply emitting a stopped state
|
|
650
|
-
this.proc.once('exit', (code, signal) => {
|
|
651
|
-
this._driverVersion = null;
|
|
652
|
-
this._desiredProtocol = null;
|
|
653
|
-
this._onlineStatus = null;
|
|
654
|
-
processIsAlive = false;
|
|
655
|
-
if (
|
|
656
|
-
this.state !== Chromedriver.STATE_STOPPED &&
|
|
657
|
-
this.state !== Chromedriver.STATE_STOPPING &&
|
|
658
|
-
this.state !== Chromedriver.STATE_RESTARTING
|
|
659
|
-
) {
|
|
660
|
-
const msg = `Chromedriver exited unexpectedly with code ${code}, signal ${signal}`;
|
|
661
|
-
this.log.error(msg);
|
|
662
|
-
this.changeState(Chromedriver.STATE_STOPPED);
|
|
663
|
-
}
|
|
664
|
-
this.proc?.removeAllListeners();
|
|
665
|
-
this.proc = null;
|
|
666
|
-
});
|
|
667
|
-
this.log.info(`Spawning Chromedriver with: ${this.chromedriver} ${args.join(' ')}`);
|
|
668
|
-
// start subproc and wait for startDetector
|
|
669
|
-
await this.proc.start(startDetector);
|
|
670
|
-
await this.waitForOnline();
|
|
671
|
-
this.syncProtocol();
|
|
672
|
-
return await this.startSession();
|
|
673
|
-
} catch (e) {
|
|
674
|
-
const err = /** @type {Error} */ (e);
|
|
675
|
-
this.log.debug(err);
|
|
676
|
-
this.emit(Chromedriver.EVENT_ERROR, err);
|
|
677
|
-
// just because we had an error doesn't mean the chromedriver process
|
|
678
|
-
// finished; we should clean up if necessary
|
|
679
|
-
if (processIsAlive) {
|
|
680
|
-
await this.proc?.stop();
|
|
681
|
-
}
|
|
682
|
-
this.proc?.removeAllListeners();
|
|
683
|
-
this.proc = null;
|
|
684
|
-
|
|
685
|
-
let message = '';
|
|
686
|
-
// often the user's Chrome version is not supported by the version of Chromedriver
|
|
687
|
-
if (err.message.includes('Chrome version must be')) {
|
|
688
|
-
message +=
|
|
689
|
-
'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
|
|
690
|
-
if (webviewVersion) {
|
|
691
|
-
message += `Chrome version on the device: ${webviewVersion}\n`;
|
|
692
|
-
}
|
|
693
|
-
const versionsSupportedByDriver =
|
|
694
|
-
/Chrome version must be (.+)/.exec(err.message)?.[1] || '';
|
|
695
|
-
if (versionsSupportedByDriver) {
|
|
696
|
-
message += `Chromedriver supports Chrome version(s): ${versionsSupportedByDriver}\n`;
|
|
697
|
-
}
|
|
698
|
-
message += 'Check the driver tutorial for troubleshooting.\n';
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
message += err.message;
|
|
702
|
-
throw this.log.errorWithException(message);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
sessionId() {
|
|
707
|
-
return this.state === Chromedriver.STATE_ONLINE ? this.jwproxy.sessionId : null;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
/**
|
|
711
|
-
* Restarts the chromedriver session
|
|
712
|
-
*
|
|
713
|
-
* @returns {Promise<SessionCapabilities>}
|
|
714
|
-
*/
|
|
715
|
-
async restart() {
|
|
716
|
-
this.log.info('Restarting chromedriver');
|
|
717
|
-
if (this.state !== Chromedriver.STATE_ONLINE) {
|
|
718
|
-
throw new Error("Can't restart when we're not online");
|
|
719
|
-
}
|
|
720
|
-
this.changeState(Chromedriver.STATE_RESTARTING);
|
|
721
|
-
await this.stop(false);
|
|
722
|
-
return await this.start(this.capabilities, false);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
async waitForOnline() {
|
|
828
|
+
private async waitForOnline(): Promise<void> {
|
|
726
829
|
// we need to make sure that CD hasn't crashed
|
|
727
830
|
let chromedriverStopped = false;
|
|
728
831
|
await retryInterval(20, 200, async () => {
|
|
@@ -731,8 +834,7 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
731
834
|
chromedriverStopped = true;
|
|
732
835
|
return;
|
|
733
836
|
}
|
|
734
|
-
|
|
735
|
-
const status = await this.getStatus();
|
|
837
|
+
const status: any = await this.getStatus();
|
|
736
838
|
if (!_.isPlainObject(status) || !status.ready) {
|
|
737
839
|
throw new Error(`The response to the /status API is not valid: ${JSON.stringify(status)}`);
|
|
738
840
|
}
|
|
@@ -750,16 +852,11 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
750
852
|
}
|
|
751
853
|
}
|
|
752
854
|
|
|
753
|
-
async getStatus() {
|
|
855
|
+
private async getStatus(): Promise<any> {
|
|
754
856
|
return await this.jwproxy.command('/status', 'GET');
|
|
755
857
|
}
|
|
756
858
|
|
|
757
|
-
|
|
758
|
-
* Starts a new session
|
|
759
|
-
*
|
|
760
|
-
* @returns {Promise<SessionCapabilities>}
|
|
761
|
-
*/
|
|
762
|
-
async startSession() {
|
|
859
|
+
private async startSession(): Promise<SessionCapabilities> {
|
|
763
860
|
const sessionCaps =
|
|
764
861
|
this._desiredProtocol === PROTOCOLS.W3C
|
|
765
862
|
? {capabilities: {alwaysMatch: toW3cCapNames(this.capabilities)}}
|
|
@@ -768,75 +865,20 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
768
865
|
`Starting ${this._desiredProtocol} Chromedriver session with capabilities: ` +
|
|
769
866
|
JSON.stringify(sessionCaps, null, 2),
|
|
770
867
|
);
|
|
771
|
-
const response =
|
|
772
|
-
await this.jwproxy.command('/session', 'POST', sessionCaps)
|
|
773
|
-
);
|
|
868
|
+
const response = (await this.jwproxy.command('/session', 'POST', sessionCaps)) as NewSessionResponse;
|
|
774
869
|
this.log.prefix = generateLogPrefix(this, this.jwproxy.sessionId);
|
|
775
870
|
this.changeState(Chromedriver.STATE_ONLINE);
|
|
776
|
-
return _.has(response, 'capabilities') ? response.capabilities : response;
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
async stop(emitStates = true) {
|
|
780
|
-
if (emitStates) {
|
|
781
|
-
this.changeState(Chromedriver.STATE_STOPPING);
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
*
|
|
785
|
-
* @param {() => Promise<any>|any} f
|
|
786
|
-
*/
|
|
787
|
-
const runSafeStep = async (f) => {
|
|
788
|
-
try {
|
|
789
|
-
return await f();
|
|
790
|
-
} catch (e) {
|
|
791
|
-
const err = /** @type {Error} */ (e);
|
|
792
|
-
this.log.warn(err.message);
|
|
793
|
-
this.log.debug(err.stack);
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
|
|
797
|
-
await runSafeStep(() => {
|
|
798
|
-
this.proc?.stop('SIGTERM', 20000);
|
|
799
|
-
this.proc?.removeAllListeners();
|
|
800
|
-
this.proc = null;
|
|
801
|
-
});
|
|
802
|
-
this.log.prefix = generateLogPrefix(this);
|
|
803
|
-
if (emitStates) {
|
|
804
|
-
this.changeState(Chromedriver.STATE_STOPPED);
|
|
805
|
-
}
|
|
871
|
+
return _.has(response, 'capabilities') && response.capabilities ? response.capabilities : (response as SessionCapabilities);
|
|
806
872
|
}
|
|
807
873
|
|
|
808
|
-
|
|
809
|
-
*
|
|
810
|
-
* @param {string} state
|
|
811
|
-
*/
|
|
812
|
-
changeState(state) {
|
|
874
|
+
private changeState(state: string): void {
|
|
813
875
|
this.state = state;
|
|
814
876
|
this.log.debug(`Changed state to '${state}'`);
|
|
815
877
|
this.emit(Chromedriver.EVENT_CHANGED, {state});
|
|
816
878
|
}
|
|
817
879
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
* @param {string} url
|
|
821
|
-
* @param {'POST'|'GET'|'DELETE'} method
|
|
822
|
-
* @param {any} body
|
|
823
|
-
* @returns
|
|
824
|
-
*/
|
|
825
|
-
async sendCommand(url, method, body) {
|
|
826
|
-
return await this.jwproxy.command(url, method, body);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
*
|
|
831
|
-
* @param {any} req
|
|
832
|
-
* @param {any} res
|
|
833
|
-
*/
|
|
834
|
-
async proxyReq(req, res) {
|
|
835
|
-
return await this.jwproxy.proxyReqRes(req, res);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
async killAll() {
|
|
839
|
-
let cmd = system.isWindows()
|
|
880
|
+
private async killAll(): Promise<void> {
|
|
881
|
+
const cmd = system.isWindows()
|
|
840
882
|
? `wmic process where "commandline like '%chromedriver.exe%--port=${this.proxyPort}%'" delete`
|
|
841
883
|
: `pkill -15 -f "${this.chromedriver}.*--port=${this.proxyPort}"`;
|
|
842
884
|
this.log.debug(`Killing any old chromedrivers, running: ${cmd}`);
|
|
@@ -858,52 +900,21 @@ export class Chromedriver extends events.EventEmitter {
|
|
|
858
900
|
}
|
|
859
901
|
|
|
860
902
|
try {
|
|
861
|
-
for (
|
|
903
|
+
for (const conn of await this.adb.getForwardList()) {
|
|
862
904
|
// chromedriver will ask ADB to forward a port like "deviceId tcp:port localabstract:webview_devtools_remote_port"
|
|
863
905
|
if (!(conn.includes('webview_devtools') && (!udid || conn.includes(udid)))) {
|
|
864
906
|
continue;
|
|
865
907
|
}
|
|
866
908
|
|
|
867
|
-
|
|
909
|
+
const params = conn.split(/\s+/);
|
|
868
910
|
if (params.length > 1) {
|
|
869
911
|
await this.adb.removePortForward(params[1].replace(/[\D]*/, ''));
|
|
870
912
|
}
|
|
871
913
|
}
|
|
872
914
|
} catch (e) {
|
|
873
|
-
const err =
|
|
915
|
+
const err = e as Error;
|
|
874
916
|
this.log.warn(`Unable to clean forwarded ports. Error: '${err.message}'. Continuing.`);
|
|
875
917
|
}
|
|
876
918
|
}
|
|
877
919
|
}
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* @returns {Promise<boolean>}
|
|
881
|
-
*/
|
|
882
|
-
async hasWorkingWebview() {
|
|
883
|
-
// sometimes chromedriver stops automating webviews. this method runs a
|
|
884
|
-
// simple command to determine our state, and responds accordingly
|
|
885
|
-
try {
|
|
886
|
-
await this.jwproxy.command('/url', 'GET');
|
|
887
|
-
return true;
|
|
888
|
-
} catch {
|
|
889
|
-
return false;
|
|
890
|
-
}
|
|
891
|
-
}
|
|
892
920
|
}
|
|
893
|
-
|
|
894
|
-
Chromedriver.EVENT_ERROR = 'chromedriver_error';
|
|
895
|
-
Chromedriver.EVENT_CHANGED = 'stateChanged';
|
|
896
|
-
Chromedriver.STATE_STOPPED = 'stopped';
|
|
897
|
-
Chromedriver.STATE_STARTING = 'starting';
|
|
898
|
-
Chromedriver.STATE_ONLINE = 'online';
|
|
899
|
-
Chromedriver.STATE_STOPPING = 'stopping';
|
|
900
|
-
Chromedriver.STATE_RESTARTING = 'restarting';
|
|
901
|
-
|
|
902
|
-
/**
|
|
903
|
-
* @typedef {import('./types').ChromedriverVersionMapping} ChromedriverVersionMapping
|
|
904
|
-
*/
|
|
905
|
-
|
|
906
|
-
/**
|
|
907
|
-
* @typedef {{capabilities: Record<string, any>}} NewSessionResponse
|
|
908
|
-
* @typedef {Record<string, any>} SessionCapabilities
|
|
909
|
-
*/
|