diff --git a/qkf/stepRegistry.js b/qkf/stepRegistry.js index 447dc5e..f0f7c06 100644 --- a/qkf/stepRegistry.js +++ b/qkf/stepRegistry.js @@ -1,125 +1,137 @@ -@echo off -setlocal enabledelayedexpansion +// qkf/stepRegistry.js -REM ============================ -REM QKF – Full Test Runner -REM ============================ +function escapeRegex(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} -echo. -echo ============================ -echo QKF Test Execution -echo ============================ -echo. +function normalizeText(t) { + return String(t || "").trim().replace(/\s+/g, " "); +} -REM ---------------------------- -REM Arguments -REM ---------------------------- -set FORCE_INSTALL=false -if "%1"=="--install" set FORCE_INSTALL=true +function literalToRegex(lit) { + // Escape + make spaces flexible + return escapeRegex(normalizeText(lit)).replace(/\s+/g, "\\s+"); +} -REM ---------------------------- -REM Config (edit if needed) -REM ---------------------------- -set QKF_BROWSER=chrome -set QKF_HEADLESS=false -set QKF_TEST_TIMEOUT_MS=120000 -set QKF_BASE_URL=http://gitea.cloud.qacg/user/login +/** + * Pattern supports "{string}" placeholders. + * Matches: + * - "quoted value" + * - 'quoted value' + * - unquoted value (single token or multi-word) + * + * Unquoted capture: + * - if there is a next literal => capture lazily until that literal (lookahead) + * - if last placeholder => capture rest of line + */ +function patternToRegex(pattern) { + const parts = String(pattern).split("{string}"); + const litParts = parts.map((p) => literalToRegex(p)); -REM ---------------------------- -REM Paths -REM ---------------------------- -set FEATURES_DIR=features -set GENERATED_DIR=test\generated -set ALLURE_RESULTS=allure-results -set ALLURE_REPORT=allure-report + // Each placeholder yields 3 capturing groups: dbl, sgl, unquoted + const QUOTED_OR_UNQUOTED = (nextLiteralRegex) => { + const dbl = `"([^"]+)"`; + const sgl = `'([^']+)'`; -REM ---------------------------- -REM Node dependencies -REM ---------------------------- -if not exist "node_modules" ( - echo. - echo node_modules not found — running npm install... - npm install - if errorlevel 1 ( - echo ❌ npm install failed - exit /b 1 - ) -) else if "%FORCE_INSTALL%"=="true" ( - echo. - echo --install flag detected — running npm install... - npm install - if errorlevel 1 ( - echo ❌ npm install failed - exit /b 1 - ) -) else ( - echo. - echo node_modules already present — skipping npm install -) + if (nextLiteralRegex && nextLiteralRegex.length > 0) { + // Capture up to the next literal (lazy) without consuming it + // Allow optional whitespace before the next literal + const unq = `(.+?)(?=\\s*${nextLiteralRegex})`; + return `(?:${dbl}|${sgl}|${unq})`; + } -REM ---------------------------- -REM Clean previous output -REM ---------------------------- -if exist "%GENERATED_DIR%" ( - echo Cleaning generated specs... - rmdir /s /q "%GENERATED_DIR%" -) + // Last placeholder: capture rest of line + const unqLast = `(.+)`; + return `(?:${dbl}|${sgl}|${unqLast})`; + }; -if exist "%ALLURE_RESULTS%" ( - echo Cleaning Allure results... - rmdir /s /q "%ALLURE_RESULTS%" -) + let re = "^\\s*"; -if exist "%ALLURE_REPORT%" ( - echo Cleaning Allure report... - rmdir /s /q "%ALLURE_REPORT%" -) + for (let i = 0; i < litParts.length; i++) { + const lit = litParts[i]; -REM ---------------------------- -REM Generate specs -REM ---------------------------- -echo. -echo [1/4] Generating specs from Gherkin... -node scripts\transform.js --features "%FEATURES_DIR%" --verbose + // Add the literal segment (if any) + if (lit) re += lit; -if errorlevel 1 ( - echo ❌ Spec generation failed - exit /b 1 -) + // If a placeholder follows this literal: + if (i < litParts.length - 1) { + const nextLit = litParts[i + 1]; -REM ---------------------------- -REM Run tests -REM ---------------------------- -echo. -echo [2/4] Running Mocha + Selenium + Allure... -npx mocha ^ - --timeout %QKF_TEST_TIMEOUT_MS% ^ - --reporter allure-mocha ^ - "%GENERATED_DIR%\*.spec.js" + // Allow whitespace between literal and value + re += "\\s+"; + re += QUOTED_OR_UNQUOTED(nextLit); + // IMPORTANT: whitespace after value is optional; next literal (if any) will handle it + re += "\\s*"; + } + } -if errorlevel 1 ( - echo ⚠️ Tests finished with failures (continuing to report generation) -) + re += "\\s*$"; + return new RegExp(re, "i"); +} -REM ---------------------------- -REM Generate Allure report -REM ---------------------------- -echo. -echo [3/4] Generating Allure report... -allure generate "%ALLURE_RESULTS%" -o "%ALLURE_REPORT%" --clean +function createStepRegistry(initialCtx = {}) { + const defs = []; -if errorlevel 1 ( - echo ❌ Allure report generation failed - exit /b 1 -) + function register(pattern, fn, meta = {}) { + defs.push({ + pattern: String(pattern), + regex: patternToRegex(pattern), + fn, + meta, + }); + } -REM ---------------------------- -REM Open Allure report -REM ---------------------------- -echo. -echo [4/4] Opening Allure report... -allure open "%ALLURE_REPORT%" + // DSL helpers + function WHEN(pattern, fn) { register(pattern, fn, { keyword: "WHEN" }); } + function AND(pattern, fn) { register(pattern, fn, { keyword: "AND" }); } + function THEN(pattern, fn) { register(pattern, fn, { keyword: "THEN" }); } + function GIVEN(pattern, fn){ register(pattern, fn, { keyword: "GIVEN" }); } + function BUT(pattern, fn) { register(pattern, fn, { keyword: "BUT" }); } -echo. -echo ✅ Done. -endlocal + async function run(stepText, extraCtx = {}) { + const text = normalizeText(stepText); + + for (const d of defs) { + const m = d.regex.exec(text); + if (!m) continue; + + // Each {string} adds 3 capture groups: dbl, sgl, unquoted + const captures = []; + for (let i = 1; i < m.length; i += 3) { + const v = m[i] || m[i + 1] || m[i + 2] || ""; + captures.push(normalizeText(v)); + } + + const ctx = { ...initialCtx, ...extraCtx }; + + // Auto-await QKF calls (if you implemented these hooks) + if (ctx.qkf && typeof ctx.qkf.__beginStep === "function") ctx.qkf.__beginStep(); + + try { + const res = d.fn.apply(null, [...captures, ctx]); + await res; + + if (ctx.qkf && typeof ctx.qkf.__endStep === "function") { + await ctx.qkf.__endStep(); + } + return; + } catch (err) { + try { + if (ctx.qkf && typeof ctx.qkf.__endStep === "function") { + await ctx.qkf.__endStep(); + } + } catch (_) {} + throw err; + } + } + + const available = defs.map((x) => `- ${x.pattern}`).join("\n"); + throw new Error( + `No step definition matched:\n "${text}"\n\nAvailable definitions:\n${available}` + ); + } + + return { defs, register, run, WHEN, AND, THEN, GIVEN, BUT }; +} + +module.exports = { createStepRegistry };