136 lines
3.8 KiB
JavaScript
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,
|
|
};
|