feat(driver-test-support): add startStoppableAppium()

This adds a function `startStoppableAppium()` which starts an Appium server (via its `main()` entry point), but resolves with an object having a `stop() => Promise<boolean>` method. This method force-quits the server, even if there are active connections.
This commit is contained in:
Christopher Hiller
2023-02-16 14:48:17 -08:00
parent d54901a9c3
commit 893bd888cf
6 changed files with 119 additions and 1 deletions

19
package-lock.json generated
View File

@@ -2405,6 +2405,14 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/stoppable": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.1.tgz",
"integrity": "sha512-b8N+fCADRIYYrGZOcmOR8ZNBOqhktWTB/bMUl5LvGtT201QKJZOOH5UsFyI3qtteM6ZAJbJqZoBcLqqxKIwjhw==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/supports-color": {
"version": "8.1.1",
"license": "MIT"
@@ -14379,6 +14387,15 @@
"node": ">= 0.8"
}
},
"node_modules/stoppable": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
"integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
"engines": {
"node": ">=4",
"npm": ">=6"
}
},
"node_modules/stream-browserify": {
"version": "2.0.2",
"dev": true,
@@ -16582,6 +16599,7 @@
"dependencies": {
"@appium/types": "^0.9.1",
"@types/lodash": "4.14.191",
"@types/stoppable": "1.1.1",
"axios": "1.3.3",
"bluebird": "3.7.2",
"chai": "4.3.7",
@@ -16589,6 +16607,7 @@
"lodash": "4.17.21",
"sinon": "15.0.1",
"source-map-support": "0.5.21",
"stoppable": "1.1.0",
"type-fest": "3.5.7"
},
"engines": {

View File

@@ -2,6 +2,9 @@ export {createSessionHelpers, driverE2ETestSuite} from './e2e-suite';
export * from './unit-suite';
export * from './helpers';
// eslint-disable-next-line import/no-unresolved
export * from './stoppable';
/**
* @typedef {import('@appium/types').DriverClass} DriverClass
* @typedef {import('@appium/types').BaseNSCapabilities} BaseNSCapabilities

View File

@@ -0,0 +1,57 @@
import type {AppiumServer} from '@appium/types';
import {main as startAppium} from 'appium';
import type {Args, CliCommandServer} from 'appium/types';
import B from 'bluebird';
import type {Server} from 'node:http';
import stoppable from 'stoppable';
import type {Asyncify} from 'type-fest';
/**
* Options for {@linkcode startStoppableAppium}
*/
export type AppiumServerOpts = Args<CliCommandServer>;
/**
* An {@linkcode AppiumServer} with a method `stop() => Promise<void>`, which closes all sockets and fully stops the server.
*
* Returned by {@linkcode startStoppableAppium}
*/
export type TestAppiumServer = Omit<NormativeAppiumServer, 'close'> & {
stop: Asyncify<stoppable.WithStop['stop']>;
close: (callback: (err?: Error) => void) => Promise<void>;
};
/**
* The {@linkcode AppiumServer} type, but with the `close` method normalized to a callback-style function.
*/
export type NormativeAppiumServer = Omit<AppiumServer, 'close'> & {
close: Server['close'];
};
/**
* Coerces {@linkcode AppiumServer} into a {@linkcode TestAppiumServer}.
* @param opts Options for {@linkcode startAppium}
* @todo This should be moved into `@appium/driver-test-support` or something
* @returns A stoppable Appium server
*/
export async function startStoppableAppium(opts: AppiumServerOpts): Promise<TestAppiumServer> {
const appiumServer = (await startAppium(opts)) as AppiumServer;
const stoppableServer = stoppable(appiumServer as unknown as NormativeAppiumServer, 0);
const originalAsyncClose = appiumServer.close;
(stoppableServer as unknown as TestAppiumServer).close = async function (
callback?: (err?: Error) => void
) {
if (callback) {
try {
await originalAsyncClose.call(this);
callback();
} catch (err) {
callback(err);
}
} else {
await originalAsyncClose.call(this);
}
};
stoppableServer.stop = B.promisify(stoppableServer.stop, {context: stoppableServer});
return stoppableServer as unknown as TestAppiumServer;
}

View File

@@ -43,6 +43,7 @@
"dependencies": {
"@appium/types": "^0.9.1",
"@types/lodash": "4.14.191",
"@types/stoppable": "1.1.1",
"axios": "1.3.3",
"bluebird": "3.7.2",
"chai": "4.3.7",
@@ -50,6 +51,7 @@
"lodash": "4.17.21",
"sinon": "15.0.1",
"source-map-support": "0.5.21",
"stoppable": "1.1.0",
"type-fest": "3.5.7"
},
"peerDependencies": {

View File

@@ -0,0 +1,37 @@
import axios from 'axios';
import B from 'bluebird';
import {Agent} from 'node:http';
import {startStoppableAppium, TestAppiumServer} from '../../lib';
import getPort from 'get-port';
const {expect} = chai;
describe('startStoppableAppium()', function () {
it('should start an Appium server', async function () {
let server: TestAppiumServer | undefined;
try {
server = await startStoppableAppium({port: await getPort()});
expect(server, 'to be an object');
} finally {
if (server) {
await expect(server.stop()).to.be.fulfilled;
}
}
});
describe('when the server has connections', function () {
it('should stop the server and resolve with a boolean', async function () {
const port = await getPort();
const server = await startStoppableAppium({port});
const getConnections = B.promisify(server.getConnections, {context: server});
await axios.get(`http://127.0.0.1:${port}/status`, {
httpAgent: new Agent({keepAlive: true}),
});
try {
await expect(getConnections()).to.eventually.be.greaterThan(0);
} finally {
await expect(server.stop()).to.eventually.be.a('boolean');
}
});
});
});

View File

@@ -10,6 +10,6 @@
},
"types": ["mocha", "chai", "chai-as-promised"]
},
"include": ["./lib"],
"include": ["lib", "test"],
"references": [{"path": "../types"}, {"path": "../base-driver"}]
}