xctest-client
Version:
Node.js client for xctest
302 lines (251 loc) • 8.19 kB
JavaScript
;
const url = require('url');
const path = require('path');
const XCTestWD = require('xctestwd');
const iOSUtils = require('ios-utils');
const detect = require('detect-port');
const EventEmitter = require('events');
const childProcess = require('child_process');
const WebDriverAgent = require('webdriveragent');
const deviceconsole = require('ios-deviceconsole');
const _ = require('./helper');
const pkg = require('../package');
const XCProxy = require('./proxy');
const logger = require('./logger');
const TEST_URL = pkg.site;
var projectPath = WebDriverAgent.projectPath;
var AGENT_URL_REG = WebDriverAgent.AGENT_URL_REG;
const envisOK = process.env.XCTESTWD === 'true';
class XCTest extends EventEmitter {
constructor(options) {
super();
this.isXCTestWD = false;
this.proxy = null;
this.capabilities = null;
this.sessionId = null;
this.device = null;
this.deviceLogProc = null;
this.runnerProc = null;
this.simWebDriverAgentPath = null;
this.iproxyProc = null;
Object.assign(this, {
proxyHost: '127.0.0.1',
proxyPort: 8900,
urlBase: 'wd/hub'
}, options || {});
this.init();
process.on('uncaughtException', (e) => {
logger.debug('Uncaught Exception: ' + e);
this.stop();
process.exit(1);
});
process.on('exit', () => {
this.stop();
});
}
init() {
if (this.isXCTestWD || envisOK) {
projectPath = XCTestWD.projectPath;
AGENT_URL_REG = XCTestWD.AGENT_URL_REG;
}
this.checkProjectPath();
}
checkProjectPath() {
if (_.isExistedDir(projectPath)) {
logger.debug(`project path: ${projectPath}`);
} else {
logger.error('project path not found');
}
}
configUrl(str) {
const urlObj = url.parse(str);
this.proxyHost = urlObj.hostname;
this.proxyPort = urlObj.port;
}
initProxy() {
this.proxy = new XCProxy({
proxyHost: this.proxyHost,
proxyPort: this.proxyPort,
urlBase: this.urlBase
});
}
*startSimLog() {
this.startBootstrap();
return _.retry(() => {
return new Promise((resolve, reject) => {
let logDir = path.resolve(this.device.getLogDir(), 'system.log');
if (!_.isExistedFile(logDir)) {
return reject();
}
let args =`-f -n 0 ${logDir}`.split(' ');
var proc = childProcess.spawn('tail', args, {});
this.deviceLogProc = proc;
proc.stderr.setEncoding('utf8');
proc.stdout.setEncoding('utf8');
proc.stdout.on('data', data => {
//logger.debug(data);
let match = AGENT_URL_REG.exec(data);
if (match) {
const url = match[1];
if (url.startsWith('http://')) {
this.configUrl(url);
resolve();
}
}
});
proc.stderr.on('data', data => {
logger.debug(data);
});
proc.stdout.on('error', (err) => {
logger.warn(`simulator log process error with ${err}`);
});
proc.on('exit', (code, signal) => {
logger.warn(`simulator log process exit with code: ${code}, signal: ${signal}`);
reject();
});
});
}, 1000, Infinity);
}
*startDeviceLog() {
let args =['-u', this.device.deviceId];
var proc = childProcess.spawn(deviceconsole.binPath, args, {});
this.deviceLogProc = proc;
proc.stderr.setEncoding('utf8');
proc.stdout.setEncoding('utf8');
yield this.startIproxy();
return new Promise((resolve, reject) => {
proc.stdout.on('data', data => {
let match = AGENT_URL_REG.exec(data);
if (match) {
const url = match[1];
if (url.startsWith('http://')) {
resolve();
}
}
// logger.debug(data);
});
proc.stderr.on('data', data => {
logger.debug(data);
});
proc.stdout.on('error', (err) => {
logger.warn(`deviceconsole error with ${err}`);
});
proc.on('exit', (code, signal) => {
logger.warn(`deviceconsole exit with code: ${code}, signal: ${signal}`);
reject();
});
this.startBootstrap();
});
}
startBootstrap() {
if (this.isXCTestWD || envisOK) {
logger.info(`XCTestWD version: ${XCTestWD.version}`);
var args = `test -project ${XCTestWD.projectPath} -scheme ${XCTestWD.schemeName} -destination id=${this.device.deviceId} XCTESTWD_PORT=8001`.split(' ');
var env = _.merge({}, process.env, {
XCTESTWD_PORT: this.proxyPort
});
} else {
logger.info(`WebDriverAgent version: ${WebDriverAgent.version}`);
var args = `test -project ${WebDriverAgent.projectPath} -scheme ${WebDriverAgent.schemeName} -destination id=${this.device.deviceId}`.split(' ');
var env = _.merge({}, process.env, {
WEBDRIVERAGENT_PORT: this.proxyPort
});
}
var proc = childProcess.spawn('xcodebuild', args, {
env: env
});
this.runnerProc = proc;
proc.stderr.setEncoding('utf8');
proc.stdout.setEncoding('utf8');
proc.stdout.on('data', () => {
});
proc.stderr.on('data', data => {
logger.debug(data);
logger.debug(`please check project: ${projectPath}`);
});
proc.stdout.on('error', (err) => {
logger.warn(`xctest client error with ${err}`);
logger.debug(`please check project: ${projectPath}`);
});
proc.on('exit', (code, signal) => {
this.stop();
logger.warn(`xctest client exit with code: ${code}, signal: ${signal}`);
});
}
*startIproxy() {
let args = [this.proxyPort, this.proxyPort, this.device.deviceId];
const IOS_USBMUXD_IPROXY = 'iproxy';
const binPath = yield _.exec(`which ${IOS_USBMUXD_IPROXY}`);
var proc = childProcess.spawn(binPath, args);
this.iproxyProc = proc;
proc.stderr.setEncoding('utf8');
proc.stdout.setEncoding('utf8');
proc.stdout.on('data', () => {
});
proc.stderr.on('data', () => {
//logger.debug(data);
});
proc.stdout.on('error', (err) => {
logger.warn(`${IOS_USBMUXD_IPROXY} error with ${err}`);
});
proc.on('exit', (code, signal) => {
logger.warn(`${IOS_USBMUXD_IPROXY} exit with code: ${code}, signal: ${signal}`);
});
}
*start(caps) {
try {
this.proxyPort = yield detect(this.proxyPort);
logger.info(`${pkg.name} start with port: ${this.proxyPort}`);
this.capabilities = caps;
const xcodeVersion = yield iOSUtils.getXcodeVersion();
logger.debug(`xcode version: ${xcodeVersion}`);
var deviceInfo = iOSUtils.getDeviceInfo(this.device.deviceId);
if (deviceInfo.isRealIOS) {
yield this.startDeviceLog();
} else {
yield this.startSimLog();
}
this.initProxy();
if (caps.desiredCapabilities.browserName === 'Safari') {
var promise = this.proxy.send('/session', 'POST', {
desiredCapabilities: {
bundleId: 'com.apple.mobilesafari'
}
});
return yield Promise.all([this.device.openURL(TEST_URL), promise]);
} else {
if (this.isXCTestWD || envisOK) {
return yield this.proxy.send('/wd/hub/session', 'POST', caps);
} else {
yield _.sleep(3000);
return yield this.proxy.send('/session', 'POST', caps);
}
}
} catch (err) {
logger.debug(`Fail to start xctest: ${err}`);
this.stop();
throw err;
}
}
stop() {
if (this.deviceLogProc) {
logger.debug(`killing deviceLogProc pid: ${this.deviceLogProc.pid}`);
this.deviceLogProc.kill('SIGKILL');
this.deviceLogProc = null;
}
if (this.runnerProc) {
logger.debug(`killing runnerProc pid: ${this.runnerProc.pid}`);
this.runnerProc.kill('SIGKILL');
this.runnerProc = null;
}
if (this.iproxyProc) {
logger.debug(`killing iproxyProc pid: ${this.iproxyProc.pid}`);
this.iproxyProc.kill('SIGKILL');
this.iproxyProc = null;
}
}
sendCommand(url, method, body) {
return this.proxy.send(url, method, body);
}
}
module.exports = XCTest;