mirror of
https://github.com/appium/appium.git
synced 2026-02-12 04:50:08 -06:00
allow loading of app by path or url from desired capabilities
also a lot of stability improvements and error handling
This commit is contained in:
committed by
Sebastian Tiedtke
parent
79f971670e
commit
a9d58a6ce7
@@ -3,6 +3,9 @@
|
||||
"use strict";
|
||||
var routing = require('./routing')
|
||||
, logger = require('../logger').get('appium')
|
||||
, helpers = require('./helpers')
|
||||
, downloadFile = helpers.downloadFile
|
||||
, unzipApp = helpers.unzipApp
|
||||
, UUID = require('uuid-js')
|
||||
, _ = require('underscore')
|
||||
, ios = require('./ios');
|
||||
@@ -21,6 +24,7 @@ var Appium = function(args) {
|
||||
this.sessions = [];
|
||||
this.counter = -1;
|
||||
this.progress = -1;
|
||||
this.tempFiles = [];
|
||||
};
|
||||
|
||||
Appium.prototype.attachTo = function(rest, cb) {
|
||||
@@ -35,9 +39,76 @@ Appium.prototype.attachTo = function(rest, cb) {
|
||||
};
|
||||
|
||||
Appium.prototype.start = function(desiredCaps, cb) {
|
||||
this.desiredCapabilities = desiredCaps;
|
||||
this.sessions[++this.counter] = { sessionId: '', callback: cb };
|
||||
this.invoke();
|
||||
this.configure(desiredCaps, _.bind(function(err) {
|
||||
this.desiredCapabilities = desiredCaps;
|
||||
if (err) {
|
||||
cb(err, null);
|
||||
} else {
|
||||
this.sessions[++this.counter] = { sessionId: '', callback: cb };
|
||||
this.invoke();
|
||||
}
|
||||
}, this));
|
||||
};
|
||||
|
||||
Appium.prototype.configure = function(desiredCaps, cb) {
|
||||
if (typeof desiredCaps.app !== "undefined") {
|
||||
if (desiredCaps.app[0] === "/") {
|
||||
this.args.app = desiredCaps.app;
|
||||
logger.info("Using local app from desiredCaps: " + desiredCaps.app);
|
||||
cb(null);
|
||||
} else if (desiredCaps.app.substring(0, 4) === "http") {
|
||||
var appUrl = desiredCaps.app;
|
||||
if (appUrl.substring(appUrl.length - 4) === ".zip") {
|
||||
try {
|
||||
this.downloadAndUnzipApp(appUrl, _.bind(function(zipErr, appPath) {
|
||||
if (zipErr) {
|
||||
cb(zipErr);
|
||||
} else {
|
||||
this.args.app = appPath;
|
||||
logger.info("Using extracted app: " + this.args.app);
|
||||
cb(null);
|
||||
}
|
||||
}, this));
|
||||
logger.info("Using downloadable app from desiredCaps: " + appUrl);
|
||||
} catch (e) {
|
||||
var err = e.toString();
|
||||
logger.error("Failed downloading app from appUrl " + appUrl);
|
||||
cb(err);
|
||||
}
|
||||
} else {
|
||||
cb("App URL (" + appUrl + ") didn't seem to end in .zip");
|
||||
}
|
||||
} else if (!this.args.app) {
|
||||
cb("Bad app passed in through desiredCaps: " + desiredCaps.app +
|
||||
". Apps need to be absolute local path or URL to zip file");
|
||||
} else {
|
||||
logger.warn("Got bad app through desiredCaps: " + desiredCaps.app);
|
||||
logger.warn("Sticking with default app: " + this.args.app);
|
||||
this.desiredCapabilities.app = this.args.app;
|
||||
cb(null);
|
||||
}
|
||||
} else if (!this.args.app) {
|
||||
cb("No app set; either start appium with --app or pass in an 'app' " +
|
||||
"value in desired capabilities");
|
||||
} else {
|
||||
logger.info("Using app from command line: " + this.args.app);
|
||||
cb(null);
|
||||
}
|
||||
};
|
||||
|
||||
Appium.prototype.downloadAndUnzipApp = function(appUrl, cb) {
|
||||
var me = this;
|
||||
downloadFile(appUrl, function(zipPath) {
|
||||
me.tempFiles.push(zipPath);
|
||||
unzipApp(zipPath, function(err, appPath) {
|
||||
if (err) {
|
||||
cb(err, null);
|
||||
} else {
|
||||
me.tempFiles.push(appPath);
|
||||
cb(null, appPath);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Appium.prototype.invoke = function() {
|
||||
|
||||
@@ -44,8 +44,7 @@ exports.createSession = function(req, res) {
|
||||
var desired = req.body.desiredCapabilities;
|
||||
req.appium.start(req.body.desiredCapabilities, function(err, instance) {
|
||||
if (err) {
|
||||
// of course we need to deal with err according to the WDJP spec.
|
||||
throw err;
|
||||
return res.send({status: status.codes.NoSuchDriver, value: err});
|
||||
}
|
||||
|
||||
if (desired && desired.newCommandTimeout) {
|
||||
|
||||
95
app/helpers.js
Normal file
95
app/helpers.js
Normal file
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
|
||||
var logger = require('../logger').get('appium')
|
||||
, url = require('url')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, exec = require('child_process').exec
|
||||
, http = require('http')
|
||||
, temp = require('temp');
|
||||
|
||||
exports.downloadFile = function(fileUrl, cb) {
|
||||
// We will be downloading the files to a directory, so make sure it's there
|
||||
// This step is not required if you have manually created the directory
|
||||
temp.open({prefix: 'appium-app', suffix: '.zip'}, function(err, info) {
|
||||
var options = {
|
||||
host: url.parse(fileUrl).host,
|
||||
port: 80,
|
||||
path: url.parse(fileUrl).pathname
|
||||
};
|
||||
var file = fs.createWriteStream(info.path);
|
||||
http.get(options, function(res) {
|
||||
res.on('data', function(data) {
|
||||
file.write(data);
|
||||
}).on('end', function() {
|
||||
file.end();
|
||||
logger.info(fileUrl + ' downloaded to ' + info.path);
|
||||
cb(info.path);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.unzipFile = function(zipPath, cb) {
|
||||
logger.info("Unzipping " + zipPath);
|
||||
var execOpts = {cwd: path.dirname(zipPath)};
|
||||
exports.testZipArchive(zipPath, function(err, valid) {
|
||||
if (valid) {
|
||||
exec('unzip -o ' + zipPath, execOpts, function(err, stderr, stdout) {
|
||||
if (!err) {
|
||||
logger.info("Unzip successful");
|
||||
cb(null, stderr);
|
||||
} else {
|
||||
logger.error("Unzip threw error " + err);
|
||||
logger.error("Stderr: " + stderr);
|
||||
logger.error("Stdout: " + stdout);
|
||||
cb("Archive could not be unzipped, check appium logs.", null);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(err, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.testZipArchive = function(zipPath, cb) {
|
||||
logger.info("Testing zip archive: " + zipPath);
|
||||
var execOpts = {cwd: path.dirname(zipPath)};
|
||||
exec("unzip -t " + zipPath, execOpts, function(err, stderr, stdout) {
|
||||
if (!err) {
|
||||
if(/No errors detected/.exec(stderr)) {
|
||||
logger.info("Zip archive tested clean");
|
||||
cb(null, true);
|
||||
} else {
|
||||
logger.error("Zip file " + zipPath + " was not valid");
|
||||
logger.error("Stderr: " + stderr);
|
||||
logger.error("Stdout: " + stdout);
|
||||
cb("Zip archive did not test successfully, check appium server logs " +
|
||||
"for output", false);
|
||||
}
|
||||
} else {
|
||||
logger.error("Test zip archive threw error " + err);
|
||||
logger.error("Stderr: " + stderr);
|
||||
logger.error("Stdout: " + stdout);
|
||||
cb("Error testing zip archive, are you sure this is a zip file?", null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.unzipApp = function(zipPath, cb) {
|
||||
exports.unzipFile(zipPath, function(err, output) {
|
||||
if (!err) {
|
||||
var match = /inflating: ([^\/]+\.app)\//.exec(output);
|
||||
if (match) {
|
||||
var appPath = path.resolve(path.dirname(zipPath), match[1]);
|
||||
cb(null, appPath);
|
||||
} else {
|
||||
cb("App zip unzipped OK, but we couldn't find a .app bundle in it. " +
|
||||
"Make sure your archive contains the .app package and nothing else",
|
||||
null);
|
||||
}
|
||||
} else {
|
||||
cb(err, null);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -43,17 +43,24 @@ var IOS = function(rest, app, udid, verbose, removeTraceDir) {
|
||||
|
||||
IOS.prototype.start = function(cb, onDie) {
|
||||
var me = this;
|
||||
var didLaunch = false;
|
||||
if (typeof onDie === "function") {
|
||||
this.onStop = onDie;
|
||||
}
|
||||
|
||||
var onLaunch = function() {
|
||||
didLaunch = true;
|
||||
logger.info('Instruments launched. Starting poll loop for new commands.');
|
||||
me.instruments.setDebug(true);
|
||||
cb(null);
|
||||
};
|
||||
|
||||
var onExit = function(code, traceDir) {
|
||||
if (!didLaunch) {
|
||||
logger.error("Instruments did not launch successfully, failing session");
|
||||
cb("Instruments did not launch successfully--please check your app " +
|
||||
"paths or bundle IDs and try again");
|
||||
}
|
||||
this.instruments = null;
|
||||
if (me.removeTraceDir && traceDir) {
|
||||
rimraf(traceDir, function() {
|
||||
|
||||
@@ -10,7 +10,7 @@ module.exports = function() {
|
||||
});
|
||||
|
||||
parser.addArgument([ '--app' ]
|
||||
, { required: true, help: 'path to simulators .app file or the bundle_id of the desired target on device'
|
||||
, { required: false, help: 'path to simulators .app file or the bundle_id of the desired target on device'
|
||||
});
|
||||
|
||||
parser.addArgument([ '-V', '--verbose' ], { required: false, help: 'verbose mode' });
|
||||
|
||||
@@ -53,10 +53,19 @@ Instruments.prototype.startSocketServer = function(sock) {
|
||||
fs.unlinkSync(sock);
|
||||
} catch (Exception) {}
|
||||
|
||||
var onSocketNeverConnect = function() {
|
||||
logger.error("Instruments socket client never checked in; timing out".red);
|
||||
this.proc.kill('SIGTERM');
|
||||
this.exitHandler(1);
|
||||
};
|
||||
|
||||
var socketConnectTimeout = setTimeout(_.bind(onSocketNeverConnect, this), 30000);
|
||||
|
||||
var server = net.createServer({allowHalfOpen: true}, _.bind(function(conn) {
|
||||
if (!this.hasConnected) {
|
||||
this.hasConnected = true;
|
||||
this.debug("Instruments is ready to receive commands");
|
||||
clearTimeout(socketConnectTimeout);
|
||||
this.readyHandler(this);
|
||||
}
|
||||
conn.setEncoding('utf8'); // get strings from sockets rather than buffers
|
||||
@@ -109,19 +118,19 @@ Instruments.prototype.launch = function() {
|
||||
var tmpDir = '/tmp/' + this.guid;
|
||||
fs.mkdir(tmpDir, function(e) {
|
||||
if (!e || (e && e.code === 'EEXIST')) {
|
||||
self.proc = self.spawnInstruments(tmpDir);
|
||||
self.proc.stdout.on('data', function(data) {
|
||||
self.outputStreamHandler(data);
|
||||
});
|
||||
self.proc.stderr.on('data', function(data) {
|
||||
self.errorStreamHandler(data);
|
||||
});
|
||||
self.proc = self.spawnInstruments(tmpDir);
|
||||
self.proc.stdout.on('data', function(data) {
|
||||
self.outputStreamHandler(data);
|
||||
});
|
||||
self.proc.stderr.on('data', function(data) {
|
||||
self.errorStreamHandler(data);
|
||||
});
|
||||
|
||||
self.proc.on('exit', function(code) {
|
||||
self.debug("Instruments exited with code " + code);
|
||||
self.exitCode = code;
|
||||
self.exitHandler(self.exitCode, self.traceDir);
|
||||
});
|
||||
self.proc.on('exit', function(code) {
|
||||
self.debug("Instruments exited with code " + code);
|
||||
self.exitCode = code;
|
||||
self.exitHandler(self.exitCode, self.traceDir);
|
||||
});
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,9 @@
|
||||
"path": "~0.4.9",
|
||||
"rimraf": "~2.1.1",
|
||||
"uuid-js": "~0.7.4",
|
||||
"winston": "~0.6.2"
|
||||
"temp": "~0.5.0",
|
||||
"winston": "~0.6.2",
|
||||
"unzip": "~0.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt lint unit"
|
||||
@@ -46,7 +48,6 @@
|
||||
"grunt-mocha-test": "0.0.1",
|
||||
"request": "~2.12.0",
|
||||
"winston": "~0.6.2",
|
||||
"temp": "~0.5.0",
|
||||
"difflib": "~0.2.4",
|
||||
"prompt": "~0.2.9"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/*global it:true */
|
||||
|
||||
"use strict";
|
||||
|
||||
var describeWd = require('../helpers/driverblock.js').describe
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
var wd = require('wd')
|
||||
, _ = require("underscore")
|
||||
, path = require("path")
|
||||
, should = require("should")
|
||||
, defaultHost = '127.0.0.1'
|
||||
, defaultPort = 4723
|
||||
, defaultCaps = {
|
||||
@@ -22,6 +24,7 @@ var driverBlock = function(tests, host, port, caps, extraCaps) {
|
||||
beforeEach(function(done) {
|
||||
driverHolder.driver = wd.remote(host, port);
|
||||
driverHolder.driver.init(caps, function(err, sessionId) {
|
||||
should.not.exist(err);
|
||||
driverHolder.sessionId = sessionId;
|
||||
done();
|
||||
});
|
||||
@@ -45,5 +48,21 @@ var describeWithDriver = function(desc, tests, host, port, caps, extraCaps) {
|
||||
});
|
||||
};
|
||||
|
||||
var describeForApp = function(app, isBundle) {
|
||||
var appPath;
|
||||
if (typeof isBundle === "undefined") {
|
||||
isBundle = false;
|
||||
}
|
||||
if (/\//.exec(app) || isBundle) {
|
||||
appPath = app;
|
||||
} else {
|
||||
appPath = path.resolve(__dirname, "../../sample-code/apps/" + app + "/build/Release-iphonesimulator/" + app + ".app");
|
||||
}
|
||||
|
||||
defaultCaps = _.extend(defaultCaps, {app: appPath});
|
||||
return describeWithDriver;
|
||||
};
|
||||
|
||||
module.exports.block = driverBlock;
|
||||
module.exports.describe = describeWithDriver;
|
||||
module.exports.describeForApp = describeForApp;
|
||||
|
||||
Reference in New Issue
Block a user