Open PatrickMunsey opened 1 year ago
Any update on this? Or duplicate issue?
@AgentEnder @FrozenPandaz @vsavkin Hi guys, I think this is a very useful feature, can be included in the Nx 19 release? Thank you
Any update on this, please?
Any updates ?
if this is not going to be implemented what is the recommended approach to serve the application after start services that the app needs (docker-compose, database, etc) ?
@codePassion-dot the recommended approach would be to have a task/target that runs docker commands (which spins up your application) and run the e2e right after
I believe it's already provided.
// jest.config.ts
export default {
displayName: 'api-e2e',
globalSetup: '<rootDir>/src/support/global-setup.ts',
globalTeardown: '<rootDir>/src/support/global-teardown.ts',
...
};
// global-setup.ts
import { spawn } from 'child_process';
module.exports = async function () {
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
console.log('\nSetting up...\n');
// Start the API server
const server = spawn('nx', ['serve', 'api'], {
shell: true,
stdio: 'inherit',
});
// Store the server process in globalThis so it can be accessed in globalTeardown
globalThis.__SERVER_PROCESS__ = server;
// Hint: Use `globalThis` to pass variables to global teardown.
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
// You might want to wait for the server to be fully up before proceeding
// This is a simplistic approach; consider polling a health endpoint instead
await new Promise((resolve) => setTimeout(resolve, 5000));
};
// global-teardown.ts
module.exports = async function () {
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
// Hint: `globalThis` is shared between setup and teardown.
console.log(globalThis.__TEARDOWN_MESSAGE__);
// Stop the server process initiated in globalSetup
if (globalThis.__SERVER_PROCESS__) {
globalThis.__SERVER_PROCESS__.kill();
}
};
yeah I ended up doing all the setup there, here it is maybe it could help someone:
// global-setup.tsx
import { v2 as compose } from 'docker-compose';
import * as path from 'path';
import { spawn, exec } from 'child_process';
import util from 'util';
/* eslint-disable */
var __TEARDOWN_MESSAGE__: string;
module.exports = async function () {
// Start services that that the app needs to run (e.g. database, docker-compose, etc.).
console.log('\nSetting up...\n');
const execAsync = util.promisify(exec);
const buildBackend = () => {
return new Promise((resolve, reject) => {
const dockerBuild = spawn('nx', ['docker-build', 'api-rest'], {
cwd: path.join(__dirname, '../../../../'),
});
dockerBuild.stdout.on('data', (data) => {
process.stdout.write(`${data}\n`);
});
dockerBuild.stderr.on('data', (data) => {
process.stderr.write(`${data}\n`);
});
dockerBuild.on('close', (code) => {
resolve(`Docker build process exited with code ${code}`);
});
dockerBuild.on('error', (err) => {
reject(`Failed to build docker image, ${err}`);
});
});
};
const startDatabase = async () => {
await compose.upAll({
cwd: path.join(__dirname, '../../../../libs/db'),
env: { UID: String(process.getuid()), GID: String(process.getgid()) },
callback: (chunk: Buffer) => {
console.log('job in progress: ', chunk.toString());
},
});
};
const startBackend = () => {
return new Promise(async (resolve, reject) => {
try {
const { stderr, stdout } = await execAsync(
'docker run --net host --env-file ./.env --name api-rest -d -t api-rest',
{
cwd: path.join(__dirname, '../../../api-rest'),
}
);
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
// NOTE: This is a workaround to give the container time to boot up.
setTimeout(resolve, 2000);
} catch (error) {
reject(error);
}
});
};
try {
await startDatabase();
const buildBackendResponse = await buildBackend();
console.log(buildBackendResponse);
await startBackend();
} catch (err) {
console.log(
'Something went wrong during docker compose boot-up:',
err.message
);
}
// Hint: Use `globalThis` to pass variables to global teardown.
globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n';
};
// global-teardown.ts
/* eslint-disable */
import path from 'path';
import { exec } from 'child_process';
import util from 'util';
import { v2 as compose } from 'docker-compose';
module.exports = async function () {
// Put clean up logic here (e.g. stopping services, docker-compose, etc.).
// Hint: `globalThis` is shared between setup and teardown.
const execAsync = util.promisify(exec);
const shutdownBackend = async () => {
const { stderr: stderrBackendStop, stdout: stdoutBackendStop } =
await execAsync('docker stop api-rest', {
cwd: path.join(__dirname, '../../../api-rest'),
});
console.log(`stdout: ${stdoutBackendStop}`);
console.error(`stderr: ${stderrBackendStop}`);
const { stderr: stderrBackendRemove, stdout: stdoutBackendRemove } =
await execAsync('docker container rm api-rest', {
cwd: path.join(__dirname, '../../../api-rest'),
});
console.log(`stdout: ${stdoutBackendRemove}`);
console.error(`stderr: ${stderrBackendRemove}`);
};
const stopDatabase = async () => {
await compose.down({
cwd: path.join(__dirname, '../../../../libs/db'),
commandOptions: [['--volumes'], ['--remove-orphans'], ['-t', '1']],
env: { UID: String(process.getuid()), GID: String(process.getgid()) },
callback: (chunk: Buffer) => {
console.log('job in progress: ', chunk.toString());
},
});
};
try {
await stopDatabase();
console.log('Database is shutdown.');
await shutdownBackend();
} catch (err) {
console.log(
'Something went wrong during docker compose shutdown:',
err.message
);
}
console.log(globalThis.__TEARDOWN_MESSAGE__);
};
I believe it's already provided. @john-james-gh , Your solution worked perfectly for me 👍. However, it can be improved to format the output on the console/terminal and make it readable by changing the
stdio: 'inherit'
tostdio:'pipe'
. Read more on this from nodejs child process// jest.config.ts
export default { displayName: 'api-e2e', globalSetup: '<rootDir>/src/support/global-setup.ts', globalTeardown: '<rootDir>/src/support/global-teardown.ts', ... };
// global-setup.ts
import { spawn } from 'child_process'; module.exports = async function () { // Start services that that the app needs to run (e.g. database, docker-compose, etc.). console.log('\nSetting up...\n'); // Start the API server const server = spawn('nx', ['serve', 'api'], { shell: true, //stdio: 'inherit', stdio:'pipe', }); // Store the server process in globalThis so it can be accessed in globalTeardown globalThis.__SERVER_PROCESS__ = server; // Hint: Use `globalThis` to pass variables to global teardown. globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n'; // You might want to wait for the server to be fully up before proceeding // This is a simplistic approach; consider polling a health endpoint instead await new Promise((resolve) => setTimeout(resolve, 5000)); };
// global-teardown.ts
module.exports = async function () { // Put clean up logic here (e.g. stopping services, docker-compose, etc.). // Hint: `globalThis` is shared between setup and teardown. console.log(globalThis.__TEARDOWN_MESSAGE__); // Stop the server process initiated in globalSetup if (globalThis.__SERVER_PROCESS__) { globalThis.__SERVER_PROCESS__.kill(); } };
This issue has been automatically marked as stale because it hasn't had any activity for 6 months. Many things may have changed within this time. The issue may have already been fixed or it may not be relevant anymore. If at this point, this is still an issue, please respond with updated information. It will be closed in 21 days if no further activity occurs. Thanks for being a part of the Nx community! 🙏
Description
Running e2e tests on the nx generated nestjs appication fails unless you have manually served the associated application before hand.
Desired behaviour
nestjs/ jests e2e applications should be able to serve their associated applications without intervention. I've seen cypress e2e executors specify a
devServerTarget
config input in the project.json file. I'm wondering if a similar setup can be made for nestjs e2e tests.Motivation
I am trying to run a command such as
yarn nx run-many -t e2e
in our ci without having to orchestrate serving at the same time, Automatically serving the application for the e2e with a single command will also make local testing easier. e2e tests for our fronted using playwright already work with the desired behaviour. e.g.yarn nx app-e2e:e2e
succeeds non-interactivelySuggested Implementation
Example project.json file for specifying what application should be served prior to and during the e2e execution
Alternate Implementations