ti-appium
Version:
An Appium wrapper to test Titanium applications
447 lines (351 loc) • 15.5 kB
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/appcelerator.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/appcelerator.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>'use strict';
const
path = require('path'),
fs = require('fs-extra'),
output = require('./output.js'),
tisdk = require('titaniumlib').sdk,
spawn = require('child_process').spawn,
exec = require('child_process').execSync;
/**
* @class Appc_Helper
* @desc
* Commands to interact with the Appcelerator and Titanium CLI. This contains a
* series of specific commands, or a more general runner command for your own
* CLI interactions.
*/
class Appc_Helper {
/**
* Login to the Appcelerator CLI using the login command.
*
* @param {Object} appc - The details for the Appcelerator run
* @param {String} appc.username - The username to authenticate with
* @param {String} appc.password - The password to authenticate with
* @param {String} appc.organisation - The relevant org ID to log in to
* @param {String} env - The Appcelerator environment to login to.
*/
static async login(appc, env) {
output.debug(`Logging into the Appcelerator CLI as '${appc.username}'`);
output.debug('Logging out of the current session');
await exec('appc logout');
output.debug(`Setting environment to ${env}`);
await exec(`appc config set defaultEnvironment ${env}`);
output.debug('Logging into the CLI');
let loginReturn = await exec(`appc login --username ${appc.username} --password ${appc.password} -O ${appc.organisation} --no-prompt`).toString();
if (loginReturn.includes('Login required to continue') || loginReturn.includes('Invalid username or password')) {
throw Error('Error During Appc CLI Login');
} else {
return;
}
}
/**
* Takes in an SDK identifier and attempts to resolve it to the applicable SDK
* version. Can take a release version, pre-release version, or branch
* identifier and attempts to resolve it to an installable identifier.
*
* @param {String} sdk - The version or branch of the SDK to validate
*/
static async parseSDK(sdk) {
output.debug(`Parsing the validity of SDK version ${sdk}`);
const
sdkTestOne = new RegExp(/^\d+\.\d+\.\d+\.GA$/),
sdkTestTwo = new RegExp(/^\d+\.\d+\.\d+\.v\d+$/),
sdkTestThree = new RegExp(/^\d+_\d+_X$/);
if (sdkTestOne.test(sdk) || sdk === 'latest') {
// Matches the version profile for a GA release
output.debug('SDK version passed has been identified as type release version');
const releases = Object.keys(await tisdk.getReleases(true));
// Latest is kinda nasty to deal with as it isn't a branch or a pretty number
if (sdk === 'latest') { sdk = releases[releases.length - 1]; }
if (!releases.includes(sdk)) { throw new Error(`${sdk} isn't a valid release`); }
return sdk;
} else if (sdkTestTwo.test(sdk)) {
// Matches the version profile for a test release
output.debug('SDK version passed has been identified as type pre-release version');
const branches = (await tisdk.getBranches()).branches;
const temp = `${(sdk.split('.').slice(0, 2).join('_'))}_X`;
if (!branches.includes(temp)) {
output.debug(`Can't find a branch matching ${temp}, checking master and next`);
const
masterBuilds = Object.keys(await tisdk.getBuilds('master')),
nextBuilds = Object.keys(await tisdk.getBuilds('next'));
if (masterBuilds.includes(sdk)) {
output.debug(`Found SDK ${sdk} in branch master`);
return sdk;
}
if (nextBuilds.includes(sdk)) {
output.debug(`Found SDK ${sdk} in branch next`);
return sdk;
}
throw new Error(`Can't find SDK ${sdk} in any branches`);
}
const builds = Object.keys(await tisdk.getBuilds(temp));
if (!builds.includes(sdk)) { throw new Error(`SDK ${sdk} isn't a valid build within ${temp}`); }
return sdk;
} else if (sdkTestThree.test(sdk) || sdk === 'master' || sdk === 'next') {
// Matches the profile for a branch name
output.debug('SDK version passed has been identified as type branch');
const branches = (await tisdk.getBranches()).branches;
if (!branches.includes(sdk)) { throw new Error(`Branch ${sdk} isn't a valid branch`); }
const builds = Object.keys(await tisdk.getBuilds(sdk));
return builds[builds.length - 1];
} else {
throw new Error(`${sdk} isn't a valid Titanium release or branch`);
}
}
/**
* Take the passed SDK, and attempt to install it. If it is a straight defined
* SDK, then install it. Otherwise if it is a branch, get the latest version
* of it.
*
* @param {String} sdk - The version or branch of the SDK to install
* @param {Boolean} force - Whether or not to force re-install the SDK
*/
static async installSDK(sdk, force = false) {
// Validate that we're installing a valid SDK
try {
sdk = await this.parseSDK(sdk);
} catch (e) { throw (e); }
output.debug(`Identified ${sdk} as version to be installed`);
const installs = await tisdk.getInstalledSDKs();
// Check if the SDK is already installed
for (const install of installs) {
if (install.name === sdk && !force) {
output.debug(`Found SDK ${sdk} already installed`);
output.debug(`Selecting ${sdk}`);
exec(`ti sdk select ${sdk}`);
return sdk;
}
}
// Install it if pre-checks haven't returned already
try {
output.debug(`Installing SDK ${sdk} with overwrite set to ${force}`);
await tisdk.install({ uri: sdk, overwrite: force });
} catch (e) { throw e; }
output.debug(`Selecting ${sdk}`);
exec(`ti sdk select ${sdk}`);
return sdk;
}
/**
* Install the latest version of the required CLI version for testing.
*
* @param {Object} appc - The details for the Appcelerator run
* @param {String} appc.cli - The version of the CLI to install
* @param {String} appc.username - The username to authenticate with
* @param {String} appc.password - The password to authenticate with
* @param {String} appc.organisation - The relevant org ID to log in to
*/
static async installCLI(appc) {
output.debug(`Installing CLI Version '${appc.cli}'`);
try {
output.debug('Fetching CLI version from the production environment');
await exec(`appc use ${appc.cli}`, {
stdio: [ 0 ]
});
} catch (err) {
if (err.toString().includes(`The version specified ${appc.cli} was not found`)) {
// Go to the pre-production environment
output.debug('Couldn\'t find the requested CLI version in production, switching to pre-production');
await this.login(appc, 'preproduction');
// Check if the CLI version we want to use is installed
output.debug(`Checking if the latest version of ${appc.cli} is installed`);
const
clis = JSON.parse(await exec('appc use -o json --prerelease')),
latest = clis.versions.find(element => element.includes(appc.cli)),
installed = clis.installed.includes(latest);
if (!latest) {
throw (new Error(`No Version Found For CLI ${appc.cli}`));
}
// If not, install it and set it as default
if (installed) {
output.debug(`Latest already installed, selecting ${latest}`);
} else {
output.debug(`Latest not installed, downloading ${latest}`);
}
await exec(`appc use ${latest}`);
// Return to the production environment
output.debug('Return to the production environment');
await this.login(appc, 'production');
}
}
}
/**
* Build a specified application for a given platform. Also allows users to
* specify their own arguments.
*
* @param {String} dir - The path to the application root
* @param {String} platform - The mobile OS the app is being built for
* @param {Object} opts - Optional arguments
* @param {String[]} opts.args - Any additional arguments to be passed to the command
* @param {Boolean} opts.ti - Whether or not to use the titanium CLI
*/
static build(dir, platform, sdk, { args = [], ti = false } = {}) {
return new Promise((resolve, reject) => {
// Validate the arguments are valid
if (args && !Array.isArray(args)) {
return reject(Error('Arguments must be an array'));
}
// Generate a path to the tiapp.xml file
const tiappFile = path.join(dir, 'tiapp.xml');
// Prepare the tiapp.xml before build
const
tiapp = require('tiapp.xml').load(tiappFile),
appName = tiapp.name;
output.debug(`Building app '${appName}'`);
output.debug(`Setting tiapp.xml SDK to ${sdk}`);
// Write the desired SDK to the tiapp.xml
tiapp.sdkVersion = sdk;
tiapp.write();
// Create our default arguments
let
cmd,
cmdArgs;
if (ti) {
cmd = 'ti';
cmdArgs = [ 'build', '-f', '-d', dir, '-p', platform, '--no-prompt', '--build-only' ];
} else {
cmd = 'appc';
cmdArgs = [ 'run', '-f', '-d', dir, '-p', platform, '--no-prompt', '--build-only' ];
}
// Add any user defined arguments into the command
if (args) {
cmdArgs = cmdArgs.concat(args);
}
// Build a command string to display to the console
let argstring = cmd;
cmdArgs.forEach(arg => {
argstring = `${argstring} ${arg}`;
});
output.debug(`Invoking command: ${argstring}`);
// Execute the run command, and listen for events
const prc = spawn(cmd, cmdArgs, { shell: true });
prc.stdout.on('data', data => {
output.debug(data.toString());
});
prc.stderr.on('data', data => {
output.debug(data.toString());
// Appc CLI doesn't always provide an error code on fail, so need to monitor the output and look for issues manually
// If statement is there so that [WARN] flags are ignored on stderr
if (data.toString().includes('[ERROR]')) {
prc.kill();
return reject(Error(data.toString().replace(/\W*\[ERROR\]\W*/, '')));
}
});
prc.on('exit', code => {
(code === 0) ? resolve(this.createAppPath(dir, platform, appName)) : reject(Error('Failed on application build'));
});
});
}
/**
* Generate a path to the built application based upon platform
*
* @param {String} dir - The path to the root of the application project
* @param {String} platform - The relevant mobile OS
* @param {String} appName - The name of the app to identify the app package
*/
static createAppPath(dir, platform, appName) {
switch (platform) {
case 'ios':
try {
const
products = path.join(dir, 'build', 'iphone', 'build', 'Products'),
productDir = fs.readdirSync(products)[0],
appPath = path.join(products, productDir, `${appName}.app`);
return appPath;
} catch (err) {
throw Error(`Issue scanning for app package: ${err}`);
}
case 'android':
const sdk = require('tiapp.xml').load(path.join(dir, 'tiapp.xml')).sdkVersion;
if (sdk[0] >= 9) {
return path.join(dir, 'build', 'android', 'app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk');
} else {
return path.join(dir, 'build', 'android', 'bin', `${appName}.apk`);
}
case 'windows':
return path.join(dir); // FIXME: Find Windows path
default:
throw Error('Invalid platform passed to function');
}
}
/**
* Build a specified application for a given platform. Also allows users to
* specify their own arguments
*
* @param {String} args - Arguments to be run after calling appc
* @param {Object} opts - Optional arguments
* @param {function} opts.matcher - A function that can be used to resolve
* @param {Boolean} opts.ti - Whether or not to use the titanium CLI
* @param {String} opts.proc - Custom name for the global, if empty no global will be created
*/
static runner(args, { matcher = undefined, ti = false, proc = undefined } = {}) {
return new Promise((resolve, reject) => {
let cmd;
if (ti) {
cmd = 'ti';
} else {
cmd = 'appc';
}
// Build a command string to display to the console
let argstring = cmd;
args.forEach(arg => {
argstring = `${argstring} ${arg}`;
});
output.debug(`Invoking command: ${argstring}`);
const prc = spawn(cmd, args, { shell: true });
// If this is going to be a persisting process, assign it a custom global
if (proc) { global[proc] = prc; }
prc.stdout.on('data', data => {
output.debug(data.toString());
if ((typeof matcher) === 'function' && matcher(data.toString())) {
return resolve();
}
});
prc.stderr.on('data', data => {
output.debug(data.toString());
// Appc CLI doesn't always provide an error code on fail, so need to monitor the output and look for issues manually
// If statement is there so that [WARN] flags are ignored on stderr
if (data.toString().includes('[ERROR]')) {
output.debug(data.toString().replace(/\W*\[ERROR\]\W*/, ''));
}
});
prc.on('exit', code => {
if (proc) { delete global[proc]; }
(code === 0) ? resolve() : reject(Error(`Command exited with code: ${code}`));
});
});
}
}
module.exports = Appc_Helper;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Namespaces</h3><ul><li><a href="WebDriverCommands.html">WebDriverCommands</a></li></ul><h3>Classes</h3><ul><li><a href="Appc_Helper.html">Appc_Helper</a></li><li><a href="Appium_Helper.html">Appium_Helper</a></li><li><a href="Device_Helper.html">Device_Helper</a></li><li><a href="Mocha_Helper.html">Mocha_Helper</a></li><li><a href="Output_Helper.html">Output_Helper</a></li><li><a href="Webdriver_Helper.html">Webdriver_Helper</a></li></ul><h3>Global</h3><ul><li><a href="global.html#appcRun">appcRun</a></li><li><a href="global.html#appcSetup">appcSetup</a></li><li><a href="global.html#banner">banner</a></li><li><a href="global.html#buildApp">buildApp</a></li><li><a href="global.html#createAppPath">createAppPath</a></li><li><a href="global.html#debug">debug</a></li><li><a href="global.html#error">error</a></li><li><a href="global.html#finish">finish</a></li><li><a href="global.html#getCert">getCert</a></li><li><a href="global.html#getProfile">getProfile</a></li><li><a href="global.html#getSimState">getSimState</a></li><li><a href="global.html#info">info</a></li><li><a href="global.html#killEmulator">killEmulator</a></li><li><a href="global.html#killSimulator">killSimulator</a></li><li><a href="global.html#parseSDK">parseSDK</a></li><li><a href="global.html#skip">skip</a></li><li><a href="global.html#startAppium">startAppium</a></li><li><a href="global.html#startClient">startClient</a></li><li><a href="global.html#step">step</a></li><li><a href="global.html#stopAppium">stopAppium</a></li><li><a href="global.html#stopClient">stopClient</a></li><li><a href="global.html#test">test</a></li><li><a href="global.html#warn">warn</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.3</a> on Fri Aug 28 2020 10:09:07 GMT+0100 (British Summer Time)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>