@badeball/cypress-parallel
Version:
[](https://github.com/badeball/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() {
if (is_npm_1.isNpm) {
return "npx cypress run";
}
else if (is_npm_1.isYarn) {
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("--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 = {
cypressRunCommand: options.cypressRunCommand || determineCypressRunCommand(),
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));
const parsedRunCmd = (0, shell_quote_1.parse)(parallelConfiguration.cypressRunCommand);
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, @badeball/cypress-parallel/knapsack-reporter",
badeballCypressParallelKnapsackReporterReporterOptions: {
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiY2xpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLDJCQUE4RDtBQUU5RCxnREFBd0I7QUFFeEIsZ0RBQXdCO0FBRXhCLGtFQUEwQztBQUUxQywyRUFHeUM7QUFFekMseUNBQTBEO0FBRTFELG1DQUF1QztBQUV2Qyw2Q0FBb0M7QUFFcEMsNkJBQW1EO0FBRW5ELG1EQUl5QjtBQUV6QixvREFBNEI7QUFFNUIsbUNBQStDO0FBRS9DLGtFQUErRDtBQUUvRCw4REFBMkQ7QUFFM0QsNkRBSThCO0FBRTlCLCtDQUFxRDtBQUVyRCx3REFBdUQ7QUFFdkQsa0RBQWdEO0FBRWhELFNBQVMsMEJBQTBCO0lBQ2pDLElBQUksY0FBSyxFQUFFO1FBQ1QsT0FBTyxpQkFBaUIsQ0FBQztLQUMxQjtTQUFNLElBQUksZUFBTSxFQUFFO1FBQ2pCLE9BQU8sa0JBQWtCLENBQUM7S0FDM0I7U0FBTTtRQUNMLE1BQU0sSUFBSSw0QkFBb0IsQ0FDNUIsOEVBQThFLENBQy9FLENBQUM7S0FDSDtBQUNILENBQUM7QUFFRCxTQUFTLHNCQUFzQixDQUFDLEtBQWE7SUFDM0MsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUVoQyxJQUFJLE1BQU0sQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFO1FBQ3ZCLE1BQU0sSUFBSSxnQ0FBb0IsQ0FDNUIsd0RBQXdELENBQ3pELENBQUM7S0FDSDtJQUVELE1BQU0sQ0FBQyxLQUFLLEVBQUUsS0FBSyxDQUFDLEdBQUcsTUFBTSxDQUFDO0lBRTlCLElBQUk7UUFDRixPQUFPLElBQUEsaURBQWlDLEVBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO0tBQ3hEO0lBQUMsT0FBTyxDQUFDLEVBQUU7UUFDVixJQUFJLENBQUMsWUFBWSwyQ0FBMkIsRUFBRTtZQUM1QyxNQUFNLElBQUksZ0NBQW9CLENBQUMsQ0FBQyxDQUFDLE9BQU8s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