feat(plugin-test-support): create
This provides setup/teardown functions to plugins for their E2E tests
Extracted from `@appium/base-plugin` by way of `@appium/test-support`
MODIFY
package.json
package.json
"@appium/gulp-plugins": "file:packages/gulp-plugins",
"@appium/images-plugin": "file:packages/images-plugin",
"@appium/opencv": "file:packages/opencv",
+ "@appium/plugin-test-support": "file:packages/plugin-test-support",
"@appium/relaxed-caps-plugin": "file:packages/relaxed-caps-plugin",
"@appium/schema": "file:packages/schema",
"@appium/support": "file:packages/support",

ADD
packages/plugin-test-support/README.md
+# @appium/pluginb-test-support
+
+> Testing utilities for [Appium](https://appium.io) plugins
+
+This package is for plugin authors to help test their plugins.
+
+[Mocha](https://mochajs.org) is the supported test framework (but can theoretically be used with others).
+
+## Usage
+
+### For E2E Tests
+
+The `pluginE2EHarness` method configures a server and driver for testing via "before all" and "after all"-style hooks.
+
+This example uses [WebdriverIO](https://webdriver.io) to communicate with a test Appium server.
+
+```js
+import {pluginE2EHarness} from '@appium/plugin-test-support';
+import {remote} from 'webdriverio';
+
+describe('MyPlugin', function() {
+ pluginE2EHarness({
+ before: global.before, // from mocha
+ after: global.after, // from mocha
+ serverArgs: SOME_EXTRA_SERVER_ARGS,
+ port: 31337,
+ host: '127.0.0.1',
+ appiumHome: process.env.APPIUM_HOME, // best practice: use a temp dir instead
+ driverName: 'fake', // driver to test with
+ driverSource: 'local', // use "local" unless you want appium to install from npm every time
+ driverSpec: FAKE_DRIVER_DIR, // path to local driver working copy or installation
+ pluginName: 'MyPlugin', // your plugin
+ pluginSource: 'local', // use "local" for this
+ pluginSpec: THIS_PLUGIN_DIR, // dir of this plugin's `package.json`
+ });
+
+ it('should use my plugin', async function() {
+ // at this point, the Appium server will be running with the plugin/driver combination of your
+ // choosing
+
+ // port/host should match what you provided to `pluginE2EHarness`
+ const browser = await remote({port: 31337, hostname: '127.0.0.1'});
+ })
+});
+```
+
+## License
+
+Apache-2.0

ADD
packages/plugin-test-support/index.js
+module.exports = require('./build/lib/harness');

ADD
packages/plugin-test-support/lib/harness.js
+/* eslint-disable no-console */
+import {fs} from 'appium/support';
+import {main as appiumServer} from 'appium';
+import getPort from 'get-port';
+import {info, success, warning} from 'log-symbols';
+import {exec} from 'teen_process';
+
+const APPIUM_BIN = require.resolve('appium');
+
+/**
+ * Creates hooks to install a driver and a plugin and starts an Appium server w/ the given extensions.
+ * @param {E2ESetupOpts} opts
+ * @returns {void}
+ */
+export function pluginE2EHarness(opts) {
+ let {
+ appiumHome,
+ before,
+ after,
+ serverArgs = {},
+ driverSource,
+ driverPackage,
+ driverName,
+ driverSpec,
+ pluginSource,
+ pluginPackage,
+ pluginSpec,
+ pluginName,
+ port,
+ host,
+ } = opts;
+
+ /**
+ * @type {AppiumServer}
+ */
+ let server;
+
+ before(async function () {
+ const setupAppiumHome = async () => {
+ /**
+ * @type {AppiumEnv}
+ */
+ const env = {...process.env};
+
+ if (appiumHome) {
+ env.APPIUM_HOME = appiumHome;
+ await fs.mkdirp(appiumHome);
+ console.log(`${info} Set \`APPIUM_HOME\` to ${appiumHome}`);
+ }
+
+ return env;
+ };
+
+ /**
+ *
+ * @param {AppiumEnv} env
+ */
+ const installDriver = async (env) => {
+ console.log(`${info} Checking if driver "${driverName}" is installed...`);
+ const driverListArgs = [APPIUM_BIN, 'driver', 'list', '--json'];
+ console.log(`${info} Running: ${process.execPath} ${driverListArgs.join(' ')}`);
+ const {stdout: driverListJson} = await exec(process.execPath, driverListArgs, {
+ env,
+ });
+ const installedDrivers = JSON.parse(driverListJson);
+
+ if (!installedDrivers[driverName]?.installed) {
+ console.log(`${warning} Driver "${driverName}" not installed; installing...`);
+ const driverArgs = [APPIUM_BIN, 'driver', 'install', '--source', driverSource, driverSpec];
+ if (driverPackage) {
+ driverArgs.push('--package', driverPackage);
+ }
+ console.log(`${info} Running: ${process.execPath} ${driverArgs.join(' ')}`);
+ await exec(process.execPath, driverArgs, {
+ env,
+ });
+ }
+ console.log(`${success} Installed driver "${driverName}"`);
+ };
+
+ /**
+ *
+ * @param {AppiumEnv} env
+ */
+ const installPlugin = async (env) => {
+ console.log(`${info} Checking if plugin "${pluginName}" is installed...`);
+ const pluginListArgs = [APPIUM_BIN, 'plugin', 'list', '--json'];
+ const {stdout: pluginListJson} = await exec(process.execPath, pluginListArgs, {
+ env,
+ });
+ const installedPlugins = JSON.parse(pluginListJson);
+
+ if (!installedPlugins[pluginName]?.installed) {
+ console.log(`${warning} Plugin "${pluginName}" not installed; installing...`);
+ const pluginArgs = [APPIUM_BIN, 'plugin', 'install', '--source', pluginSource, pluginSpec];
+ if (pluginPackage) {
+ pluginArgs.push('--package', pluginPackage);
+ }
+ console.log(`${info} Running: ${process.execPath} ${pluginArgs.join(' ')}`);
+ await exec(process.execPath, pluginArgs, {
+ env,
+ });
+ }
+ console.log(`${success} Installed plugin "${pluginName}"`);
+ };
+
+ const createServer = async () => {
+ if (!port) {
+ port = await getPort();
+ }
+ console.log(`${info} Will use port ${port} for Appium server`);
+ this.port = port;
+
+ /** @type {import('appium').Args} */
+ const args = {
+ port,
+ address: host,
+ usePlugins: [pluginName],
+ useDrivers: [driverName],
+ appiumHome,
+ ...serverArgs,
+ };
+ server = /** @type {AppiumServer} */ (await appiumServer(args));
+ };
+
+ const env = await setupAppiumHome();
+ await installDriver(env);
+ await installPlugin(env);
+ await createServer();
+ });
+
+ after(async function () {
+ if (server) {
+ await server.close();
+ }
+ });
+}
+
+/**
+ * @typedef E2ESetupOpts
+ * @property {string} [appiumHome] - Path to Appium home directory
+ * @property {Mocha.before} before - Mocha "before all" hook function
+ * @property {Mocha.after} after - Mocha "after all" hook function
+ * @property {Partial<import('appium').Args>} [serverArgs] - Arguments to pass to Appium server
+ * @property {import('appium/types').InstallType & string} driverSource - Source of driver to install
+ * @property {string} [driverPackage] - Package name of driver to install
+ * @property {string} driverName - Name of driver to install
+ * @property {string} driverSpec - Spec of driver to install
+ * @property {import('appium/types').InstallType & string} pluginSource - Source of plugin to install
+ * @property {string} [pluginPackage] - Package name of plugin to install
+ * @property {string} pluginSpec - Spec of plugin to install
+ * @property {string} pluginName - Name of plugin to install
+ * @property {number} [port] - Port to use for Appium server
+ * @property {string} [host] - Host to use for Appium server
+ */
+
+/**
+ * @typedef {import('@appium/types').AppiumServer} AppiumServer
+ */
+
+/**
+ * @typedef {import('appium/types').AppiumEnv} AppiumEnv
+ */

ADD
packages/plugin-test-support/package.json
+{
+ "name": "@appium/plugin-test-support",
+ "version": "0.1.0",
+ "description": "Testing utilities for Appium plugins",
+ "keywords": [
+ "automation",
+ "javascript",
+ "selenium",
+ "webdriver",
+ "ios",
+ "android",
+ "firefoxos",
+ "testing"
+ ],
+ "homepage": "https://appium.io",
+ "bugs": {
+ "url": "https://github.com/appium/appium/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/appium/appium.git",
+ "directory": "packages/plugin-test-support"
+ },
+ "license": "Apache-2.0",
+ "author": "https://github.com/appium",
+ "main": "index.js",
+ "types": "build/lib/harness.d.ts",
+ "directories": {
+ "lib": "lib"
+ },
+ "files": [
+ "lib",
+ "index.js",
+ "build"
+ ],
+ "scripts": {
+ "build": "babel lib --root-mode=upward --out-dir=build/lib",
+ "dev": "npm run build -- --watch",
+ "fix": "npm run lint -- --fix",
+ "lint": "eslint -c ../../.eslintrc --ignore-path ../../.eslintignore .",
+ "prepare": "npm run build",
+ "test": "npm run test:unit",
+ "test:smoke": "node ./index.js",
+ "test:unit": "exit 0"
+ },
+ "dependencies": {
+ "@appium/types": "0.4.0",
+ "@babel/runtime": "7.18.9",
+ "get-port": "5.1.1",
+ "log-symbols": "4.1.0",
+ "source-map-support": "0.5.21",
+ "teen_process": "1.16.0"
+ },
+ "peerDependencies": {
+ "appium": "^2.0.0-beta.43",
+ "mocha": "*"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=8"
+ },
+ "publishConfig": {
+ "access": "public"
+ }
+}

ADD
packages/plugin-test-support/tsconfig.json
+{
+ "extends": "../../config/tsconfig.base.json",
+ "compilerOptions": {
+ "rootDir": ".",
+ "outDir": "build",
+ "checkJs": true,
+ "paths": {
+ "@appium/types": ["../types"],
+ "appium/support": ["../appium/support"]
+ },
+ "types": [
+ "mocha", "chai", "sinon", "sinon-chai", "chai-as-promised"
+ ]
+ },
+ "include": ["./lib"],
+ "references": [
+ {"path": "../types"},
+ {"path": "../support"}
+ ]
+}

MODIFY
tsconfig.json
tsconfig.json
{
"path": "packages/driver-test-support"
},
+ {
+ "path": "packages/plugin-test-support"
+ },
{
"path": "packages/schema"
},

feat(plugin-test-support): create @appium/plugin-test-support

This provides setup/teardown functions to plugins for their E2E tests

Extracted from `@appium/base-plugin` by way of `@appium/test-support`