188 lines
5.4 KiB
TypeScript
188 lines
5.4 KiB
TypeScript
/* eslint-disable no-console */
|
|
import globby from "globby";
|
|
import minimatch from "minimatch";
|
|
import { exec } from "child_process";
|
|
|
|
const fs = require("fs/promises");
|
|
|
|
type GetEnvOptions = {
|
|
required?: boolean;
|
|
};
|
|
|
|
// some default properties
|
|
const ignoreTestFiles = [
|
|
"cypress/e2e/**/spec_utility.ts",
|
|
"cypress/e2e/GSheet/**/*",
|
|
];
|
|
|
|
// used to roughly determine how many tests are in a file
|
|
const testPattern = /(^|\s)(it)\(/g;
|
|
|
|
// This function will get all the spec paths using the pattern
|
|
async function getSpecFilePaths(specPattern: any): Promise<string[]> {
|
|
const files = globby.sync(specPattern, {
|
|
ignore: ignoreTestFiles,
|
|
});
|
|
|
|
// ignore the files that doesn't match
|
|
const ignorePatterns = [...(ignoreTestFiles || [])];
|
|
|
|
// a function which returns true if the file does NOT match
|
|
const doesNotMatchAllIgnoredPatterns = (file: string) => {
|
|
// using {dot: true} here so that folders with a '.' in them are matched
|
|
const MINIMATCH_OPTIONS = { dot: true, matchBase: true };
|
|
return ignorePatterns.every((pattern) => {
|
|
return !minimatch(file, pattern, MINIMATCH_OPTIONS);
|
|
});
|
|
};
|
|
const filtered = files.filter(doesNotMatchAllIgnoredPatterns);
|
|
return filtered;
|
|
}
|
|
|
|
// This function will determine the test counts in each file to sort it further
|
|
async function getTestCount(filePath: string): Promise<number> {
|
|
const content = await fs.readFile(filePath, "utf8");
|
|
return content.match(testPattern)?.length || 0;
|
|
}
|
|
|
|
// Sorting the spec files as per the test count in it
|
|
async function sortSpecFilesByTestCount(
|
|
specPathsOriginal: string[],
|
|
): Promise<string[]> {
|
|
const specPaths = [...specPathsOriginal];
|
|
|
|
const testPerSpec: Record<string, number> = {};
|
|
|
|
for (const specPath of specPaths) {
|
|
testPerSpec[specPath] = await getTestCount(specPath);
|
|
}
|
|
|
|
return (
|
|
Object.entries(testPerSpec)
|
|
// Sort by the number of tests per spec file. And this will create a consistent file list/ordering so that file division proper.
|
|
.sort((a, b) => b[1] - a[1])
|
|
.map((x) => x[0])
|
|
);
|
|
}
|
|
|
|
// This function will split the specs between the runners by calculating the modulus between spec index and the totalRunners
|
|
function splitSpecs(
|
|
specs: string[],
|
|
totalRunnersCount: number,
|
|
currentRunner: number,
|
|
): string[] {
|
|
let specs_to_run = specs.filter((_, index) => {
|
|
return index % totalRunnersCount === currentRunner;
|
|
});
|
|
return specs_to_run;
|
|
}
|
|
|
|
// This function will finally get the specs as a comma separated string to pass the specs to the command
|
|
async function getSpecsToRun(
|
|
totalRunnersCount = 0,
|
|
currentRunner = 0,
|
|
specPattern = "cypress/e2e/**/**/*.{js,ts}",
|
|
): Promise<string> {
|
|
{
|
|
try {
|
|
const specFilePaths = await sortSpecFilesByTestCount(
|
|
await getSpecFilePaths(specPattern),
|
|
);
|
|
|
|
if (!specFilePaths.length) {
|
|
throw Error("No spec files found.");
|
|
}
|
|
const specsToRun = splitSpecs(
|
|
specFilePaths,
|
|
totalRunnersCount,
|
|
currentRunner,
|
|
);
|
|
return specsToRun.join(",");
|
|
} catch (err) {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This function will help to get and convert the env variables
|
|
function getEnvNumber(
|
|
varName: string,
|
|
{ required = false }: GetEnvOptions = {},
|
|
): number {
|
|
if (required && process.env[varName] === undefined) {
|
|
throw Error(`${varName} is not set.`);
|
|
}
|
|
const value = Number(process.env[varName]);
|
|
if (isNaN(value)) {
|
|
throw Error(`${varName} is not a number.`);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// This function will helps to check and get env variables
|
|
function getEnvValue(
|
|
varName: string,
|
|
{ required = false }: GetEnvOptions = {},
|
|
) {
|
|
if (required && process.env[varName] === undefined) {
|
|
throw Error(`${varName} is not set.`);
|
|
}
|
|
const value = process.env[varName] === undefined ? "" : process.env[varName];
|
|
return value;
|
|
}
|
|
|
|
// This is to fetch the env variables from CI
|
|
function getArgs() {
|
|
return {
|
|
totalRunners: getEnvNumber("TOTAL_RUNNERS", { required: true }),
|
|
thisRunner: getEnvNumber("THIS_RUNNER", { required: true }),
|
|
specsPattern: getEnvValue("CYPRESS_SPEC_PATTERN", { required: true }),
|
|
env: getEnvValue("CYPRESS_ENV", { required: false }),
|
|
configFile: getEnvValue("CYPRESS_CONFIG_FILE", { required: false }),
|
|
headless: getEnvValue("CYPRESS_HEADLESS", { required: false }),
|
|
browser: getEnvValue("CYPRESS_BROWSER", { required: false }),
|
|
};
|
|
}
|
|
|
|
// This will finally segregate the specs and run the command to execute cypress
|
|
(async () => {
|
|
try {
|
|
const {
|
|
browser,
|
|
configFile,
|
|
env,
|
|
headless,
|
|
specsPattern,
|
|
thisRunner,
|
|
totalRunners,
|
|
} = getArgs();
|
|
|
|
let command = "yarn cypress run ";
|
|
command += browser != "" ? `--browser ${browser} ` : "";
|
|
command += configFile != "" ? `--config-file ${configFile} ` : "";
|
|
command += env != "" ? `--env ${env} ` : "";
|
|
command += headless == "false" ? `--headed ` : "";
|
|
|
|
const specs = await getSpecsToRun(totalRunners, thisRunner, specsPattern);
|
|
const final_command = `${command} --spec "${specs}"`;
|
|
const commandProcess = exec(final_command);
|
|
|
|
// pipe output because we want to see the results of the run
|
|
if (commandProcess.stdout) {
|
|
commandProcess.stdout.pipe(process.stdout);
|
|
}
|
|
|
|
if (commandProcess.stderr) {
|
|
commandProcess.stderr.pipe(process.stderr);
|
|
}
|
|
|
|
commandProcess.on("exit", (code) => {
|
|
process.exit(code || 0);
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
process.exit(1);
|
|
}
|
|
})();
|