feat(testing): add continuous tasks support for jest e2e with node (#30675)
## Current Behavior When generating `node` projects with an `e2e` project using Jest, we do not supply any method for the node application to actually be started before running the tests. ## Expected Behavior Using Continuous Tasks, have the e2e project dependOn the serve of the `node` project such that it is available for the e2e tests to run against it.
This commit is contained in:
parent
a1cd4e31ad
commit
e5c7f6db18
@ -58,15 +58,11 @@ describe('Node Applications + webpack', () => {
|
|||||||
|
|
||||||
async function runE2eTests(appName: string, port: number = 5000) {
|
async function runE2eTests(appName: string, port: number = 5000) {
|
||||||
process.env.PORT = `${port}`;
|
process.env.PORT = `${port}`;
|
||||||
const childProcess = await runCommandUntil(`serve ${appName}`, (output) => {
|
|
||||||
return output.includes(`http://localhost:${port}`);
|
|
||||||
});
|
|
||||||
const result = runCLI(`e2e ${appName}-e2e --verbose`);
|
const result = runCLI(`e2e ${appName}-e2e --verbose`);
|
||||||
expect(result).toContain('Setting up...');
|
expect(result).toContain('Setting up...');
|
||||||
expect(result).toContain('Tearing down..');
|
expect(result).toContain('Tearing down..');
|
||||||
expect(result).toContain('Successfully ran target e2e');
|
expect(result).toContain('Successfully ran target e2e');
|
||||||
|
|
||||||
await promisifiedTreeKill(childProcess.pid, 'SIGKILL');
|
|
||||||
await killPort(port);
|
await killPort(port);
|
||||||
process.env.PORT = '';
|
process.env.PORT = '';
|
||||||
}
|
}
|
||||||
@ -96,7 +92,7 @@ describe('Node Applications + webpack', () => {
|
|||||||
`generate @nx/node:app apps/${koaApp} --framework=koa --port=7002 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
|
`generate @nx/node:app apps/${koaApp} --framework=koa --port=7002 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
|
||||||
);
|
);
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/node:app apps/${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest`
|
`generate @nx/node:app apps/${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --verbose`
|
||||||
);
|
);
|
||||||
|
|
||||||
addLibImport(expressApp, testLib1);
|
addLibImport(expressApp, testLib1);
|
||||||
|
|||||||
@ -337,13 +337,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
await killPorts(port);
|
||||||
|
await promisifiedTreeKill(p.pid, 'SIGKILL');
|
||||||
|
|
||||||
const e2eRsult = await runCLIAsync(`e2e ${nestapp}-e2e`);
|
const e2eRsult = await runCLIAsync(`e2e ${nestapp}-e2e`);
|
||||||
|
|
||||||
expect(e2eRsult.combinedOutput).toContain('Test Suites: 1 passed, 1 total');
|
expect(e2eRsult.combinedOutput).toContain('Test Suites: 1 passed, 1 total');
|
||||||
|
|
||||||
await killPorts(port);
|
|
||||||
await promisifiedTreeKill(p.pid, 'SIGKILL');
|
|
||||||
}, 120000);
|
}, 120000);
|
||||||
|
|
||||||
it('should generate a nest application with docker', async () => {
|
it('should generate a nest application with docker', async () => {
|
||||||
|
|||||||
@ -444,6 +444,7 @@ describe('application generator', () => {
|
|||||||
"e2e": {
|
"e2e": {
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"@proj/myapp:build",
|
"@proj/myapp:build",
|
||||||
|
"@proj/myapp:serve",
|
||||||
],
|
],
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
@ -35,7 +35,9 @@
|
|||||||
"@nx/devkit": "file:../devkit",
|
"@nx/devkit": "file:../devkit",
|
||||||
"@nx/jest": "file:../jest",
|
"@nx/jest": "file:../jest",
|
||||||
"@nx/js": "file:../js",
|
"@nx/js": "file:../js",
|
||||||
"@nx/eslint": "file:../eslint"
|
"@nx/eslint": "file:../eslint",
|
||||||
|
"tcp-port-used": "^1.0.2",
|
||||||
|
"kill-port": "^1.6.1"
|
||||||
},
|
},
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
|
|||||||
@ -934,6 +934,7 @@ describe('app', () => {
|
|||||||
"e2e": {
|
"e2e": {
|
||||||
"dependsOn": [
|
"dependsOn": [
|
||||||
"@proj/myapp:build",
|
"@proj/myapp:build",
|
||||||
|
"@proj/myapp:serve",
|
||||||
],
|
],
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export async function e2eProjectGeneratorInternal(
|
|||||||
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
||||||
passWithNoTests: true,
|
passWithNoTests: true,
|
||||||
},
|
},
|
||||||
dependsOn: [`${options.project}:build`],
|
dependsOn: [`${options.project}:build`, `${options.project}:serve`],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -93,7 +93,7 @@ export async function e2eProjectGeneratorInternal(
|
|||||||
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
||||||
passWithNoTests: true,
|
passWithNoTests: true,
|
||||||
},
|
},
|
||||||
dependsOn: [`${options.project}:build`],
|
dependsOn: [`${options.project}:build`, `${options.project}:serve`],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { waitForPortOpen } from '@nx/node/utils';
|
||||||
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
var __TEARDOWN_MESSAGE__: string;
|
var __TEARDOWN_MESSAGE__: string;
|
||||||
|
|
||||||
@ -5,6 +7,10 @@ module.exports = async function() {
|
|||||||
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
|
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
|
||||||
console.log('\nSetting up...\n');
|
console.log('\nSetting up...\n');
|
||||||
|
|
||||||
|
const host = process.env.HOST ?? 'localhost';
|
||||||
|
const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;
|
||||||
|
await waitForPortOpen(port, { host });
|
||||||
|
|
||||||
// Hint: Use `globalThis` to pass variables to global teardown.
|
// Hint: Use `globalThis` to pass variables to global teardown.
|
||||||
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
|
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
|
import { killPort } from '@nx/node/utils';
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
module.exports = async function() {
|
module.exports = async function() {
|
||||||
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
|
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
|
||||||
// Hint: `globalThis` is shared between setup and teardown.
|
// Hint: `globalThis` is shared between setup and teardown.
|
||||||
|
const port = process.env.PORT ? Number(process.env.PORT) : <%= port %>;
|
||||||
|
await killPort(port);
|
||||||
console.log(globalThis.__TEARDOWN_MESSAGE__);
|
console.log(globalThis.__TEARDOWN_MESSAGE__);
|
||||||
};
|
};
|
||||||
|
|||||||
38
packages/node/src/utils/kill-port.ts
Normal file
38
packages/node/src/utils/kill-port.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { logger } from '@nx/devkit';
|
||||||
|
import { check as portCheck } from 'tcp-port-used';
|
||||||
|
|
||||||
|
export const kill = require('kill-port');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kills the process on the given port
|
||||||
|
* @param port
|
||||||
|
* @param killPortDelay
|
||||||
|
*/
|
||||||
|
export async function killPort(
|
||||||
|
port: number,
|
||||||
|
killPortDelay = 2500
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (await portCheck(port)) {
|
||||||
|
let killPortResult;
|
||||||
|
try {
|
||||||
|
logger.info(`Attempting to close port ${port}`);
|
||||||
|
killPortResult = await kill(port);
|
||||||
|
await new Promise<void>((resolve) =>
|
||||||
|
setTimeout(() => resolve(), killPortDelay)
|
||||||
|
);
|
||||||
|
if (await portCheck(port)) {
|
||||||
|
logger.error(
|
||||||
|
`Port ${port} still open ${JSON.stringify(killPortResult)}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.info(`Port ${port} successfully closed`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
logger.error(`Port ${port} closing failed`);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
packages/node/src/utils/wait-for-port-open.ts
Normal file
71
packages/node/src/utils/wait-for-port-open.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import * as net from 'net';
|
||||||
|
import { logger } from '@nx/devkit';
|
||||||
|
|
||||||
|
interface WaitForPortOpenOptions {
|
||||||
|
/**
|
||||||
|
* The host to connect to
|
||||||
|
* @default 'localhost'
|
||||||
|
*/
|
||||||
|
host?: string;
|
||||||
|
/**
|
||||||
|
* The number of retries to attempt
|
||||||
|
* @default 120
|
||||||
|
*/
|
||||||
|
retries?: number;
|
||||||
|
/**
|
||||||
|
* The delay between retries
|
||||||
|
* @default 1000
|
||||||
|
*/
|
||||||
|
retryDelay?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for the given port to be open
|
||||||
|
* @param port
|
||||||
|
* @param options
|
||||||
|
*/
|
||||||
|
export function waitForPortOpen(
|
||||||
|
port: number,
|
||||||
|
options: WaitForPortOpenOptions = {}
|
||||||
|
): Promise<void> {
|
||||||
|
const host = options.host ?? 'localhost';
|
||||||
|
const allowedErrorCodes = ['ECONNREFUSED', 'ECONNRESET', 'ETIMEDOUT'];
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const checkPort = (retries = options.retries ?? 120) => {
|
||||||
|
const client = new net.Socket();
|
||||||
|
const cleanupClient = () => {
|
||||||
|
client.removeAllListeners('connect');
|
||||||
|
client.removeAllListeners('error');
|
||||||
|
client.end();
|
||||||
|
client.destroy();
|
||||||
|
client.unref();
|
||||||
|
};
|
||||||
|
client.once('connect', () => {
|
||||||
|
cleanupClient();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
client.once('error', (err) => {
|
||||||
|
if (retries === 0 || !allowedErrorCodes.includes(err['code'])) {
|
||||||
|
if (process.env['NX_VERBOSE_LOGGING'] === 'true') {
|
||||||
|
logger.info(
|
||||||
|
`Error connecting on ${host}:${port}: ${err['code'] || err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cleanupClient();
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
setTimeout(() => checkPort(retries - 1), options.retryDelay ?? 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (process.env['NX_VERBOSE_LOGGING'] === 'true') {
|
||||||
|
logger.info(`Connecting on ${host}:${port}`);
|
||||||
|
}
|
||||||
|
client.connect({ port, host });
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPort();
|
||||||
|
});
|
||||||
|
}
|
||||||
2
packages/node/utils.ts
Normal file
2
packages/node/utils.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { waitForPortOpen } from './src/utils/wait-for-port-open';
|
||||||
|
export { killPort } from './src/utils/kill-port';
|
||||||
Loading…
x
Reference in New Issue
Block a user