Skip to content

Commit

Permalink
Handle Chromedriver arguments (#298)
Browse files Browse the repository at this point in the history
* Handle Chromedriver arguments

* Address review comments

* Log the merging

* Do not prefer unprefixed chromeOptions

* Use endsWith for prefixed chromeOptions
  • Loading branch information
imurchie committed Dec 27, 2017
1 parent ddd636a commit 9bb02d7
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 48 deletions.
14 changes: 13 additions & 1 deletion lib/commands/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,19 @@ async function setupNewChromedriver (opts, curDeviceId, adb) {
verbose: !!opts.showChromedriverLog,
};
let chromedriver = new Chromedriver(chromeArgs);
let appPackage = (opts.chromeOptions && opts.chromeOptions.androidPackage) || opts.appPackage;

// make sure there are chromeOptions
opts.chromeOptions = opts.chromeOptions || {};
// try out any prefixed chromeOptions,
// and strip the prefix
for (let opt of _.keys(opts)) {
if (opt.endsWith(':chromeOptions')) {
logger.warn(`Merging '${opt}' into 'chromeOptions'. This may cause unexpected behavior`);
_.merge(opts.chromeOptions, opts[opt]);
}
}

let appPackage = opts.chromeOptions.androidPackage || opts.appPackage;
let caps = {
chromeOptions: {
androidPackage: appPackage,
Expand Down
5 changes: 5 additions & 0 deletions lib/webview-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ helpers.getWebviews = async function (adb, deviceSocket) {
helpers.decorateChromeOptions = function (caps, opts, deviceId) {
// add options from appium session caps
if (opts.chromeOptions) {
if (opts.chromeOptions.Arguments) {
// merge `Arguments` and `args`
opts.chromeOptions.args = [...(opts.chromeOptions.args || []), ...opts.chromeOptions.Arguments];
delete opts.chromeOptions.Arguments;
}
for (let [opt, val] of _.pairs(opts.chromeOptions)) {
if (_.isUndefined(caps.chromeOptions[opt])) {
caps.chromeOptions[opt] = val;
Expand Down
122 changes: 75 additions & 47 deletions test/unit/commands/context-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let expect = chai.expect;
chai.should();
chai.use(chaiAsPromised);

describe('Context', () => {
describe('Context', function () {
beforeEach(() => {
sandbox.stub(PortFinder, 'getPort', function (cb) { // eslint-disable-line promise/prefer-await-to-callbacks
return cb(null, 4444); // eslint-disable-line promise/prefer-await-to-callbacks
Expand All @@ -36,109 +36,109 @@ describe('Context', () => {
stubbedChromedriver.stop = sandbox.stub().throws();
stubbedChromedriver.removeAllListeners = sandbox.stub();
});
afterEach(() => {
afterEach(function () {
sandbox.restore();
});
describe('getCurrentContext', () => {
it('should return current context', async () => {
describe('getCurrentContext', function () {
it('should return current context', async function () {
driver.curContext = 'current_context';
await driver.getCurrentContext().should.become('current_context');
});
});
describe('getContexts', () => {
it('should get Chromium context where appropriate', async () => {
describe('getContexts', function () {
it('should get Chromium context where appropriate', async function () {
driver = new AndroidDriver({browserName: 'Chrome'});
expect(await driver.getContexts()).to.include(CHROMIUM_WIN);
});
it('should use ADB to figure out which webviews are available', async () => {
it('should use ADB to figure out which webviews are available', async function () {
sandbox.stub(webviewHelpers, 'getWebviews');
expect(await driver.getContexts()).to.not.include(CHROMIUM_WIN);
webviewHelpers.getWebviews.calledOnce.should.be.true;
});
});
describe('setContext', () => {
describe('setContext', function () {
beforeEach(() => {
sandbox.stub(driver, 'getContexts').returns(['DEFAULT', 'WV', 'ANOTHER']);
sandbox.stub(driver, 'switchContext');
});
it('should switch to default context if name is null', async () => {
it('should switch to default context if name is null', async function () {
sandbox.stub(driver, 'defaultContextName').returns('DEFAULT');
await driver.setContext(null);
driver.switchContext.calledWithExactly('DEFAULT').should.be.true;
driver.curContext.should.be.equal('DEFAULT');
});
it('should switch to default web view if name is WEBVIEW', async () => {
it('should switch to default web view if name is WEBVIEW', async function () {
sandbox.stub(driver, 'defaultWebviewName').returns('WV');
await driver.setContext(WEBVIEW_WIN);
driver.switchContext.calledWithExactly('WV').should.be.true;
driver.curContext.should.be.equal('WV');
});
it('should throw error if context does not exist', async () => {
it('should throw error if context does not exist', async function () {
await driver.setContext('fake')
.should.be.rejectedWith(errors.NoSuchContextError);
});
it('should not switch to context if already in it', async () => {
it('should not switch to context if already in it', async function () {
driver.curContext = 'ANOTHER';
await driver.setContext('ANOTHER');
driver.switchContext.notCalled.should.be.true;
});
});
describe('switchContext', () => {
describe('switchContext', function () {
beforeEach(() => {
sandbox.stub(driver, 'stopChromedriverProxies');
sandbox.stub(driver, 'startChromedriverProxy');
sandbox.stub(driver, 'suspendChromedriverProxy');
sandbox.stub(driver, 'isChromedriverContext');
driver.curContext = 'current_cntx';
});
it('should start chrome driver proxy if requested context is webview', async () => {
it('should start chrome driver proxy if requested context is webview', async function () {
driver.isChromedriverContext.returns(true);
await driver.switchContext('context');
driver.startChromedriverProxy.calledWithExactly('context').should.be.true;
});
it('should stop chromedriver proxy if current context is webview and requested context is not', async () => {
it('should stop chromedriver proxy if current context is webview and requested context is not', async function () {
driver.opts = {recreateChromeDriverSessions: true};
driver.isChromedriverContext.withArgs('requested_cntx').returns(false);
driver.isChromedriverContext.withArgs('current_cntx').returns(true);
await driver.switchContext('requested_cntx');
driver.stopChromedriverProxies.calledOnce.should.be.true;
});
it('should suspend chrome driver proxy if current context is webview and requested context is not', async () => {
it('should suspend chrome driver proxy if current context is webview and requested context is not', async function () {
driver.opts = {recreateChromeDriverSessions: false};
driver.isChromedriverContext.withArgs('requested_cntx').returns(false);
driver.isChromedriverContext.withArgs('current_cntx').returns(true);
await driver.switchContext('requested_cntx');
driver.suspendChromedriverProxy.calledOnce.should.be.true;
});
it('should throw error if requested and current context are not webview', async () => {
it('should throw error if requested and current context are not webview', async function () {
driver.isChromedriverContext.withArgs('requested_cntx').returns(false);
driver.isChromedriverContext.withArgs('current_cntx').returns(false);
await driver.switchContext('requested_cntx')
.should.be.rejectedWith(/switching to context/);
});
});
describe('defaultContextName', () => {
it('should return NATIVE_WIN', async () => {
describe('defaultContextName', function () {
it('should return NATIVE_WIN', async function () {
await driver.defaultContextName().should.be.equal(NATIVE_WIN);
});
});
describe('defaultWebviewName', () => {
it('should return WEBVIEW with package', async () => {
describe('defaultWebviewName', function () {
it('should return WEBVIEW with package', async function () {
driver.opts = {appPackage: 'pkg'};
await driver.defaultWebviewName().should.be.equal(WEBVIEW_BASE + 'pkg');
});
});
describe('isWebContext', () => {
it('should return true if current context is not native', async () => {
describe('isWebContext', function () {
it('should return true if current context is not native', async function () {
driver.curContext = 'current_context';
await driver.isWebContext().should.be.true;
});
});
describe('startChromedriverProxy', () => {
describe('startChromedriverProxy', function () {
beforeEach(() => {
sandbox.stub(driver, 'onChromedriverStop');
});
it('should start new chromedriver session', async () => {
it('should start new chromedriver session', async function () {
await driver.startChromedriverProxy('WEBVIEW_1');
driver.sessionChromedrivers.WEBVIEW_1.should.be.equal(driver.chromedriver);
driver.chromedriver.start.getCall(0).args[0]
Expand All @@ -148,72 +148,72 @@ describe('Context', () => {
driver.proxyReqRes.should.be.equal('proxy');
driver.jwpProxyActive.should.be.true;
});
it('should be able to extract package from context name', async () => {
it('should be able to extract package from context name', async function () {
driver.opts.appPackage = 'pkg';
driver.opts.extractChromeAndroidPackageFromContextName = true;
await driver.startChromedriverProxy('WEBVIEW_com.pkg');
driver.chromedriver.start.getCall(0).args[0]
.chromeOptions.should.be.deep.include({androidPackage: 'com.pkg'});
});
it('should use package from opts if package extracted from context is empty', async () => {
it('should use package from opts if package extracted from context is empty', async function () {
driver.opts.appPackage = 'pkg';
driver.opts.extractChromeAndroidPackageFromContextName = true;
await driver.startChromedriverProxy('WEBVIEW_');
driver.chromedriver.start.getCall(0).args[0]
.chromeOptions.should.be.deep.include({androidPackage: 'pkg'});
});
it('should handle chromedriver event with STATE_STOPPED state', async () => {
it('should handle chromedriver event with STATE_STOPPED state', async function () {
await driver.startChromedriverProxy('WEBVIEW_1');
await driver.chromedriver.emit(Chromedriver.EVENT_CHANGED,
{state: Chromedriver.STATE_STOPPED});
driver.onChromedriverStop.calledWithExactly('WEBVIEW_1').should.be.true;
});
it('should ignore events if status is not STATE_STOPPED', async () => {
it('should ignore events if status is not STATE_STOPPED', async function () {
await driver.startChromedriverProxy('WEBVIEW_1');
await driver.chromedriver.emit(Chromedriver.EVENT_CHANGED,
{state: 'unhandled_state'});
driver.onChromedriverStop.notCalled.should.be.true;
});
it('should reconnect if session already exists', async () => {
it('should reconnect if session already exists', async function () {
stubbedChromedriver.hasWorkingWebview = sinon.stub().returns(true);
driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver};
await driver.startChromedriverProxy('WEBVIEW_1');
driver.chromedriver.restart.notCalled.should.be.true;
driver.chromedriver.should.be.equal(stubbedChromedriver);
});
it('should restart if chromedriver has not working web view', async () => {
it('should restart if chromedriver has not working web view', async function () {
stubbedChromedriver.hasWorkingWebview = sinon.stub().returns(false);
driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver};
await driver.startChromedriverProxy('WEBVIEW_1');
driver.chromedriver.restart.calledOnce.should.be.true;
});
});
describe('suspendChromedriverProxy', () => {
it('should suspend chrome driver proxy', async () => {
describe('suspendChromedriverProxy', function () {
it('should suspend chrome driver proxy', async function () {
await driver.suspendChromedriverProxy();
(driver.chromedriver == null).should.be.true;
(driver.proxyReqRes == null).should.be.true;
driver.jwpProxyActive.should.be.false;
});
});
describe('onChromedriverStop', () => {
it('should call startUnexpectedShutdown if chromedriver in active context', async () => {
describe('onChromedriverStop', function () {
it('should call startUnexpectedShutdown if chromedriver in active context', async function () {
sinon.stub(driver, 'startUnexpectedShutdown');
driver.curContext = 'WEBVIEW_1';
await driver.onChromedriverStop('WEBVIEW_1');
let arg0 = driver.startUnexpectedShutdown.getCall(0).args[0];
arg0.should.be.an('error');
arg0.message.should.include('Chromedriver quit unexpectedly during session');
});
it('should delete session if chromedriver in non-active context', async () => {
it('should delete session if chromedriver in non-active context', async function () {
driver.curContext = 'WEBVIEW_1';
driver.sessionChromedrivers = {WEBVIEW_2: 'CHROMIUM'};
await driver.onChromedriverStop('WEBVIEW_2');
driver.sessionChromedrivers.should.be.empty;
});
});
describe('stopChromedriverProxies', () => {
it('should stop all chromedriver', async () => {
describe('stopChromedriverProxies', function () {
it('should stop all chromedriver', async function () {
driver.sessionChromedrivers = {WEBVIEW_1: stubbedChromedriver, WEBVIEW_2: stubbedChromedriver};
sandbox.stub(driver, 'suspendChromedriverProxy');
await driver.stopChromedriverProxies();
Expand All @@ -225,40 +225,68 @@ describe('Context', () => {
driver.sessionChromedrivers.should.be.empty;
});
});
describe('isChromedriverContext', () => {
it('should return true if context is webview or chromium', async () => {
describe('isChromedriverContext', function () {
it('should return true if context is webview or chromium', async function () {
await driver.isChromedriverContext(WEBVIEW_WIN + '_1').should.be.true;
await driver.isChromedriverContext(CHROMIUM_WIN).should.be.true;
});
});
describe('setupNewChromedriver', () => {
it('should be able to set app package from chrome options', async () => {
describe('setupNewChromedriver', function () {
it('should be able to set app package from chrome options', async function () {
let chromedriver = await setupNewChromedriver({chromeOptions: {androidPackage: 'apkg'}});
chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage
.should.be.equal('apkg');
});
it('should be able to set androidActivity chrome option', async () => {
it('should use prefixed chromeOptions', async function () {
let chromedriver = await setupNewChromedriver({
'goog:chromeOptions': {
androidPackage: 'apkg',
},
});
chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage
.should.be.equal('apkg');
});
it('should merge chromeOptions', async function () {
let chromedriver = await setupNewChromedriver({
chromeOptions: {
androidPackage: 'apkg',
},
'goog:chromeOptions': {
androidWaitPackage: 'bpkg',
},
'appium:chromeOptions': {
androidActivity: 'aact',
},
});
chromedriver.start.getCall(0).args[0].chromeOptions.androidPackage
.should.be.equal('apkg');
chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity
.should.be.equal('aact');
chromedriver.start.getCall(0).args[0].chromeOptions.androidWaitPackage
.should.be.equal('bpkg');
});
it('should be able to set androidActivity chrome option', async function () {
let chromedriver = await setupNewChromedriver({chromeAndroidActivity: 'act'});
chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity
.should.be.equal('act');
});
it('should be able to set androidProcess chrome option', async () => {
it('should be able to set androidProcess chrome option', async function () {
let chromedriver = await setupNewChromedriver({chromeAndroidProcess: 'proc'});
chromedriver.start.getCall(0).args[0].chromeOptions.androidProcess
.should.be.equal('proc');
});
it('should be able to set loggingPrefs capability', async () => {
it('should be able to set loggingPrefs capability', async function () {
let chromedriver = await setupNewChromedriver({enablePerformanceLogging: true});
chromedriver.start.getCall(0).args[0].loggingPrefs
.should.deep.equal({performance: 'ALL'});
});
it('should set androidActivity to appActivity if browser name is chromium-webview', async () => {
it('should set androidActivity to appActivity if browser name is chromium-webview', async function () {
let chromedriver = await setupNewChromedriver({browserName: 'chromium-webview',
appActivity: 'app_act'});
chromedriver.start.getCall(0).args[0].chromeOptions.androidActivity
.should.be.equal('app_act');
});
it('should be able to set loggingPrefs capability', async () => {
it('should be able to set loggingPrefs capability', async function () {
let chromedriver = await setupNewChromedriver({pageLoadStrategy: "strategy"});
chromedriver.start.getCall(0).args[0].pageLoadStrategy
.should.be.equal("strategy");
Expand Down

0 comments on commit 9bb02d7

Please sign in to comment.