Files
appium/test/basedriver/driver-e2e-tests.js
T

490 lines
17 KiB
JavaScript

import _ from 'lodash';
import { server, routeConfiguringFunction, DeviceSettings, errors } from '../..';
import { W3C_ELEMENT_KEY, MJSONWP_ELEMENT_KEY } from '../../lib/protocol/protocol';
import axios from 'axios';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import B from 'bluebird';
const should = chai.should();
const DEFAULT_ARGS = {
address: 'localhost',
port: 8181
};
chai.use(chaiAsPromised);
function baseDriverE2ETests (DriverClass, defaultCaps = {}) {
describe('BaseDriver (e2e)', function () {
let baseServer, d = new DriverClass(DEFAULT_ARGS);
before(async function () {
baseServer = await server({
routeConfiguringFunction: routeConfiguringFunction(d),
port: DEFAULT_ARGS.port,
});
});
after(async function () {
await baseServer.close();
});
async function startSession (caps) {
return (await axios({
url: 'http://localhost:8181/wd/hub/session',
method: 'POST',
data: {desiredCapabilities: caps, requiredCapabilities: {}},
})).data;
}
async function endSession (id) {
return (await axios({
url: `http://localhost:8181/wd/hub/session/${id}`,
method: 'DELETE',
validateStatus: null,
})).data;
}
async function getSession (id) {
return (await axios({
url: `http://localhost:8181/wd/hub/session/${id}`,
})).data;
}
describe('session handling', function () {
it('should handle idempotency while creating sessions', async function () {
const sessionIds = [];
let times = 0;
do {
const {sessionId} = (await axios({
url: 'http://localhost:8181/wd/hub/session',
headers: {
'X-Idempotency-Key': '123456',
},
method: 'POST',
data: {desiredCapabilities: defaultCaps, requiredCapabilities: {}},
simple: false,
resolveWithFullResponse: true
})).data;
sessionIds.push(sessionId);
times++;
} while (times < 2);
_.uniq(sessionIds).length.should.equal(1);
const {status, data} = await axios({
url: `http://localhost:8181/wd/hub/session/${sessionIds[0]}`,
method: 'DELETE',
});
status.should.equal(200);
data.status.should.equal(0);
});
it('should handle idempotency while creating parallel sessions', async function () {
const reqs = [];
let times = 0;
do {
reqs.push(axios({
url: 'http://localhost:8181/wd/hub/session',
headers: {
'X-Idempotency-Key': '12345',
},
method: 'POST',
data: {desiredCapabilities: defaultCaps, requiredCapabilities: {}},
}));
times++;
} while (times < 2);
const sessionIds = (await B.all(reqs)).map((x) => x.data.sessionId);
_.uniq(sessionIds).length.should.equal(1);
const {status, data} = await axios({
url: `http://localhost:8181/wd/hub/session/${sessionIds[0]}`,
method: 'DELETE',
});
status.should.equal(200);
data.status.should.equal(0);
});
it('should create session and retrieve a session id, then delete it', async function () {
let {status, data} = await axios({
url: 'http://localhost:8181/wd/hub/session',
method: 'POST',
data: {desiredCapabilities: defaultCaps, requiredCapabilities: {}},
});
status.should.equal(200);
data.status.should.equal(0);
should.exist(data.sessionId);
data.value.should.eql(defaultCaps);
({status, data} = await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}`,
method: 'DELETE',
}));
status.should.equal(200);
data.status.should.equal(0);
should.equal(d.sessionId, null);
});
});
it.skip('should throw NYI for commands not implemented', async function () {
});
describe('command timeouts', function () {
let originalFindElement, originalFindElements;
async function startTimeoutSession (timeout) {
let caps = _.clone(defaultCaps);
caps.newCommandTimeout = timeout;
return await startSession(caps);
}
before(function () {
originalFindElement = d.findElement;
d.findElement = function () {
return 'foo';
}.bind(d);
originalFindElements = d.findElements;
d.findElements = async function () {
await B.delay(200);
return ['foo'];
}.bind(d);
});
after(function () {
d.findElement = originalFindElement;
d.findElements = originalFindElements;
});
it('should set a default commandTimeout', async function () {
let newSession = await startTimeoutSession();
d.newCommandTimeoutMs.should.be.above(0);
await endSession(newSession.sessionId);
});
it('should timeout on commands using commandTimeout cap', async function () {
let newSession = await startTimeoutSession(0.25);
await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
const {data} = await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}`,
validateStatus: null,
});
data.status.should.equal(6);
should.equal(d.sessionId, null);
const {status} = await endSession(newSession.sessionId);
status.should.equal(6);
});
it('should not timeout with commandTimeout of false', async function () {
let newSession = await startTimeoutSession(0.1);
let start = Date.now();
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}/elements`,
method: 'POST',
data: {using: 'name', value: 'foo'},
})).data;
(Date.now() - start).should.be.above(150);
value.should.eql(['foo']);
await endSession(newSession.sessionId);
});
it('should not timeout with commandTimeout of 0', async function () {
d.newCommandTimeoutMs = 2;
let newSession = await startTimeoutSession(0);
await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
let {status} = (await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}`,
})).data;
status.should.equal(0);
({status} = await endSession(newSession.sessionId));
status.should.equal(0);
d.newCommandTimeoutMs = 60 * 1000;
});
it('should not timeout if its just the command taking awhile', async function () {
let newSession = await startTimeoutSession(0.25);
await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}/element`,
method: 'POST',
data: {using: 'name', value: 'foo'},
});
await B.delay(400);
let {status} = (await axios({
url: `http://localhost:8181/wd/hub/session/${d.sessionId}`,
validateStatus: null,
})).data;
status.should.equal(6);
should.equal(d.sessionId, null);
({status} = await endSession(newSession.sessionId));
status.should.equal(6);
});
it('should not have a timer running before or after a session', async function () {
should.not.exist(d.noCommandTimer);
let newSession = await startTimeoutSession(0.25);
newSession.sessionId.should.equal(d.sessionId);
should.exist(d.noCommandTimer);
await endSession(newSession.sessionId);
should.not.exist(d.noCommandTimer);
});
});
describe('settings api', function () {
before(function () {
d.settings = new DeviceSettings({ignoreUnimportantViews: false});
});
it('should be able to get settings object', function () {
d.settings.getSettings().ignoreUnimportantViews.should.be.false;
});
it('should throw error when updateSettings method is not defined', async function () {
await d.settings.update({ignoreUnimportantViews: true}).should.eventually
.be.rejectedWith('onSettingsUpdate');
});
it('should throw error for invalid update object', async function () {
await d.settings.update('invalid json').should.eventually
.be.rejectedWith('JSON');
});
});
describe('unexpected exits', function () {
it('should reject a current command when the driver crashes', async function () {
d._oldGetStatus = d.getStatus;
try {
d.getStatus = async function () {
await B.delay(5000);
}.bind(d);
const reqPromise = axios({
url: 'http://localhost:8181/wd/hub/status',
validateStatus: null,
});
// make sure that the request gets to the server before our shutdown
await B.delay(100);
const shutdownEventPromise = new B((resolve, reject) => {
setTimeout(() => reject(new Error('onUnexpectedShutdown event is expected to be fired within 5 seconds timeout')), 5000);
d.onUnexpectedShutdown(resolve);
});
d.startUnexpectedShutdown(new Error('Crashytimes'));
const {status, value} = (await reqPromise).data;
status.should.equal(13);
value.message.should.contain('Crashytimes');
await shutdownEventPromise;
} finally {
d.getStatus = d._oldGetStatus;
}
});
});
describe('event timings', function () {
it('should not add timings if not using opt-in cap', async function () {
let session = await startSession(defaultCaps);
let res = await getSession(session.sessionId);
should.not.exist(res.events);
await endSession(session.sessionId);
});
it('should add start session timings', async function () {
let caps = Object.assign({}, defaultCaps, {eventTimings: true});
let session = await startSession(caps);
let res = (await getSession(session.sessionId)).value;
should.exist(res.events);
should.exist(res.events.newSessionRequested);
should.exist(res.events.newSessionStarted);
res.events.newSessionRequested[0].should.be.a('number');
res.events.newSessionStarted[0].should.be.a('number');
await endSession(session.sessionId);
});
});
describe('execute driver script', function () {
// mock some methods on BaseDriver that aren't normally there except in
// a fully blown driver
let originalFindElement, sessionId;
before(function () {
d.allowInsecure = ['execute_driver_script'];
originalFindElement = d.findElement;
d.findElement = (function (strategy, selector) {
if (strategy === 'accessibility id' && selector === 'amazing') {
return {[W3C_ELEMENT_KEY]: 'element-id-1'};
}
throw new errors.NoSuchElementError('not found');
}).bind(d);
});
beforeEach(async function () {
({sessionId} = await startSession(defaultCaps));
});
after(function () {
d.findElement = originalFindElement;
});
afterEach(async function () {
await endSession(sessionId);
});
it('should not work unless the allowInsecure feature flag is set', async function () {
d._allowInsecure = d.allowInsecure;
try {
d.allowInsecure = [];
const script = `return 'foo'`;
await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'wd'},
}).should.eventually.be.rejected;
await endSession(sessionId);
} finally {
d.allowInsecure = d._allowInsecure;
}
});
it('should execute a webdriverio script in the context of session', async function () {
const script = `
const timeouts = await driver.getTimeouts();
const status = await driver.status();
return [timeouts, status];
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'webdriverio'},
})).data;
const expectedTimeouts = {command: 250, implicit: 0};
const expectedStatus = {};
value.result.should.eql([expectedTimeouts, expectedStatus]);
});
it('should fail with any script type other than webdriverio currently', async function () {
const script = `return 'foo'`;
await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script, type: 'wd'},
}).should.eventually.be.rejected;
});
it('should execute a webdriverio script that returns elements correctly', async function () {
const script = `
return await driver.$("~amazing");
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
value.result.should.eql({
[W3C_ELEMENT_KEY]: 'element-id-1',
[MJSONWP_ELEMENT_KEY]: 'element-id-1'
});
});
it('should execute a webdriverio script that returns elements in deep structure', async function () {
const script = `
const el = await driver.$("~amazing");
return {element: el, elements: [el, el]};
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
const elObj = {
[W3C_ELEMENT_KEY]: 'element-id-1',
[MJSONWP_ELEMENT_KEY]: 'element-id-1'
};
value.result.should.eql({element: elObj, elements: [elObj, elObj]});
});
it('should store and return logs to the user', async function () {
const script = `
console.log("foo");
console.log("foo2");
console.warn("bar");
console.error("baz");
return null;
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
value.logs.should.eql({log: ['foo', 'foo2'], warn: ['bar'], error: ['baz']});
});
it('should have appium specific commands available', async function () {
const script = `
return typeof driver.lock;
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
data: {script},
})).data;
value.result.should.eql('function');
});
it('should correctly handle errors that happen in a webdriverio script', async function () {
const script = `
return await driver.$("~notfound");
`;
const {data} = await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script},
});
data.should.eql({
sessionId,
status: 13,
value: {message: 'An unknown server-side error occurred while processing the command. Original error: Could not execute driver script. Original error was: Error: not found'}
});
});
it('should correctly handle errors that happen when a script cannot be compiled', async function () {
const script = `
return {;
`;
const {data} = await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script},
});
sessionId.should.eql(data.sessionId);
data.status.should.eql(13);
data.value.should.have.property('message');
data.value.message.should.match(/An unknown server-side error occurred while processing the command. Original error: Could not execute driver script. Original error was: Error: Unexpected token '?;'?/);
});
it('should be able to set a timeout on a driver script', async function () {
const script = `
await Promise.delay(1000);
return true;
`;
const {value} = (await axios({
url: `http://localhost:8181/wd/hub/session/${sessionId}/appium/execute_driver`,
method: 'POST',
validateStatus: null,
data: {script, timeout: 50},
})).data;
value.message.should.match(/.+50.+timeout.+/);
});
});
});
}
export default baseDriverE2ETests;