UNPKG

tm-playwright-framework

Version:

Playwright Cucumber TS framework - The easiest way to learn

332 lines (331 loc) 12.1 kB
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();