appium-chromedriver 8.1.0 → 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.
@@ -61,29 +61,55 @@ const WEBVIEW_BUNDLE_IDS = ['com.google.android.webview', 'com.android.webview']
61
61
  const VERSION_PATTERN = /([\d.]+)/;
62
62
  const CD_VERSION_TIMEOUT = 5000;
63
63
  class Chromedriver extends events_1.default.EventEmitter {
64
- /**
65
- *
66
- * @param {import('./types').ChromedriverOpts} args
67
- */
64
+ static EVENT_ERROR = 'chromedriver_error';
65
+ static EVENT_CHANGED = 'stateChanged';
66
+ static STATE_STOPPED = 'stopped';
67
+ static STATE_STARTING = 'starting';
68
+ static STATE_ONLINE = 'online';
69
+ static STATE_STOPPING = 'stopping';
70
+ static STATE_RESTARTING = 'restarting';
71
+ _log;
72
+ proxyHost;
73
+ proxyPort;
74
+ adb;
75
+ cmdArgs;
76
+ proc;
77
+ useSystemExecutable;
78
+ chromedriver;
79
+ executableDir;
80
+ mappingPath;
81
+ bundleId;
82
+ executableVerified;
83
+ state;
84
+ _execFunc;
85
+ jwproxy;
86
+ isCustomExecutableDir;
87
+ verbose;
88
+ logPath;
89
+ disableBuildCheck;
90
+ storageClient;
91
+ details;
92
+ capabilities;
93
+ _desiredProtocol;
94
+ _driverVersion;
95
+ _onlineStatus;
68
96
  constructor(args = {}) {
69
97
  super();
70
98
  const { host = DEFAULT_HOST, port = DEFAULT_PORT, useSystemExecutable = false, executable, executableDir, bundleId, mappingPath, cmdArgs, adb, verbose, logPath, disableBuildCheck, details, isAutodownloadEnabled = false, reqBasePath, } = args;
71
99
  this._log = support_1.logger.getLogger((0, utils_1.generateLogPrefix)(this));
72
100
  this.proxyHost = host;
73
- this.proxyPort = port;
101
+ this.proxyPort = parseInt(String(port), 10);
74
102
  this.adb = adb;
75
103
  this.cmdArgs = cmdArgs;
76
104
  this.proc = null;
77
105
  this.useSystemExecutable = useSystemExecutable;
78
106
  this.chromedriver = executable;
79
- this.executableDir = executableDir;
80
107
  this.mappingPath = mappingPath;
81
108
  this.bundleId = bundleId;
82
109
  this.executableVerified = false;
83
110
  this.state = Chromedriver.STATE_STOPPED;
84
111
  // to mock in unit test
85
112
  this._execFunc = teen_process_1.exec;
86
- /** @type {Record<string, any>} */
87
113
  const proxyOpts = {
88
114
  server: this.proxyHost,
89
115
  port: this.proxyPort,
@@ -93,13 +119,14 @@ class Chromedriver extends events_1.default.EventEmitter {
93
119
  proxyOpts.reqBasePath = reqBasePath;
94
120
  }
95
121
  this.jwproxy = new base_driver_1.JWProxy(proxyOpts);
96
- if (this.executableDir) {
122
+ if (executableDir) {
97
123
  // Expects the user set the executable directory explicitly
124
+ this.executableDir = executableDir;
98
125
  this.isCustomExecutableDir = true;
99
126
  }
100
127
  else {
101
- this.isCustomExecutableDir = false;
102
128
  this.executableDir = (0, utils_1.getChromedriverDir)();
129
+ this.isCustomExecutableDir = false;
103
130
  }
104
131
  this.verbose = verbose;
105
132
  this.logPath = logPath;
@@ -108,25 +135,236 @@ class Chromedriver extends events_1.default.EventEmitter {
108
135
  ? new storage_client_1.ChromedriverStorageClient({ chromedriverDir: this.executableDir })
109
136
  : null;
110
137
  this.details = details;
111
- /** @type {any} */
112
138
  this.capabilities = {};
113
- /** @type {keyof PROTOCOLS | null} */
114
139
  this._desiredProtocol = null;
115
140
  // Store the running driver version
116
- /** @type {string|null} */
117
141
  this._driverVersion = null;
118
- /** @type {Record<string, any> | null} */
119
142
  this._onlineStatus = null;
120
143
  }
144
+ /**
145
+ * Gets the logger instance for this Chromedriver instance.
146
+ * @returns The logger instance.
147
+ */
121
148
  get log() {
122
149
  return this._log;
123
150
  }
124
151
  /**
125
- * @returns {string | null}
152
+ * Gets the version of the currently running Chromedriver.
153
+ * @returns The driver version string, or null if not yet determined.
126
154
  */
127
155
  get driverVersion() {
128
156
  return this._driverVersion;
129
157
  }
158
+ /**
159
+ * Starts a new Chromedriver session with the given capabilities.
160
+ * @param caps - The session capabilities to use.
161
+ * @param emitStartingState - Whether to emit the starting state event (default: true).
162
+ * @returns A promise that resolves to the session capabilities returned by Chromedriver.
163
+ * @throws {Error} If Chromedriver fails to start or crashes during startup.
164
+ */
165
+ async start(caps, emitStartingState = true) {
166
+ this.capabilities = lodash_1.default.cloneDeep(caps);
167
+ // set the logging preferences to ALL the console logs
168
+ this.capabilities.loggingPrefs = lodash_1.default.cloneDeep((0, protocol_helpers_1.getCapValue)(caps, 'loggingPrefs', {}));
169
+ if (lodash_1.default.isEmpty(this.capabilities.loggingPrefs.browser)) {
170
+ this.capabilities.loggingPrefs.browser = 'ALL';
171
+ }
172
+ if (emitStartingState) {
173
+ this.changeState(Chromedriver.STATE_STARTING);
174
+ }
175
+ const args = this.buildChromedriverArgs();
176
+ // what are the process stdout/stderr conditions wherein we know that
177
+ // the process has started to our satisfaction?
178
+ const startDetector = (stdout) => stdout.startsWith('Starting ');
179
+ let processIsAlive = false;
180
+ let webviewVersion;
181
+ try {
182
+ const chromedriverPath = await this.initChromedriverPath();
183
+ await this.killAll();
184
+ // set up our subprocess object
185
+ this.proc = new teen_process_1.SubProcess(chromedriverPath, args);
186
+ processIsAlive = true;
187
+ // handle log output
188
+ for (const streamName of ['stderr', 'stdout']) {
189
+ this.proc.on(`line-${streamName}`, (line) => {
190
+ // if the cd output is not printed, find the chrome version and print
191
+ // will get a response like
192
+ // DevTools response: {
193
+ // "Android-Package": "io.appium.sampleapp",
194
+ // "Browser": "Chrome/55.0.2883.91",
195
+ // "Protocol-Version": "1.2",
196
+ // "User-Agent": "...",
197
+ // "WebKit-Version": "537.36"
198
+ // }
199
+ if (!webviewVersion) {
200
+ const match = /"Browser": "([^"]+)"/.exec(line);
201
+ if (match) {
202
+ webviewVersion = match[1];
203
+ this.log.debug(`Webview version: '${webviewVersion}'`);
204
+ }
205
+ }
206
+ if (this.verbose) {
207
+ // give the output if it is requested
208
+ this.log.debug(`[${streamName.toUpperCase()}] ${line}`);
209
+ }
210
+ });
211
+ }
212
+ // handle out-of-bound exit by simply emitting a stopped state
213
+ this.proc.once('exit', (code, signal) => {
214
+ this._driverVersion = null;
215
+ this._desiredProtocol = null;
216
+ this._onlineStatus = null;
217
+ processIsAlive = false;
218
+ if (this.state !== Chromedriver.STATE_STOPPED &&
219
+ this.state !== Chromedriver.STATE_STOPPING &&
220
+ this.state !== Chromedriver.STATE_RESTARTING) {
221
+ const msg = `Chromedriver exited unexpectedly with code ${code}, signal ${signal}`;
222
+ this.log.error(msg);
223
+ this.changeState(Chromedriver.STATE_STOPPED);
224
+ }
225
+ this.proc?.removeAllListeners();
226
+ this.proc = null;
227
+ });
228
+ this.log.info(`Spawning Chromedriver with: ${this.chromedriver} ${args.join(' ')}`);
229
+ // start subproc and wait for startDetector
230
+ await this.proc.start(startDetector);
231
+ await this.waitForOnline();
232
+ this.syncProtocol();
233
+ return await this.startSession();
234
+ }
235
+ catch (e) {
236
+ const err = e;
237
+ this.log.debug(err);
238
+ this.emit(Chromedriver.EVENT_ERROR, err);
239
+ // just because we had an error doesn't mean the chromedriver process
240
+ // finished; we should clean up if necessary
241
+ if (processIsAlive) {
242
+ await this.proc?.stop();
243
+ }
244
+ this.proc?.removeAllListeners();
245
+ this.proc = null;
246
+ let message = '';
247
+ // often the user's Chrome version is not supported by the version of Chromedriver
248
+ if (err.message.includes('Chrome version must be')) {
249
+ message +=
250
+ 'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
251
+ if (webviewVersion) {
252
+ message += `Chrome version on the device: ${webviewVersion}\n`;
253
+ }
254
+ const versionsSupportedByDriver = /Chrome version must be (.+)/.exec(err.message)?.[1] || '';
255
+ if (versionsSupportedByDriver) {
256
+ message += `Chromedriver supports Chrome version(s): ${versionsSupportedByDriver}\n`;
257
+ }
258
+ message += 'Check the driver tutorial for troubleshooting.\n';
259
+ }
260
+ message += err.message;
261
+ throw this.log.errorWithException(message);
262
+ }
263
+ }
264
+ /**
265
+ * Gets the current session ID if the driver is online.
266
+ * @returns The session ID string, or null if the driver is not online.
267
+ */
268
+ sessionId() {
269
+ return this.state === Chromedriver.STATE_ONLINE ? this.jwproxy.sessionId : null;
270
+ }
271
+ /**
272
+ * Restarts the Chromedriver session.
273
+ * The session will be stopped and then started again with the same capabilities.
274
+ * @returns A promise that resolves to the session capabilities returned by Chromedriver.
275
+ * @throws {Error} If the driver is not online or if restart fails.
276
+ */
277
+ async restart() {
278
+ this.log.info('Restarting chromedriver');
279
+ if (this.state !== Chromedriver.STATE_ONLINE) {
280
+ throw new Error("Can't restart when we're not online");
281
+ }
282
+ this.changeState(Chromedriver.STATE_RESTARTING);
283
+ await this.stop(false);
284
+ return await this.start(this.capabilities, false);
285
+ }
286
+ /**
287
+ * Stops the Chromedriver session and terminates the process.
288
+ * @param emitStates - Whether to emit state change events during shutdown (default: true).
289
+ * @returns A promise that resolves when the session has been stopped.
290
+ */
291
+ async stop(emitStates = true) {
292
+ if (emitStates) {
293
+ this.changeState(Chromedriver.STATE_STOPPING);
294
+ }
295
+ const runSafeStep = async (f) => {
296
+ try {
297
+ return await f();
298
+ }
299
+ catch (e) {
300
+ const err = e;
301
+ this.log.warn(err.message);
302
+ this.log.debug(err.stack);
303
+ }
304
+ };
305
+ await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
306
+ await runSafeStep(() => {
307
+ this.proc?.stop('SIGTERM', 20000);
308
+ this.proc?.removeAllListeners();
309
+ this.proc = null;
310
+ });
311
+ this.log.prefix = (0, utils_1.generateLogPrefix)(this);
312
+ if (emitStates) {
313
+ this.changeState(Chromedriver.STATE_STOPPED);
314
+ }
315
+ }
316
+ /**
317
+ * Sends a command to the Chromedriver server.
318
+ * @param url - The endpoint URL (e.g., '/url', '/session').
319
+ * @param method - The HTTP method to use ('POST', 'GET', or 'DELETE').
320
+ * @param body - Optional request body for POST requests.
321
+ * @returns A promise that resolves to the response from Chromedriver.
322
+ */
323
+ async sendCommand(url, method, body = null) {
324
+ return await this.jwproxy.command(url, method, body);
325
+ }
326
+ /**
327
+ * Proxies an HTTP request/response to the Chromedriver server.
328
+ * @param req - The incoming HTTP request object.
329
+ * @param res - The outgoing HTTP response object.
330
+ * @returns A promise that resolves when the proxying is complete.
331
+ */
332
+ async proxyReq(req, res) {
333
+ await this.jwproxy.proxyReqRes(req, res);
334
+ }
335
+ /**
336
+ * Checks if Chromedriver is currently able to automate webviews.
337
+ * Sometimes Chromedriver stops automating webviews; this method runs a simple
338
+ * command to determine the current state.
339
+ * @returns A promise that resolves to true if webviews are working, false otherwise.
340
+ */
341
+ async hasWorkingWebview() {
342
+ try {
343
+ await this.jwproxy.command('/url', 'GET');
344
+ return true;
345
+ }
346
+ catch {
347
+ return false;
348
+ }
349
+ }
350
+ // Private methods at the tail of the class
351
+ buildChromedriverArgs() {
352
+ const args = [`--port=${this.proxyPort}`];
353
+ if (this.adb?.adbPort) {
354
+ args.push(`--adb-port=${this.adb.adbPort}`);
355
+ }
356
+ if (lodash_1.default.isArray(this.cmdArgs)) {
357
+ args.push(...this.cmdArgs);
358
+ }
359
+ if (this.logPath) {
360
+ args.push(`--log-path=${this.logPath}`);
361
+ }
362
+ if (this.disableBuildCheck) {
363
+ args.push('--disable-build-check');
364
+ }
365
+ args.push('--verbose');
366
+ return args;
367
+ }
130
368
  async getDriversMapping() {
131
369
  let mapping = lodash_1.default.cloneDeep(utils_1.CHROMEDRIVER_CHROME_MAPPING);
132
370
  if (this.mappingPath) {
@@ -140,7 +378,7 @@ class Chromedriver extends events_1.default.EventEmitter {
140
378
  mapping = JSON.parse(await support_1.fs.readFile(this.mappingPath, 'utf8'));
141
379
  }
142
380
  catch (e) {
143
- const err = /** @type {Error} */ (e);
381
+ const err = e;
144
382
  this.log.warn(`Error parsing mapping from '${this.mappingPath}': ${err.message}`);
145
383
  this.log.info('Defaulting to the static Chromedriver->Chrome mapping');
146
384
  }
@@ -161,9 +399,6 @@ class Chromedriver extends events_1.default.EventEmitter {
161
399
  }
162
400
  return mapping;
163
401
  }
164
- /**
165
- * @param {ChromedriverVersionMapping} mapping
166
- */
167
402
  async getChromedrivers(mapping) {
168
403
  // go through the versions available
169
404
  const executables = await support_1.fs.glob('*', {
@@ -174,10 +409,7 @@ class Chromedriver extends events_1.default.EventEmitter {
174
409
  this.log.debug(`Found ${support_1.util.pluralize('executable', executables.length, true)} ` +
175
410
  `in '${this.executableDir}'`);
176
411
  const cds = (await (0, asyncbox_1.asyncmap)(executables, async (executable) => {
177
- /**
178
- * @param {{message: string, stdout?: string, stderr?: string}} opts
179
- */
180
- const logError = ({ message, stdout, stderr }) => {
412
+ const logError = ({ message, stdout, stderr, }) => {
181
413
  let errMsg = `Cannot retrieve version number from '${path_1.default.basename(executable)}' Chromedriver binary. ` +
182
414
  `Make sure it returns a valid version string in response to '--version' command line argument. ${message}`;
183
415
  if (stdout) {
@@ -197,7 +429,7 @@ class Chromedriver extends events_1.default.EventEmitter {
197
429
  }));
198
430
  }
199
431
  catch (e) {
200
- const err = /** @type {import('teen_process').ExecError} */ (e);
432
+ const err = e;
201
433
  if (!(err.message || '').includes('timed out') &&
202
434
  !(err.stdout || '').includes('Starting ChromeDriver')) {
203
435
  return logError(err);
@@ -211,13 +443,13 @@ class Chromedriver extends events_1.default.EventEmitter {
211
443
  return logError({ message: 'Cannot parse the version string', stdout, stderr });
212
444
  }
213
445
  let version = match[1];
214
- let minChromeVersion = mapping[version];
446
+ let minChromeVersion = mapping[version] || null;
215
447
  const coercedVersion = semver.coerce(version);
216
448
  if (coercedVersion) {
217
449
  // before 2019-03-06 versions were of the form major.minor
218
450
  if (coercedVersion.major < NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
219
- version = /** @type {keyof typeof mapping} */ (`${coercedVersion.major}.${coercedVersion.minor}`);
220
- minChromeVersion = mapping[version];
451
+ version = `${coercedVersion.major}.${coercedVersion.minor}`;
452
+ minChromeVersion = mapping[version] || null;
221
453
  }
222
454
  if (!minChromeVersion && coercedVersion.major >= NEW_CD_VERSION_FORMAT_MAJOR_VERSION) {
223
455
  // Assume the major Chrome version is the same as the corresponding driver major version
@@ -301,11 +533,6 @@ class Chromedriver extends events_1.default.EventEmitter {
301
533
  // make sure it is semver, so later checks won't fail
302
534
  return chromeVersion ? semver.coerce(chromeVersion) : null;
303
535
  }
304
- /**
305
- *
306
- * @param {ChromedriverVersionMapping} newMapping
307
- * @returns {Promise<void>}
308
- */
309
536
  async updateDriversMapping(newMapping) {
310
537
  let shouldUpdateStaticMapping = true;
311
538
  if (!this.mappingPath) {
@@ -318,7 +545,7 @@ class Chromedriver extends events_1.default.EventEmitter {
318
545
  shouldUpdateStaticMapping = false;
319
546
  }
320
547
  catch (e) {
321
- const err = /** @type {Error} */ (e);
548
+ const err = e;
322
549
  this.log.warn(`Cannot store the updated chromedrivers mapping into '${this.mappingPath}'. ` +
323
550
  `This may reduce the performance of further executions. Original error: ${err.message}`);
324
551
  }
@@ -327,11 +554,6 @@ class Chromedriver extends events_1.default.EventEmitter {
327
554
  Object.assign(utils_1.CHROMEDRIVER_CHROME_MAPPING, newMapping);
328
555
  }
329
556
  }
330
- /**
331
- * When executableDir is given explicitly for non-adb environment,
332
- * this method will respect the executableDir rather than the system installed binary.
333
- * @returns {Promise<string>}
334
- */
335
557
  async getCompatibleChromedriver() {
336
558
  if (!this.adb && !this.isCustomExecutableDir) {
337
559
  return await (0, utils_1.getChromedriverBinaryPath)();
@@ -341,10 +563,6 @@ class Chromedriver extends events_1.default.EventEmitter {
341
563
  this.log.debug(`The most recent known Chrome version: ${lodash_1.default.values(mapping)[0]}`);
342
564
  }
343
565
  let didStorageSync = false;
344
- /**
345
- *
346
- * @param {import('semver').SemVer} chromeVersion
347
- */
348
566
  const syncChromedrivers = async (chromeVersion) => {
349
567
  didStorageSync = true;
350
568
  if (!this.storageClient) {
@@ -363,14 +581,13 @@ class Chromedriver extends events_1.default.EventEmitter {
363
581
  const { version, minBrowserVersion } = retrievedMapping[x];
364
582
  acc[version] = minBrowserVersion;
365
583
  return acc;
366
- }, /** @type {ChromedriverVersionMapping} */ ({}));
584
+ }, {});
367
585
  Object.assign(mapping, synchronizedDriversMapping);
368
586
  await this.updateDriversMapping(mapping);
369
587
  return true;
370
588
  };
371
- do {
589
+ while (true) {
372
590
  const cds = await this.getChromedrivers(mapping);
373
- /** @type {ChromedriverVersionMapping} */
374
591
  const missingVersions = {};
375
592
  for (const { version, minChromeVersion } of cds) {
376
593
  if (!minChromeVersion || mapping[version]) {
@@ -427,7 +644,7 @@ class Chromedriver extends events_1.default.EventEmitter {
427
644
  }
428
645
  }
429
646
  catch (e) {
430
- const err = /** @type {Error} */ (e);
647
+ const err = e;
431
648
  this.log.warn(`Cannot synchronize local chromedrivers with the remote storage: ${err.message}`);
432
649
  this.log.debug(err.stack);
433
650
  }
@@ -443,12 +660,11 @@ class Chromedriver extends events_1.default.EventEmitter {
443
660
  this.log.debug(`If a specific version is required, specify it with the 'chromedriverExecutable'` +
444
661
  ` capability.`);
445
662
  return binPath;
446
- // eslint-disable-next-line no-constant-condition
447
- } while (true);
663
+ }
448
664
  }
449
665
  async initChromedriverPath() {
450
666
  if (this.executableVerified && this.chromedriver) {
451
- return /** @type {string} */ (this.chromedriver);
667
+ return this.chromedriver;
452
668
  }
453
669
  let chromedriver = this.chromedriver;
454
670
  // the executable might be set (if passed in)
@@ -461,18 +677,12 @@ class Chromedriver extends events_1.default.EventEmitter {
461
677
  }
462
678
  if (!(await support_1.fs.exists(chromedriver))) {
463
679
  throw new Error(`Trying to use a chromedriver binary at the path ` +
464
- `${this.chromedriver}, but it doesn't exist!`);
680
+ `${chromedriver}, but it doesn't exist!`);
465
681
  }
466
682
  this.executableVerified = true;
467
- this.log.info(`Set chromedriver binary as: ${this.chromedriver}`);
468
- return /** @type {string} */ (this.chromedriver);
683
+ this.log.info(`Set chromedriver binary as: ${chromedriver}`);
684
+ return chromedriver;
469
685
  }
470
- /**
471
- * Determines the driver communication protocol
472
- * based on various validation rules.
473
- *
474
- * @returns {keyof PROTOCOLS}
475
- */
476
686
  syncProtocol() {
477
687
  if (this.driverVersion) {
478
688
  const coercedVersion = semver.coerce(this.driverVersion);
@@ -504,142 +714,6 @@ class Chromedriver extends events_1.default.EventEmitter {
504
714
  this._desiredProtocol = base_driver_1.PROTOCOLS.W3C;
505
715
  return this._desiredProtocol;
506
716
  }
507
- /**
508
- *
509
- * @param {SessionCapabilities} caps
510
- * @param {boolean} emitStartingState
511
- * @returns {Promise<SessionCapabilities>}
512
- */
513
- async start(caps, emitStartingState = true) {
514
- this.capabilities = lodash_1.default.cloneDeep(caps);
515
- // set the logging preferences to ALL the console logs
516
- this.capabilities.loggingPrefs = lodash_1.default.cloneDeep((0, protocol_helpers_1.getCapValue)(caps, 'loggingPrefs', {}));
517
- if (lodash_1.default.isEmpty(this.capabilities.loggingPrefs.browser)) {
518
- this.capabilities.loggingPrefs.browser = 'ALL';
519
- }
520
- if (emitStartingState) {
521
- this.changeState(Chromedriver.STATE_STARTING);
522
- }
523
- const args = [`--port=${this.proxyPort}`];
524
- if (this.adb?.adbPort) {
525
- args.push(`--adb-port=${this.adb.adbPort}`);
526
- }
527
- if (lodash_1.default.isArray(this.cmdArgs)) {
528
- args.push(...this.cmdArgs);
529
- }
530
- if (this.logPath) {
531
- args.push(`--log-path=${this.logPath}`);
532
- }
533
- if (this.disableBuildCheck) {
534
- args.push('--disable-build-check');
535
- }
536
- args.push('--verbose');
537
- // what are the process stdout/stderr conditions wherein we know that
538
- // the process has started to our satisfaction?
539
- const startDetector = /** @param {string} stdout */ (stdout) => stdout.startsWith('Starting ');
540
- let processIsAlive = false;
541
- /** @type {string|undefined} */
542
- let webviewVersion;
543
- try {
544
- const chromedriverPath = await this.initChromedriverPath();
545
- await this.killAll();
546
- // set up our subprocess object
547
- this.proc = new teen_process_1.SubProcess(chromedriverPath, args);
548
- processIsAlive = true;
549
- // handle log output
550
- for (const streamName of ['stderr', 'stdout']) {
551
- this.proc.on(`line-${streamName}`, (line) => {
552
- // if the cd output is not printed, find the chrome version and print
553
- // will get a response like
554
- // DevTools response: {
555
- // "Android-Package": "io.appium.sampleapp",
556
- // "Browser": "Chrome/55.0.2883.91",
557
- // "Protocol-Version": "1.2",
558
- // "User-Agent": "...",
559
- // "WebKit-Version": "537.36"
560
- // }
561
- if (!webviewVersion) {
562
- const match = /"Browser": "([^"]+)"/.exec(line);
563
- if (match) {
564
- webviewVersion = match[1];
565
- this.log.debug(`Webview version: '${webviewVersion}'`);
566
- }
567
- }
568
- if (this.verbose) {
569
- // give the output if it is requested
570
- this.log.debug(`[${streamName.toUpperCase()}] ${line}`);
571
- }
572
- });
573
- }
574
- // handle out-of-bound exit by simply emitting a stopped state
575
- this.proc.once('exit', (code, signal) => {
576
- this._driverVersion = null;
577
- this._desiredProtocol = null;
578
- this._onlineStatus = null;
579
- processIsAlive = false;
580
- if (this.state !== Chromedriver.STATE_STOPPED &&
581
- this.state !== Chromedriver.STATE_STOPPING &&
582
- this.state !== Chromedriver.STATE_RESTARTING) {
583
- const msg = `Chromedriver exited unexpectedly with code ${code}, signal ${signal}`;
584
- this.log.error(msg);
585
- this.changeState(Chromedriver.STATE_STOPPED);
586
- }
587
- this.proc?.removeAllListeners();
588
- this.proc = null;
589
- });
590
- this.log.info(`Spawning Chromedriver with: ${this.chromedriver} ${args.join(' ')}`);
591
- // start subproc and wait for startDetector
592
- await this.proc.start(startDetector);
593
- await this.waitForOnline();
594
- this.syncProtocol();
595
- return await this.startSession();
596
- }
597
- catch (e) {
598
- const err = /** @type {Error} */ (e);
599
- this.log.debug(err);
600
- this.emit(Chromedriver.EVENT_ERROR, err);
601
- // just because we had an error doesn't mean the chromedriver process
602
- // finished; we should clean up if necessary
603
- if (processIsAlive) {
604
- await this.proc?.stop();
605
- }
606
- this.proc?.removeAllListeners();
607
- this.proc = null;
608
- let message = '';
609
- // often the user's Chrome version is not supported by the version of Chromedriver
610
- if (err.message.includes('Chrome version must be')) {
611
- message +=
612
- 'Unable to automate Chrome version because it is not supported by this version of Chromedriver.\n';
613
- if (webviewVersion) {
614
- message += `Chrome version on the device: ${webviewVersion}\n`;
615
- }
616
- const versionsSupportedByDriver = /Chrome version must be (.+)/.exec(err.message)?.[1] || '';
617
- if (versionsSupportedByDriver) {
618
- message += `Chromedriver supports Chrome version(s): ${versionsSupportedByDriver}\n`;
619
- }
620
- message += 'Check the driver tutorial for troubleshooting.\n';
621
- }
622
- message += err.message;
623
- throw this.log.errorWithException(message);
624
- }
625
- }
626
- sessionId() {
627
- return this.state === Chromedriver.STATE_ONLINE ? this.jwproxy.sessionId : null;
628
- }
629
- /**
630
- * Restarts the chromedriver session
631
- *
632
- * @returns {Promise<SessionCapabilities>}
633
- */
634
- async restart() {
635
- this.log.info('Restarting chromedriver');
636
- if (this.state !== Chromedriver.STATE_ONLINE) {
637
- throw new Error("Can't restart when we're not online");
638
- }
639
- this.changeState(Chromedriver.STATE_RESTARTING);
640
- await this.stop(false);
641
- return await this.start(this.capabilities, false);
642
- }
643
717
  async waitForOnline() {
644
718
  // we need to make sure that CD hasn't crashed
645
719
  let chromedriverStopped = false;
@@ -649,7 +723,6 @@ class Chromedriver extends events_1.default.EventEmitter {
649
723
  chromedriverStopped = true;
650
724
  return;
651
725
  }
652
- /** @type {any} */
653
726
  const status = await this.getStatus();
654
727
  if (!lodash_1.default.isPlainObject(status) || !status.ready) {
655
728
  throw new Error(`The response to the /status API is not valid: ${JSON.stringify(status)}`);
@@ -671,80 +744,24 @@ class Chromedriver extends events_1.default.EventEmitter {
671
744
  async getStatus() {
672
745
  return await this.jwproxy.command('/status', 'GET');
673
746
  }
674
- /**
675
- * Starts a new session
676
- *
677
- * @returns {Promise<SessionCapabilities>}
678
- */
679
747
  async startSession() {
680
748
  const sessionCaps = this._desiredProtocol === base_driver_1.PROTOCOLS.W3C
681
749
  ? { capabilities: { alwaysMatch: (0, protocol_helpers_1.toW3cCapNames)(this.capabilities) } }
682
750
  : { desiredCapabilities: this.capabilities };
683
751
  this.log.info(`Starting ${this._desiredProtocol} Chromedriver session with capabilities: ` +
684
752
  JSON.stringify(sessionCaps, null, 2));
685
- const response = /** @type {NewSessionResponse} */ (await this.jwproxy.command('/session', 'POST', sessionCaps));
753
+ const response = (await this.jwproxy.command('/session', 'POST', sessionCaps));
686
754
  this.log.prefix = (0, utils_1.generateLogPrefix)(this, this.jwproxy.sessionId);
687
755
  this.changeState(Chromedriver.STATE_ONLINE);
688
- return lodash_1.default.has(response, 'capabilities') ? response.capabilities : response;
756
+ return lodash_1.default.has(response, 'capabilities') && response.capabilities ? response.capabilities : response;
689
757
  }
690
- async stop(emitStates = true) {
691
- if (emitStates) {
692
- this.changeState(Chromedriver.STATE_STOPPING);
693
- }
694
- /**
695
- *
696
- * @param {() => Promise<any>|any} f
697
- */
698
- const runSafeStep = async (f) => {
699
- try {
700
- return await f();
701
- }
702
- catch (e) {
703
- const err = /** @type {Error} */ (e);
704
- this.log.warn(err.message);
705
- this.log.debug(err.stack);
706
- }
707
- };
708
- await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
709
- await runSafeStep(() => {
710
- this.proc?.stop('SIGTERM', 20000);
711
- this.proc?.removeAllListeners();
712
- this.proc = null;
713
- });
714
- this.log.prefix = (0, utils_1.generateLogPrefix)(this);
715
- if (emitStates) {
716
- this.changeState(Chromedriver.STATE_STOPPED);
717
- }
718
- }
719
- /**
720
- *
721
- * @param {string} state
722
- */
723
758
  changeState(state) {
724
759
  this.state = state;
725
760
  this.log.debug(`Changed state to '${state}'`);
726
761
  this.emit(Chromedriver.EVENT_CHANGED, { state });
727
762
  }
728
- /**
729
- *
730
- * @param {string} url
731
- * @param {'POST'|'GET'|'DELETE'} method
732
- * @param {any} body
733
- * @returns
734
- */
735
- async sendCommand(url, method, body) {
736
- return await this.jwproxy.command(url, method, body);
737
- }
738
- /**
739
- *
740
- * @param {any} req
741
- * @param {any} res
742
- */
743
- async proxyReq(req, res) {
744
- return await this.jwproxy.proxyReqRes(req, res);
745
- }
746
763
  async killAll() {
747
- let cmd = support_1.system.isWindows()
764
+ const cmd = support_1.system.isWindows()
748
765
  ? `wmic process where "commandline like '%chromedriver.exe%--port=${this.proxyPort}%'" delete`
749
766
  : `pkill -15 -f "${this.chromedriver}.*--port=${this.proxyPort}"`;
750
767
  this.log.debug(`Killing any old chromedrivers, running: ${cmd}`);
@@ -765,51 +782,23 @@ class Chromedriver extends events_1.default.EventEmitter {
765
782
  this.log.debug(`Cleaning any old adb forwarded port socket connections`);
766
783
  }
767
784
  try {
768
- for (let conn of await this.adb.getForwardList()) {
785
+ for (const conn of await this.adb.getForwardList()) {
769
786
  // chromedriver will ask ADB to forward a port like "deviceId tcp:port localabstract:webview_devtools_remote_port"
770
787
  if (!(conn.includes('webview_devtools') && (!udid || conn.includes(udid)))) {
771
788
  continue;
772
789
  }
773
- let params = conn.split(/\s+/);
790
+ const params = conn.split(/\s+/);
774
791
  if (params.length > 1) {
775
792
  await this.adb.removePortForward(params[1].replace(/[\D]*/, ''));
776
793
  }
777
794
  }
778
795
  }
779
796
  catch (e) {
780
- const err = /** @type {Error} */ (e);
797
+ const err = e;
781
798
  this.log.warn(`Unable to clean forwarded ports. Error: '${err.message}'. Continuing.`);
782
799
  }
783
800
  }
784
801
  }
785
- /**
786
- * @returns {Promise<boolean>}
787
- */
788
- async hasWorkingWebview() {
789
- // sometimes chromedriver stops automating webviews. this method runs a
790
- // simple command to determine our state, and responds accordingly
791
- try {
792
- await this.jwproxy.command('/url', 'GET');
793
- return true;
794
- }
795
- catch {
796
- return false;
797
- }
798
- }
799
802
  }
800
803
  exports.Chromedriver = Chromedriver;
801
- Chromedriver.EVENT_ERROR = 'chromedriver_error';
802
- Chromedriver.EVENT_CHANGED = 'stateChanged';
803
- Chromedriver.STATE_STOPPED = 'stopped';
804
- Chromedriver.STATE_STARTING = 'starting';
805
- Chromedriver.STATE_ONLINE = 'online';
806
- Chromedriver.STATE_STOPPING = 'stopping';
807
- Chromedriver.STATE_RESTARTING = 'restarting';
808
- /**
809
- * @typedef {import('./types').ChromedriverVersionMapping} ChromedriverVersionMapping
810
- */
811
- /**
812
- * @typedef {{capabilities: Record<string, any>}} NewSessionResponse
813
- * @typedef {Record<string, any>} SessionCapabilities
814
- */
815
804
  //# sourceMappingURL=chromedriver.js.map