mirror of
https://github.com/appium/appium.git
synced 2026-10-26 02:29:38 +08:00
also ensure that each session gets its own plugin instances so plugins for different sessions can't share state
319 lines
11 KiB
JavaScript
319 lines
11 KiB
JavaScript
// transpile:mocha
|
|
|
|
import _ from 'lodash';
|
|
import path from 'path';
|
|
import B from 'bluebird';
|
|
import chai from 'chai';
|
|
import chaiAsPromised from 'chai-as-promised';
|
|
import axios from 'axios';
|
|
import { remote as wdio } from 'webdriverio';
|
|
import { main as appiumServer } from '../lib/main';
|
|
import { DEFAULT_APPIUM_HOME, INSTALL_TYPE_LOCAL, DRIVER_TYPE } from '../lib/extension-config';
|
|
import { W3C_PREFIXED_CAPS, TEST_FAKE_APP, TEST_HOST, TEST_PORT } from './helpers';
|
|
import { BaseDriver } from 'appium-base-driver';
|
|
import DriverConfig from '../lib/driver-config';
|
|
import { runExtensionCommand } from '../lib/cli/extension';
|
|
import { removeAppiumPrefixes } from '../lib/utils';
|
|
import sinon from 'sinon';
|
|
|
|
chai.use(chaiAsPromised);
|
|
|
|
const TEST_SERVER = `http://${TEST_HOST}:${TEST_PORT}`;
|
|
const FAKE_DRIVER_DIR = path.resolve(__dirname, '..', '..', 'node_modules', 'appium-fake-driver');
|
|
|
|
const should = chai.should();
|
|
const shouldStartServer = process.env.USE_RUNNING_SERVER !== '0';
|
|
const caps = W3C_PREFIXED_CAPS;
|
|
const wdOpts = {
|
|
hostname: TEST_HOST,
|
|
port: TEST_PORT,
|
|
connectionRetryCount: 0,
|
|
};
|
|
|
|
describe('FakeDriver - via HTTP', function () {
|
|
let server = null;
|
|
const appiumHome = DEFAULT_APPIUM_HOME;
|
|
// since we update the FakeDriver.prototype below, make sure we update the FakeDriver which is
|
|
// actually going to be required by Appium
|
|
let FakeDriver = null;
|
|
const baseUrl = `${TEST_SERVER}/session`;
|
|
before(async function () {
|
|
// first ensure we have fakedriver installed
|
|
const driverList = await runExtensionCommand({
|
|
appiumHome,
|
|
driverCommand: 'list',
|
|
showInstalled: true,
|
|
}, DRIVER_TYPE);
|
|
if (!_.has(driverList, 'fake')) {
|
|
await runExtensionCommand({
|
|
appiumHome,
|
|
driverCommand: 'install',
|
|
driver: FAKE_DRIVER_DIR,
|
|
installType: INSTALL_TYPE_LOCAL,
|
|
}, DRIVER_TYPE);
|
|
}
|
|
|
|
const config = new DriverConfig(appiumHome);
|
|
await config.read();
|
|
FakeDriver = config.require('fake');
|
|
|
|
// then start server if we need to
|
|
if (shouldStartServer) {
|
|
let args = {port: TEST_PORT, host: TEST_HOST, appiumHome};
|
|
server = await appiumServer(args);
|
|
}
|
|
});
|
|
|
|
after(async function () {
|
|
if (server) {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
describe('server updating', function () {
|
|
it('should allow drivers to update the server in arbitrary ways', async function () {
|
|
const {data} = await axios.get(`${TEST_SERVER}/fakedriver`);
|
|
data.should.eql({fakedriver: 'fakeResponse'});
|
|
});
|
|
});
|
|
|
|
describe('session handling', function () {
|
|
it('should start and stop a session and not allow commands after session stopped', async function () {
|
|
let driver = await wdio({...wdOpts, capabilities: caps});
|
|
should.exist(driver.sessionId);
|
|
driver.sessionId.should.be.a('string');
|
|
await driver.deleteSession();
|
|
await driver.getTitle().should.eventually.be.rejectedWith(/terminated/);
|
|
});
|
|
|
|
it('should be able to run two FakeDriver sessions simultaneously', async function () {
|
|
let driver1 = await wdio({...wdOpts, capabilities: caps});
|
|
should.exist(driver1.sessionId);
|
|
driver1.sessionId.should.be.a('string');
|
|
let driver2 = await wdio({...wdOpts, capabilities: caps});
|
|
should.exist(driver2.sessionId);
|
|
driver2.sessionId.should.be.a('string');
|
|
driver2.sessionId.should.not.equal(driver1.sessionId);
|
|
await driver1.deleteSession();
|
|
await driver2.deleteSession();
|
|
});
|
|
|
|
it('should not be able to run two FakeDriver sessions simultaneously when one is unique', async function () {
|
|
let uniqueCaps = _.clone(caps);
|
|
uniqueCaps['appium:uniqueApp'] = true;
|
|
let driver1 = await wdio({...wdOpts, capabilities: uniqueCaps});
|
|
should.exist(driver1.sessionId);
|
|
driver1.sessionId.should.be.a('string');
|
|
await wdio({...wdOpts, capabilities: caps}).should.eventually.be.rejected;
|
|
await driver1.deleteSession();
|
|
});
|
|
|
|
it('should use the newCommandTimeout of the inner Driver on session creation', async function () {
|
|
let localCaps = Object.assign({
|
|
'appium:newCommandTimeout': 0.25,
|
|
}, caps);
|
|
let driver = await wdio({...wdOpts, capabilities: localCaps});
|
|
should.exist(driver.sessionId);
|
|
|
|
await B.delay(250);
|
|
await driver.getPageSource().should.eventually.be.rejectedWith(/terminated/);
|
|
});
|
|
|
|
it('should accept valid W3C capabilities and start a W3C session', async function () {
|
|
// Try with valid capabilities and check that it returns a session ID
|
|
const w3cCaps = {
|
|
capabilities: {
|
|
alwaysMatch: {'appium:automationName': 'Fake', platformName: 'Fake'},
|
|
firstMatch: [{'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}],
|
|
}
|
|
};
|
|
|
|
// Create the session
|
|
const {status, value, sessionId} = (await axios.post(baseUrl, w3cCaps)).data;
|
|
try {
|
|
should.not.exist(status); // Test that it's a W3C session by checking that 'status' is not in the response
|
|
should.not.exist(sessionId);
|
|
value.sessionId.should.be.a.string;
|
|
value.should.exist;
|
|
value.capabilities.should.deep.equal({
|
|
automationName: 'Fake',
|
|
platformName: 'Fake',
|
|
deviceName: 'Fake',
|
|
app: TEST_FAKE_APP,
|
|
});
|
|
|
|
// Now use that sessionId to call /screenshot
|
|
const {status: screenshotStatus, value: screenshotValue} = (await axios({url: `${baseUrl}/${value.sessionId}/screenshot`})).data;
|
|
should.not.exist(screenshotStatus);
|
|
screenshotValue.should.match(/^iVBOR/); // should be a png
|
|
|
|
// Now use that sessionID to call an arbitrary W3C-only endpoint that isn't implemented to see if it responds with correct error
|
|
await axios.post(
|
|
`${baseUrl}/${value.sessionId}/execute/async`,
|
|
{script: '', args: ['a']}).should.eventually.be.rejectedWith(/405/);
|
|
} finally {
|
|
// End session
|
|
await axios.delete(`${baseUrl}/${value.sessionId}`);
|
|
}
|
|
});
|
|
|
|
it('should reject invalid W3C capabilities and respond with a 400 Bad Parameters error', async function () {
|
|
const badW3Ccaps = {
|
|
capabilities: {
|
|
alwaysMatch: {},
|
|
firstMatch: [{'appium:deviceName': 'Fake', 'appium:app': TEST_FAKE_APP}],
|
|
}
|
|
};
|
|
|
|
await axios.post(baseUrl, badW3Ccaps).should.eventually.be.rejectedWith(/400/);
|
|
});
|
|
|
|
it('should accept a combo of W3C and JSONWP capabilities but completely ignore JSONWP', async function () {
|
|
const combinedCaps = {
|
|
'desiredCapabilities': {
|
|
...caps,
|
|
jsonwpParam: 'jsonwpParam',
|
|
},
|
|
'capabilities': {
|
|
'alwaysMatch': {...caps},
|
|
'firstMatch': [{
|
|
'appium:w3cParam': 'w3cParam',
|
|
}],
|
|
}
|
|
};
|
|
|
|
const {status, value, sessionId} = (await axios.post(baseUrl, combinedCaps)).data;
|
|
try {
|
|
should.not.exist(status); // If it's a W3C session, should not respond with 'status'
|
|
should.not.exist(sessionId);
|
|
value.sessionId.should.exist;
|
|
value.capabilities.should.deep.equal({
|
|
...removeAppiumPrefixes(caps),
|
|
w3cParam: 'w3cParam',
|
|
});
|
|
} finally {
|
|
// End session
|
|
await axios.delete(`${baseUrl}/${value.sessionId}`);
|
|
}
|
|
});
|
|
|
|
it('should reject bad automation name with an appropriate error', async function () {
|
|
const w3cCaps = {
|
|
capabilities: {
|
|
alwaysMatch: {
|
|
...caps,
|
|
'appium:automationName': 'BadAutomationName',
|
|
},
|
|
},
|
|
};
|
|
await axios.post(baseUrl, w3cCaps).should.eventually.be.rejectedWith(/500/);
|
|
});
|
|
|
|
it('should accept capabilities that are provided in the firstMatch array', async function () {
|
|
const w3cCaps = {
|
|
capabilities: {
|
|
alwaysMatch: {},
|
|
firstMatch: [{}, {
|
|
...caps
|
|
}],
|
|
},
|
|
};
|
|
const {value, sessionId, status} = (await axios.post(baseUrl, w3cCaps)).data;
|
|
try {
|
|
should.not.exist(status);
|
|
should.not.exist(sessionId);
|
|
value.capabilities.should.deep.equal(removeAppiumPrefixes(caps));
|
|
} finally {
|
|
// End session
|
|
await axios.delete(`${baseUrl}/${value.sessionId}`);
|
|
}
|
|
});
|
|
|
|
it('should not fall back to MJSONWP if w3c caps are invalid', async function () {
|
|
const combinedCaps = {
|
|
desiredCapabilities: {
|
|
...caps,
|
|
},
|
|
capabilities: {
|
|
alwaysMatch: {},
|
|
firstMatch: [{}, {
|
|
...caps,
|
|
platformName: null,
|
|
'appium:automationName': null,
|
|
'appium:deviceName': null,
|
|
}],
|
|
},
|
|
};
|
|
const res = (await axios.post(baseUrl, combinedCaps, {validateStatus: null}));
|
|
res.status.should.eql(400);
|
|
res.data.value.error.should.match(/invalid argument/);
|
|
});
|
|
|
|
it('should not fall back to MJSONWP even if Inner Driver is not ready for W3C', async function () {
|
|
const combinedCaps = {
|
|
desiredCapabilities: {
|
|
...caps,
|
|
},
|
|
capabilities: {
|
|
alwaysMatch: {
|
|
...caps,
|
|
'appium:deviceName': 'Fake',
|
|
},
|
|
},
|
|
};
|
|
const createSessionStub = sinon.stub(FakeDriver.prototype, 'createSession').callsFake(async function (jsonwpCaps) {
|
|
const res = await BaseDriver.prototype.createSession.call(this, jsonwpCaps);
|
|
this.protocol.should.equal('MJSONWP');
|
|
return res;
|
|
});
|
|
|
|
const res = (await axios.post(baseUrl, combinedCaps, {validateStatus: null}));
|
|
const {data, status} = res;
|
|
status.should.eql(500);
|
|
data.value.message.should.match(/older capabilities/);
|
|
|
|
createSessionStub.restore();
|
|
});
|
|
|
|
it('should allow drivers to update the method map with new routes and commands', async function () {
|
|
let driver = await wdio({...wdOpts, capabilities: caps});
|
|
const {sessionId} = driver;
|
|
try {
|
|
await axios.post(`${baseUrl}/${sessionId}/fakedriver`, {thing: {yes: 'lolno'}});
|
|
(await axios.get(`${baseUrl}/${sessionId}/fakedriver`)).data.value.should.eql({yes: 'lolno'});
|
|
} finally {
|
|
await driver.deleteSession();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Logsink', function () {
|
|
let server = null;
|
|
let logs = [];
|
|
let logHandler = function (level, message) {
|
|
logs.push([level, message]);
|
|
};
|
|
let args = {
|
|
port: TEST_PORT,
|
|
host: TEST_HOST,
|
|
appiumHome: DEFAULT_APPIUM_HOME,
|
|
logHandler,
|
|
};
|
|
|
|
before(async function () {
|
|
server = await appiumServer(args);
|
|
});
|
|
|
|
after(async function () {
|
|
await server.close();
|
|
});
|
|
|
|
it('should send logs to a logHandler passed in by a parent package', function () {
|
|
logs.length.should.be.above(1);
|
|
let welcomeIndex = logs[0][1].includes('versions of node') ? 1 : 0;
|
|
logs[welcomeIndex].length.should.equal(2);
|
|
logs[welcomeIndex][1].should.include('Welcome to Appium');
|
|
});
|
|
});
|