Files
JS-MOCHA-SE-AL/qkf/loaders.js
2026-02-08 20:37:34 -06:00

136 lines
3.8 KiB
JavaScript

/**
* qkf/loaders.js
* Loads your existing (non-standard) JS files without modifying them:
* - pages/*.js may contain "export const X = ..." and use "By"
* - step-definitons/*.js may call global WHEN/AND/etc and use qkf/pages globals
*/
const fs = require("fs");
const path = require("path");
const vm = require("vm");
function readText(p) {
return fs.readFileSync(p, "utf-8");
}
function listJsFiles(dir) {
if (!fs.existsSync(dir)) return [];
return fs
.readdirSync(dir)
.filter((f) => f.toLowerCase().endsWith(".js"))
.map((f) => path.join(dir, f))
.sort();
}
function createBy() {
// Try selenium-webdriver's By. If not present, return a safe fallback.
try {
const { By } = require("selenium-webdriver");
return By;
} catch {
return {
id: (v) => ({ using: "id", value: v }),
xpath: (v) => ({ using: "xpath", value: v }),
css: (v) => ({ using: "css", value: v }),
name: (v) => ({ using: "name", value: v }),
};
}
}
/**
* Transforms tiny ESM snippets into CommonJS exports:
* - "export const Login = ..." -> "exports.Login = ..."
* Only handles the patterns present in your zip (simple export const).
*/
function transformEsmExportsToCjs(src) {
return String(src)
.replace(/^\s*export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*/gm, "exports.$1 = ");
}
/**
* Runs a JS file in a vm context and returns module.exports/exports.
*/
function runFileInVm(filePath, { injectedGlobals = {}, filenameLabel } = {}) {
const codeRaw = readText(filePath);
const code = transformEsmExportsToCjs(codeRaw);
const module = { exports: {} };
const exports = module.exports;
const sandbox = {
module,
exports,
require,
__filename: filePath,
__dirname: path.dirname(filePath),
console,
// ✅ Node globals your step files may rely on
process,
Buffer,
setTimeout,
clearTimeout,
setInterval,
clearInterval,
...injectedGlobals,
};
vm.createContext(sandbox);
const wrapped = `(function(){\n${code}\n})();`;
vm.runInContext(wrapped, sandbox, {
filename: filenameLabel || filePath,
displayErrors: true,
});
return sandbox.module.exports;
}
/**
* Loads all page selector modules from pagesDir.
* Returns a flat object like { Login: {...}, OtherPage: {...} }
*/
function loadPages(pagesDir) {
const By = createBy();
const files = listJsFiles(pagesDir);
const pages = {};
for (const f of files) {
const exp = runFileInVm(f, { injectedGlobals: { By }, filenameLabel: `pages:${path.basename(f)}` });
Object.assign(pages, exp);
}
return pages;
}
/**
* Loads all step-definition modules from stepDefsDir and executes them.
* Your current step file just calls WHEN/AND directly at top-level, so executing it is enough.
*/
function loadStepDefinitions(stepDefsDir, registry, { qkf, pages } = {}) {
const files = listJsFiles(stepDefsDir);
for (const f of files) {
runFileInVm(f, {
filenameLabel: `steps:${path.basename(f)}`,
injectedGlobals: {
// bind DSL to registry
WHEN: registry.WHEN,
AND: registry.AND,
THEN: registry.THEN,
GIVEN: registry.GIVEN,
BUT: registry.BUT,
// inject the things your step files expect
qkf,
...pages, // makes "Login" available directly if they do "Login.username"
},
});
}
}
module.exports = {
loadPages,
loadStepDefinitions,
};