@yathomasi/cypress-parallel
Version:
[](https://github.com/yathomasi/cypress-parallel/actions/workflows/build.yml) [ || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const fs_1 = require("fs");
const path_1 = __importDefault(require("path"));
const util_1 = __importDefault(require("util"));
const child_process_1 = __importDefault(require("child_process"));
const cypress_configuration_1 = require("@badeball/cypress-configuration");
const commander_1 = require("commander");
const is_npm_1 = require("is-npm");
const shell_quote_1 = require("shell-quote");
const ci_1 = require("./ci");
const configuration_1 = require("./configuration");
const debug_1 = __importDefault(require("./debug"));
const error_1 = require("./error");
const distribute_1 = require("./unweighed-strategies/distribute");
const estimate_1 = require("./unweighed-strategies/estimate");
const unweighed_strategy_1 = require("./unweighed-strategy");
const type_guards_1 = require("./type-guards");
const utils_1 = require("./unweighed-strategies/utils");
const package_json_1 = require("../package.json");
function determineCypressRunCommand(useYarn = false) {
if (useYarn) {
console.log("Running: yarn cypress run");
return "yarn cypress run";
}
if (is_npm_1.isNpm) {
console.log("Running: npx cypress run");
return "npx cypress run";
}
else if (is_npm_1.isYarn) {
console.log("Running: yarn cypress run");
return "yarn cypress run";
}
else {
throw new error_1.CypressParallelError("Unable to determine how to run Cypress, please specify a cypress run command");
}
}
function parseNodeConfiguration(value) {
const values = value.split(":");
if (values.length !== 2) {
throw new commander_1.InvalidArgumentError("Expected --node configuration matching <index>:<total>");
}
const [index, count] = values;
try {
return (0, configuration_1.parseAndValidateNodeConfiguration)(index, count);
}
catch (e) {
if (e instanceof configuration_1.NodeConfigurationParseError) {
throw new commander_1.InvalidArgumentError(e.message);
}
else {
throw e;
}
}
}
function parseUnweighedStrategy(value) {
if (value !== "estimate" && value !== "distribute") {
throw new commander_1.InvalidArgumentError("Valid unweighed strategies are 'estimate' and 'distribute'");
}
return value;
}
async function readKnapsack(filepath) {
const aboluteFilepath = path_1.default.isAbsolute(filepath)
? filepath
: path_1.default.join(process.cwd(), filepath);
let knapsackContent;
try {
knapsackContent = (await fs_1.promises.readFile(aboluteFilepath)).toString();
}
catch (e) {
if ((e === null || e === void 0 ? void 0 : e.code) === "ENOENT") {
const knapsackName = path_1.default.basename(aboluteFilepath);
console.warn(`Unable to find ${knapsackName}, continuing without it...`);
return {};
}
else {
throw new error_1.CypressParallelError(`Unable to read knapsack: ${e.message}`);
}
}
let maybeKnapsack;
try {
maybeKnapsack = JSON.parse(knapsackContent);
}
catch (_a) {
throw new error_1.CypressParallelError(`Knapsack isn't valid JSON, got ${util_1.default.inspect(knapsackContent)}`);
}
if ((0, type_guards_1.isKnapsack)(maybeKnapsack)) {
return maybeKnapsack;
}
else {
throw new error_1.CypressParallelError(`Knapsack is wrongly formatted, got ${util_1.default.inspect(maybeKnapsack)}`);
}
}
const program = new commander_1.Command();
program.version(`${package_json_1.name}-v${package_json_1.version}`, "-v, --version");
program.allowUnknownOption();
program.option("--use-yarn", "specifies which package manager to run the command", false);
program.option("--cypress-run-command <cmd>", "specifies the command to run cypress (in non-interactive mode), defaults to 'npx cypress run' or 'yarn cypress run' depending on how invoked");
program.option("--node <index>:<count>", "specifies number of buckets and which to run", parseNodeConfiguration);
program.option("--knapsack <path>", "specifies the path to the knapsack file", "knapsack.json");
program.option("--read-knapsack <path>", "specifies the path to the knapsack file to read");
program.option("--write-knapsack <path>", "specifies the path to the knapsack file to write");
program.option("--disable-knapsack-output", "disables knapsack output", false);
program.option("--unweighed-strategy <strategy>", "strategy to utilize for unweighed test files ('estimate' (default) | 'distribute')", parseUnweighedStrategy, "estimate");
async function run(argv, env, cwd) {
var _a, _b;
try {
program.parse(argv);
const cypressArgs = program.parseOptions(argv).unknown;
if (cypressArgs.includes("-s") || cypressArgs.includes("--spec")) {
throw new error_1.CypressParallelError("Unable to parallelize tests that are already scoped");
}
const options = program.opts();
const node = options.node || (0, ci_1.tryResolveNodeConfiguration)(env);
if (!node) {
throw new error_1.CypressParallelError("Unable to determine node index and node count, please specify --node <index>:<count>");
}
let parallelConfiguration = {
useYarn: options.useYarn,
cypressRunCommand: options.cypressRunCommand ||
determineCypressRunCommand(options.useYarn),
node,
unweighedStrategy: (await (0, unweighed_strategy_1.resolveCustomStrategy)()) || options.unweighedStrategy,
knapsack: options.knapsack,
readKnapsack: options.readKnapsack,
writeKnapsack: options.writeKnapsack,
disableKnapsackOutput: options.disableKnapsackOutput,
};
const cypressConfiguration = (0, cypress_configuration_1.getConfiguration)({
testingType: "e2e",
argv,
env,
cwd,
});
const reporterOptions = ["-r", "--reporter", "-o", "--reporter-options"];
for (const reporterOption of reporterOptions) {
if (cypressArgs.includes(reporterOption)) {
parallelConfiguration = Object.assign(Object.assign({}, parallelConfiguration), { disableKnapsackOutput: true });
break;
}
}
let unweighedStrategy;
if (parallelConfiguration.unweighedStrategy === "estimate") {
unweighedStrategy = estimate_1.estimate;
}
else if (parallelConfiguration.unweighedStrategy === "distribute") {
unweighedStrategy = distribute_1.distribute;
}
else {
unweighedStrategy = parallelConfiguration.unweighedStrategy;
}
const knapsack = await readKnapsack((_a = parallelConfiguration.readKnapsack) !== null && _a !== void 0 ? _a : parallelConfiguration.knapsack);
const testFiles = (0, cypress_configuration_1.getTestFiles)(cypressConfiguration);
const weighedFiles = await Object.entries(knapsack).reduce(async (weighedFilesPromise, entry) => {
const weighedFiles = await weighedFilesPromise;
const file = path_1.default.join(cypressConfiguration.projectRoot, entry[0]);
if (!testFiles.includes(file)) {
return weighedFiles;
}
try {
await fs_1.promises.access(file, fs_1.constants.F_OK);
}
catch (_a) {
return weighedFiles;
}
return [
...weighedFiles,
{
file,
content: (await fs_1.promises.readFile(file)).toString(),
weight: entry[1],
},
];
}, Promise.resolve([]));
const unweighedFiles = await testFiles
.map((testFile) => path_1.default.relative(cypressConfiguration.projectRoot, testFile))
.filter((testFile) => !Object.keys(knapsack).includes(testFile))
.reduce(async (testFilesPromise, testFile) => {
const testFiles = await testFilesPromise;
const file = path_1.default.join(cypressConfiguration.projectRoot, testFile);
return [
...testFiles,
{
file,
content: (await fs_1.promises.readFile(file)).toString(),
},
];
}, Promise.resolve([]));
const schedule = unweighedStrategy(weighedFiles, unweighedFiles, parallelConfiguration.node.count);
(0, debug_1.default)(`Schedule determined ${util_1.default.inspect(schedule.map((group) => group.map((file) => file.file)))}`);
/**
* Validate the generated schedule.
*/
const outputFiles = schedule
.flatMap((node) => node.map((file) => file.file))
.sort(utils_1.compare);
for (const testFile of testFiles) {
if (!outputFiles.includes(testFile)) {
const relativePath = path_1.default.relative(cypressConfiguration.projectRoot, testFile);
throw new error_1.CypressParallelError(`Test file ${relativePath} wasn't distributed by the configured strategy`);
}
}
for (const outputFile of outputFiles) {
if (!testFiles.includes(outputFile)) {
const relativePath = path_1.default.relative(cypressConfiguration.projectRoot, outputFile);
throw new error_1.CypressParallelError(`The configured strategy produced ${relativePath}, which wasn't part of the input`);
}
}
const testFilesForNode = schedule[parallelConfiguration.node.index - 1].map((testFile) => path_1.default.relative(cypressConfiguration.projectRoot, testFile.file));
// log the cypress run command
console.log(`cypressRunCommand: ${parallelConfiguration.cypressRunCommand}`);
const parsedRunCmd = (0, shell_quote_1.parse)(parallelConfiguration.cypressRunCommand);
// log parsed cypress run command
console.log(`parsedRunCmd: ${parsedRunCmd}`);
if (!parsedRunCmd.every(type_guards_1.isString)) {
throw new Error(`Expected a run command without shell operators (such as '&&'), but go ${util_1.default.inspect(parallelConfiguration.cypressRunCommand)}`);
}
const reporterArgs = parallelConfiguration.disableKnapsackOutput
? []
: [
"--reporter",
"cypress-multi-reporters",
"--reporter-options",
JSON.stringify({
reporterEnabled: "spec, @yathomasi/cypress-parallel/knapsack-reporter",
yathomasiCypressParallelKnapsackReporterReporterOptions: {
output: (_b = parallelConfiguration.writeKnapsack) !== null && _b !== void 0 ? _b : parallelConfiguration.knapsack,
},
}),
];
const [cmd, ...args] = parsedRunCmd;
const fullArgs = [
...args,
...cypressArgs,
...reporterArgs,
"--spec",
testFilesForNode.join(","),
];
(0, debug_1.default)(`Running ${util_1.default.inspect(cmd)} with ${util_1.default.inspect(fullArgs)}`);
const proc = child_process_1.default.spawn(cmd, fullArgs, {
stdio: "inherit",
});
proc.on("exit", function (code, signal) {
process.on("exit", function () {
if (signal) {
process.kill(process.pid, signal);
}
else if (code) {
process.exitCode = code;
}
});
});
process.on("SIGINT", () => proc.kill("SIGINT"));
process.on("SIGTERM", () => proc.kill("SIGTERM"));
}
catch (e) {
if (e instanceof error_1.CypressParallelError) {
console.error(e.message);
}
else if (e instanceof Error) {
console.error(e.stack);
}
else {
console.error(util_1.default.inspect(e));
}
process.exitCode = 1;
}
}
exports.run = run;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLDJCQUE4RDtBQUU5RCxnREFBd0I7QUFFeEIsZ0RBQXdCO0FBRXhCLGtFQUEwQztBQUUxQywyRUFHeUM7QUFFekMseUNBQTBEO0FBRTFELG1DQUF1QztBQUV2Qyw2Q0FBb0M7QUFFcEMsNkJBQW1EO0FBRW5ELG1EQUl5QjtBQUV6QixvREFBNEI7QUFFNUIsbUNBQStDO0FBRS9DLGtFQUErRDtBQUUvRCw4REFBMkQ7QUFFM0QsNkRBSThCO0FBRTlCLCtDQUFxRDtBQUVyRCx3REFBdUQ7QUFFdkQsa0RBQWdEO0FBRWhELFNBQVMsMEJBQTBCLENBQUMsVUFBbUIsS0FBSztJQUMxRCxJQUFJLE9BQU8sRUFBRTtRQUNYLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUN6QyxPQUFPLGtCQUFrQixDQUFDO0tBQzNCO0lBQ0QsSUFBSSxjQUFLLEVBQUU7UUFDVCxPQUFPLENBQUMsR0FBRyxDQUFDLDBCQUEwQixDQUFDLENBQUM7UUFDeEMsT0FBTyxpQkFBaUIsQ0FBQztLQUMxQjtTQUFNLElBQUksZUFBTSxFQUFFO1FBQ2pCLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUN6QyxPQUFPLGtCQUFrQixDQUFDO0tBQzNCO1NBQU07UUFDTCxNQUFNLElBQUksNEJBQW9CLENBQzVCLDhFQUE4RSxDQUMvRSxDQUFDO0tBQ0g7QUFDSCxDQUFDO0FBRUQsU0FBUyxzQkFBc0IsQ0FBQyxLQUFhO0lBQzNDLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFaEMsSUFBSSxNQUFNLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRTtRQUN2QixNQUFNLElBQUksZ0NBQW9CLENBQzVCLHdEQUF3RCxDQUN6RCxDQUFDO0tBQ0g7SUFFRCxNQUFNLENBQUMsS0FBSyxF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