Skip to content

Commit

Permalink
feat: Add doctor checks (#2292)
Browse files Browse the repository at this point in the history
  • Loading branch information
mykola-mokhnach authored Jan 5, 2024
1 parent 1bab7fc commit 804e50a
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 2 deletions.
1 change: 1 addition & 0 deletions .github/workflows/functional-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ jobs:
cd ~
npm install -g appium
appium driver install --source=local "$cwd"
appium driver doctor xcuitest
appium driver run xcuitest build-wda
echo "Starting Appium server on $APPIUM_TEST_SERVER_HOST:$APPIUM_TEST_SERVER_PORT"
nohup appium server \
Expand Down
6 changes: 6 additions & 0 deletions docs/setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ On top of standard Appium requirements XCUITest driver also expects the followin
- [WIX AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils) could be used to improve some Simulator interactions
- [py-ios-device](https://github.com/YueChen-C/py-ios-device) is required in several `mobile:` extensions and to improve the general testing experience for _real_ iOS devices

### Doctor

Since driver version 5.13.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 xcuitest` server command.


## Xcode version support

Expand Down
94 changes: 94 additions & 0 deletions lib/doctor/optional-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/* eslint-disable require-await */
import {resolveExecutablePath} from './utils';
import {doctor} from '@appium/support';
import '@colors/colors';

/** @satisfies {import('@appium/types').IDoctorCheck} */
export class OptionalIdbCommandCheck {
IDB_README_URL = 'https://git.io/JnxQc';

async diagnose() {
const fbIdbPath = await resolveExecutablePath('idb');
const fbCompanionIdbPath = await resolveExecutablePath('idb_companion');
if (fbIdbPath && fbCompanionIdbPath) {
return doctor.okOptional('idb and idb_companion are installed');
}

if (!fbIdbPath && fbCompanionIdbPath) {
return doctor.nokOptional('idb is not installed');
} else if (fbIdbPath && !fbCompanionIdbPath) {
return doctor.nokOptional('idb_companion is not installed');
}
return doctor.nokOptional('idb and idb_companion are not installed');
}

async fix() {
return `Why ${'idb'.bold} is needed and how to install it: ${this.IDB_README_URL}`;
}

hasAutofix() {
return false;
}

isOptional() {
return true;
}
}
export const optionalIdbCheck = new OptionalIdbCommandCheck();


/** @satisfies {import('@appium/types').IDoctorCheck} */
export class OptionalApplesimutilsCommandCheck {
README_LINK = 'https://github.com/appium/appium-xcuitest-driver/blob/master/docs/execute-methods.md#mobile-setpermission';

async diagnose() {
const applesimutilsPath = await resolveExecutablePath('applesimutils');
return applesimutilsPath
? doctor.okOptional(`applesimutils is installed at: ${applesimutilsPath}`)
: doctor.nokOptional('applesimutils are not installed');
}

async fix() {
return `Why ${'applesimutils'.bold} is needed and how to install it: ${this.README_LINK}`;
}

hasAutofix() {
return false;
}

isOptional() {
return true;
}
}
export const optionalApplesimutilsCheck = new OptionalApplesimutilsCommandCheck();


/** @satisfies {import('@appium/types').IDoctorCheck} */
export class OptionalFfmpegCheck {
FFMPEG_BINARY = 'ffmpeg';
FFMPEG_INSTALL_LINK = 'https://www.ffmpeg.org/download.html';

async diagnose() {
const ffmpegPath = await resolveExecutablePath(this.FFMPEG_BINARY);

return ffmpegPath
? doctor.okOptional(`${this.FFMPEG_BINARY} exists at '${ffmpegPath}'`)
: doctor.nokOptional(`${this.FFMPEG_BINARY} cannot be found`);
}

async fix() {
return (
`${`${this.FFMPEG_BINARY}`.bold} is used to capture screen recordings from the device under test. ` +
`Please read ${this.FFMPEG_INSTALL_LINK}.`
);
}

hasAutofix() {
return false;
}

isOptional() {
return true;
}
}
export const optionalFfmpegCheck = new OptionalFfmpegCheck();
124 changes: 124 additions & 0 deletions lib/doctor/required-checks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/* eslint-disable require-await */
import {fs, doctor} from '@appium/support';
import {exec} from 'teen_process';
import { getPath as getXcodePath } from 'appium-xcode';
import '@colors/colors';


/** @satisfies {import('@appium/types').IDoctorCheck} */
export class XcodeCheck {
async diagnose() {
try {
const xcodePath = await getXcodePath();
return doctor.ok(`xCode is installed at '${xcodePath}'`);
} catch (err) {
return doctor.nok(err.message);
}
}

async fix() {
return `Manually install ${'Xcode'.bold} and configure the active developer directory path using the xcode-select tool`;
}

hasAutofix() {
return false;
}

isOptional() {
return false;
}
}
export const xcodeCheck = new XcodeCheck();


/** @satisfies {import('@appium/types').IDoctorCheck} */
export class XcodeToolsCheck {
async diagnose() {
try {
// https://github.com/appium/appium/issues/12093#issuecomment-459358120
await exec('xcrun', ['simctl', 'help']);
} catch (err) {
return doctor.nok(`Cannot run 'xcrun simctl': ${err.stderr || err.message}`);
}
try {
await exec('xcodebuild', ['-version']);
} catch (err) {
return doctor.nok(`Cannot run 'xcodebuild': ${err.stderr || err.message}`);
}
return doctor.ok(`xCode tools are installed and work properly`);
}

async fix() {
return `Fix the problems xCode tools are compliaining about`;
}

hasAutofix() {
return false;
}

isOptional() {
return false;
}
}
export const xcodeToolsCheck = new XcodeToolsCheck();


/**
* @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 {
ENVIRONMENT_VARS_TUTORIAL_URL = 'https://github.com/appium/java-client/blob/master/docs/environment.md';

/**
* @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.nok(`${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!`;
return doctor.nok(errMsg);
}

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

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

async fix() {
return (
`Make sure the environment variable ${this.varName.bold} is properly configured for the Appium process. ` +
`Refer ${this.ENVIRONMENT_VARS_TUTORIAL_URL} for more details.`
);
}

hasAutofix() {
return false;
}

isOptional() {
return false;
}
}
export const homeEnvVarCheck = new EnvVarAndPathCheck('HOME', {expectDir: true});
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;
}
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@
"additionalProperties": false,
"title": "XCUITest Driver Configuration",
"description": "Appium configuration schema for the XCUITest driver."
},
"doctor": {
"checks": [
"./build/lib/doctor/required-checks.js",
"./build/lib/doctor/optional-checks.js"
]
}
},
"main": "./build/index.js",
Expand All @@ -70,6 +76,7 @@
],
"types": "./build/index.d.ts",
"dependencies": {
"@colors/colors": "^1.6.0",
"appium-idb": "^1.6.13",
"appium-ios-device": "^2.5.4",
"appium-ios-simulator": "^5.5.1",
Expand All @@ -81,7 +88,7 @@
"bluebird": "^3.7.2",
"css-selector-parser": "^3.0.0",
"fancy-log": "^2.0.0",
"js2xmlparser2": "^0.2.0",
"js2xmlparser2": "^0.x",
"lodash": "^4.17.21",
"lru-cache": "^10.0.0",
"moment": "^2.29.4",
Expand Down Expand Up @@ -130,7 +137,7 @@
"singleQuote": true
},
"peerDependencies": {
"appium": "^2.0.0"
"appium": "^2.4.1"
},
"devDependencies": {
"@appium/docutils": "^0.x",
Expand Down

0 comments on commit 804e50a

Please sign in to comment.