appium-xcuitest-driver
Version:
Appium driver for iOS using XCUITest for backend
245 lines • 10.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.assertIDB = assertIDB;
exports.parseXCTestStdout = parseXCTestStdout;
exports.mobileRunXCTest = mobileRunXCTest;
exports.mobileInstallXCTestBundle = mobileInstallXCTestBundle;
exports.mobileListXCTestBundles = mobileListXCTestBundles;
exports.mobileListXCTestsInTestBundle = mobileListXCTestsInTestBundle;
const bluebird_1 = __importDefault(require("bluebird"));
const support_1 = require("appium/support");
const lodash_1 = __importDefault(require("lodash"));
const driver_1 = require("appium/driver");
const XCTEST_TIMEOUT = 360000; // 60 minute timeout
const xctestLog = support_1.logger.getLogger('XCTest');
/**
* Asserts that IDB is present and that launchWithIDB was used.
*
* @param opts - Opts object from the driver instance
* @returns The IDB instance
* @throws {Error} If IDB is not available or launchWithIDB is not enabled
*/
function assertIDB(opts) {
const device = this.device;
if (!device?.idb || !opts.launchWithIDB) {
throw new Error(`To use XCTest runner, IDB (https://github.com/facebook/idb) must be installed ` +
`and sessions must be run with the "launchWithIDB" capability`);
}
return device.idb;
}
/**
* Parse the stdout of XC test log.
*
* @param stdout - A line of standard out from `idb xctest run ...`
* @returns The final output of the XCTest run
*/
function parseXCTestStdout(stdout) {
// Parses a 'key' into JSON format
function parseKey(name) {
const words = name.split(' ');
let out = '';
for (const word of words) {
out += word.substr(0, 1).toUpperCase() + word.substr(1);
}
return out.substr(0, 1).toLowerCase() + out.substr(1);
}
// Parses a 'value' into JSON format
function parseValue(value) {
value = value || '';
switch (value.toLowerCase()) {
case 'true':
return true;
case 'false':
return false;
case '':
return null;
default:
break;
}
if (!isNaN(Number(value))) {
if (!lodash_1.default.isString(value)) {
return 0;
}
else if (value.indexOf('.') > 0) {
return parseFloat(value);
}
return parseInt(value, 10);
}
return value;
}
if (!stdout) {
return [];
}
// Parse each line into an array
const lines = stdout.trim().split('\n');
// One single string, just return the string
if (lines.length === 1 && !lines[0].includes('|')) {
return [lines[0]];
}
const results = [];
for (const line of lines) {
// The properties are split up by pipes and each property
// has the format "Some Key : Some Value"
const properties = line.split('|');
// Parse each property
const output = {};
let entryIndex = 0;
for (const prop of properties) {
if (entryIndex === 0) {
// The first property only contains one string that contains
// the test name (e.g.: 'XCTesterAppUITests - XCTesterAppUITests.XCTesterAppUITests/testExample')
output.testName = prop.trim();
}
else if (prop.trim().startsWith('Location')) {
// The Location property has a value that comes after 'Location' without colon.
// e.g. Location /path/to/XCTesterAppUITests/XCTesterAppUITests.swift:36
output.location = prop.substring(prop.indexOf('Location') + 8).trim();
}
else {
const [key, value] = prop.split(':');
output[parseKey(key.trim())] = parseValue(value ? value.trim() : '');
}
entryIndex++;
}
// keep backward compatibility
// old pattern: XCTesterAppUITests - XCTesterAppUITests.XCTesterAppUITests/testExample | Passed: True | Crashed: False | Duration: 1.485 | Failure message: | Location :0
// latest pattern: XCTesterAppUITests - XCTesterAppUITests.XCTesterAppUITests/testExample | Status: passed | Duration: 1.9255789518356323
if (!output.passed) {
output.passed = output.status === 'passed';
output.crashed = output.status === 'crashed';
}
else if (!output.status) {
if (output.passed) {
output.status = 'passed';
}
else if (output.crashed) {
output.status = 'crashed';
}
else {
output.status = 'failed';
}
}
// Add this line to the results
results.push(output);
}
return results;
}
/**
* Run a native XCTest script.
*
* Launches a subprocess that runs the XC Test and blocks until it is completed. Parses the stdout of the process and returns its result as an array.
*
* **Facebook's [IDB](https://github.com/facebook/idb) tool is required** to run such tests; see [the idb docs](https://fbidb.io/docs/test-execution/) for reference.
*
* @param testRunnerBundleId - Test app bundle (e.g.: `io.appium.XCTesterAppUITests.xctrunner`)
* @param appUnderTestBundleId - App-under-test bundle
* @param xcTestBundleId - XCTest bundle ID
* @param args - Launch arguments to start the test with (see [reference documentation](https://developer.apple.com/documentation/xctest/xcuiapplication/1500477-launcharguments))
* @param testType - XC test type
* @param env - Environment variables passed to test
* @param timeout - Timeout (in ms) for session completion
* @returns The array of test results
* @throws {XCUITestError} Error thrown if subprocess returns non-zero exit code
*/
async function mobileRunXCTest(testRunnerBundleId, appUnderTestBundleId, xcTestBundleId, args = [], testType = 'ui', env, timeout = XCTEST_TIMEOUT) {
const subproc = await assertIDB.call(this, this.opts).runXCUITest(testRunnerBundleId, appUnderTestBundleId, xcTestBundleId, { env, args, testType });
return await new bluebird_1.default((resolve, reject) => {
let mostRecentLogObject = null;
let xctestTimeout;
let lastErrorMessage = null;
if (timeout > 0) {
xctestTimeout = setTimeout(() => reject(new driver_1.errors.TimeoutError(`Timed out after '${timeout}ms' waiting for XCTest to complete`)), timeout);
}
subproc.on('output', (stdout, stderr) => {
if (stdout) {
try {
mostRecentLogObject = parseXCTestStdout(stdout);
}
catch (err) {
// Fails if log parsing fails.
// This is in case IDB changes the way that logs are formatted and
// it breaks 'parseXCTestStdout'. If that happens we still want the process
// to finish
this.log.warn(`Failed to parse logs from test output: '${stdout}'`);
this.log.debug(err.stack);
}
}
if (stderr) {
lastErrorMessage = stderr;
xctestLog.error(stderr);
}
if (stdout) {
xctestLog.info(stdout);
}
});
subproc.on('exit', (code, signal) => {
if (xctestTimeout) {
clearTimeout(xctestTimeout);
}
if (code !== 0) {
const err = new Error(lastErrorMessage || String(mostRecentLogObject));
err.code = code ?? -1;
if (signal != null) {
err.signal = signal;
}
if (mostRecentLogObject) {
err.result = mostRecentLogObject;
}
return reject(err);
}
resolve({
code: code ?? 0,
signal: signal ?? null,
results: mostRecentLogObject,
passed: true,
});
});
});
}
/**
* Installs an XCTest bundle to the device under test.
*
* **Facebook's [IDB](https://github.com/facebook/idb) tool is required** for this command to work.
*
* @param xctestApp - Path of the XCTest app (URL or filename with extension `.app`)
*/
async function mobileInstallXCTestBundle(xctestApp) {
if (!lodash_1.default.isString(xctestApp)) {
throw new driver_1.errors.InvalidArgumentError(`'xctestApp' is a required parameter for 'installXCTestBundle' and ` +
`must be a string. Found '${xctestApp}'`);
}
xctestLog.info(`Installing bundle '${xctestApp}'`);
const idb = assertIDB.call(this, this.opts);
const res = await this.helpers.configureApp(xctestApp, '.xctest');
await idb.installXCTestBundle(res);
}
/**
* List XCTest bundles that are installed on the device.
*
* **Facebook's [IDB](https://github.com/facebook/idb) tool is required** for this command to work.
*
* @returns List of XCTest bundles (e.g.: `XCTesterAppUITests.XCTesterAppUITests/testLaunchPerformance`)
*/
async function mobileListXCTestBundles() {
return await assertIDB.call(this, this.opts).listXCTestBundles();
}
/**
* List XCTests in a test bundle.
*
* **Facebook's [IDB](https://github.com/facebook/idb) tool is required** for this command to work.
*
* @param bundle - Bundle ID of the XCTest
* @returns The list of xctests in the test bundle (e.g., `['XCTesterAppUITests.XCTesterAppUITests/testExample', 'XCTesterAppUITests.XCTesterAppUITests/testLaunchPerformance']`)
*/
async function mobileListXCTestsInTestBundle(bundle) {
if (!lodash_1.default.isString(bundle)) {
throw new driver_1.errors.InvalidArgumentError(`'bundle' is a required parameter for 'listXCTestsInTestBundle' and ` +
`must be a string. Found '${bundle}'`);
}
const idb = assertIDB.call(this, this.opts);
return await idb.listXCTestsInTestBundle(bundle);
}
//# sourceMappingURL=xctest.js.map