Skip to content

Commit

Permalink
feat: Add doctor checks (#97)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Feb 10, 2024
1 parent 530d528 commit c256614
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 22 deletions.
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,30 @@ Under the hood this driver is a wrapper/proxy over `geckodriver` binary. Check t

> **Note**
>
> Since version 1.0.0 Gecko driver has dropped the support of Appium 1, and is only compatible to Appium 2. Use the `appium driver install gecko`
> Since version 1.0.0 Gecko driver has dropped the support of Appium 1, and is only compatible to Appium 2. Use the `appium driver install gecko`
> command to add it to your Appium 2 dist.
## Requirements

## Usage

It is mandatory to have both Firefox browser installed and the geckodriver binary downloaded on the platform where automated tests are going to be executed. Firefox could be downloaded from the [official download site](https://www.mozilla.org/en-GB/firefox/all/) and the driver binary could be retrieved from the GitHub [releases page](https://github.com/mozilla/geckodriver/releases). The binary must be put into one of the folders included to PATH environment variable. On macOS it also might be necessary to run `xcattr -cr "<binary_path>"` to avoid [notarization](https://firefox-source-docs.mozilla.org/testing/geckodriver/Notarization.html) issues.
It is mandatory to have both Firefox browser installed and the geckodriver binary downloaded on the platform where automated tests are going to be executed. Firefox could be downloaded from the [official download site](https://www.mozilla.org/en-GB/firefox/all/) and the driver binary could be retrieved from the GitHub [releases page](https://github.com/mozilla/geckodriver/releases). The binary must be put into one of the folders included to PATH environment variable. On macOS it also might be necessary to run `xattr -cr "<binary_path>"` to avoid [notarization](https://firefox-source-docs.mozilla.org/testing/geckodriver/Notarization.html) issues.

Then you need to decide where the automated test is going to be executed. Gecko driver supports the following target platforms:
- macOS
- Windows
- Linux
- Android (note that `android` *cannot* be passed as a value to `platformName` capability; it should always equal to the *host* platform name)

In order to run your automated tests on Android it is necessary to have [Android SDK](https://developer.android.com/studio) installed, so the destination device is marked as `online` in the `adb devices -l` command output.
In order to run your automated tests on an Android device it is necessary to have [Android SDK](https://developer.android.com/studio) installed, so the destination device is marked as `online` in the `adb devices -l` command output.

### Doctor

Since driver version 1.3.0 you can automate the validation for the most of the above
requirements as well as various optional ones needed by driver extensions by running the
`appium driver doctor gecko` server command.

Gecko driver allows to define multiple criterions for platform selection and also to fine-tune your automation session properties. This could be done via the following session capabilities:
## Capabilities

Gecko driver allows defining of multiple criterions for platform selection and also to fine-tune your automation session properties. This could be done via the following session capabilities:

Capability Name | Description
--- | ---
Expand All @@ -49,7 +56,6 @@ setWindowRect | See https://www.w3.org/TR/webdriver/#capabilities
timeouts | See https://www.w3.org/TR/webdriver/#capabilities
unhandledPromptBehavior | See https://www.w3.org/TR/webdriver/#capabilities


## Example

```python
Expand Down Expand Up @@ -146,7 +152,6 @@ def test_feature_status_page_filters(driver):
filt.click()
```


## Development

```bash
Expand Down
123 changes: 123 additions & 0 deletions lib/doctor/optional-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-disable require-await */
import {system, fs, doctor} from '@appium/support';
import {getAndroidBinaryPath, getSdkRootFromEnv} from 'appium-adb';

const ENVIRONMENT_VARS_TUTORIAL_URL =
'https://github.com/appium/java-client/blob/master/docs/environment.md';
const ANDROID_SDK_LINK1 = 'https://developer.android.com/studio#cmdline-tools';
const ANDROID_SDK_LINK2 = 'https://developer.android.com/studio/intro/update#sdk-manager';

/**
* @typedef EnvVarCheckOptions
* @property {boolean} [expectDir] If set to true then
* the path is expected to be a valid folder
* @property {boolean} [expectFile] If set to true then
* the path is expected to be a valid file
*/

/** @satisfies {import('@appium/types').IDoctorCheck} */
class EnvVarAndPathCheck {
/**
* @param {string} varName
* @param {EnvVarCheckOptions} [opts={}]
*/
constructor(varName, opts = {}) {
this.varName = varName;
this.opts = opts;
}

async diagnose() {
const varValue = process.env[this.varName];
if (!varValue) {
return doctor.nokOptional(`${this.varName} environment variable is NOT set!`);
}

if (!(await fs.exists(varValue))) {
let errMsg = `${this.varName} is set to '${varValue}' but this path does not exist!`;
if (system.isWindows() && varValue.includes('%')) {
errMsg += ` Consider replacing all references to other environment variables with absolute paths.`;
}
return doctor.nokOptional(errMsg);
}

const stat = await fs.stat(varValue);
if (this.opts.expectDir && !stat.isDirectory()) {
return doctor.nokOptional(
`${this.varName} is expected to be a valid folder, got a file path instead`,
);
}
if (this.opts.expectFile && stat.isDirectory()) {
return doctor.nokOptional(
`${this.varName} is expected to be a valid file, got a folder path instead`,
);
}

return doctor.okOptional(`${this.varName} is set to: ${varValue}`);
}

async fix() {
return (
`Make sure the environment variable ${this.varName} is properly configured for the Appium process. ` +
`Android SDK is required if you want to run your tests on an Android device. ` +
`Refer ${ENVIRONMENT_VARS_TUTORIAL_URL} for more details.`
);
}

hasAutofix() {
return false;
}

isOptional() {
return true;
}
}
export const androidHomeCheck = new EnvVarAndPathCheck('ANDROID_HOME', {expectDir: true});

/** @satisfies {import('@appium/types').IDoctorCheck} */
export class AndroidSdkCheck {
/** @type {import('@appium/types').AppiumLogger} */
log;

TOOL_NAMES = ['adb', 'emulator'];

async diagnose() {
const listOfTools = this.TOOL_NAMES.join(', ');
const sdkRoot = getSdkRootFromEnv();
if (!sdkRoot) {
return doctor.nokOptional(`${listOfTools} could not be found because ANDROID_HOME is NOT set!`);
}

this.log.info(` Checking ${listOfTools}`);
const missingBinaries = [];
for (const binary of this.TOOL_NAMES) {
try {
this.log.info(` '${binary}' exists in ${await getAndroidBinaryPath(binary)}`);
} catch (e) {
missingBinaries.push(binary);
}
}

if (missingBinaries.length > 0) {
return doctor.nokOptional(`${missingBinaries.join(', ')} could NOT be found in '${sdkRoot}'!`);
}

return doctor.okOptional(`${listOfTools} exist in '${sdkRoot}'`);
}

async fix() {
return (
`Manually install Android SDK and set ANDROID_HOME enviornment variable. ` +
`Android SDK is required if you want to run your tests on an Android device. ` +
`Read ${[ANDROID_SDK_LINK1, ANDROID_SDK_LINK2].join(' and ')}.`
);
}

hasAutofix() {
return false;
}

isOptional() {
return true;
}
}
export const androidSdkCheck = new AndroidSdkCheck();
34 changes: 34 additions & 0 deletions lib/doctor/required-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable require-await */
import {resolveExecutablePath} from './utils';
import {system, doctor} from '@appium/support';

const GD_DOWNLOAD_LINK = 'https://github.com/mozilla/geckodriver/releases';
const GD_BINARY = `geckodriver${system.isWindows() ? '.exe' : ''}`;

/** @satisfies {import('@appium/types').IDoctorCheck} */
export class GeckodriverCheck {
async diagnose() {
const gdPath = await resolveExecutablePath(GD_BINARY);
return gdPath
? doctor.ok(`${GD_BINARY} is installed at: ${gdPath}`)
: doctor.nok(`${GD_BINARY} cannot be found`);
}

async fix() {
return (
`${GD_BINARY} is required to pass W3C commands to the remote browser. ` +
`Please download the binary from ${GD_DOWNLOAD_LINK} and store it ` +
`to any folder listed in the PATH environment variable. Folders that ` +
`are currently present in PATH: ${process.env.PATH}`
);
}

hasAutofix() {
return false;
}

isOptional() {
return false;
}
}
export const geckodriverCheck = new GeckodriverCheck();
17 changes: 17 additions & 0 deletions lib/doctor/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {fs} from '@appium/support';

/**
* Return an executable path of cmd
*
* @param {string} cmd Standard output by command
* @return {Promise<string?>} The full path of cmd. `null` if the cmd is not found.
*/
export async function resolveExecutablePath(cmd) {
try {
const executablePath = await fs.which(cmd);
if (executablePath && (await fs.exists(executablePath))) {
return executablePath;
}
} catch (err) {}
return null;
}
23 changes: 9 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@
"node": ">=14",
"npm": ">=8"
},
"lint-staged": {
"*.js": [
"eslint --fix"
]
},
"prettier": {
"bracketSpacing": false,
"printWidth": 100,
Expand All @@ -48,9 +43,10 @@
"lib": "lib"
},
"peerDependencies": {
"appium": "^2.0.0-beta.40"
"appium": "^2.4.1"
},
"dependencies": {
"appium-adb": "^12.0.3",
"asyncbox": "^3.0.0",
"bluebird": "^3.5.1",
"lodash": "^4.17.4",
Expand All @@ -65,16 +61,11 @@
"dev": "npm run build -- --watch",
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"precommit-msg": "echo 'Pre-commit checks...' && exit 0",
"precommit-lint": "lint-staged",
"prepare": "npm run build",
"format": "prettier -w ./lib",
"test": "mocha --exit --timeout 1m \"./test/unit/**/*-specs.js\"",
"e2e-test": "mocha --exit --timeout 5m \"./test/functional/**/*-specs.js\""
},
"pre-commit": [
"precommit-msg",
"precommit-lint"
],
"devDependencies": {
"@appium/eslint-config-appium": "^8.0.4",
"@appium/eslint-config-appium-ts": "^0.x",
Expand Down Expand Up @@ -102,14 +93,18 @@
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-mocha": "^10.1.0",
"eslint-plugin-promise": "^6.1.1",
"lint-staged": "^15.0.2",
"mocha": "^10.0.0",
"pre-commit": "^1.1.3",
"rimraf": "^5.0.0",
"semantic-release": "^23.0.0",
"sinon": "^17.0.0",
"ts-node": "^10.9.1",
"typescript": "~5.2",
"webdriverio": "^8.0.5"
},
"doctor": {
"checks": [
"./build/lib/doctor/required-checks.js",
"./build/lib/doctor/optional-checks.js"
]
}
}

0 comments on commit c256614

Please sign in to comment.