mirror of
https://github.com/appium/appium.git
synced 2026-10-28 20:31:51 +08:00
860 lines
28 KiB
TypeScript
860 lines
28 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import {after, afterEach, before, beforeEach, describe, it} from 'node:test';
|
|
|
|
import {getTestPort, TEST_HOST} from '@appium/driver-test-support';
|
|
import type {RouteMatcher} from '@appium/types';
|
|
import axios from 'axios';
|
|
import type {Application, Request, Response} from 'express';
|
|
import {StatusCodes as HTTPStatusCodes} from 'http-status-codes';
|
|
import {createSandbox} from 'sinon';
|
|
|
|
import {errors, WebDriverProxy} from '../../../lib';
|
|
import {MJSONWP_ELEMENT_KEY, W3C_ELEMENT_KEY} from '../../../lib/constants';
|
|
import {createServer} from '../../helpers';
|
|
import {FakeDriver} from './fake-driver';
|
|
import {createProxyServer} from './helpers';
|
|
|
|
describe('Protocol', function () {
|
|
let sandbox: sinon.SinonSandbox;
|
|
|
|
beforeEach(function () {
|
|
sandbox = createSandbox();
|
|
});
|
|
|
|
afterEach(function () {
|
|
sandbox.restore();
|
|
});
|
|
|
|
describe('direct to driver', function () {
|
|
const d = new FakeDriver();
|
|
it('should return response values directly from the driver', async function () {
|
|
assert.ok((await d.setUrl('http://google.com')).includes('google'));
|
|
});
|
|
});
|
|
|
|
describe('via express router', function () {
|
|
let driver: FakeDriver;
|
|
let baseUrl: string;
|
|
let teardown: () => Promise<void> | undefined;
|
|
|
|
before(async function () {
|
|
driver = new FakeDriver();
|
|
driver.sessionId = 'foo';
|
|
const {baseUrl: baseUrlStr, setup, teardown: teardownFn} = await createServer(driver);
|
|
baseUrl = baseUrlStr;
|
|
teardown = teardownFn;
|
|
await setup();
|
|
});
|
|
|
|
after(async function () {
|
|
await teardown?.();
|
|
});
|
|
|
|
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'},
|
|
});
|
|
assert.deepStrictEqual(data, {
|
|
value: 'Navigated to: http://google.com',
|
|
});
|
|
});
|
|
|
|
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'},
|
|
});
|
|
assert.deepStrictEqual(data, {
|
|
value: 'Navigated to: http://google.com',
|
|
});
|
|
});
|
|
|
|
it('should respond to x-www-form-urlencoded as well as json requests', async function () {
|
|
const reqData = new URLSearchParams();
|
|
reqData.set('url', 'http://google.com');
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
headers: {
|
|
'Content-Type': 'application/x-www-form-urlencoded',
|
|
},
|
|
method: 'POST',
|
|
data: reqData.toString(),
|
|
});
|
|
assert.deepStrictEqual(data, {
|
|
value: 'Navigated to: http://google.com',
|
|
});
|
|
});
|
|
|
|
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: {},
|
|
});
|
|
assert.deepStrictEqual(data, {
|
|
value: '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: {},
|
|
});
|
|
assert.deepStrictEqual(data.value, ['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`,
|
|
});
|
|
assert.deepStrictEqual(data.value, ['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,
|
|
});
|
|
assert.strictEqual(status, 400);
|
|
assert.ok(JSON.stringify(data).includes('url'));
|
|
});
|
|
|
|
it('should reject requests with a badly formatted body and not crash', async function () {
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: 'oh hello',
|
|
}),
|
|
Error,
|
|
);
|
|
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
assert.deepStrictEqual(data, {
|
|
value: 'Navigated to: http://google.com',
|
|
});
|
|
});
|
|
|
|
it('should get 404 for bad routes', async function () {
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/blargimarg`,
|
|
}),
|
|
/404/,
|
|
);
|
|
});
|
|
|
|
it('4xx responses should have content-type of application/json', async function () {
|
|
const {headers} = await axios({
|
|
url: `${baseUrl}/blargimargarita`,
|
|
validateStatus: null,
|
|
});
|
|
|
|
assert.ok(String(headers['content-type']).includes('application/json'));
|
|
});
|
|
|
|
it('should return unknown command for routes without a command mapping', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/location`,
|
|
validateStatus: null,
|
|
});
|
|
|
|
assert.strictEqual(status, 404);
|
|
assert.strictEqual(data.value.error, 'unknown command');
|
|
assert.match(data.value.message, /The requested resource could not be found/);
|
|
});
|
|
|
|
it('should return unknown command for ignored legacy routes', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/buttonup`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {},
|
|
});
|
|
|
|
assert.strictEqual(status, 404);
|
|
assert.strictEqual(data.value.error, 'unknown command');
|
|
assert.match(data.value.message, /The requested resource could not be found/);
|
|
});
|
|
|
|
it('should get 400 for bad parameters', async function () {
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/session/foo/url`,
|
|
method: 'POST',
|
|
data: {},
|
|
}),
|
|
/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'], text: 'bar'},
|
|
});
|
|
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {id: 'baz'},
|
|
}),
|
|
/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/settings`,
|
|
method: 'GET',
|
|
validateStatus: null,
|
|
});
|
|
assert.strictEqual(status, 500);
|
|
assert.strictEqual(data.value.error, 'unknown error');
|
|
assert.strictEqual(data.value.message, 'Mishandled Driver Error');
|
|
assert.ok(!data.sessionId);
|
|
});
|
|
|
|
describe('w3c sendkeys migration', function () {
|
|
it('should not accept value for sendkeys', async function () {
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {value: 'text to type'},
|
|
}),
|
|
/400/,
|
|
);
|
|
});
|
|
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'},
|
|
});
|
|
assert.deepStrictEqual(data.value, ['text to type', 'bar']);
|
|
});
|
|
it('should accept value and text for sendkeys, and use text', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/foo/element/bar/value`,
|
|
method: 'POST',
|
|
data: {value: 'text to ignore', text: 'text to type'},
|
|
});
|
|
assert.deepStrictEqual(data.value, ['text to type', 'bar']);
|
|
});
|
|
});
|
|
|
|
describe('create sessions via HTTP endpoint', function () {
|
|
let sessionId: string | null;
|
|
|
|
beforeEach(function () {
|
|
sessionId = null;
|
|
});
|
|
afterEach(async function () {
|
|
if (sessionId) {
|
|
await axios.delete(`${baseUrl}/session/${sessionId}`);
|
|
}
|
|
});
|
|
|
|
it('should not allow create session with desired caps (MJSONWP)', async function () {
|
|
const desiredCapabilities = {a: 'b'};
|
|
await assert.rejects(
|
|
axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {desiredCapabilities},
|
|
}),
|
|
/500/,
|
|
);
|
|
});
|
|
it('should allow create session with capabilities (W3C)', async function () {
|
|
const w3cCapabilities = {alwaysMatch: {'appium:e': 'f'}};
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {capabilities: w3cCapabilities},
|
|
});
|
|
assert.ok(!data.status);
|
|
assert.ok(!data.sessionId);
|
|
assert.deepStrictEqual(data.value.capabilities, w3cCapabilities);
|
|
assert.ok(data.value.sessionId);
|
|
sessionId = data.value.sessionId;
|
|
});
|
|
|
|
describe('w3c endpoints', function () {
|
|
let sessionUrl: string;
|
|
|
|
beforeEach(async function () {
|
|
// Start a W3C session
|
|
const {value} = (
|
|
await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
capabilities: {
|
|
alwaysMatch: {
|
|
platformName: 'Fake',
|
|
'appium:deviceName': 'Commodore 64',
|
|
},
|
|
firstMatch: [{}],
|
|
},
|
|
},
|
|
})
|
|
).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',
|
|
},
|
|
});
|
|
assert.strictEqual(status, 400);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
assert.match(message, /following required parameter/);
|
|
assert.match(stacktrace, /protocol\.(js|ts)/);
|
|
assert.strictEqual(typeof w3cError, 'string');
|
|
assert.strictEqual(w3cError, 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: [],
|
|
},
|
|
});
|
|
assert.strictEqual(status, 405);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
assert.match(message, /Method has not yet been implemented/);
|
|
assert.match(stacktrace, /protocol\.(js|ts)/);
|
|
assert.strictEqual(typeof w3cError, 'string');
|
|
assert.strictEqual(w3cError, errors.NotYetImplementedError.error());
|
|
});
|
|
|
|
it(`should throw 500 Unknown Error if the command throws an unexpected exception`, async function () {
|
|
(driver as any).performActions = () => {
|
|
throw new Error(`Didn't work`);
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {
|
|
actions: [],
|
|
},
|
|
});
|
|
assert.strictEqual(status, 500);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
assert.match(stacktrace, /protocol\.(js|ts)/);
|
|
assert.strictEqual(typeof w3cError, 'string');
|
|
assert.strictEqual(w3cError, errors.UnknownError.error());
|
|
assert.match(message, /Didn't work/);
|
|
|
|
delete (driver as any).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 = () => Promise.resolve(retValue as any);
|
|
const {data} = await axios.post(`${sessionUrl}/elements`, {
|
|
using: 'whatever',
|
|
value: 'whatever',
|
|
});
|
|
assert.deepStrictEqual(data.value, expectedValue);
|
|
driver.findElements = findElementsBackup;
|
|
});
|
|
|
|
it(`should fail with a 408 error if it throws a TimeoutError exception`, async function () {
|
|
const setUrlStub = sandbox.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/',
|
|
},
|
|
});
|
|
assert.strictEqual(status, 408);
|
|
|
|
const {error: w3cError, message, stacktrace} = data.value;
|
|
assert.match(stacktrace, /protocol\.(js|ts)/);
|
|
assert.strictEqual(typeof w3cError, 'string');
|
|
assert.strictEqual(w3cError, errors.TimeoutError.error());
|
|
assert.match(message, /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 as any).performActions = (actions: object[]) => 'It works ' + actions.join('');
|
|
const {status, value, sessionId} = (
|
|
await axios.post(`${sessionUrl}/actions`, {
|
|
actions: ['a', 'b', 'c'],
|
|
})
|
|
).data;
|
|
assert.ok(!sessionId);
|
|
assert.ok(!status);
|
|
assert.strictEqual(value, 'It works abc');
|
|
delete (driver as any).performActions;
|
|
});
|
|
|
|
describe('wdproxy', function () {
|
|
let port: number;
|
|
let server: ReturnType<Application['listen']>;
|
|
let wdproxy: WebDriverProxy;
|
|
let app: Application;
|
|
|
|
before(async function () {
|
|
port = await getTestPort();
|
|
});
|
|
|
|
beforeEach(function () {
|
|
const res = createProxyServer(port);
|
|
server = res.server;
|
|
app = res.app;
|
|
wdproxy = new WebDriverProxy({server: TEST_HOST, port});
|
|
wdproxy.sessionId = sessionId;
|
|
(driver as any).performActions = async (actions: object[]) =>
|
|
await wdproxy.command('/perform-actions', 'POST', actions);
|
|
});
|
|
|
|
afterEach(async function () {
|
|
delete (driver as any).performActions;
|
|
await server.close();
|
|
});
|
|
|
|
it('should work if a proxied request returns a response with status 200', async function () {
|
|
app.post('/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;
|
|
assert.deepStrictEqual(value, [1, 2, 3]);
|
|
assert.ok(!status);
|
|
assert.ok(!sessionId);
|
|
});
|
|
|
|
it('should return error if a proxied request returns a MJSONWP error response', async function () {
|
|
app.post('/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],
|
|
},
|
|
});
|
|
assert.strictEqual(status, HTTPStatusCodes.NOT_FOUND);
|
|
assert.match(JSON.stringify(data), /A problem occurred/);
|
|
});
|
|
|
|
it('should return W3C error if a proxied request returns a W3C error response', async function () {
|
|
app.post('/session/:sessionId/perform-actions', (req, res) => {
|
|
res.status(500).json({
|
|
value: {
|
|
error: 'unknown error',
|
|
message: 'Some error occurred',
|
|
stacktrace: 'Some error occurred',
|
|
},
|
|
});
|
|
});
|
|
const {status, data} = await axios({
|
|
url: `${sessionUrl}/actions`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {actions: [1, 2, 3]},
|
|
});
|
|
assert.strictEqual(status, 500);
|
|
const {error: w3cError, message: errMessage, stacktrace} = data.value;
|
|
assert.strictEqual(w3cError, 'unknown error');
|
|
assert.match(stacktrace, /Some error occurred/);
|
|
assert.strictEqual(errMessage, 'Some error occurred');
|
|
});
|
|
|
|
it('should return error if a proxied request returns a MJSONWP error response but HTTP status code is 200', async function () {
|
|
app.post('/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],
|
|
},
|
|
});
|
|
assert.strictEqual(status, HTTPStatusCodes.NOT_FOUND);
|
|
const {error: w3cError, message: errMessage, stacktrace} = data.value;
|
|
assert.strictEqual(w3cError, 'no such element');
|
|
assert.match(errMessage, /A problem occurred/);
|
|
assert.ok(stacktrace);
|
|
});
|
|
|
|
it('should return error if a proxied request returns a W3C error response', async function () {
|
|
app.post('/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],
|
|
},
|
|
});
|
|
assert.strictEqual(status, HTTPStatusCodes.NOT_FOUND);
|
|
const {error: w3cError, stacktrace} = data.value;
|
|
assert.strictEqual(w3cError, 'no such element');
|
|
assert.match(stacktrace, /arbitrary stacktrace/);
|
|
});
|
|
|
|
it('should return an error if a proxied request returns a W3C error response', async function () {
|
|
app.post('/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],
|
|
},
|
|
});
|
|
assert.strictEqual(status, HTTPStatusCodes.INTERNAL_SERVER_ERROR);
|
|
const {error: w3cError, stacktrace} = data.value;
|
|
assert.strictEqual(w3cError, 'unknown error');
|
|
assert.match(stacktrace, /arbitrary stacktrace/);
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
it('should send 404 response for invalid session id', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/foo/refresh`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
});
|
|
|
|
assert.strictEqual(status, 404);
|
|
assert.strictEqual(data.value.error, 'invalid session id');
|
|
assert.strictEqual(data.value.message, 'A session is either terminated or not started');
|
|
});
|
|
});
|
|
|
|
describe('session Ids', function () {
|
|
let driver: FakeDriver;
|
|
let baseUrl: string;
|
|
let teardown: () => Promise<void> | undefined;
|
|
|
|
before(async function () {
|
|
driver = new FakeDriver();
|
|
const {baseUrl: baseUrlStr, setup, teardown: teardownFn} = await createServer(driver);
|
|
baseUrl = baseUrlStr;
|
|
teardown = teardownFn;
|
|
await setup();
|
|
});
|
|
|
|
after(async function () {
|
|
await teardown?.();
|
|
});
|
|
|
|
afterEach(function () {
|
|
driver.sessionId = null;
|
|
});
|
|
|
|
it('responds with no session ID in the request', async function () {
|
|
const sessionId = 'Vader Sessions';
|
|
driver.sessionId = sessionId;
|
|
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'},
|
|
validateStatus: null,
|
|
});
|
|
|
|
assert.ok(!data.sessionId);
|
|
});
|
|
|
|
it('should return a new session ID on create', async function () {
|
|
const {data} = await axios({
|
|
url: `${baseUrl}/session`,
|
|
method: 'POST',
|
|
data: {
|
|
capabilities: {
|
|
alwaysMatch: {'appium:greeting': 'hello'},
|
|
firstMatch: [{}],
|
|
},
|
|
},
|
|
});
|
|
|
|
try {
|
|
assert.ok(data.value.sessionId);
|
|
assert.strictEqual(data.value.sessionId.indexOf('fakeSession_'), 0);
|
|
assert.deepStrictEqual(data.value.capabilities, {
|
|
alwaysMatch: {'appium:greeting': 'hello'},
|
|
firstMatch: [{}],
|
|
});
|
|
} finally {
|
|
if (data.value.sessionId) {
|
|
await axios.delete(`${baseUrl}/session/${data.value.sessionId}`);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('via drivers jsonwp proxy', function () {
|
|
let driver: FakeDriver;
|
|
const sessionId = 'foo';
|
|
let baseUrl: string;
|
|
let teardown: () => Promise<void> | undefined;
|
|
|
|
beforeEach(async function () {
|
|
driver = new FakeDriver();
|
|
driver.sessionId = sessionId;
|
|
driver.proxyActive = () => true;
|
|
driver.canProxy = () => true;
|
|
const {
|
|
baseUrl: baseUrlStr,
|
|
setup,
|
|
teardown: teardownFn,
|
|
} = await createServer(driver, {extraMethodMap: FakeDriver.newMethodMap});
|
|
baseUrl = baseUrlStr;
|
|
teardown = teardownFn;
|
|
await setup();
|
|
});
|
|
|
|
afterEach(async function () {
|
|
await teardown?.();
|
|
});
|
|
|
|
it('should give a nice error if proxying is set but no proxy function exists', async function () {
|
|
(driver as any).canProxy = () => false;
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
assert.strictEqual(status, 500);
|
|
assert.strictEqual(data.value.error, 'unknown error');
|
|
assert.strictEqual(data.value.message, 'Trying to proxy to a server but the driver is unable to proxy');
|
|
});
|
|
|
|
it('should pass on any errors in proxying', async function () {
|
|
(driver as any).proxyReqRes = async function () {
|
|
throw new Error('foo');
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
assert.strictEqual(status, 500);
|
|
assert.strictEqual(data.value.error, 'unknown error');
|
|
assert.match(data.value.message, /Proxy error: foo/);
|
|
});
|
|
|
|
it('should able to throw ProxyRequestError in proxying', async function () {
|
|
(driver as any).proxyReqRes = async function () {
|
|
const 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'},
|
|
});
|
|
|
|
assert.strictEqual(status, 500);
|
|
assert.strictEqual(data.value.error, 'unknown error');
|
|
assert.strictEqual(data.value.message, 'No such context found.');
|
|
});
|
|
|
|
it('should let the proxy handle req/res', async function () {
|
|
(driver as any).proxyReqRes = async function (req: Request, res: Response) {
|
|
res.status(200).json({custom: 'data'});
|
|
};
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
assert.strictEqual(status, 200);
|
|
assert.deepStrictEqual(data, {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'},
|
|
});
|
|
|
|
assert.strictEqual(status, 200);
|
|
assert.deepStrictEqual(data, {
|
|
value: 'Navigated to: http://google.com',
|
|
});
|
|
});
|
|
|
|
it('should fail if avoid proxy list is malformed in some way', async function () {
|
|
async function badProxyAvoidanceList(list: RouteMatcher[]) {
|
|
driver.getProxyAvoidList = () => list;
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/url`,
|
|
method: 'POST',
|
|
validateStatus: null,
|
|
data: {url: 'http://google.com'},
|
|
});
|
|
|
|
assert.strictEqual(status, 500);
|
|
assert.ok(data.value.message.includes('roxy'));
|
|
}
|
|
const lists = ['foo', [['foo']], [['BAR', /lol/]], [['GET', 'foo']]];
|
|
for (const list of lists) {
|
|
await badProxyAvoidanceList(list as RouteMatcher[]);
|
|
}
|
|
});
|
|
|
|
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`,
|
|
});
|
|
|
|
assert.strictEqual(status, 200);
|
|
assert.deepStrictEqual(data, {
|
|
value: "I'm fine",
|
|
});
|
|
});
|
|
|
|
it('should avoid proxying deleteSession commands', async function () {
|
|
driver.getProxyAvoidList = () => [['POST', new RegExp('')]];
|
|
|
|
assert.strictEqual(driver.sessionId, sessionId);
|
|
const {status} = await axios.delete(`${baseUrl}/session/${sessionId}`);
|
|
|
|
assert.strictEqual(status, 200);
|
|
assert.ok(!driver.sessionId);
|
|
assert.strictEqual(driver.wdProxyActive, false);
|
|
});
|
|
|
|
it('should avoid proxying when command spec specifies neverProxy', async function () {
|
|
const {status, data} = await axios({
|
|
url: `${baseUrl}/session/${sessionId}/noproxy`,
|
|
method: 'GET',
|
|
});
|
|
|
|
assert.strictEqual(status, 200);
|
|
assert.deepStrictEqual(data, {
|
|
value: 'This was not proxied',
|
|
});
|
|
});
|
|
});
|
|
});
|