Building Doctor Checks
The idea of Appium Doctor is to assist users with driver or plugin preconditions setup. Sometimes such preconditions might be quite complicated and require non-trivial technical knowledge. Doctor checks, which are vanilla Node.js class instances written by extension authors, simplify the setup process by automating diagnostics and possible fixes for the found issues. These checks might also be interactive to ensure better usage experience.
This tutorial is supposed to be used by plugin or driver authors that would like to help their users to deal with complicated setup or configuration steps.
Adding Doctor Checks¶
Typing Requirements¶
The term Doctor Check
literally describes a single javascript class instance that implements the
IDoctorCheck interface.
The interface defines the following methods and properties:
diagnose(): Promise<DoctorCheckResult>
: Contains the code to diagnose a possible issuefix(): Promise<string|null>
: Either fixes the actual problem ifhasAutofix()
returns true or returns a string description for possible manual fixes. If this method throws an exception namedFixSkippedError
andhasAutofix()
returns true then the result of the method invocation is going to be ignored.hasAutofix(): boolean
: Whether callingfix()
would resolve the found issueisOptional(): boolean
: Whether the found issue can be ignored and is not a showstopperlog: AppiumLogger
: May be used for logging. This property may be assigned by the instance itself or by the Appium server if it is left unassigned.
The DoctorCheckResult
object returned by the diagnose()
method must contain the following properties:
ok: boolean
: Whether the diagnosis found no issuesoptional: boolean
: Whether the diagnosed issue is safe to ignoremessage: string
: The text message describing the diagnostic result
Manifest Requirements¶
A single extension may export multiple Doctor checks to Appium. In order for these checks to be properly
picked up by the server CLI after the corresponding extension is installed they might be listed in the
package .json manifest under the appium.doctor.checks
section similar to the definition below:
// ...
"appium": {
"driverName": "fake",
"automationName": "Fake",
"platformNames": [
"Fake"
],
"mainClass": "FakeDriver",
"schema": "./build/lib/fake-driver-schema.js",
"scripts": {
"fake-error": "./build/lib/scripts/fake-error.js",
"fake-success": "./build/lib/scripts/fake-success.js",
"fake-stdin": "./build/lib/scripts/fake-stdin.js"
},
"doctor": {
"checks": [
"./doctor/fake1.js",
"./doctor/fake2.js"
// ...
]
}
},
// ...
Also, it makes sense to include the @appium/types import to the package dev dependencies.
Implementation Example¶
The below example is a "raw" Node.JS implementation that does not use any transpilation:
const {fs, doctor} = require('@appium/support');
/** @satisfies {import('@appium/types').IDoctorCheck} */
class EnvVarAndPathCheck {
/**
* @param {string} varName
*/
constructor(varName) {
this.varName = varName;
}
async diagnose() {
const varValue = process.env[this.varName];
if (typeof varValue === 'undefined') {
return doctor.nok(`${this.varName} environment variable is NOT set!`);
}
if (await fs.exists(varValue)) {
return doctor.ok(`${this.varName} is set to: ${varValue}`);
}
return doctor.nok(`${this.varName} is set to '${varValue}' but this is NOT a valid path!`);
}
async fix() {
return (
`Make sure the environment variable ${this.varName} is properly configured for the Appium server process`
);
}
hasAutofix() {
return false;
}
isOptional() {
return false;
}
}
const androidHomeCheck = new EnvVarAndPathCheck('ANDROID_HOME');
module.exports = {androidHomeCheck};
/**
* @typedef {import('@appium/types').DoctorCheckResult} CheckResult
*/
This file could be saved as doctor/android-home-check.js
and then added to the package.json manifest
as