UNPKG

cypress-parallel-extended

Version:

Reduce up to 40% your Cypress suite execution time parallelizing the test run on the same machine.

146 lines (123 loc) 4.41 kB
import { spawn } from 'cross-spawn'; import fs from 'fs'; import globEscape from 'glob-escape'; import { isYarn } from 'is-npm'; import camelCase from 'lodash.camelcase'; import path from 'path'; import { settings } from './settings.js'; function getPackageManager() { return isYarn ? 'yarn' : process.platform === 'win32' ? 'npm.cmd' : 'npm'; } async function createReporterConfigFile(index) { const reporterConfigPath = path.join( process.cwd(), `multi-reporter-config-${index}.json` ); const defaultReporters = [ 'cypress-parallel-extended/json-stream.reporter.cjs', 'cypress-parallel-extended/simple-spec.reporter.cjs', ]; const reporterEnabled = settings.reporter ? ['cypress-parallel-extended/json-stream.reporter.cjs', settings.reporter] : defaultReporters; const content = { reporterEnabled: reporterEnabled.join(', '), }; reporterEnabled.forEach((reporter) => { let optionName; if (reporter === 'cypress-parallel-extended/json-stream.reporter.cjs') { optionName = 'cypressParallelExtendedJsonStreamReporterCjsReporterOptions'; } else if (reporter === 'cypress-parallel-extended/simple-spec.reporter.cjs') { optionName = 'cypressParallelExtendedSimpleSpecReporterCjsReporterOptions'; } else { optionName = `${camelCase(reporter)}ReporterOptions`; } content[optionName] = { reportDir: settings.runnerResults || 'runner-results', }; }); // Handling user config if the file exists try { if (settings.reporterOptionsPath) { await fs.promises.access(settings.reporterOptionsPath, fs.constants.F_OK); const userConfig = JSON.parse( await fs.promises.readFile(settings.reporterOptionsPath, 'utf-8') ); const userReporters = userConfig.reporterEnabled ? userConfig.reporterEnabled.split(', ') : []; content.reporterEnabled = Array.from( new Set([...content.reporterEnabled.split(', '), ...userReporters]) ).join(', '); Object.keys(userConfig).forEach((key) => { if (key !== 'reporterEnabled') { content[key] = { ...(content[key] || {}), ...userConfig[key], }; } }); } } catch (err) { console.error('Error reading user config file:', err); } // Ensure the path is passed as a string, not an array await fs.promises.writeFile(reporterConfigPath, JSON.stringify(content, null, 2)); return reporterConfigPath; // Ensure it's a string path } async function createCommandArguments(thread, index) { const specFiles = thread.list.map((path) => globEscape(path)).join(','); const childOptions = [ 'run', `${settings.script}`, isYarn ? '' : '--', '--spec', specFiles, ]; // Make sure this is a string path, not an array const reporterConfigPath = await createReporterConfigFile(index); childOptions.push('--reporter', settings.reporterModulePath); childOptions.push('--reporter-options', `configFile=${reporterConfigPath}`); childOptions.push(...settings.scriptArguments); return childOptions; } async function executeThread(thread, index) { const packageManager = getPackageManager(); const commandArguments = await createCommandArguments(thread, index); console.log(`Thread ${index + 1}: Executing with ${thread.list.length} spec(s)`); if (settings.isVerbose) { console.log(`Thread ${index + 1} specs:`, thread.list); console.log(`Thread ${index + 1} command:`, [packageManager, ...commandArguments].join(' ')); } const timeMap = new Map(); const promise = new Promise((resolve, reject) => { const processOptions = { cwd: process.cwd(), stdio: 'inherit', env: { ...process.env, CYPRESS_THREAD: (index + 1).toString(), }, }; const child = spawn(packageManager, commandArguments, processOptions); child.on('exit', (exitCode) => { if (settings.isVerbose) { console.log( `Thread ${index} likely finished with failure count: ${exitCode}` ); } if (settings.shouldBail && exitCode > 0) { console.error( 'BAIL set and thread exited with errors, exit early with error' ); process.exit(exitCode); } resolve(timeMap); }); child.on('error', (err) => { reject(err); }); }); return promise; } export { executeThread };