mirror of
https://github.com/appium/appium.git
synced 2026-10-25 02:00:57 +08:00
* chore(deps): bump http-status-codes from 1.4.0 to 2.1.1 Bumps [http-status-codes](https://github.com/prettymuchbryce/node-http-status) from 1.4.0 to 2.1.1. - [Release notes](https://github.com/prettymuchbryce/node-http-status/releases) - [Commits](https://github.com/prettymuchbryce/node-http-status/compare/v1.4.0...2.1.1) Signed-off-by: dependabot-preview[bot] <support@dependabot.com> * Update imports Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com> Co-authored-by: Mykola Mokhnach <mokhnach@gmail.com>
1087 lines
34 KiB
JavaScript
1087 lines
34 KiB
JavaScript
// transpile:mocha
|
|
|
|
import {
|
|
server, routeConfiguringFunction, errors, JWProxy, BaseDriver
|
|
} from '../..';
|
|
import { FakeDriver } from './fake-driver';
|
|
import _ from 'lodash';
|
|
import axios from 'axios';
|
|
import chai from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import sinon from 'sinon';
|
|
import { StatusCodes as HTTPStatusCodes } from 'http-status-codes';
|
|
import { createProxyServer } from './helpers';
|
|
import {
|
|
MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY
|
|
} from '../../lib/constants';
|
|
import qs from 'querystring';
|
|
|
|
|
|
let should = chai.should();
|
|
chai.use(chaiAsPromised);
|
|
|
|
const serverPort = 8181;
|
|
const baseUrl = `http://localhost:${serverPort}/wd/hub`;
|
|
|
|
describe('Protocol', function () {
|
|
|
|
//TODO: more tests!:
|
|
// Unknown commands should return 404
|
|
|
|
describe('direct to driver', function () {
|
|
let d = new FakeDriver();
|
|
it('should return response values directly from the driver', async function () {
|
|
(await d.setUrl('http://google.com')).should.contain('google');
|
|
});
|
|
});
|
|
|
|
describe('via express router', function () {
|
|
let mjsonwpServer;
|
|
let driver;
|
|
|
|
before(async function () {
|
|
driver = new FakeDriver();
|
|
driver.sessionId = 'foo';
|
|
mjsonwpServer = await server({
|
|
routeConfiguringFunction: routeConfiguringFunction(driver),
|
|
port: serverPort,
|
|
});
|
|
});
|
|
|
|
after(async function () {
|
|
await mjsonwpServer.close();
|
|
});
|
|
|
|
it('should proxy to driver and return valid jsonwp response', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'}
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'Navigated to: http://google.com',
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should assume requests without a Content-Type are json requests', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'Navigated to: http://google.com',
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should respond to x-www-form-urlencoded as well as json requests', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
method: 'POST',
|
|
data: qs.stringify({
|
|
url: 'http://google.com',
|
|
}),
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'Navigated to: http://google.com',
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should include url request parameters for methods to use - sessionid', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/back`,
|
|
method: 'POST',
|
|
data: {},
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'foo',
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should include url request parameters for methods to use - elementid', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/click`,
|
|
method: 'POST',
|
|
data: {},
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['bar', 'foo']);
|
|
});
|
|
|
|
it('should include url req params in the order: custom, element, session', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/attribute/baz`,
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['baz', 'bar', 'foo']);
|
|
});
|
|
|
|
it('should respond with 400 Bad Request if parameters missing', async function () {
|
|
const {data, status} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {},
|
|
validateStatus: null,
|
|
});
|
|
status.should.equal(400);
|
|
JSON.stringify(data).should.contain('url');
|
|
});
|
|
|
|
it('should reject requests with a badly formatted body and not crash', async function () {
|
|
await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: 'oh hello'
|
|
}).should.eventually.be.rejected;
|
|
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'}
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'Navigated to: http://google.com',
|
|
sessionId: 'foo'
|
|
});
|
|
|
|
});
|
|
|
|
it('should get 404 for bad routes', async function () {
|
|
await axios({
|
|
url: `${baseUrl}/blargimarg`,
|
|
}).should.eventually.be.rejectedWith(/404/);
|
|
});
|
|
|
|
// TODO pass this test
|
|
// https://github.com/appium/node-mobile-json-wire-protocol/issues/3
|
|
it('4xx responses should have content-type of text/plain', async function () {
|
|
const {headers} = await axios({
|
|
url: `${baseUrl}/blargimargarita`,
|
|
validateStatus: null,
|
|
});
|
|
|
|
headers['content-type'].should.include('text/plain');
|
|
});
|
|
|
|
it('should throw not yet implemented for unfilledout commands', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/location`,
|
|
validateStatus: null,
|
|
});
|
|
|
|
status.should.equal(501);
|
|
data.should.eql({
|
|
status: 405,
|
|
value: {
|
|
message: 'Method has not yet been implemented'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should throw not implemented for ignored commands', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/buttonup`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {},
|
|
});
|
|
|
|
status.should.equal(501);
|
|
data.should.eql({
|
|
status: 405,
|
|
value: {
|
|
message: 'Method has not yet been implemented'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should get 400 for bad parameters', async function () {
|
|
await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {}
|
|
}).should.eventually.be.rejectedWith(/400/);
|
|
});
|
|
|
|
it('should ignore special extra payload params in the right contexts', async function () {
|
|
await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {id: 'baz', sessionId: 'lol', value: ['a']}
|
|
});
|
|
|
|
await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {id: 'baz'}
|
|
}).should.eventually.be.rejectedWith(/400/);
|
|
|
|
// make sure adding the optional 'id' doesn't clobber a route where we
|
|
// have an actual required 'id'
|
|
await axios({
|
|
url: `${baseUrl}/session/foo/frame`,
|
|
method: 'POST',
|
|
data: {id: 'baz'}
|
|
});
|
|
});
|
|
|
|
it('should return the correct error even if driver does not throw', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/appium/receive_async_response`,
|
|
method: 'POST',
|
|
data: {response: 'baz'},
|
|
validateStatus: null,
|
|
});
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 13,
|
|
value: {
|
|
message: 'An unknown server-side error occurred while processing ' +
|
|
'the command. Original error: Mishandled Driver Error'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
describe('w3c sendkeys migration', function () {
|
|
it('should accept value for sendkeys', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {value: 'text to type'}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['text to type', 'bar']);
|
|
});
|
|
it('should accept text for sendkeys', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {text: 'text to type'}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['text to type', 'bar']);
|
|
});
|
|
it('should accept value and text for sendkeys, and use value', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {value: 'text to type', text: 'text to ignore'}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['text to type', 'bar']);
|
|
});
|
|
});
|
|
|
|
describe('multiple sets of arguments', function () {
|
|
describe('optional', function () {
|
|
it('should allow moveto with element', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/moveto`,
|
|
method: 'POST',
|
|
data: {element: '3'}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(['3', null, null]);
|
|
});
|
|
it('should allow moveto with xoffset/yoffset', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/moveto`,
|
|
method: 'POST',
|
|
data: {xoffset: 42, yoffset: 17}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql([null, 42, 17]);
|
|
});
|
|
});
|
|
describe('required', function () {
|
|
it('should allow removeApp with appId', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/appium/device/remove_app`,
|
|
method: 'POST',
|
|
data: {appId: 42}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(42);
|
|
});
|
|
it('should allow removeApp with bundleId', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/appium/device/remove_app`,
|
|
method: 'POST',
|
|
data: {bundleId: 42}
|
|
});
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(42);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('default param wrap', function () {
|
|
it('should wrap', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/touch/perform`,
|
|
method: 'POST',
|
|
data: [{'action': 'tap', 'options': {'element': '3'}}]
|
|
});
|
|
data.value.should.deep.equal([[{'action': 'tap', 'options': {'element': '3'}}], 'foo']);
|
|
});
|
|
|
|
it('should not wrap twice', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/touch/perform`,
|
|
method: 'POST',
|
|
data: {actions: [{'action': 'tap', 'options': {'element': '3'}}]}
|
|
});
|
|
data.value.should.deep.equal([[{'action': 'tap', 'options': {'element': '3'}}], 'foo']);
|
|
});
|
|
|
|
});
|
|
|
|
describe('create sessions via HTTP endpoint', function () {
|
|
let desiredCapabilities = {a: 'b'};
|
|
let requiredCapabilities = {c: 'd'};
|
|
let capabilities = {e: 'f'};
|
|
|
|
let sessionId;
|
|
|
|
beforeEach(function () {
|
|
sessionId = null;
|
|
});
|
|
afterEach(async function () {
|
|
if (sessionId) {
|
|
await axios.delete(`${baseUrl}/session/${sessionId}`);
|
|
}
|
|
});
|
|
|
|
it('should allow create session with desired caps (MJSONWP)', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {desiredCapabilities}
|
|
});
|
|
sessionId = data.sessionId;
|
|
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(desiredCapabilities);
|
|
});
|
|
it('should allow create session with desired and required caps', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
desiredCapabilities,
|
|
requiredCapabilities
|
|
}
|
|
});
|
|
sessionId = data.sessionId;
|
|
|
|
data.status.should.equal(0);
|
|
data.value.should.eql(_.extend({}, desiredCapabilities, requiredCapabilities));
|
|
});
|
|
it('should fail to create session without capabilities or desiredCapabilities', async function () {
|
|
await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {},
|
|
}).should.eventually.be.rejectedWith(/400/);
|
|
});
|
|
it('should allow create session with capabilities (W3C)', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
capabilities,
|
|
}
|
|
});
|
|
sessionId = data.sessionId;
|
|
|
|
should.not.exist(data.status);
|
|
should.not.exist(data.sessionId);
|
|
data.value.capabilities.should.eql(capabilities);
|
|
data.value.sessionId.should.exist;
|
|
});
|
|
it('should fall back to MJSONWP if driver does not support W3C yet', async function () {
|
|
const createSessionStub = sinon.stub(driver, 'createSession').callsFake(function (capabilities) {
|
|
driver.sessionId = null;
|
|
return BaseDriver.prototype.createSession.call(driver, capabilities);
|
|
});
|
|
try {
|
|
let caps = {
|
|
...desiredCapabilities,
|
|
platformName: 'Fake',
|
|
deviceName: 'Fake',
|
|
};
|
|
// let {status, value, sessionId} = await request({
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
desiredCapabilities: caps,
|
|
capabilities: {
|
|
alwaysMatch: caps,
|
|
firstMatch: [{}],
|
|
},
|
|
}
|
|
});
|
|
sessionId = data.sessionId;
|
|
|
|
should.exist(data.status);
|
|
should.exist(data.sessionId);
|
|
data.value.should.eql(caps);
|
|
} finally {
|
|
createSessionStub.restore();
|
|
}
|
|
});
|
|
|
|
describe('w3c endpoints', function () {
|
|
let w3cCaps = {
|
|
alwaysMatch: {
|
|
platformName: 'Fake',
|
|
deviceName: 'Commodore 64',
|
|
},
|
|
firstMatch: [{}],
|
|
};
|
|
let sessionUrl;
|
|
|
|
beforeEach(async function () {
|
|
// Start a W3C session
|
|
const {value} = (await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
capabilities: w3cCaps,
|
|
},
|
|
})).data;
|
|
sessionId = value.sessionId;
|
|
sessionUrl = `${baseUrl}/session/${sessionId}`;
|
|
});
|
|
|
|
it(`should throw 400 Bad Parameters exception if the parameters are bad`, async function () {
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
bad: 'params',
|
|
}
|
|
});
|
|
status.should.equal(400);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
message.should.match(/Parameters were incorrect/);
|
|
stacktrace.should.match(/protocol.js/);
|
|
w3cError.should.be.a.string;
|
|
w3cError.should.equal(errors.InvalidArgumentError.error());
|
|
});
|
|
|
|
it(`should throw 405 exception if the command hasn't been implemented yet`, async function () {
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [],
|
|
},
|
|
});
|
|
status.should.equal(405);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
message.should.match(/Method has not yet been implemented/);
|
|
stacktrace.should.match(/protocol.js/);
|
|
w3cError.should.be.a.string;
|
|
w3cError.should.equal(errors.NotYetImplementedError.error());
|
|
message.should.match(/Method has not yet been implemented/);
|
|
});
|
|
|
|
it(`should throw 500 Unknown Error if the command throws an unexpected exception`, async function () {
|
|
driver.performActions = () => { throw new Error(`Didn't work`); };
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [],
|
|
}
|
|
});
|
|
status.should.equal(500);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
stacktrace.should.match(/protocol.js/);
|
|
w3cError.should.be.a.string;
|
|
w3cError.should.equal(errors.UnknownError.error());
|
|
message.should.match(/Didn't work/);
|
|
|
|
delete driver.performActions;
|
|
});
|
|
|
|
it(`should translate element format from MJSONWP to W3C`, async function () {
|
|
const retValue = [
|
|
{
|
|
something: {
|
|
[MJSONWP_ELEMENT_KEY]: 'fooo',
|
|
other: 'bar'
|
|
}
|
|
}, {
|
|
[MJSONWP_ELEMENT_KEY]: 'bar'
|
|
},
|
|
'ignore',
|
|
];
|
|
|
|
const expectedValue = [
|
|
{
|
|
something: {
|
|
[MJSONWP_ELEMENT_KEY]: 'fooo',
|
|
[W3C_ELEMENT_KEY]: 'fooo',
|
|
other: 'bar'
|
|
}
|
|
}, {
|
|
[MJSONWP_ELEMENT_KEY]: 'bar',
|
|
[W3C_ELEMENT_KEY]: 'bar'
|
|
},
|
|
'ignore',
|
|
];
|
|
|
|
const findElementsBackup = driver.findElements;
|
|
driver.findElements = () => retValue;
|
|
const {data} = await axios.post(`${sessionUrl}/elements`, {
|
|
using: 'whatever',
|
|
value: 'whatever',
|
|
});
|
|
data.value.should.eql(expectedValue);
|
|
driver.findElements = findElementsBackup;
|
|
});
|
|
|
|
it(`should fail with a 408 error if it throws a TimeoutError exception`, async function () {
|
|
let setUrlStub = sinon.stub(driver, 'setUrl').callsFake(function () {
|
|
throw new errors.TimeoutError;
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
url: 'https://example.com/',
|
|
}
|
|
});
|
|
status.should.equal(408);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
stacktrace.should.match(/protocol.js/);
|
|
w3cError.should.be.a.string;
|
|
w3cError.should.equal(errors.TimeoutError.error());
|
|
message.should.match(/An operation did not complete before its timeout expired/);
|
|
|
|
setUrlStub.restore();
|
|
});
|
|
|
|
it(`should pass with 200 HTTP status code if the command returns a value`, async function () {
|
|
driver.performActions = (actions) => 'It works ' + actions.join('');
|
|
const {status, value, sessionId} = (await axios.post(`${sessionUrl}/actions`, {
|
|
actions: ['a', 'b', 'c'],
|
|
})).data;
|
|
should.not.exist(sessionId);
|
|
should.not.exist(status);
|
|
value.should.equal('It works abc');
|
|
delete driver.performActions;
|
|
});
|
|
|
|
describe('jwproxy', function () {
|
|
const port = 56562;
|
|
let server, jwproxy, app;
|
|
|
|
beforeEach(function () {
|
|
const res = createProxyServer(sessionId, port);
|
|
server = res.server;
|
|
app = res.app;
|
|
jwproxy = new JWProxy({host: 'localhost', port});
|
|
jwproxy.sessionId = sessionId;
|
|
driver.performActions = async (actions) => await jwproxy.command('/perform-actions', 'POST', actions);
|
|
});
|
|
|
|
afterEach(async function () {
|
|
delete driver.performActions;
|
|
await server.close();
|
|
});
|
|
|
|
it('should work if a proxied request returns a response with status 200', async function () {
|
|
app.post('/wd/hub/session/:sessionId/perform-actions', (req, res) => {
|
|
res.json({
|
|
sessionId: req.params.sessionId,
|
|
value: req.body,
|
|
status: 0,
|
|
});
|
|
});
|
|
|
|
const {status, value, sessionId} = (await axios.post(`${sessionUrl}/actions`, {
|
|
actions: [1, 2, 3],
|
|
})).data;
|
|
value.should.eql([1, 2, 3]);
|
|
should.not.exist(status);
|
|
should.not.exist(sessionId);
|
|
});
|
|
|
|
it('should return error if a proxied request returns a MJSONWP error response', async function () {
|
|
app.post('/wd/hub/session/:sessionId/perform-actions', (req, res) => {
|
|
res.status(500).json({
|
|
sessionId,
|
|
status: 6,
|
|
value: 'A problem occurred',
|
|
});
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [1, 2, 3],
|
|
}
|
|
});
|
|
status.should.equal(HTTPStatusCodes.NOT_FOUND);
|
|
JSON.stringify(data).should.match(/A problem occurred/);
|
|
});
|
|
|
|
it('should return W3C error if a proxied request returns a W3C error response', async function () {
|
|
const error = new Error(`Some error occurred`);
|
|
error.w3cStatus = 414;
|
|
const executeCommandStub = sinon.stub(driver, 'executeCommand').returns({
|
|
protocol: 'W3C',
|
|
error,
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {actions: [1, 2, 3]},
|
|
});
|
|
status.should.equal(414);
|
|
const {error: w3cError, message: errMessage, stacktrace} = data.value;
|
|
w3cError.should.equal('unknown error');
|
|
stacktrace.should.match(/Some error occurred/);
|
|
errMessage.should.equal('Some error occurred');
|
|
executeCommandStub.restore();
|
|
});
|
|
|
|
it('should return error if a proxied request returns a MJSONWP error response but HTTP status code is 200', async function () {
|
|
app.post('/wd/hub/session/:sessionId/perform-actions', (req, res) => {
|
|
res.status(200).json({
|
|
sessionId: 'Fake Session Id',
|
|
status: 7,
|
|
value: 'A problem occurred',
|
|
});
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [1, 2, 3],
|
|
}
|
|
});
|
|
status.should.equal(HTTPStatusCodes.NOT_FOUND);
|
|
const {error: w3cError, message: errMessage, stacktrace} = data.value;
|
|
w3cError.should.equal('no such element');
|
|
errMessage.should.match(/A problem occurred/);
|
|
stacktrace.should.exist;
|
|
});
|
|
|
|
it('should return error if a proxied request returns a W3C error response', async function () {
|
|
app.post('/wd/hub/session/:sessionId/perform-actions', (req, res) => {
|
|
res.status(404).json({
|
|
value: {
|
|
error: 'no such element',
|
|
message: 'does not make a difference',
|
|
stacktrace: 'arbitrary stacktrace',
|
|
},
|
|
});
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [1, 2, 3],
|
|
}
|
|
});
|
|
status.should.equal(HTTPStatusCodes.NOT_FOUND);
|
|
const {error: w3cError, stacktrace} = data.value;
|
|
w3cError.should.equal('no such element');
|
|
stacktrace.should.match(/arbitrary stacktrace/);
|
|
});
|
|
|
|
it('should return an error if a proxied request returns a W3C error response', async function () {
|
|
app.post('/wd/hub/session/:sessionId/perform-actions', (req, res) => {
|
|
res.set('Connection', 'close');
|
|
res.status(444).json({
|
|
value: {
|
|
error: 'bogus error code',
|
|
message: 'does not make a difference',
|
|
stacktrace: 'arbitrary stacktrace',
|
|
},
|
|
});
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [1, 2, 3],
|
|
}
|
|
});
|
|
status.should.equal(HTTPStatusCodes.INTERNAL_SERVER_ERROR);
|
|
const {error: w3cError, stacktrace} = data.value;
|
|
w3cError.should.equal('unknown error');
|
|
stacktrace.should.match(/arbitrary stacktrace/);
|
|
});
|
|
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should handle commands with no response values', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/forward`,
|
|
method: 'POST',
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: null,
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should allow empty string response values', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/text`,
|
|
});
|
|
data.should.eql({
|
|
status: 0,
|
|
value: '',
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should send 500 response and an Unknown object for rejected commands', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/refresh`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 13,
|
|
value: {
|
|
message: 'An unknown server-side error occurred while processing ' +
|
|
'the command. Original error: Too Fresh!'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should not throw UnknownError when known', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo`,
|
|
validateStatus: null,
|
|
});
|
|
|
|
status.should.equal(404);
|
|
data.should.eql({
|
|
status: 6,
|
|
value: {
|
|
message: 'A session is either terminated or not started'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('session Ids', function () {
|
|
let driver = new FakeDriver();
|
|
let mjsonwpServer;
|
|
|
|
before(async function () {
|
|
mjsonwpServer = await server({
|
|
routeConfiguringFunction: routeConfiguringFunction(driver),
|
|
port: serverPort,
|
|
});
|
|
});
|
|
|
|
after(async function () {
|
|
await mjsonwpServer.close();
|
|
});
|
|
|
|
afterEach(function () {
|
|
driver.sessionId = null;
|
|
});
|
|
|
|
it('should return null SessionId for commands without sessionIds', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/status`,
|
|
});
|
|
|
|
should.equal(data.sessionId, null);
|
|
});
|
|
|
|
it('responds with the same session ID in the request', async function () {
|
|
let sessionId = 'Vader Sessions';
|
|
driver.sessionId = sessionId;
|
|
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'}
|
|
});
|
|
|
|
should.exist(data.sessionId);
|
|
data.sessionId.should.eql(sessionId);
|
|
});
|
|
|
|
it('yells if no session exists', async function () {
|
|
let sessionId = 'Vader Sessions';
|
|
|
|
const {data, status} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(404);
|
|
data.status.should.equal(6);
|
|
data.value.message.should.contain('session');
|
|
});
|
|
|
|
it('yells if invalid session is sent', async function () {
|
|
let sessionId = 'Vader Sessions';
|
|
driver.sessionId = 'recession';
|
|
|
|
const {data, status} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(404);
|
|
data.status.should.equal(6);
|
|
data.value.message.should.contain('session');
|
|
});
|
|
|
|
it('should have session IDs in error responses', async function () {
|
|
let sessionId = 'Vader Sessions';
|
|
driver.sessionId = sessionId;
|
|
|
|
const {data, status} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/refresh`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 13,
|
|
value: {
|
|
message: 'An unknown server-side error occurred while processing ' +
|
|
'the command. Original error: Too Fresh!'
|
|
},
|
|
sessionId
|
|
});
|
|
});
|
|
|
|
it('should return a new session ID on create', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {desiredCapabilities: {greeting: 'hello'}, requiredCapabilities: {valediction: 'bye'}}
|
|
});
|
|
|
|
should.exist(data.sessionId);
|
|
data.sessionId.indexOf('fakeSession_').should.equal(0);
|
|
data.value.should.eql({greeting: 'hello', valediction: 'bye'});
|
|
});
|
|
});
|
|
|
|
describe('via drivers jsonwp proxy', function () {
|
|
let driver;
|
|
let sessionId = 'foo';
|
|
let mjsonwpServer;
|
|
|
|
beforeEach(async function () {
|
|
driver = new FakeDriver();
|
|
driver.sessionId = sessionId;
|
|
driver.proxyActive = () => true;
|
|
driver.canProxy = () => true;
|
|
|
|
mjsonwpServer = await server({
|
|
routeConfiguringFunction: routeConfiguringFunction(driver),
|
|
port: serverPort,
|
|
});
|
|
});
|
|
|
|
afterEach(async function () {
|
|
await mjsonwpServer.close();
|
|
});
|
|
|
|
it('should give a nice error if proxying is set but no proxy function exists', async function () {
|
|
driver.canProxy = () => false;
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 13,
|
|
value: {
|
|
message: 'An unknown server-side error occurred while processing ' +
|
|
'the command. Original error: Trying to proxy to a JSONWP ' +
|
|
'server but driver is unable to proxy'
|
|
},
|
|
sessionId
|
|
});
|
|
});
|
|
|
|
it('should pass on any errors in proxying', async function () {
|
|
driver.proxyReqRes = async function () { // eslint-disable-line require-await
|
|
throw new Error('foo');
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 13,
|
|
value: {
|
|
message: 'An unknown server-side error occurred while processing ' +
|
|
'the command. Original error: Could not proxy. Proxy ' +
|
|
'error: foo'
|
|
},
|
|
sessionId
|
|
});
|
|
});
|
|
|
|
it('should able to throw ProxyRequestError in proxying', async function () {
|
|
driver.proxyReqRes = async function () { // eslint-disable-line require-await
|
|
let jsonwp = {status: 35, value: 'No such context found.', sessionId: 'foo'};
|
|
throw new errors.ProxyRequestError(`Could not proxy command to remote server. `, jsonwp);
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.should.eql({
|
|
status: 35,
|
|
value: {
|
|
message: 'No such context found.'
|
|
},
|
|
sessionId: 'foo'
|
|
});
|
|
});
|
|
|
|
it('should let the proxy handle req/res', async function () {
|
|
driver.proxyReqRes = async function (req, res) { // eslint-disable-line require-await
|
|
res.status(200).json({custom: 'data'});
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'}
|
|
});
|
|
|
|
status.should.equal(200);
|
|
data.should.eql({custom: 'data'});
|
|
});
|
|
|
|
it('should avoid jsonwp proxying when path matches avoidance list', async function () {
|
|
driver.getProxyAvoidList = () => [['POST', new RegExp('^/session/[^/]+/url$')]];
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(200);
|
|
data.should.eql({
|
|
status: 0,
|
|
value: 'Navigated to: http://google.com',
|
|
sessionId
|
|
});
|
|
});
|
|
|
|
it('should fail if avoid proxy list is malformed in some way', async function () {
|
|
async function badProxyAvoidanceList (list) {
|
|
driver.getProxyAvoidList = () => list;
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
status.should.equal(500);
|
|
data.status.should.equal(13);
|
|
data.value.message.should.contain('roxy');
|
|
}
|
|
const lists = [
|
|
'foo',
|
|
[['foo']],
|
|
[['BAR', /lol/]],
|
|
[['GET', 'foo']]
|
|
];
|
|
for (let list of lists) {
|
|
await badProxyAvoidanceList(list);
|
|
}
|
|
});
|
|
|
|
it('should avoid proxying non-session commands even if not in the list', async function () {
|
|
driver.getProxyAvoidList = () => [['POST', new RegExp('')]];
|
|
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/status`,
|
|
});
|
|
|
|
status.should.equal(200);
|
|
data.should.eql({
|
|
status: 0,
|
|
value: "I'm fine",
|
|
sessionId: null
|
|
});
|
|
});
|
|
|
|
it('should avoid proxying deleteSession commands', async function () {
|
|
driver.getProxyAvoidList = () => [['POST', new RegExp('')]];
|
|
|
|
driver.sessionId.should.equal(sessionId);
|
|
const {status} = await axios.delete(`${baseUrl}/session/${sessionId}`);
|
|
|
|
status.should.equal(200);
|
|
should.not.exist(driver.sessionId);
|
|
driver.jwpProxyActive.should.be.false;
|
|
});
|
|
});
|
|
});
|