mirror of
				https://github.com/strapi/strapi.git
				synced 2025-10-31 18:08:11 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			244 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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);
 | |
|               /**
 | |
|                * Always build! Just incase.
 | |
|                */
 | |
|               console.log(`building admin panel for test app at path: ${chalk.bold(appPath)}`);
 | |
|               await execa('yarn', ['strapi', 'build'], {
 | |
|                 stdio: 'inherit',
 | |
|                 cwd: appPath,
 | |
|               });
 | |
|             })
 | |
|           );
 | |
| 
 | |
|           console.log(
 | |
|             `${chalk.green('Successfully')} setup test apps for the following domains: ${chalk.bold(
 | |
|               domains.join(', ')
 | |
|             )}`
 | |
|           );
 | |
|         } else {
 | |
|           console.log(
 | |
|             `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,
 | |
|                     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();
 | 
