tm-playwright-framework
Version:
Playwright Cucumber TS framework - The easiest way to learn
332 lines (331 loc) • 12.1 kB
JavaScript
import minimist from 'minimist';
import { spawn } from 'child_process';
import fs from "fs-extra";
let additionalArgs;
const SUPPORTED_BROWSERS = ["chrome", "firefox", "webkit"];
const args = minimist(process.argv.slice(2));
let BROWSER_VERSION, EXEC_ENV, EXEC_TAGS, EXEC_FEATURES, EXEC_BROWSER, EXEC_HEADLESS, EXEC_REPORTPATH, script;
function cartesianProduct(arrays) {
return arrays.reduce((acc, curr) => {
const res = [];
acc.forEach(a => {
curr.forEach(b => {
res.push([...a, b]);
});
});
return res;
}, [[]]);
}
async function runCommand(cmd, envVars = {}) {
return new Promise((resolve, reject) => {
const child = spawn(cmd, {
shell: true,
stdio: 'inherit',
env: { ...process.env, ...envVars }
});
child.on('close', code => {
if (code !== 0)
reject(new Error(`Failed with code ${code}`));
else
resolve(undefined);
});
child.on('error', reject);
});
}
export async function parseCommandLineArgs(args) {
additionalArgs = args;
// Validating Features argument
let featurePath = args["FEATURES"];
if (featurePath) {
const featurePaths = featurePath
.split(',')
.map(path => path.trim())
.filter(Boolean);
featurePaths.forEach(fturePath => {
// Check if the provided directory exists within the project folder
if (fs.existsSync(fturePath)) {
// If the directory exists, use it
console.log(`Info: Using features from directory '${fturePath}'.`);
}
else {
// If the directory doesn't exist or is not a directory
console.error(`Error: The directory '${fturePath}' does not exist or is not a valid directory.`);
process.exit(1); // Exit with an error code
}
EXEC_FEATURES = featurePaths;
});
}
else {
// Default behavior if FEATURES argument isn't provided
const defaultPath = "app/test/features/";
EXEC_FEATURES = defaultPath;
console.log(`Info: No FEATURES argument has been passed. Defaulted to run all features under folder '${defaultPath}'`);
// Validate default directory exists
if (fs.existsSync(defaultPath) && fs.lstatSync(defaultPath).isDirectory()) {
}
else {
// If the default directory doesn't exist
console.error(`Error: The default directory '${defaultPath}' does not exist. Please check the path and try again.`);
process.exit(1); // Exit with an error code
}
}
// Validating Tags argument
if (args["TAGS"]) {
EXEC_TAGS = args["TAGS"];
}
else
console.log("Info: No TAGS argument has been passed.");
// Validating Browser argument
if (args["BROWSER"]) {
args["BROWSER"].split(",").forEach(brwsr => {
if (!SUPPORTED_BROWSERS.includes(brwsr.toLowerCase())) {
console.warn(`Warning: The browser '${brwsr}' is invalid. Defaulting to 'chrome'. Supported browsers are : ${SUPPORTED_BROWSERS}`);
}
});
EXEC_BROWSER = args["BROWSER"];
}
else {
console.log("Info: No BROWSER argument has been passed. Defaulted to run with Chrome");
}
// Validating Headless argument
if (args["HEADLESS"]) {
if (["true", "false"].includes(args["HEADLESS"])) {
EXEC_HEADLESS = args["HEADLESS"];
}
else {
EXEC_HEADLESS = "true";
console.warn("Warning: Invalid value for HEADLESS. Defaulting to 'true'.");
}
}
else {
EXEC_HEADLESS = "true";
console.log("Info: No HEADLESS argument has been passed. Defaulted to true");
}
// Validating Client argument
if (args["CLIENT"]) {
process.env.CLIENT = `${args["CLIENT"]}`;
}
// Validating Report Path argument
if (args["REPORT_PATH"]) {
EXEC_REPORTPATH = args["REPORT_PATH"];
process.env.REPORT_PATH = `${args["REPORT_PATH"]}`;
}
// Validating Report Path argument
if (args["PARALLEL_WORKER"]) {
}
}
async function runMatrix(concurrency = 2) {
// Separate array and scalar args
await parseCommandLineArgs(args);
const arrayParams = {};
const scalarParams = {};
Object.entries(args).forEach(([key, value]) => {
if (key === '_')
return;
if (typeof value === 'string' && value.includes(',')) {
arrayParams[key] = value.split(',').map(s => s.trim());
}
else {
scalarParams[key] = value;
}
});
// Build matrix keys and arrays for cartesian product
const keys = Object.keys(arrayParams);
const arrays = Object.values(arrayParams);
console.log(`Scalar parameters: ${JSON.stringify(scalarParams)}`);
console.log(`Array parameters: ${JSON.stringify(arrayParams)}`);
// Cartesian product of array args, or empty if no arrays
const combos = keys.length > 0 ? cartesianProduct(arrays) : [[]];
// Build test matrix entries
const matrix = combos.map(combo => {
const comboObj = {};
combo.forEach((val, idx) => {
comboObj[keys[idx]] = val;
});
return { ...scalarParams, ...comboObj };
});
console.log(`Running ${matrix.length} test combinations with concurrency ${concurrency}`);
// Simple concurrency pool to limit parallel runs
let running = 0;
let index = 0;
return new Promise((resolve, reject) => {
const results = [];
const errors = [];
const runNext = () => {
if (index >= matrix.length && running === 0) {
if (errors.length) {
console.error(`${errors.length} test run(s) failed.`);
errors.forEach(e => console.error(`Failure for: ${JSON.stringify(e.entry)}\nReason: ${e.error.message}`));
reject(errors);
}
else
resolve(results);
return;
}
while (running < concurrency && index < matrix.length) {
const entry = matrix[index++];
running++;
// Build environment variables or CLI flags
// Here we build env vars for the child process
let envVars = {};
Object.entries(entry).forEach(([k, v]) => {
envVars[k.toUpperCase()] = v;
});
if (matrix.length > 1)
envVars['REPORT_PREFIX'] = `parallel-reports/Thread-${index}/`;
// Command to run your test script - change this as needed
// Using npm script with -- to pass args, or you can modify as environment variables
const cmd = `npx cross-env cucumber-js --config=node_modules/tm-playwright-framework/dist/config/cucumber.js`;
console.log(`Starting test run with params: ${JSON.stringify(entry)}`);
runCommand(cmd, envVars)
.then(() => {
console.log(`Test run succeeded: ${JSON.stringify(entry)}`);
results.push(entry);
})
.catch((err) => {
console.error(`Test run failed: ${JSON.stringify(entry)} - ${err.message}`);
errors.push({ entry, error: err });
})
.finally(() => {
running--;
runNext();
});
}
};
runNext();
});
}
// Running Pre-Test
async function preScript() {
try {
console.log("npm run build");
await runScript("npm run build && cls");
console.log("Build step is completed successfully");
await runScript("npx ts-node node_modules/tm-playwright-framework/dist/report/init.js");
console.log("Pre-test has been completed successfully");
}
catch (error) {
console.log("Pre-test has been completed with the following error:");
console.error(error);
}
return;
}
// Get browser version
async function getBrowserVersion() {
process.env.BROWSER = `${EXEC_BROWSER}`;
// const BROWSER = await invokeBrowser(EXEC_BROWSER);
// BROWSER_VERSION = BROWSER.version();
// console.log(BROWSER_VERSION);
// BROWSER.close();
return;
}
// Running Post-Test
async function postScript() {
// Running Post-Test
try {
await getBrowserVersion();
let script = `npx ts-node node_modules/tm-playwright-framework/dist/report/report.js ENV=${EXEC_ENV} `;
if (EXEC_FEATURES) {
script += ` FEATURES=${EXEC_FEATURES} `;
}
else {
script += ` FEATURES=} `;
}
if (args["TAGS"]) {
script += ` TAGS="${EXEC_TAGS}" `;
}
else {
script += ` TAGS=`;
}
if (args["BROWSER"]) {
script += ` BROWSER=${EXEC_BROWSER} `;
}
else
script += ` BROWSER=Chrome`;
if (args["HEADLESS"]) {
script += ` HEADLESS=${EXEC_HEADLESS} `;
}
else {
script += ` HEADLESS=false`;
}
if (BROWSER_VERSION) {
script += ` VERSION=${BROWSER_VERSION.split(".")[0]}`;
}
else {
script += ` VERSION= `;
}
if (args["CLIENT"]) {
script += ` CLIENT=${args["CLIENT"]} `;
process.env.CLIENT = `${args["CLIENT"]}`;
}
script += ` REPORT_PATH=${EXEC_REPORTPATH || 'parallel-reports'}`;
console.log(script);
await runScript(script);
console.log("Post-test execution has been completed successfully");
return;
}
catch (error) {
console.log("Post-test execution is completed with the following error:");
console.error(error);
return;
}
}
// Function to run a script and wait for it to complete
async function runScript(scriptName, envVars = {}) {
// Execute Script
process.env.CUCUMBER_TEST = "true";
return new Promise((resolve, reject) => {
const script = spawn(scriptName, [], {
env: { ...process.env, ...envVars },
shell: true,
});
// Output Listener
script.stdout.on("data", (data) => {
console.log(`stdout: ${data}`);
});
// Error Listener
script.stderr.on("data", (data) => {
console.error(`stderr: ${data}`);
});
// Execute when script completes
script.on("close", (code) => {
if (code !== 0) {
reject(`Error executing ${scriptName}, exited with code ${code}`);
}
else {
resolve("");
}
});
// Execute when script erred
script.on("error", (error) => {
reject(`Error executing ${scriptName}: ${error.message}`);
});
});
return;
}
// Execute scripts in a specific order
async function runScripts() {
// Main method to Call internal method for execution of the test
if (args["ENV"]) {
EXEC_ENV = args["ENV"];
// Running Pre-Test
await preScript();
(async () => {
try {
const concurrency = process.env.CONCURRENCY ? parseInt(process.env.CONCURRENCY, 10) : 2;
await runMatrix(concurrency);
console.log('All test runs finished');
await postScript();
}
catch (errs) {
console.error('Some test runs failed:', errs);
await postScript();
process.exit(1);
}
})();
}
else {
throw new Error("ENV variable is required. Please set the 'ENV' environment variable.\n Syntax: npm run ccs_run_test -- --ENV=<ENV>");
}
}
runScripts();