Skip to content

Commit 7eb8555

Browse files
chore: Optimize process events handling (#415)
1 parent c3fc9f3 commit 7eb8555

File tree

3 files changed

+71
-51
lines changed

3 files changed

+71
-51
lines changed

.github/workflows/unit-test.yml

+3
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ jobs:
1818
strategy:
1919
matrix:
2020
node-version: ${{ fromJSON(needs.prepare_matrix.outputs.versions) }}
21+
# TODO: Remove after node 22.5.0 is fixed
22+
fail-fast: false
2123
runs-on: ubuntu-latest
2224
steps:
2325
- uses: actions/checkout@v3
2426
- name: Use Node.js
2527
uses: actions/setup-node@v3
2628
with:
2729
node-version: ${{ matrix.node-version }}
30+
check-latest: true
2831
- run: npm install
2932
name: Install dev dependencies
3033
- run: npm run lint

lib/chromedriver.js

+67-50
Original file line numberDiff line numberDiff line change
@@ -513,69 +513,71 @@ export class Chromedriver extends events.EventEmitter {
513513

514514
/**
515515
* Sync the WebDriver protocol if current on going protocol is W3C or MJSONWP.
516-
* Does nothing if this.driverVersion was null.
516+
* Does nothing if this.driverVersion is null.
517+
*
518+
* @returns {typeof PROTOCOLS[keyof typeof PROTOCOLS]}
517519
*/
518520
syncProtocol() {
519-
if (this.driverVersion === null) {
521+
if (!this.driverVersion) {
520522
// Keep the default protocol if the driverVersion was unsure.
521-
return;
523+
return this.desiredProtocol;
522524
}
523525

526+
this.desiredProtocol = PROTOCOLS.MJSONWP;
524527
const coercedVersion = semver.coerce(this.driverVersion);
525528
if (!coercedVersion || coercedVersion.major < MIN_CD_VERSION_WITH_W3C_SUPPORT) {
526-
this.log.debug(
527-
`The WebDriver v. ${this.driverVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` +
529+
this.log.info(
530+
`The ChromeDriver v. ${this.driverVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` +
528531
`Defaulting to ${PROTOCOLS.MJSONWP}`,
529532
);
530-
return;
533+
return this.desiredProtocol;
531534
}
532-
533535
// Check only chromeOptions for now.
534536
const chromeOptions = getCapValue(this.capabilities, 'chromeOptions', {});
535537
if (chromeOptions.w3c === false) {
536538
this.log.info(
537-
`The WebDriver v. ${this.driverVersion} supports ${PROTOCOLS.W3C} protocol, ` +
539+
`The ChromeDriver v. ${this.driverVersion} supports ${PROTOCOLS.W3C} protocol, ` +
538540
`but ${PROTOCOLS.MJSONWP} one has been explicitly requested`,
539541
);
540-
return;
542+
return this.desiredProtocol;
541543
}
544+
542545
this.desiredProtocol = PROTOCOLS.W3C;
543-
// given caps might not be properly prefixed
544-
// so we try to fix them in order to properly init
545-
// the new W3C session
546-
this.capabilities = toW3cCapNames(this.capabilities);
546+
this.log.info(`Set ChromeDriver communication protocol to ${PROTOCOLS.W3C}`);
547+
return this.desiredProtocol;
547548
}
548549

549550
/**
550551
* Sync the protocol by reading the given output
551552
*
552-
* @param {string} out The output of WebDriver process start
553+
* @param {string} line The output of ChromeDriver process
554+
* @returns {typeof PROTOCOLS[keyof typeof PROTOCOLS] | null}
553555
*/
554-
detectWebDriverProtocol(out) {
556+
detectWebDriverProtocol(line) {
555557
if (this.driverVersion) {
556-
// Nothing is done if the protocol was already detected
557-
return;
558+
return this.syncProtocol();
558559
}
559560

560561
// also print chromedriver version to logs
561562
// will output something like
562563
// Starting ChromeDriver 2.33.506106 (8a06c39c4582fbfbab6966dbb1c38a9173bfb1a2) on port 9515
563564
// Or MSEdge:
564565
// Starting Microsoft Edge WebDriver 111.0.1661.41 (57be51b50d1be232a9e8186a10017d9e06b1fd16) on port 9515
565-
const match = WEBDRIVER_VERSION_PATTERN.exec(out);
566+
const match = WEBDRIVER_VERSION_PATTERN.exec(line);
566567
if (match && match.length === 3) {
567568
this.log.debug(`${match[1]} version: '${match[2]}'`);
568569
this.driverVersion = match[2];
569570
try {
570-
this.syncProtocol();
571+
return this.syncProtocol();
571572
} catch (e) {
572573
this.driverVersion = null;
573-
this.log.error(`Failed to sync the protocol as '${e}'. Stopping the driver process.`);
574+
this.log.error(`Stopping the chromedriver process. Cannot determinate the protocol: ${e}`);
574575
this.stop();
575576
}
576577
// Does not print else condition log since the log could be
577578
// very noisy when this.verbose option is true.
578579
}
580+
return null;
579581
}
580582

581583
/**
@@ -615,7 +617,9 @@ export class Chromedriver extends events.EventEmitter {
615617
const startDetector = /** @param {string} stdout */ (stdout) => stdout.startsWith('Starting ');
616618

617619
let processIsAlive = false;
620+
/** @type {string|undefined} */
618621
let webviewVersion;
622+
let didDetectProtocol = false;
619623
try {
620624
const chromedriverPath = await this.initChromedriverPath();
621625
await this.killAll();
@@ -625,40 +629,45 @@ export class Chromedriver extends events.EventEmitter {
625629
processIsAlive = true;
626630

627631
// handle log output
628-
this.proc.on('output', (stdout, stderr) => {
629-
// if the cd output is not printed, find the chrome version and print
630-
// will get a response like
631-
// DevTools response: {
632-
// "Android-Package": "io.appium.sampleapp",
633-
// "Browser": "Chrome/55.0.2883.91",
634-
// "Protocol-Version": "1.2",
635-
// "User-Agent": "...",
636-
// "WebKit-Version": "537.36"
637-
// }
638-
const out = stdout + stderr;
639-
let match = /"Browser": "(.*)"/.exec(out);
640-
if (match) {
641-
webviewVersion = match[1];
642-
this.log.debug(`Webview version: '${webviewVersion}'`);
643-
}
644-
645-
this.detectWebDriverProtocol(out);
632+
for (const streamName of ['stderr', 'stdout']) {
633+
this.proc.on(`line-${streamName}`, (line) => {
634+
// if the cd output is not printed, find the chrome version and print
635+
// will get a response like
636+
// DevTools response: {
637+
// "Android-Package": "io.appium.sampleapp",
638+
// "Browser": "Chrome/55.0.2883.91",
639+
// "Protocol-Version": "1.2",
640+
// "User-Agent": "...",
641+
// "WebKit-Version": "537.36"
642+
// }
643+
if (!webviewVersion) {
644+
const match = /"Browser": "([^"]+)"/.exec(line);
645+
if (match) {
646+
webviewVersion = match[1];
647+
this.log.debug(`Webview version: '${webviewVersion}'`);
648+
}
649+
}
646650

647-
// give the output if it is requested
648-
if (this.verbose) {
649-
for (let line of (stdout || '').trim().split('\n')) {
650-
if (!line.trim().length) continue; // eslint-disable-line curly
651-
this.log.debug(`[STDOUT] ${line}`);
651+
if (!didDetectProtocol) {
652+
const proto = this.detectWebDriverProtocol(line);
653+
if (proto === PROTOCOLS.W3C) {
654+
// given caps might not be properly prefixed
655+
// so we try to fix them in order to properly init
656+
// the new W3C session
657+
this.capabilities = toW3cCapNames(this.capabilities);
658+
}
659+
didDetectProtocol = true;
652660
}
653-
for (let line of (stderr || '').trim().split('\n')) {
654-
if (!line.trim().length) continue; // eslint-disable-line curly
655-
this.log.error(`[STDERR] ${line}`);
661+
662+
if (this.verbose) {
663+
// give the output if it is requested
664+
this.log.debug(`[${streamName.toUpperCase()}] ${line}`);
656665
}
657-
}
658-
});
666+
});
667+
}
659668

660669
// handle out-of-bound exit by simply emitting a stopped state
661-
this.proc.on('exit', (code, signal) => {
670+
this.proc.once('exit', (code, signal) => {
662671
this.driverVersion = null;
663672
processIsAlive = false;
664673
if (
@@ -670,6 +679,8 @@ export class Chromedriver extends events.EventEmitter {
670679
this.log.error(msg);
671680
this.changeState(Chromedriver.STATE_STOPPED);
672681
}
682+
this.proc?.removeAllListeners();
683+
this.proc = null;
673684
});
674685
this.log.info(`Spawning chromedriver with: ${this.chromedriver} ${args.join(' ')}`);
675686
// start subproc and wait for startDetector
@@ -685,6 +696,8 @@ export class Chromedriver extends events.EventEmitter {
685696
if (processIsAlive) {
686697
await this.proc?.stop();
687698
}
699+
this.proc?.removeAllListeners();
700+
this.proc = null;
688701

689702
let message = '';
690703
// often the user's Chrome version is not supported by the version of Chromedriver
@@ -776,7 +789,11 @@ export class Chromedriver extends events.EventEmitter {
776789
}
777790
};
778791
await runSafeStep(() => this.jwproxy.command('', 'DELETE'));
779-
await runSafeStep(() => this.proc?.stop('SIGTERM', 20000));
792+
await runSafeStep(() => {
793+
this.proc?.stop('SIGTERM', 20000);
794+
this.proc?.removeAllListeners();
795+
this.proc = null;
796+
});
780797
this.log.prefix = generateLogPrefix(this);
781798
if (emitStates) {
782799
this.changeState(Chromedriver.STATE_STOPPED);

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"lodash": "^4.17.4",
5555
"semver": "^7.0.0",
5656
"source-map-support": "^0.x",
57-
"teen_process": "^2.0.0",
57+
"teen_process": "^2.2.0",
5858
"xpath": "^0.x"
5959
},
6060
"scripts": {

0 commit comments

Comments
 (0)