allow automation of native chrome elements (fix #3434)

This commit is contained in:
Jonathan Lipps
2014-04-23 17:30:35 -07:00
parent 07eac83e3d
commit fc9726893b
3 changed files with 112 additions and 10 deletions
+84 -9
View File
@@ -3,10 +3,18 @@
var Android = require('./android.js')
, _ = require('underscore')
, logger = require('../../server/logger.js').get('appium')
, status = require('../../server/status.js')
, deviceCommon = require('../common.js')
, jwpSuccess = deviceCommon.jwpSuccess
, async = require('async')
, ADB = require('./adb.js')
, UiAutomator = require('./uiautomator.js')
, Chromedriver = require('./chromedriver.js');
var NATIVE_WIN = "NATIVE_APP";
var WEBVIEW_WIN = "WEBVIEW";
var WEBVIEW_BASE = WEBVIEW_WIN + "_";
var ChromeAndroid = function () {
this.init();
};
@@ -15,10 +23,25 @@ _.extend(ChromeAndroid.prototype, Android.prototype);
ChromeAndroid.prototype._androidInit = Android.prototype.init;
ChromeAndroid.prototype.init = function () {
this._androidInit();
this.avoidProxy = [];
this.isProxy = true;
this.adb = null;
this.onDie = null;
this.setChromedriverMode();
};
ChromeAndroid.prototype.setChromedriverMode = function () {
logger.info("Set mode: Proxying straight through to Chromedriver");
this.isProxy = true;
// when proxying to chromedriver, we need to make sure the context endpoints
// are trapped by appium for its own purposes
this.avoidProxy = [
['POST', new RegExp('^/wd/hub/session/[^/]+/context')],
['GET', new RegExp('^/wd/hub/session/[^/]+/context')]
];
};
ChromeAndroid.prototype.setNativeMode = function () {
logger.info("Set mode: Proxying to Appium Bootstrap");
this.isProxy = false;
};
ChromeAndroid.prototype.configure = function (args, caps, cb) {
@@ -45,23 +68,28 @@ ChromeAndroid.prototype.configure = function (args, caps, cb) {
delete this.capabilities.appActivity;
this.setAndroidArgs();
this.args.app = null;
this.args.systemPort = this.args.chromeDriverPort;
this.args.proxyPort = this.args.systemPort;
this.args.proxyPort = this.args.chromeDriverPort;
cb();
};
ChromeAndroid.prototype.start = function (cb, onDie) {
this.adb = new ADB(this.args);
this.uiautomator = new UiAutomator(this.adb, this.args);
this.uiautomator.setExitHandler(this.onUiautomatorExit.bind(this));
this.onDie = onDie;
async.series([
this.prepareDevice.bind(this),
this.prepareChromedriver.bind(this),
this.unlock.bind(this),
this.forwardPort.bind(this),
this.pushAppium.bind(this),
this.uiautomator.start.bind(this.uiautomator),
this.getDataDir.bind(this),
this.createSession.bind(this)
], function (err, results) {
if (err) return cb(err);
var sessionId = results[3];
var sessionId = results[results.length - 1];
cb(null, sessionId);
});
};
@@ -105,11 +133,13 @@ ChromeAndroid.prototype.createSession = function (cb) {
};
ChromeAndroid.prototype.stop = function (cb) {
this.chromedriver.stop(function (err) {
if (err) return cb(err);
this.adb.forceStop(this.args.appPackage, function (err) {
this.uiautomator.shutdown(function () {
this.chromedriver.stop(function (err) {
if (err) return cb(err);
this.adb.stopLogcat(cb);
this.adb.forceStop(this.args.appPackage, function (err) {
if (err) return cb(err);
this.adb.stopLogcat(cb);
}.bind(this));
}.bind(this));
}.bind(this));
};
@@ -124,4 +154,49 @@ ChromeAndroid.prototype.onChromedriverExit = function () {
}.bind(this));
};
// since we're in chrome, our default context is not the native mode, but web
ChromeAndroid.prototype.defaultContext = function () {
return WEBVIEW_BASE + "1";
};
// write a new getContexts function that hard-codes the two available contexts
ChromeAndroid.prototype.getContexts = function (cb) {
this.contexts = [NATIVE_WIN, this.defaultContext()];
logger.info("Available contexts: " + this.contexts);
cb(null, {
status: status.codes.Success.code
, value: this.contexts
});
};
// write a new setContext function that handles starting and stopping of
// chrome mode; the default android context controller method won't work here
// because here we don't need to worry about starting/stopping chromedriver
// itself; it's already on
ChromeAndroid.prototype.setContext = function (name, cb) {
if (name === null) {
name = this.defaultContext();
} else if (name === WEBVIEW_WIN) {
name = this.defaultContext();
}
this.getContexts(function () {
if (!_.contains(this.contexts, name)) {
return cb(null, {
status: status.codes.NoSuchContext.code
, value: "Context '" + name + "' does not exist"
});
}
if (name === this.curContext) {
return jwpSuccess(cb);
}
if (name.indexOf(WEBVIEW_WIN) !== -1) {
this.setChromedriverMode();
} else {
this.setNativeMode();
}
this.curContext = name;
jwpSuccess(cb);
}.bind(this));
};
module.exports = ChromeAndroid;
+1 -1
View File
@@ -181,7 +181,7 @@ Chromedriver.prototype.proxyNewSession = function (data, cb) {
this.chromeSessionId = null;
try {
if (body.status === 0 && body.sessionId) {
logger.debug("Successfully started chrome session");
logger.debug("Successfully started chrome session " + body.sessionId);
this.chromeSessionId = body.sessionId;
}
} catch (ignore) {}
@@ -0,0 +1,27 @@
"use strict";
var desired = require('./desired')
, webviewHelper = require("../../../helpers/webview")
, loadWebView = webviewHelper.loadWebView
, setup = require("../../common/setup-base");
describe("chrome @android-arm-only", function () {
describe('contexts', function () {
var driver;
setup(this, desired, {'no-reset': true}).then(function (d) { driver = d; });
before(function (done) {
loadWebView(desired, driver).nodeify(done);
});
it('should be able to switch contexts', function (done) {
driver
.title().should.eventually.include("I am a page title")
.contexts().then(function (ctxs) {
ctxs.should.contain("NATIVE_APP");
return driver.context("NATIVE_APP");
})
.source().should.eventually.include("android.widget.Button")
.nodeify(done);
});
});
});