strapi/test/scripts/run-e2e-tests.js

236 lines
7.4 KiB
JavaScript
Raw Normal View History

'use strict';
const path = require('path');
const execa = require('execa');
const fs = require('node:fs/promises');
const yargs = require('yargs');
const { cleanTestApp, generateTestApp } = require('../helpers/test-app');
const { createConfig } = require('../../playwright.base.config');
const chalk = require('chalk');
const cwd = path.resolve(__dirname, '../..');
const testAppDirectory = path.join(cwd, 'test-apps', 'e2e');
yargs
.parserConfiguration({
/**
* This lets us pass any other arguments to playwright
* e.g. the name of a specific test or the project we want to run
*/
'unknown-options-as-args': true,
})
.command({
command: '*',
description: 'run the E2E test suite',
builder: async (yarg) => {
const domains = await fs.readdir(path.join(cwd, 'e2e', 'tests'));
yarg.option('concurrency', {
alias: 'c',
type: 'number',
default: domains.length,
describe:
'Number of concurrent test apps to run, a test app runs an entire test suite domain',
});
yarg.option('domains', {
alias: 'd',
describe: 'Run a specific test suite domain',
type: 'array',
choices: domains,
default: domains,
});
yarg.option('setup', {
alias: 'f',
describe: 'Force the setup process of the test apps',
type: 'boolean',
default: false,
});
},
handler: async (argv) => {
try {
const { concurrency, domains, setup } = argv;
/**
* We don't need to spawn more apps than we have domains,
* but equally if someone sets the concurrency to 1
* then we should only spawn one and run every domain on there.
*/
const testAppsToSpawn = Math.min(domains.length, concurrency);
if (testAppsToSpawn === 0) {
throw new Error('No test apps to spawn');
}
const testAppPaths = Array.from({ length: testAppsToSpawn }, (_, i) =>
path.join(testAppDirectory, `test-app-${i}`)
);
let currentTestApps = [];
try {
currentTestApps = await fs.readdir(testAppDirectory);
} catch (err) {
// no test apps exist, okay to fail silently
}
/**
* If we don't have enough test apps, we make enough.
* You can also force this setup if desired, e.g. you
* update the app-template.
*/
if (setup || currentTestApps.length < testAppsToSpawn) {
/**
* this will effectively clean the entire directory before hand
* as opposed to cleaning the ones we aim to spawn.
*/
await Promise.all(
currentTestApps.map(async (testAppName) => {
const appPath = path.join(testAppDirectory, testAppName);
console.log(`cleaning test app at path: ${chalk.bold(appPath)}`);
await cleanTestApp(appPath);
})
);
await Promise.all(
testAppPaths.map(async (appPath) => {
console.log(`generating test apps at path: ${chalk.bold(appPath)}`);
await generateTestApp({
appPath,
database: {
client: 'sqlite',
connection: {
filename: './.tmp/data.db',
},
useNullAsDefault: true,
},
template: path.join(cwd, 'e2e', 'app-template'),
link: true,
});
/**
* Because we're running multiple test apps at the same time
* and the env file is generated by the generator with no way
* to override it, we manually remove the PORT key/value so when
* we set it further down for each playwright instance it works.
*/
const pathToEnv = path.join(appPath, '.env');
const envFile = (await fs.readFile(pathToEnv)).toString();
const envWithoutPort = envFile.replace('PORT=1337', '');
await fs.writeFile(pathToEnv, envWithoutPort);
})
);
console.log(
`${chalk.green('Successfully')} setup test apps for the following domains: ${chalk.bold(
domains.join(', ')
)}`
);
} else {
console.log(
2023-07-19 16:34:58 +01:00
`Skipping setting up test apps, use ${chalk.bold('--setup')} to force the setup process`
);
}
/**
* You can't change the webserver configuration of playwright directly so they'd
* all be looking at the same test app which we don't want, instead we'll generate
* a playwright config based off the base one
*/
const chunkedDomains = domains.reduce((acc, _, i) => {
if (i % testAppsToSpawn === 0) acc.push(domains.slice(i, i + testAppsToSpawn));
return acc;
}, []);
for (let i = 0; i < chunkedDomains.length; i++) {
const domains = chunkedDomains[i];
await Promise.all(
domains.map(async (domain, j) => {
const testAppPath = testAppPaths[j];
const port = 8000 + j;
const pathToPlaywrightConfig = path.join(testAppPath, 'playwright.config.js');
console.log(
`Creating playwright config for domain: ${chalk.blue(
domain
)}, at path: ${chalk.yellow(testAppPath)}`
);
const config = createConfig({
testDir: path.join(cwd, 'e2e', 'tests', domain),
port,
appDir: testAppPath,
});
const configFileTemplate = `
const config = ${JSON.stringify(config)}
module.exports = config
`;
await fs.writeFile(pathToPlaywrightConfig, configFileTemplate);
console.log(`Running ${chalk.blue(domain)} e2e tests`);
await execa(
'yarn',
['playwright', 'test', '--config', pathToPlaywrightConfig, ...argv._],
{
stdio: 'inherit',
cwd,
env: {
PORT: port,
2023-07-19 16:34:58 +01:00
HOST: '127.0.0.1',
},
}
);
})
);
}
} catch (err) {
console.error(chalk.red('Error running e2e tests:'));
/**
* This is a ExecaError, if we were in TS we could do `instanceof`
*/
if (err.shortMessage) {
console.error(err.shortMessage);
process.exit(1);
}
console.error(err);
process.exit(1);
}
},
})
.command({
command: 'clean',
description: 'clean the test app directory of all test apps',
handler: async () => {
try {
const currentTestApps = await fs.readdir(testAppDirectory);
if (currentTestApps.length === 0) {
console.log('No test apps to clean');
return;
}
await Promise.all(
currentTestApps.map(async (testAppName) => {
const appPath = path.join(testAppDirectory, testAppName);
console.log(`cleaning test app at path: ${chalk.bold(appPath)}`);
await cleanTestApp(appPath);
})
);
} catch (err) {
console.error(chalk.red('Error cleaning test apps:'));
console.error(err);
process.exit(1);
}
},
})
.help()
.parse();