webdriverio-automation
Version:
WebdriverIO-Automation android ios project
538 lines (488 loc) • 19.3 kB
JavaScript
import _ from 'lodash';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import B from 'bluebird';
import { DeviceSettings } from '../..';
import sinon from 'sinon';
const should = chai.should();
chai.use(chaiAsPromised);
// wrap these tests in a function so we can export the tests and re-use them
// for actual driver implementations
function baseDriverUnitTests (DriverClass, defaultCaps = {}) {
const w3cCaps = {
alwaysMatch: Object.assign({}, defaultCaps, {
platformName: 'Fake',
deviceName: 'Commodore 64',
}),
firstMatch: [{}],
};
describe('BaseDriver', function () {
let d;
beforeEach(function () {
d = new DriverClass();
});
afterEach(async function () {
await d.deleteSession();
});
it('should return an empty status object', async function () {
let status = await d.getStatus();
status.should.eql({});
});
it('should return a sessionId from createSession', async function () {
let [sessId] = await d.createSession(defaultCaps);
should.exist(sessId);
sessId.should.be.a('string');
sessId.length.should.be.above(5);
});
it('should not be able to start two sessions without closing the first', async function () {
await d.createSession(defaultCaps);
await d.createSession(defaultCaps).should.eventually.be.rejectedWith('session');
});
it('should be able to delete a session', async function () {
let sessionId1 = await d.createSession(defaultCaps);
await d.deleteSession();
should.equal(d.sessionId, null);
let sessionId2 = await d.createSession(defaultCaps);
sessionId1.should.not.eql(sessionId2);
});
it('should get the current session', async function () {
let [, caps] = await d.createSession(defaultCaps);
caps.should.equal(await d.getSession());
});
it('should return sessions if no session exists', async function () {
let sessions = await d.getSessions();
sessions.length.should.equal(0);
});
it('should return sessions', async function () {
let caps = _.clone(defaultCaps);
caps.a = 'cap';
await d.createSession(caps);
let sessions = await d.getSessions();
sessions.length.should.equal(1);
sessions[0].should.eql({
id: d.sessionId,
capabilities: caps
});
});
it('should fulfill an unexpected driver quit promise', async function () {
// make a command that will wait a bit so we can crash while it's running
d.getStatus = async function () {
await B.delay(1000);
return 'good status';
}.bind(d);
let cmdPromise = d.executeCommand('getStatus');
await B.delay(10);
d.startUnexpectedShutdown(new Error('We crashed'));
await cmdPromise.should.be.rejectedWith(/We crashed/);
await d.onUnexpectedShutdown.should.be.rejectedWith(/We crashed/);
});
it('should not allow commands in middle of unexpected shutdown', async function () {
// make a command that will wait a bit so we can crash while it's running
d.oldDeleteSession = d.deleteSession;
d.deleteSession = async function () {
await B.delay(100);
await this.oldDeleteSession();
}.bind(d);
let caps = _.clone(defaultCaps);
await d.createSession(caps);
d.startUnexpectedShutdown(new Error('We crashed'));
await d.onUnexpectedShutdown.should.be.rejectedWith(/We crashed/);
await d.executeCommand('getSession').should.be.rejectedWith(/shut down/);
});
it('should allow new commands after done shutting down', async function () {
// make a command that will wait a bit so we can crash while it's running
d.oldDeleteSession = d.deleteSession;
d.deleteSession = async function () {
await B.delay(100);
await this.oldDeleteSession();
}.bind(d);
let caps = _.clone(defaultCaps);
await d.createSession(caps);
d.startUnexpectedShutdown(new Error('We crashed'));
await d.onUnexpectedShutdown.should.be.rejectedWith(/We crashed/);
await d.executeCommand('getSession').should.be.rejectedWith(/shut down/);
await B.delay(100);
await d.executeCommand('createSession', caps);
await d.deleteSession();
});
it('should distinguish between W3C and JSONWP session', async function () {
// Test JSONWP
await d.executeCommand('createSession', Object.assign({}, defaultCaps, {
platformName: 'Fake',
deviceName: 'Commodore 64',
}));
d.protocol.should.equal('MJSONWP');
await d.executeCommand('deleteSession');
// Test W3C (leave first 2 args null because those are the JSONWP args)
await d.executeCommand('createSession', null, null, {
alwaysMatch: Object.assign({}, defaultCaps, {
platformName: 'Fake',
deviceName: 'Commodore 64',
}),
firstMatch: [{}],
});
d.protocol.should.equal('W3C');
});
describe('protocol detection', function () {
it('should use MJSONWP if only JSONWP caps are provided', async function () {
await d.createSession(defaultCaps);
d.protocol.should.equal('MJSONWP');
});
it('should use W3C if only W3C caps are provided', async function () {
await d.createSession(null, null, {alwaysMatch: defaultCaps, firstMatch: [{}]});
d.protocol.should.equal('W3C');
});
});
it('should have a method to get driver for a session', async function () {
let [sessId] = await d.createSession(defaultCaps);
d.driverForSession(sessId).should.eql(d);
});
describe('command queue', function () {
let d = new DriverClass();
let waitMs = 10;
d.getStatus = async function () {
await B.delay(waitMs);
return Date.now();
}.bind(d);
d.getSessions = async function () {
await B.delay(waitMs);
throw new Error('multipass');
}.bind(d);
afterEach(function () {
d.clearNewCommandTimeout();
});
it('should queue commands and.executeCommand/respond in the order received', async function () {
let numCmds = 10;
let cmds = [];
for (let i = 0; i < numCmds; i++) {
cmds.push(d.executeCommand('getStatus'));
}
let results = await B.all(cmds);
for (let i = 1; i < numCmds; i++) {
if (results[i] <= results[i - 1]) {
throw new Error('Got result out of order');
}
}
});
it('should handle errors correctly when queuing', async function () {
let numCmds = 10;
let cmds = [];
for (let i = 0; i < numCmds; i++) {
if (i === 5) {
cmds.push(d.executeCommand('getSessions'));
} else {
cmds.push(d.executeCommand('getStatus'));
}
}
let results = await B.settle(cmds);
for (let i = 1; i < 5; i++) {
if (results[i].value() <= results[i - 1].value()) {
throw new Error('Got result out of order');
}
}
results[5].reason().message.should.contain('multipass');
for (let i = 7; i < numCmds; i++) {
if (results[i].value() <= results[i - 1].value()) {
throw new Error('Got result out of order');
}
}
});
it('should not care if queue empties for a bit', async function () {
let numCmds = 10;
let cmds = [];
for (let i = 0; i < numCmds; i++) {
cmds.push(d.executeCommand('getStatus'));
}
let results = await B.all(cmds);
cmds = [];
for (let i = 0; i < numCmds; i++) {
cmds.push(d.executeCommand('getStatus'));
}
results = await B.all(cmds);
for (let i = 1; i < numCmds; i++) {
if (results[i] <= results[i - 1]) {
throw new Error('Got result out of order');
}
}
});
});
describe('timeouts', function () {
before(async function () {
await d.createSession(defaultCaps);
});
describe('command', function () {
it('should exist by default', function () {
d.newCommandTimeoutMs.should.equal(60000);
});
it('should be settable through `timeouts`', async function () {
await d.timeouts('command', 20);
d.newCommandTimeoutMs.should.equal(20);
});
});
describe('implicit', function () {
it('should not exist by default', function () {
d.implicitWaitMs.should.equal(0);
});
it('should be settable through `timeouts`', async function () {
await d.timeouts('implicit', 20);
d.implicitWaitMs.should.equal(20);
});
});
});
describe('timeouts (W3C)', function () {
beforeEach(async function () {
await d.createSession(null, null, w3cCaps);
});
afterEach(async function () {
await d.deleteSession();
});
it('should get timeouts that we set', async function () {
await d.timeouts(undefined, undefined, undefined, undefined, 1000);
await d.getTimeouts().should.eventually.have.property('implicit', 1000);
await d.timeouts('command', 2000);
await d.getTimeouts().should.eventually.deep.equal({
implicit: 1000,
command: 2000,
});
await d.timeouts(undefined, undefined, undefined, undefined, 3000);
await d.getTimeouts().should.eventually.deep.equal({
implicit: 3000,
command: 2000,
});
});
});
describe('reset compatibility', function () {
it('should not allow both fullReset and noReset to be true', async function () {
let newCaps = Object.assign({}, defaultCaps, {
fullReset: true,
noReset: true
});
await d.createSession(newCaps).should.eventually.be.rejectedWith(
/noReset.+fullReset/);
});
});
describe('proxying', function () {
let sessId;
beforeEach(async function () {
[sessId] = await d.createSession(defaultCaps);
});
describe('#proxyActive', function () {
it('should exist', function () {
d.proxyActive.should.be.an.instanceof(Function);
});
it('should return false', function () {
d.proxyActive(sessId).should.be.false;
});
it('should throw an error when sessionId is wrong', function () {
(() => { d.proxyActive('aaa'); }).should.throw;
});
});
describe('#getProxyAvoidList', function () {
it('should exist', function () {
d.getProxyAvoidList.should.be.an.instanceof(Function);
});
it('should return an array', function () {
d.getProxyAvoidList(sessId).should.be.an.instanceof(Array);
});
it('should throw an error when sessionId is wrong', function () {
(() => { d.getProxyAvoidList('aaa'); }).should.throw;
});
});
describe('#canProxy', function () {
it('should have a #canProxy method', function () {
d.canProxy.should.be.an.instanceof(Function);
});
it('should return false from #canProxy', function () {
d.canProxy(sessId).should.be.false;
});
it('should throw an error when sessionId is wrong', function () {
(() => { d.canProxy(); }).should.throw;
});
});
describe('#proxyRouteIsAvoided', function () {
it('should validate form of avoidance list', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /\/foo/], ['GET']]);
(() => { d.proxyRouteIsAvoided(); }).should.throw;
avoidStub.returns([['POST', /\/foo/], ['GET', /^foo/, 'bar']]);
(() => { d.proxyRouteIsAvoided(); }).should.throw;
avoidStub.restore();
});
it('should reject bad http methods', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /^foo/], ['BAZETE', /^bar/]]);
(() => { d.proxyRouteIsAvoided(); }).should.throw;
avoidStub.restore();
});
it('should reject non-regex routes', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /^foo/], ['GET', '/bar']]);
(() => { d.proxyRouteIsAvoided(); }).should.throw;
avoidStub.restore();
});
it('should return true for routes in the avoid list', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /^\/foo/]]);
d.proxyRouteIsAvoided(null, 'POST', '/foo/bar').should.be.true;
avoidStub.restore();
});
it('should strip away any wd/hub prefix', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /^\/foo/]]);
d.proxyRouteIsAvoided(null, 'POST', '/wd/hub/foo/bar').should.be.true;
avoidStub.restore();
});
it('should return false for routes not in the avoid list', function () {
const avoidStub = sinon.stub(d, 'getProxyAvoidList');
avoidStub.returns([['POST', /^\/foo/]]);
d.proxyRouteIsAvoided(null, 'GET', '/foo/bar').should.be.false;
d.proxyRouteIsAvoided(null, 'POST', '/boo').should.be.false;
avoidStub.restore();
});
});
});
describe('event timing framework', function () {
let beforeStartTime;
beforeEach(async function () {
beforeStartTime = Date.now();
d.shouldValidateCaps = false;
await d.executeCommand('createSession', defaultCaps);
});
describe('#eventHistory', function () {
it('should have an eventHistory property', function () {
should.exist(d.eventHistory);
should.exist(d.eventHistory.commands);
});
it('should have a session start timing after session start', function () {
let {newSessionRequested, newSessionStarted} = d.eventHistory;
newSessionRequested.should.have.length(1);
newSessionStarted.should.have.length(1);
newSessionRequested[0].should.be.a('number');
newSessionStarted[0].should.be.a('number');
(newSessionRequested[0] >= beforeStartTime).should.be.true;
(newSessionStarted[0] >= newSessionRequested[0]).should.be.true;
});
it('should include a commands list', async function () {
await d.executeCommand('getStatus', []);
d.eventHistory.commands.length.should.equal(2);
d.eventHistory.commands[1].cmd.should.equal('getStatus');
d.eventHistory.commands[1].startTime.should.be.a('number');
d.eventHistory.commands[1].endTime.should.be.a('number');
});
});
describe('#logEvent', function () {
it('should allow logging arbitrary events', function () {
d.logEvent('foo');
d.eventHistory.foo[0].should.be.a('number');
(d.eventHistory.foo[0] >= beforeStartTime).should.be.true;
});
it('should not allow reserved or oddly formed event names', function () {
(() => {
d.logEvent('commands');
}).should.throw();
(() => {
d.logEvent(1);
}).should.throw();
(() => {
d.logEvent({});
}).should.throw();
});
});
it('should allow logging the same event multiple times', function () {
d.logEvent('bar');
d.logEvent('bar');
d.eventHistory.bar.should.have.length(2);
d.eventHistory.bar[1].should.be.a('number');
(d.eventHistory.bar[1] >= d.eventHistory.bar[0]).should.be.true;
});
describe('getSession decoration', function () {
it('should decorate getSession response if opt-in cap is provided', async function () {
let res = await d.getSession();
should.not.exist(res.events);
d.caps.eventTimings = true;
res = await d.getSession();
should.exist(res.events);
should.exist(res.events.newSessionRequested);
res.events.newSessionRequested[0].should.be.a('number');
});
});
});
describe('.reset', function () {
it('should reset as W3C if the original session was W3C', async function () {
const caps = {
alwaysMatch: Object.assign({}, {
app: 'Fake',
deviceName: 'Fake',
automationName: 'Fake',
platformName: 'Fake',
}, defaultCaps),
firstMatch: [{}],
};
await d.createSession(undefined, undefined, caps);
d.protocol.should.equal('W3C');
await d.reset();
d.protocol.should.equal('W3C');
});
it('should reset as MJSONWP if the original session was MJSONWP', async function () {
const caps = Object.assign({}, {
app: 'Fake',
deviceName: 'Fake',
automationName: 'Fake',
platformName: 'Fake',
}, defaultCaps);
await d.createSession(caps);
d.protocol.should.equal('MJSONWP');
await d.reset();
d.protocol.should.equal('MJSONWP');
});
});
});
describe('DeviceSettings', function () {
it('should not hold on to reference of defaults in constructor', function () {
let obj = {foo: 'bar'};
let d1 = new DeviceSettings(obj);
let d2 = new DeviceSettings(obj);
d1._settings.foo = 'baz';
d1._settings.should.not.eql(d2._settings);
});
});
describe('.isFeatureEnabled', function () {
const d = new DriverClass();
afterEach(function () {
d.denyInsecure = null;
d.allowInsecure = null;
d.relaxedSecurityEnabled = null;
});
it('should say a feature is enabled when it is explicitly allowed', function () {
d.allowInsecure = ['foo', 'bar'];
d.isFeatureEnabled('foo').should.be.true;
d.isFeatureEnabled('bar').should.be.true;
d.isFeatureEnabled('baz').should.be.false;
});
it('should say a feature is not enabled if it is not enabled', function () {
d.allowInsecure = [];
d.isFeatureEnabled('foo').should.be.false;
});
it('should prefer denyInsecure to allowInsecure', function () {
d.allowInsecure = ['foo', 'bar'];
d.denyInsecure = ['foo'];
d.isFeatureEnabled('foo').should.be.false;
d.isFeatureEnabled('bar').should.be.true;
d.isFeatureEnabled('baz').should.be.false;
});
it('should allow global setting for insecurity', function () {
d.relaxedSecurityEnabled = true;
d.isFeatureEnabled('foo').should.be.true;
d.isFeatureEnabled('bar').should.be.true;
d.isFeatureEnabled('baz').should.be.true;
});
it('global setting should be overrideable', function () {
d.relaxedSecurityEnabled = true;
d.denyInsecure = ['foo', 'bar'];
d.isFeatureEnabled('foo').should.be.false;
d.isFeatureEnabled('bar').should.be.false;
d.isFeatureEnabled('baz').should.be.true;
});
});
}
export default baseDriverUnitTests;