Skip to content

Commit 73a8656

Browse files
KazuCocoajlipps
andauthored
feat: add chromedriverExecutableDir and autodownload support (#48)
* feat: add chromedriverExecutableDir and autodownload support * set browser from UA if the browser was an empty * fix bad reference * add JSON.stringify * Update README.md Co-authored-by: Jonathan Lipps <[email protected]> --------- Co-authored-by: Jonathan Lipps <[email protected]>
1 parent 28efa50 commit 73a8656

File tree

7 files changed

+202
-89
lines changed

7 files changed

+202
-89
lines changed

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ package in your `package.json`)
3636
|`appium:appId`|[Required] The app package ID, if you want Appium to use an app already on the TV. Exclusive with `appium:app`|
3737
|`appium:app`|[Optional] An absolute path to your `.ipk` app file, if you want Appium to install the app.|
3838
|`appium:debuggerPort`|[Optional; default `9998`] The port on the device exposed for remote Chromium debugging.|
39-
|`appium:chromedriverExecutable`|[Optional] Most LG TVs run a very old version of Chrome. Because this driver uses Chromedriver under the hood, you'll need to have a very old version of Chromedriver handy that works with the version of Chrome backing the apps on your TV. In our testing, we've found Chromedriver 2.36 to work with most TVs. You need to tell the driver where you've installed this version of Chromedriver using the `appium:chromedriverExecutable` capability, passing in an absolute path to the Chromedriver binary.|
39+
|`appium:chromedriverExecutable`(*)|[Optional] Most LG TVs run a very old version of Chrome. Because this driver uses Chromedriver under the hood, you'll need to have a very old version of Chromedriver handy that works with the version of Chrome backing the apps on your TV. In our testing, we've found Chromedriver 2.36 to work with most TVs. You need to tell the driver where you've installed this version of Chromedriver using the `appium:chromedriverExecutable` capability, passing in an absolute path to the Chromedriver binary.|
40+
| `appium:chromedriverExecutableDir`(*) | [Optional] Full path to the folder where chromedriver executables are located. This folder is used then to store the downloaded chromedriver executables if automatic download is enabled with `chromedriver_autodownload` security flag. Please read [Automatic Discovery of Compatible Chromedriver in appium-uiautomator2-driver](https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#automatic-discovery-of-compatible-chromedriver) for more details. |
4041
|`appium:websocketPort`|[Optional; default `3000`] The websocket port on the device exposed for remote control|
4142
|`appium:websocketPortSecure`|[Optional; default `3001`] The secure websocket port on the device exposed for remote control|
4243
|`appium:useSecureWebsocket`|[Optional; default `false`] Flag that enables use of `websocketPortSecure` port, also starts WebSocket over https instead. **DISCLAIMER** Enabling this flag, it is required to set environment variable `export NODE_TLS_REJECT_UNAUTHORIZED=0`, which can be a potential security risk. A new session request might get `unable to get local issuer certificate` error message.|
@@ -49,6 +50,9 @@ package in your `package.json`)
4950
|`appium:rcMode`|[Optional; default `js`; must be `rc` or `js`] When the value is `js`, the `webos: pressKey` command will operate with JS executed via Chromedriver. Otherwise, keys will be sent using the websocket remote control API. Note that when `appium:remoteOnly` is set to true, the value of `appium:rcMode` will always behave as if set to `rc`.|
5051
|`appium:keyCooldown`|[Optional; default `750`] How long to wait in between remote key presses|
5152

53+
(*) `appium:chromedriverExecutable` or `appium:chromedriverExecutableDir` are required. The chromedriver autodwonload works only when `appium:chromedriverExecutableDir` is provided.
54+
If both capabilities are given, `appium:chromedriverExecutableDir` will take priority.
55+
5256
## Supported Commands
5357

5458
These are the WebDriver (and extension) commands supported by this driver. Note that in its normal

lib/constraints.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export const CAP_CONSTRAINTS = Object.freeze(
3434
},
3535
chromedriverExecutable: {
3636
isString: true,
37-
//presence: true
37+
},
38+
chromedriverExecutableDir: {
39+
isString: true,
3840
},
3941
autoExtendDevMode: {
4042
isBoolean: true,
@@ -72,4 +74,3 @@ export const DEFAULT_CAPS = Object.freeze(/** @type {const} */({
7274
/**
7375
* @typedef {typeof CAP_CONSTRAINTS} WebOsConstraints
7476
*/
75-

lib/driver.js

+81-4
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {AsyncScripts, SyncScripts} from './scripts';
1414
// @ts-ignore
1515
import Chromedriver from 'appium-chromedriver';
1616
import getPort from 'get-port';
17+
import got from 'got';
1718
import {KEYMAP} from './keys';
1819
import log from './logger';
1920
import {LGRemoteKeys} from './remote/lg-remote-client';
@@ -26,6 +27,16 @@ export {KEYS} from './keys';
2627
// some app stays running (otherwise the TV might shut off)
2728
const DEV_MODE_ID = 'com.palmdts.devmode';
2829

30+
/**
31+
* A security flag to enable chromedriver auto download feature
32+
*/
33+
const CHROMEDRIVER_AUTODOWNLOAD_FEATURE = 'chromedriver_autodownload';
34+
35+
/**
36+
* To get chrome driver version in the UA
37+
*/
38+
const REGEXP_CHROME_VERSION_IN_UA = new RegExp('Chrome\\/(\\S+)');
39+
2940
// don't proxy any 'appium' routes
3041
/** @type {RouteMatcher[]} */
3142
const NO_PROXY = [
@@ -99,6 +110,7 @@ export class WebOSDriver extends BaseDriver {
99110
deviceHost,
100111
debuggerPort,
101112
chromedriverExecutable,
113+
chromedriverExecutableDir,
102114
appLaunchCooldown,
103115
remoteOnly,
104116
websocketPort,
@@ -172,7 +184,9 @@ export class WebOSDriver extends BaseDriver {
172184
await this.startChromedriver({
173185
debuggerHost: deviceHost,
174186
debuggerPort,
175-
executable: chromedriverExecutable,
187+
executable: /** @type {string} */ (chromedriverExecutable),
188+
executableDir: /** @type {string} */ (chromedriverExecutableDir),
189+
isAutodownloadEnabled: /** @type {Boolean} */ (this.#isChromedriverAutodownloadEnabled()),
176190
});
177191

178192
log.info('Waiting for app launch to take effect');
@@ -186,18 +200,81 @@ export class WebOSDriver extends BaseDriver {
186200
return [sessionId, caps];
187201
}
188202

203+
204+
/**
205+
* Use UserAgent info for "Browser" if the chrome response did not include
206+
* browser name properly.
207+
* @param {object} browserVersionInfo
208+
*/
209+
useUAForBrowserIfNotPresent(browserVersionInfo) {
210+
if (!_.isEmpty(browserVersionInfo.Browser)) {
211+
return browserVersionInfo;
212+
}
213+
214+
const ua = browserVersionInfo['User-Agent'];
215+
if (_.isEmpty(ua)) {
216+
return browserVersionInfo;
217+
}
218+
219+
const chromeVersion = ua.match(REGEXP_CHROME_VERSION_IN_UA);
220+
if (_.isEmpty(chromeVersion)) {
221+
return browserVersionInfo;
222+
}
223+
224+
log.info(`The response did not have Browser, thus set the Browser value from UA as ${JSON.stringify(browserVersionInfo)}`);
225+
browserVersionInfo.Browser = chromeVersion[0];
226+
return browserVersionInfo;
227+
}
228+
229+
/**
230+
* Returns whether the session can enable autodownloadd feature.
231+
* @returns {boolean}
232+
*/
233+
#isChromedriverAutodownloadEnabled() {
234+
if (this.isFeatureEnabled(CHROMEDRIVER_AUTODOWNLOAD_FEATURE)) {
235+
return true;
236+
}
237+
this.log.debug(
238+
`Automated Chromedriver download is disabled. ` +
239+
`Use '${CHROMEDRIVER_AUTODOWNLOAD_FEATURE}' server feature to enable it`,
240+
);
241+
return false;
242+
}
243+
189244
/**
190245
* @param {StartChromedriverOptions} opts
191246
*/
192-
async startChromedriver({debuggerHost, debuggerPort, executable}) {
247+
async startChromedriver({debuggerHost, debuggerPort, executable, executableDir, isAutodownloadEnabled}) {
248+
const debuggerAddress = `${debuggerHost}:${debuggerPort}`;
249+
250+
251+
let result;
252+
if (executableDir) {
253+
// get the result of chrome info to use auto detection.
254+
try {
255+
result = await got.get(`http://${debuggerAddress}/json/version`).json();
256+
log.info(`The response of http://${debuggerAddress}/json/version was ${JSON.stringify(result)}`);
257+
result = this.useUAForBrowserIfNotPresent(result);
258+
259+
// To respect the executableDir.
260+
executable = undefined;
261+
} catch (err) {
262+
throw new errors.SessionNotCreatedError(
263+
`Could not get the chrome browser information to detect proper chromedriver version. Error: ${err.message}`
264+
);
265+
}
266+
}
267+
193268
this.#chromedriver = new Chromedriver({
194269
// @ts-ignore bad types
195270
port: await getPort(),
196271
executable,
272+
executableDir,
273+
isAutodownloadEnabled,
274+
// @ts-ignore
275+
details: {info: result}
197276
});
198277

199-
const debuggerAddress = `${debuggerHost}:${debuggerPort}`;
200-
201278
// XXX: goog:chromeOptions in newer versions, chromeOptions in older
202279
await this.#chromedriver.start({
203280
chromeOptions: {

lib/types.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ export type Serializable = SerializableObject | Serializable[] | JsonPrimitive;
5050
export interface StartChromedriverOptions {
5151
debuggerHost: string;
5252
debuggerPort: number;
53-
executable: string;
53+
executable?: string;
54+
executableDir?: string;
55+
isAutodownloadEnabled: boolean;
5456
}
5557

5658
/**

0 commit comments

Comments
 (0)