-
-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Determine chromedriver version from the /status API output #456
Merged
+74
−144
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
93ecc57
feat: Determine chromedriver version from the /status API output
mykola-mokhnach 555ac37
Update tests
mykola-mokhnach 6fd987d
fix lint
mykola-mokhnach b060b29
Update tests
mykola-mokhnach 268a5fc
moar
mykola-mokhnach a5608ad
Add exception for opera driver
mykola-mokhnach 581c679
tune comments
mykola-mokhnach 83bbccb
Update
mykola-mokhnach 66344d8
moar
mykola-mokhnach ed26800
avoid hardcode
mykola-mokhnach File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ import _ from 'lodash'; | |
import path from 'path'; | ||
import {compareVersions} from 'compare-versions'; | ||
import { ChromedriverStorageClient } from './storage-client/storage-client'; | ||
import {toW3cCapNames, getCapValue} from './protocol-helpers'; | ||
import {toW3cCapNames, getCapValue, toW3cCapName} from './protocol-helpers'; | ||
|
||
const NEW_CD_VERSION_FORMAT_MAJOR_VERSION = 73; | ||
const DEFAULT_HOST = '127.0.0.1'; | ||
|
@@ -27,7 +27,6 @@ const CHROME_BUNDLE_ID = 'com.android.chrome'; | |
const WEBVIEW_SHELL_BUNDLE_ID = 'org.chromium.webview_shell'; | ||
const WEBVIEW_BUNDLE_IDS = ['com.google.android.webview', 'com.android.webview']; | ||
const VERSION_PATTERN = /([\d.]+)/; | ||
const WEBDRIVER_VERSION_PATTERN = /Starting (ChromeDriver|Microsoft Edge WebDriver) ([.\d]+)/; | ||
|
||
const CD_VERSION_TIMEOUT = 5000; | ||
|
||
|
@@ -92,17 +91,27 @@ export class Chromedriver extends events.EventEmitter { | |
this.details = details; | ||
/** @type {any} */ | ||
this.capabilities = {}; | ||
/** @type {keyof PROTOCOLS} */ | ||
this.desiredProtocol = PROTOCOLS.MJSONWP; | ||
/** @type {keyof PROTOCOLS | null} */ | ||
this._desiredProtocol = null; | ||
|
||
// Store the running driver version | ||
this.driverVersion = /** @type {string|null} */ null; | ||
/** @type {string|null} */ | ||
this._driverVersion = null; | ||
/** @type {Record<string, any> | null} */ | ||
this._onlineStatus = null; | ||
} | ||
|
||
get log() { | ||
return this._log; | ||
} | ||
|
||
/** | ||
* @returns {string | null} | ||
*/ | ||
get driverVersion() { | ||
return this._driverVersion; | ||
} | ||
|
||
async getDriversMapping() { | ||
let mapping = _.cloneDeep(CHROMEDRIVER_CHROME_MAPPING); | ||
if (this.mappingPath) { | ||
|
@@ -477,8 +486,8 @@ export class Chromedriver extends events.EventEmitter { | |
`capable of automating Chrome '${chromeVersion}'.\nChoosing the most recent, '${binPath}'.`, | ||
); | ||
this.log.debug( | ||
'If a specific version is required, specify it with the `chromedriverExecutable`' + | ||
'desired capability.', | ||
`If a specific version is required, specify it with the 'chromedriverExecutable'` + | ||
` capability.`, | ||
); | ||
return binPath; | ||
// eslint-disable-next-line no-constant-condition | ||
|
@@ -512,72 +521,45 @@ export class Chromedriver extends events.EventEmitter { | |
} | ||
|
||
/** | ||
* Sync the WebDriver protocol if current on going protocol is W3C or MJSONWP. | ||
* Does nothing if this.driverVersion is null. | ||
* Determines the driver communication protocol | ||
* based on various validation rules. | ||
* | ||
* @returns {typeof PROTOCOLS[keyof typeof PROTOCOLS]} | ||
* @returns {keyof PROTOCOLS} | ||
*/ | ||
syncProtocol() { | ||
if (!this.driverVersion) { | ||
// Keep the default protocol if the driverVersion was unsure. | ||
return this.desiredProtocol; | ||
if (this.driverVersion) { | ||
const coercedVersion = semver.coerce(this.driverVersion); | ||
if (!coercedVersion || coercedVersion.major < MIN_CD_VERSION_WITH_W3C_SUPPORT) { | ||
this.log.info( | ||
`The ChromeDriver v. ${this.driverVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` + | ||
`Defaulting to ${PROTOCOLS.MJSONWP}`, | ||
); | ||
this._desiredProtocol = PROTOCOLS.MJSONWP; | ||
return this._desiredProtocol; | ||
} | ||
} | ||
|
||
this.desiredProtocol = PROTOCOLS.MJSONWP; | ||
const coercedVersion = semver.coerce(this.driverVersion); | ||
if (!coercedVersion || coercedVersion.major < MIN_CD_VERSION_WITH_W3C_SUPPORT) { | ||
this.log.info( | ||
`The ChromeDriver v. ${this.driverVersion} does not fully support ${PROTOCOLS.W3C} protocol. ` + | ||
`Defaulting to ${PROTOCOLS.MJSONWP}`, | ||
); | ||
return this.desiredProtocol; | ||
} | ||
// Check only chromeOptions for now. | ||
const chromeOptions = getCapValue(this.capabilities, 'chromeOptions', {}); | ||
if (chromeOptions.w3c === false) { | ||
const isOperaDriver = _.includes(this._onlineStatus?.message, 'OperaDriver'); | ||
const chromeOptions = getCapValue(this.capabilities, 'chromeOptions'); | ||
if (_.isPlainObject(chromeOptions) && chromeOptions.w3c === false) { | ||
this.log.info( | ||
`The ChromeDriver v. ${this.driverVersion} supports ${PROTOCOLS.W3C} protocol, ` + | ||
`but ${PROTOCOLS.MJSONWP} one has been explicitly requested`, | ||
); | ||
return this.desiredProtocol; | ||
} | ||
|
||
this.desiredProtocol = PROTOCOLS.W3C; | ||
this.log.info(`Set ChromeDriver communication protocol to ${PROTOCOLS.W3C}`); | ||
return this.desiredProtocol; | ||
} | ||
|
||
/** | ||
* Sync the protocol by reading the given output | ||
* | ||
* @param {string} line The output of ChromeDriver process | ||
* @returns {typeof PROTOCOLS[keyof typeof PROTOCOLS] | null} | ||
*/ | ||
detectWebDriverProtocol(line) { | ||
if (this.driverVersion) { | ||
return this.syncProtocol(); | ||
} | ||
|
||
// also print chromedriver version to logs | ||
// will output something like | ||
// Starting ChromeDriver 2.33.506106 (8a06c39c4582fbfbab6966dbb1c38a9173bfb1a2) on port 9515 | ||
// Or MSEdge: | ||
// Starting Microsoft Edge WebDriver 111.0.1661.41 (57be51b50d1be232a9e8186a10017d9e06b1fd16) on port 9515 | ||
const match = WEBDRIVER_VERSION_PATTERN.exec(line); | ||
if (match && match.length === 3) { | ||
this.log.debug(`${match[1]} version: '${match[2]}'`); | ||
this.driverVersion = match[2]; | ||
try { | ||
return this.syncProtocol(); | ||
} catch (e) { | ||
this.driverVersion = null; | ||
this.log.error(`Stopping the chromedriver process. Cannot determinate the protocol: ${e}`); | ||
this.stop(); | ||
this._desiredProtocol = PROTOCOLS.MJSONWP; | ||
KazuCocoa marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return this._desiredProtocol; | ||
} else if (isOperaDriver) { | ||
// OperaDriver needs the W3C protocol to be requested explcitly, | ||
// otherwise it defaults to JWP | ||
if (_.isPlainObject(chromeOptions)) { | ||
chromeOptions.w3c = true; | ||
} else { | ||
this.capabilities[toW3cCapName('chromeOptions')] = {w3c: true}; | ||
} | ||
// Does not print else condition log since the log could be | ||
// very noisy when this.verbose option is true. | ||
} | ||
return null; | ||
|
||
this._desiredProtocol = PROTOCOLS.W3C; | ||
return this._desiredProtocol; | ||
} | ||
|
||
/** | ||
|
@@ -619,7 +601,6 @@ export class Chromedriver extends events.EventEmitter { | |
let processIsAlive = false; | ||
/** @type {string|undefined} */ | ||
let webviewVersion; | ||
let didDetectProtocol = false; | ||
try { | ||
const chromedriverPath = await this.initChromedriverPath(); | ||
await this.killAll(); | ||
|
@@ -648,17 +629,6 @@ export class Chromedriver extends events.EventEmitter { | |
} | ||
} | ||
|
||
if (!didDetectProtocol) { | ||
const proto = this.detectWebDriverProtocol(line); | ||
if (proto === PROTOCOLS.W3C) { | ||
// given caps might not be properly prefixed | ||
// so we try to fix them in order to properly init | ||
// the new W3C session | ||
this.capabilities = toW3cCapNames(this.capabilities); | ||
} | ||
didDetectProtocol = true; | ||
} | ||
|
||
if (this.verbose) { | ||
// give the output if it is requested | ||
this.log.debug(`[${streamName.toUpperCase()}] ${line}`); | ||
|
@@ -668,7 +638,9 @@ export class Chromedriver extends events.EventEmitter { | |
|
||
// handle out-of-bound exit by simply emitting a stopped state | ||
this.proc.once('exit', (code, signal) => { | ||
this.driverVersion = null; | ||
this._driverVersion = null; | ||
this._desiredProtocol = null; | ||
this._onlineStatus = null; | ||
processIsAlive = false; | ||
if ( | ||
this.state !== Chromedriver.STATE_STOPPED && | ||
|
@@ -682,10 +654,11 @@ export class Chromedriver extends events.EventEmitter { | |
this.proc?.removeAllListeners(); | ||
this.proc = null; | ||
}); | ||
this.log.info(`Spawning chromedriver with: ${this.chromedriver} ${args.join(' ')}`); | ||
this.log.info(`Spawning Chromedriver with: ${this.chromedriver} ${args.join(' ')}`); | ||
// start subproc and wait for startDetector | ||
await this.proc.start(startDetector); | ||
await this.waitForOnline(); | ||
this.syncProtocol(); | ||
return await this.startSession(); | ||
} catch (e) { | ||
const err = /** @type {Error} */ (e); | ||
|
@@ -743,7 +716,19 @@ export class Chromedriver extends events.EventEmitter { | |
chromedriverStopped = true; | ||
return; | ||
} | ||
await this.getStatus(); | ||
/** @type {any} */ | ||
const status = await this.getStatus(); | ||
if (!_.isPlainObject(status) || !status.ready) { | ||
throw new Error(`The response to the /status API is not valid: ${JSON.stringify(status)}`); | ||
} | ||
this._onlineStatus = status; | ||
const versionMatch = VERSION_PATTERN.exec(status.build?.version ?? ''); | ||
if (versionMatch) { | ||
this._driverVersion = versionMatch[1]; | ||
this.log.info(`Chromedriver version: ${this._driverVersion}`); | ||
} else { | ||
this.log.info('Chromedriver version cannot be determined from the /status API response'); | ||
} | ||
}); | ||
if (chromedriverStopped) { | ||
throw new Error('ChromeDriver crashed during startup.'); | ||
|
@@ -756,19 +741,19 @@ export class Chromedriver extends events.EventEmitter { | |
|
||
async startSession() { | ||
const sessionCaps = | ||
this.desiredProtocol === PROTOCOLS.W3C | ||
? {capabilities: {alwaysMatch: this.capabilities}} | ||
this._desiredProtocol === PROTOCOLS.W3C | ||
? {capabilities: {alwaysMatch: toW3cCapNames(this.capabilities)}} | ||
: {desiredCapabilities: this.capabilities}; | ||
this.log.info( | ||
`Starting ${this.desiredProtocol} Chromedriver session with capabilities: ` + | ||
`Starting ${this._desiredProtocol} Chromedriver session with capabilities: ` + | ||
JSON.stringify(sessionCaps, null, 2), | ||
); | ||
const {capabilities} = /** @type {NewSessionResponse} */ ( | ||
const response = /** @type {NewSessionResponse} */ ( | ||
await this.jwproxy.command('/session', 'POST', sessionCaps) | ||
); | ||
this.log.prefix = generateLogPrefix(this, this.jwproxy.sessionId); | ||
this.changeState(Chromedriver.STATE_ONLINE); | ||
return capabilities; | ||
return _.has(response, 'capabilities') ? response.capabilities : response; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
|
||
async stop(emitStates = true) { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it would be nice to add
ms:edgeOptions
for MSEdge as well?MSEdge supports
chormeOptions
naming still, so I don't have any strong opinion for this. I don't remember well, but probably edge prior edgeOptions than chormeOptionsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
edgeOptions map does not contain the
w3c
flag, so I assume this logic could remain unchanged for now: https://learn.microsoft.com/de-de/microsoft-edge/webdriver-chromium/capabilities-edge-options