mirror of
https://github.com/appium/appium.git
synced 2026-02-09 03:09:02 -06:00
the server will error sessions which provide invalid caps or don't provid valid ones fix #2500
421 lines
13 KiB
JavaScript
421 lines
13 KiB
JavaScript
"use strict";
|
|
|
|
var loggerjs = require('./server/logger.js')
|
|
, logger = loggerjs.get('appium')
|
|
, UUID = require('uuid-js')
|
|
, _ = require('underscore')
|
|
, Capabilities = require('./server/capabilities')
|
|
, IOS = require('./devices/ios/ios.js')
|
|
, Safari = require('./devices/ios/safari.js')
|
|
, Android = require('./devices/android/android.js')
|
|
, Selendroid = require('./devices/android/selendroid.js')
|
|
, Chrome = require('./devices/android/chrome.js')
|
|
, FirefoxOs = require('./devices/firefoxos/firefoxos.js')
|
|
, jwpResponse = require('./devices/common.js').jwpResponse
|
|
, status = require("./server/status.js");
|
|
|
|
var DT_IOS = "ios"
|
|
, DT_SAFARI = "safari"
|
|
, DT_ANDROID = "android"
|
|
, DT_CHROME = "chrome"
|
|
, DT_SELENDROID = "selendroid"
|
|
, DT_FIREFOX_OS = "firefoxos";
|
|
|
|
var Appium = function (args) {
|
|
this.args = args;
|
|
this.serverArgs = _.clone(args);
|
|
this.rest = null;
|
|
this.webSocket = null;
|
|
this.deviceType = null;
|
|
this.device = null;
|
|
this.sessionId = null;
|
|
this.desiredCapabilities = {};
|
|
this.oldDesiredCapabilities = {};
|
|
this.session = null;
|
|
this.preLaunched = false;
|
|
this.sessionOverride = this.args.sessionOverride;
|
|
this.resetting = false;
|
|
this.defCommandTimeoutMs = this.args.defaultCommandTimeout * 1000;
|
|
this.commandTimeoutMs = this.defCommandTimeoutMs;
|
|
this.commandTimeout = null;
|
|
};
|
|
|
|
Appium.prototype.attachTo = function (rest) {
|
|
this.rest = rest;
|
|
};
|
|
|
|
Appium.prototype.attachSocket = function (webSocket) {
|
|
this.webSocket = webSocket;
|
|
};
|
|
|
|
Appium.prototype.registerConfig = function (configObj) {
|
|
this.serverConfig = configObj;
|
|
};
|
|
|
|
Appium.prototype.deviceIsRegistered = function (deviceType) {
|
|
if (deviceType === DT_SAFARI) deviceType = DT_IOS;
|
|
if (deviceType === DT_CHROME) deviceType = DT_ANDROID;
|
|
return _.has(this.serverConfig, deviceType);
|
|
};
|
|
|
|
Appium.prototype.preLaunch = function (cb) {
|
|
logger.info("Pre-launching app");
|
|
var caps = {};
|
|
this.start(caps, function (err) {
|
|
if (err) {
|
|
cb(err, null);
|
|
} else {
|
|
this.preLaunched = true;
|
|
cb(null, this);
|
|
}
|
|
}.bind(this));
|
|
};
|
|
|
|
Appium.prototype.setArgFromCap = function (arg, cap) {
|
|
if (typeof this.desiredCapabilities[cap] !== "undefined") {
|
|
this.args[arg] = this.desiredCapabilities[cap];
|
|
}
|
|
};
|
|
|
|
Appium.prototype.updateResetArgsFromCaps = function () {
|
|
this.setArgFromCap("noReset", "noReset");
|
|
this.setArgFromCap("fullReset", "fullReset");
|
|
|
|
// user can set noReset or fullReset
|
|
var caps = this.desiredCapabilities;
|
|
if (caps.noReset === true) this.args.fullReset = false;
|
|
if (caps.fullReset === true) this.args.noReset = false;
|
|
|
|
// not user visible via caps
|
|
this.args.fastReset = !this.args.fullReset && !this.args.noReset;
|
|
this.args.skipUninstall = this.args.fastReset || this.args.noReset;
|
|
};
|
|
|
|
Appium.prototype.start = function (desiredCaps, cb) {
|
|
this.desiredCapabilities = new Capabilities(desiredCaps);
|
|
this.updateResetArgsFromCaps();
|
|
|
|
this.args.webSocket = this.webSocket; // allow to persist over many sessions
|
|
if (this.sessionId === null || this.sessionOverride) {
|
|
this.configure(this.args, this.desiredCapabilities, function (err) {
|
|
if (err) {
|
|
logger.info("Got configuration error, not starting session");
|
|
this.cleanupSession();
|
|
cb(err, null);
|
|
} else {
|
|
this.invoke(cb);
|
|
}
|
|
}.bind(this));
|
|
} else {
|
|
return cb(new Error("Requested a new session but one was in progress"));
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceType = function (args, caps) {
|
|
var type = this.getDeviceFromMJSONWP(args, caps) ||
|
|
this.getDeviceTypeFromArgsOrNamedApp(args, caps) ||
|
|
this.getDeviceTypeFromAppOrPackage(args, caps);
|
|
if (type) {
|
|
return type;
|
|
}
|
|
throw new Error("Could not determine your device from Appium arguments " +
|
|
"or desired capabilities. Please make sure to specify the " +
|
|
"'deviceName' capability");
|
|
};
|
|
|
|
Appium.prototype.getDeviceFromMJSONWP = function (args, caps) {
|
|
var platform = caps.platformName || args.platformName;
|
|
platform = platform ? platform.toString().toLowerCase() : '';
|
|
var browser = caps.browserName || args.browserName;
|
|
browser = browser ? browser.toString().toLowerCase() : '';
|
|
var automation = caps.automationName || args.automationName;
|
|
automation = automation ? automation.toString().toLowerCase() : '';
|
|
|
|
var validPlatforms = ['ios', 'android', 'firefoxos'];
|
|
if (platform && !_.contains(validPlatforms, platform)) {
|
|
throw new Error("Could not determine your device. You sent in a " +
|
|
"platformName capability of '" + platform + "' but that " +
|
|
"is not a supported platform. Supported platforms are: " +
|
|
"iOS, Android and FirefoxOS");
|
|
}
|
|
var type = this.getDeviceTypeFromAutomationName(automation) ||
|
|
this.getDeviceTypeFromBrowserName(browser) ||
|
|
this.getDeviceTypeFromPlatformCap(platform);
|
|
if (type) return type;
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromArgsOrNamedApp = function (args, caps) {
|
|
var app = args.app || caps.app;
|
|
app = app ? app.toString().toLowerCase() : '';
|
|
var type = this.getDeviceTypeFromNamedApp(app) ||
|
|
this.getDeviceTypeFromArgs(args);
|
|
if (type) return type;
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromAppOrPackage = function (args, caps) {
|
|
var app = args.app || caps.app;
|
|
app = app ? app.toString().toLowerCase() : '';
|
|
var pkg = args.androidPackage || caps.appPackage;
|
|
pkg = pkg ? pkg.toString().toLowerCase() : '';
|
|
var type = this.getDeviceTypeFromApp(app) ||
|
|
this.getDeviceTypeFromPackage(pkg);
|
|
if (type) return type;
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromAutomationName = function (automation) {
|
|
if (automation.indexOf('selendroid') !== -1) return DT_SELENDROID;
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromBrowserName = function (browser) {
|
|
if (browser === "safari") {
|
|
return DT_SAFARI;
|
|
} else if (_.contains(["chrome", "chromium", "chromebeta", "browser"], browser)) {
|
|
return DT_CHROME;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromPlatformCap = function (caps) {
|
|
var device = null;
|
|
switch (caps) {
|
|
case 'ios':
|
|
device = DT_IOS;
|
|
break;
|
|
case 'android':
|
|
device = DT_ANDROID;
|
|
break;
|
|
case 'firefoxos':
|
|
device = DT_FIREFOX_OS;
|
|
break;
|
|
}
|
|
return device;
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromDeviceCap = function (device) {
|
|
|
|
if (device.indexOf('iphone') !== -1) {
|
|
return DT_IOS;
|
|
} else if (device.indexOf('ipad') !== -1) {
|
|
return DT_IOS;
|
|
} else if (device.indexOf('selendroid') !== -1) {
|
|
return DT_SELENDROID;
|
|
} else if (device.indexOf('firefox') !== -1) {
|
|
return DT_FIREFOX_OS;
|
|
} else if (device.indexOf('android') !== -1) {
|
|
return DT_ANDROID;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromNamedApp = function (app) {
|
|
if (app === "safari") {
|
|
return DT_SAFARI;
|
|
} else if (app === "settings") {
|
|
return DT_IOS;
|
|
} else if (_.contains(["chrome", "chromium", "chromebeta", "browser"], app)) {
|
|
return DT_CHROME;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromApp = function (app) {
|
|
if (/\.app$/.test(app) || /\.app\.zip$/.test(app)) {
|
|
return DT_IOS;
|
|
} else if (/\.apk$/.test(app) || /\.apk\.zip$/.test(app)) {
|
|
return DT_ANDROID;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromPackage = function (pkg) {
|
|
var chromePkgs = [
|
|
"com.android.chrome"
|
|
, "com.chrome.beta"
|
|
, "org.chromium.chrome.testshell"
|
|
, "com.android.browser"
|
|
];
|
|
if (_.contains(chromePkgs, pkg)) {
|
|
return DT_CHROME;
|
|
} else if (pkg) {
|
|
return DT_ANDROID;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getDeviceTypeFromArgs = function (args) {
|
|
if (args.ipa && /\.ipa$/.test(args.ipa.toString().toLowerCase())) {
|
|
return DT_IOS;
|
|
} else if (args.safari) {
|
|
return DT_SAFARI;
|
|
} else if (args.forceIphone || args.forceIpad) {
|
|
return DT_IOS;
|
|
}
|
|
};
|
|
|
|
Appium.prototype.configure = function (args, desiredCaps, cb) {
|
|
var deviceType;
|
|
|
|
try {
|
|
deviceType = this.getDeviceType(args, desiredCaps);
|
|
if (args.enforceStrictCaps) {
|
|
desiredCaps.checkStrictValidity(deviceType);
|
|
}
|
|
} catch (e) {
|
|
logger.error(e.message);
|
|
return cb(e);
|
|
}
|
|
|
|
if (!this.deviceIsRegistered(deviceType)) {
|
|
logger.error("Trying to run a session for device '" + deviceType + "' " +
|
|
"but that device hasn't been configured. Run config");
|
|
return cb(new Error("Device " + deviceType + " not configured yet"));
|
|
}
|
|
this.device = this.getNewDevice(deviceType);
|
|
this.device.configure(args, desiredCaps, cb);
|
|
};
|
|
|
|
Appium.prototype.invoke = function (cb) {
|
|
this.sessionId = UUID.create().hex;
|
|
logger.info('Creating new appium session ' + this.sessionId);
|
|
|
|
if (this.device.args.autoLaunch === false) {
|
|
// if user has passed in desiredCaps.autoLaunch = false
|
|
// meaning they will manage app install / launching
|
|
cb(null, this.device);
|
|
} else {
|
|
// the normal case, where we launch the device for folks
|
|
|
|
var onStart = function (err, sessionIdOverride) {
|
|
if (sessionIdOverride) {
|
|
this.sessionId = sessionIdOverride;
|
|
logger.info("Overriding session id with " +
|
|
JSON.stringify(sessionIdOverride));
|
|
}
|
|
if (err) return this.cleanupSession(err, cb);
|
|
logger.info("Device launched! Ready for commands");
|
|
this.setCommandTimeout(this.desiredCapabilities.newCommandTimeout);
|
|
cb(null, this.device);
|
|
}.bind(this);
|
|
|
|
this.device.start(onStart, _.once(this.cleanupSession.bind(this)));
|
|
}
|
|
};
|
|
|
|
Appium.prototype.getNewDevice = function (deviceType) {
|
|
var DeviceClass = (function () {
|
|
switch (deviceType) {
|
|
case DT_IOS:
|
|
return IOS;
|
|
case DT_SAFARI:
|
|
return Safari;
|
|
case DT_ANDROID:
|
|
return Android;
|
|
case DT_CHROME:
|
|
return Chrome;
|
|
case DT_SELENDROID:
|
|
return Selendroid;
|
|
case DT_FIREFOX_OS:
|
|
return FirefoxOs;
|
|
default:
|
|
throw new Error("Tried to start a device that doesn't exist: " +
|
|
deviceType);
|
|
}
|
|
})();
|
|
return new DeviceClass();
|
|
};
|
|
|
|
Appium.prototype.timeoutWaitingForCommand = function () {
|
|
logger.info("Didn't get a new command in " + (this.commandTimeoutMs / 1000) +
|
|
" secs, shutting down...");
|
|
this.stop(function () {
|
|
logger.info("We shut down because no new commands came in");
|
|
}.bind(this));
|
|
};
|
|
|
|
Appium.prototype.cleanupSession = function (err, cb) {
|
|
logger.info("Cleaning up appium session");
|
|
if (this.commandTimeout) {
|
|
clearTimeout(this.commandTimeout);
|
|
this.commandTimeout = null;
|
|
}
|
|
this.commandTimeoutMs = this.defCommandTimeoutMs;
|
|
var dyingSession = this.sessionId;
|
|
this.sessionId = null;
|
|
this.sessionOverride = false;
|
|
this.device = null;
|
|
this.args = _.clone(this.serverArgs);
|
|
this.oldDesiredCapabilities = _.clone(this.desiredCapabilities);
|
|
this.desiredCapabilities = {};
|
|
if (cb) {
|
|
if (err) return cb(err);
|
|
cb(null, {status: status.codes.Success.code, value: null,
|
|
sessionId: dyingSession});
|
|
}
|
|
};
|
|
|
|
Appium.prototype.resetTimeout = function () {
|
|
if (this.commandTimeout) {
|
|
clearTimeout(this.commandTimeout);
|
|
}
|
|
if (this.commandTimeoutMs && this.commandTimeoutMs > 0) {
|
|
this.commandTimeout = setTimeout(this.timeoutWaitingForCommand.bind(this),
|
|
this.commandTimeoutMs);
|
|
}
|
|
};
|
|
|
|
Appium.prototype.setCommandTimeout = function (secs, cb) {
|
|
if (typeof secs === "undefined") {
|
|
secs = this.defCommandTimeoutMs / 1000;
|
|
logger.info("Setting command timeout to the default of " + secs + " secs");
|
|
} else {
|
|
logger.info("Setting command timeout to " + secs + " secs");
|
|
}
|
|
this.commandTimeoutMs = secs * 1000;
|
|
this.resetTimeout();
|
|
if (typeof cb === "function") {
|
|
jwpResponse(null, secs, cb);
|
|
}
|
|
};
|
|
|
|
Appium.prototype.stop = function (cb) {
|
|
if (this.sessionId === null || this.device === null) {
|
|
logger.info("Trying to stop appium but there's no session, doing nothing");
|
|
return cb();
|
|
}
|
|
|
|
logger.info('Shutting down appium session...');
|
|
this.device.stop(function (code) {
|
|
var err;
|
|
if (code && code > 0) {
|
|
err = new Error("Device exited with code: " + code);
|
|
}
|
|
this.cleanupSession(err, cb);
|
|
}.bind(this));
|
|
};
|
|
|
|
Appium.prototype.reset = function (cb) {
|
|
logger.info("Resetting app mid-session");
|
|
if (typeof this.device.resetAndStartApp === "function") {
|
|
logger.info("Running device specific reset");
|
|
this.device.resetAndStartApp(function (err) {
|
|
jwpResponse(err, cb);
|
|
});
|
|
} else {
|
|
logger.info("Running generic full reset");
|
|
var oldImpWait = this.device.implicitWaitMs
|
|
, oldCommandTimeoutMs = this.commandTimeoutMs
|
|
, oldId = this.sessionId;
|
|
|
|
this.resetting = true;
|
|
this.stop(function () {
|
|
logger.info("Restarting app");
|
|
this.start(this.oldDesiredCapabilities, function (err) {
|
|
if (err) return cb(err);
|
|
this.resetting = false;
|
|
this.device.implicitWaitMs = oldImpWait;
|
|
this.sessionId = oldId;
|
|
this.setCommandTimeout(oldCommandTimeoutMs / 1000, cb);
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}
|
|
};
|
|
|
|
module.exports = function (args) {
|
|
return new Appium(args);
|
|
};
|