@qa-gentic/stlc-agents 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qa-gentic/stlc-agents",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "QA STLC Agents — five MCP servers + skills for AI-powered test case, Gherkin, Playwright generation, and Helix-QA file writing against Azure DevOps and Jira Cloud. Full pipeline for both: fetch → test cases → Gherkin → Playwright → Helix-QA. Works with Claude Code, GitHub Copilot, Cursor, Windsurf.",
5
5
  "keywords": [
6
6
  "playwright",
@@ -57,7 +57,7 @@ BOILERPLATE: dict[str, str] = {
57
57
  "src/utils/locators/review-server.ts": "/**\n * Healix Review Server — standalone post-run heal review tool\n *\n * Run after tests complete:\n * npm run healix:review\n *\n * Open http://localhost:7891 to:\n * • Review AI-healed selectors\n * • Approve / reject each one\n * • Apply approved heals to source files and create a GitHub / ADO PR\n *\n * Environment:\n * HEALIX_REVIEW_PORT Port (default: 7891)\n * HEAL_STORE_PATH Path to healed-locators.json\n * HEAL_SEARCH_ROOTS Comma-separated dirs to search for selectors\n * HEAL_TARGET_REPO Git repo root\n * HEAL_PR_TITLE PR title\n */\n\nimport * as http from 'http';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { HealApplicator, HealRecord } from './HealApplicator';\n\nconst PORT = parseInt(process.env.HEALIX_REVIEW_PORT ?? '7891', 10);\nconst HEAL_STORE_PATH = path.resolve(process.env.HEAL_STORE_PATH ?? 'self-heal/healed-locators.json');\n\nconst cors = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET,POST,PATCH,OPTIONS',\n 'Access-Control-Allow-Headers': 'Content-Type',\n};\n\nfunction readStore(): Record<string, HealRecord> {\n try { if (!fs.existsSync(HEAL_STORE_PATH)) return {}; return JSON.parse(fs.readFileSync(HEAL_STORE_PATH, 'utf8')); }\n catch { return {}; }\n}\n\nfunction writeStore(store: Record<string, HealRecord>): void {\n const dir = path.dirname(HEAL_STORE_PATH);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(HEAL_STORE_PATH, JSON.stringify(store, null, 2), 'utf8');\n}\n\nfunction readBody(req: http.IncomingMessage): Promise<string> {\n return new Promise(resolve => {\n const chunks: Buffer[] = [];\n req.on('data', c => chunks.push(c));\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));\n });\n}\n\nasync function handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {\n const url = req.url ?? '/';\n const method = req.method ?? 'GET';\n\n if (method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }\n\n if (url === '/api/store' && method === 'GET') {\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify(readStore(), null, 2)); return;\n }\n\n if (url === '/api/approve' && method === 'PATCH') {\n const { key, approved } = JSON.parse(await readBody(req)) as { key: string; approved: boolean };\n const store = readStore();\n if (store[key]) { store[key].approved = approved; writeStore(store); res.writeHead(200, { 'Content-Type': 'application/json', ...cors }); res.end(JSON.stringify({ ok: true, key, approved })); }\n else { res.writeHead(404, { 'Content-Type': 'application/json', ...cors }); res.end(JSON.stringify({ ok: false, error: `Key \"${key}\" not found` })); }\n return;\n }\n\n if (url === '/api/approve-all' && method === 'POST') {\n const { approved } = JSON.parse(await readBody(req)) as { approved: boolean };\n const store = readStore();\n for (const k of Object.keys(store)) store[k].approved = approved;\n writeStore(store);\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: true, count: Object.keys(store).length })); return;\n }\n\n if (url === '/api/apply' && method === 'POST') {\n const store = readStore();\n const approved = Object.values(store).filter(r => r.approved);\n if (approved.length === 0) {\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: false, message: 'No approved heals to apply' })); return;\n }\n const applicator = new HealApplicator();\n const result = applicator.apply(store);\n let prUrl = '', prError = '';\n if (result.changedFiles.length > 0) {\n try { prUrl = applicator.createPR(result.changedFiles, result.applied); }\n catch (err) { prError = String(err); }\n for (const a of result.applied) { if (store[a.key]) delete store[a.key].approved; }\n writeStore(store);\n }\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({\n ok: result.errors.length === 0, applied: result.applied, skipped: result.skipped,\n skippedDetails: Object.fromEntries(result.skipped.filter(k => store[k]).map(k => [k, { originalSelector: store[k].originalSelector, healedSelector: store[k].healedSelector }])),\n errors: result.errors, prUrl, prError, changedFiles: result.changedFiles,\n }, null, 2)); return;\n }\n\n if (url === '/api/clear' && method === 'POST') {\n writeStore({});\n res.writeHead(200, { 'Content-Type': 'application/json', ...cors });\n res.end(JSON.stringify({ ok: true })); return;\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });\n res.end(buildReviewHtml());\n}\n\nfunction escHtml(s: string): string {\n return String(s ?? '').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;');\n}\n\nfunction buildRow(record: HealRecord, key: string): string {\n const stratClass = ['ai-vision','ax-tree','role','label','text','cached'].includes(record.strategy) ? record.strategy : '';\n const rowClass = record.approved === true ? 'approved' : record.approved === false ? 'rejected' : '';\n const dotClass = record.approved === true ? 'approved' : record.approved === false ? 'rejected' : 'pending';\n const dateShort = record.lastHealedAt ? new Date(record.lastHealedAt).toLocaleString() : '';\n return `<tr class=\"${rowClass}\" data-key=\"${escHtml(key)}\" data-approved=\"${String(!!record.approved)}\">\n <td><span class=\"status-dot ${dotClass}\"></span></td>\n <td><code>${escHtml(key)}</code></td>\n <td><span class=\"badge ${stratClass}\">${escHtml(record.strategy)}</span></td>\n <td><code style=\"color:var(--muted)\">${escHtml(record.originalSelector)}</code></td>\n <td><code style=\"color:var(--green);font-weight:600\">${escHtml(record.healedSelector)}</code></td>\n <td style=\"font-size:12px;color:var(--muted);max-width:220px\">${escHtml(record.intent)}<br><span style=\"font-size:10px\">${dateShort}</span></td>\n <td style=\"text-align:center\">${record.healCount}</td>\n <td style=\"white-space:nowrap\">\n <button class=\"approve-btn\" onclick=\"setApproved('${escHtml(key)}', true)\">✓ Approve</button>\n &nbsp;<button class=\"reject-btn\" onclick=\"setApproved('${escHtml(key)}', false)\">✗ Reject</button>\n </td>\n </tr>`;\n}\n\nfunction buildReviewHtml(): string {\n const store = readStore();\n const keys = Object.keys(store);\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head><meta charset=\"UTF-8\"><title>Healix — Heal Review</title>\n<style>\n:root{--bg:#0d1117;--surface:#161b22;--border:#30363d;--accent:#58a6ff;--green:#3fb950;--red:#f85149;--yellow:#d2994a;--purple:#bc8cff;--muted:#8b949e;--text:#e6edf3}\n*{box-sizing:border-box;margin:0;padding:0}\nbody{background:var(--bg);color:var(--text);font:14px/1.5 'Segoe UI',system-ui,sans-serif;padding:24px}\nheader{display:flex;align-items:center;gap:16px;margin-bottom:32px;border-bottom:1px solid var(--border);padding-bottom:16px}\nheader h1{font-size:20px;font-weight:600}.subtitle{color:var(--muted);font-size:13px}\n.toolbar{display:flex;gap:12px;align-items:center;margin-bottom:20px;flex-wrap:wrap}\nbutton{cursor:pointer;border:none;border-radius:6px;padding:7px 16px;font:inherit;font-weight:600;font-size:13px;transition:opacity .15s}\nbutton:hover{opacity:.85}\n.btn-approve-all{background:rgba(63,185,80,.15);color:var(--green);border:1px solid var(--green)}\n.btn-reject-all{background:rgba(248,81,73,.1);color:var(--red);border:1px solid var(--red)}\n.btn-apply{background:var(--accent);color:#0d1117;font-size:14px;padding:9px 22px}\n.btn-apply:disabled{opacity:.4;cursor:not-allowed}\n.btn-clear{background:rgba(248,81,73,.1);color:var(--red);border:1px solid var(--red);margin-left:auto}\n.count-badge{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:2px 10px;font-size:12px;color:var(--muted)}\ntable{width:100%;border-collapse:collapse;font-size:13px}\nth{text-align:left;padding:10px 14px;border-bottom:2px solid var(--border);color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.05em;white-space:nowrap}\ntd{padding:10px 14px;border-bottom:1px solid var(--border);vertical-align:top}\ntr:last-child td{border-bottom:none}tr.approved td{background:rgba(63,185,80,.04)}tr.rejected td{background:rgba(248,81,73,.04);opacity:.6}\ncode{font-family:monospace;font-size:11px;background:rgba(255,255,255,.05);border-radius:4px;padding:2px 6px;word-break:break-all}\n.badge{display:inline-block;border-radius:4px;padding:2px 8px;font-size:11px;font-weight:600}\n.badge.ai-vision{background:rgba(188,140,255,.15);color:var(--purple)}.badge.ax-tree{background:rgba(210,153,34,.15);color:var(--yellow)}\n.badge.role,.badge.label,.badge.text{background:rgba(63,185,80,.1);color:var(--green)}\n.approve-btn{background:rgba(63,185,80,.12);color:var(--green);border:1px solid rgba(63,185,80,.3);padding:4px 12px;font-size:12px;border-radius:4px}\n.reject-btn{background:rgba(248,81,73,.1);color:var(--red);border:1px solid rgba(248,81,73,.3);padding:4px 12px;font-size:12px;border-radius:4px}\n.status-dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:6px}\n.status-dot.approved{background:var(--green)}.status-dot.rejected{background:var(--red)}.status-dot.pending{background:var(--muted)}\n#pr-result{margin-top:24px;padding:16px;border-radius:8px;border:1px solid var(--border);font-size:13px;display:none}\n#pr-result.success{border-color:var(--green);background:rgba(63,185,80,.06)}#pr-result.error{border-color:var(--red);background:rgba(248,81,73,.06)}\n.empty{text-align:center;padding:48px;color:var(--muted)}\n</style>\n</head>\n<body>\n<header>\n <span style=\"font-size:28px\">🩺</span>\n <div><h1>Healix — Heal Review</h1><div class=\"subtitle\">Review AI-healed selectors, approve what's valid, then apply to the repo and create a PR</div></div>\n <span class=\"count-badge\" id=\"approved-count\">0 approved</span>\n</header>\n<div class=\"toolbar\">\n <button class=\"btn-approve-all\" onclick=\"approveAll(true)\">✓ Approve all</button>\n <button class=\"btn-reject-all\" onclick=\"approveAll(false)\">✗ Reject all</button>\n <button class=\"btn-apply\" id=\"apply-btn\" onclick=\"applyHeals()\" disabled>🚀 Apply approved &amp; create PR</button>\n <button class=\"btn-clear\" onclick=\"clearAll()\">🗑 Clear all heals</button>\n</div>\n<table>\n <thead><tr><th>Status</th><th>Key</th><th>Strategy</th><th>Original selector</th><th>Healed selector ✓</th><th>Intent</th><th>Heals</th><th>Actions</th></tr></thead>\n <tbody id=\"heal-body\">\n ${keys.length === 0 ? '<tr><td colspan=\"8\" class=\"empty\">No healed selectors in store — run your tests first</td></tr>' : keys.map(k => buildRow(store[k], k)).join('')}\n </tbody>\n</table>\n<div id=\"pr-result\"></div>\n<script>\nfunction escHtml(s){return String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;').replace(/'/g,'&#39;');}\nfunction updateCount(){const rows=document.querySelectorAll('#heal-body tr[data-key]');let c=0;rows.forEach(r=>{if(r.dataset.approved==='true')c++;});document.getElementById('approved-count').textContent=c+' approved';document.getElementById('apply-btn').disabled=c===0;}\nasync function setApproved(key,approved){await fetch('/api/approve',{method:'PATCH',headers:{'Content-Type':'application/json'},body:JSON.stringify({key,approved})});const row=document.querySelector('tr[data-key=\"'+CSS.escape(key)+'\"]');if(row){row.dataset.approved=approved;row.className=approved?'approved':'rejected';const dot=row.querySelector('.status-dot');dot.className='status-dot '+(approved?'approved':'rejected');}updateCount();}\nasync function approveAll(approved){await fetch('/api/approve-all',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({approved})});location.reload();}\nasync function applyHeals(){const btn=document.getElementById('apply-btn');btn.disabled=true;btn.textContent='⏳ Applying…';const panel=document.getElementById('pr-result');panel.style.display='none';try{const d=await(await fetch('/api/apply',{method:'POST'})).json();if(d.prUrl){panel.className='success';panel.style.display='block';panel.innerHTML='✅ PR created: <a href=\"'+escHtml(d.prUrl)+'\" target=\"_blank\" style=\"color:var(--accent)\">'+escHtml(d.prUrl)+'</a>';setTimeout(()=>location.reload(),3000);}else if(d.changedFiles&&d.changedFiles.length>0){panel.className='success';panel.style.display='block';panel.innerHTML='✅ '+d.applied.length+' file(s) updated locally.'+(d.prError?'<br>⚠ PR failed: <code>'+escHtml(d.prError)+'</code>':'');setTimeout(()=>location.reload(),3000);}else if(d.skipped&&d.skipped.length>0){panel.className='error';panel.style.display='block';panel.innerHTML='⚠ '+d.skipped.length+' heal(s) skipped — original selector not found in source files:<br>'+d.skipped.map(k=>'<code>'+escHtml(k)+'</code>').join('<br>');btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}else{panel.className='error';panel.style.display='block';panel.innerHTML='⚠ '+(d.message??'Nothing was applied.');btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}}catch(err){panel.className='error';panel.style.display='block';panel.innerHTML='✖ '+escHtml(String(err));btn.disabled=false;btn.textContent='🚀 Apply approved & create PR';}}\nasync function clearAll(){if(!confirm('Clear all stored heals?'))return;await fetch('/api/clear',{method:'POST'});location.reload();}\nupdateCount();\n</script>\n</body></html>`;\n}\n\nconst server = http.createServer((req, res) => {\n handleRequest(req, res).catch(err => { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end(String(err)); });\n});\n\nserver.listen(PORT, '127.0.0.1', () => {\n console.log('');\n console.log(' ╔══════════════════════════════════════════════════════════════╗');\n console.log(` ║ 🩺 Healix Review Server → http://localhost:${PORT} ║`);\n console.log(' ║ Review AI-healed selectors, approve what is valid, ║');\n console.log(' ║ then click \"Apply approved & create PR\". ║');\n console.log(' ║ Ctrl+C to exit after reviewing. ║');\n console.log(' ╚══════════════════════════════════════════════════════════════╝');\n console.log('');\n const total = Object.keys(readStore()).length;\n if (total === 0) console.log(' ⚠ No heals in store yet — run your tests first.');\n else console.log(` 📋 ${total} healed selector(s) ready for review`);\n console.log('');\n});\n\nserver.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') console.error(` ✖ Port ${PORT} in use. Set HEALIX_REVIEW_PORT.`);\n else console.error(` ✖ Server error: ${err.message}`);\n process.exit(1);\n});\n",
58
58
  "src/utils/storage-state/AuthManager.ts": "import { Browser, BrowserContext, Page } from '@playwright/test';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { logger } from '@utils/helpers/Logger';\nimport { environment } from '@config/environment';\n\nexport interface AuthCredentials {\n username: string;\n password: string;\n role?: string;\n}\n\n/**\n * AuthManager — manages browser storage-state for authenticated sessions.\n *\n * Usage:\n * const auth = new AuthManager(browser);\n * await auth.login(); // saves storage-state to disk\n * const ctx = await auth.createAuthenticatedContext();\n *\n * Customize `performLoginActions()` and `waitForLoginSuccess()` to match your app's\n * login flow and post-login indicators.\n */\nexport class AuthManager {\n private storageStatePath: string;\n\n constructor(private readonly browser: Browser) {\n const env = environment.getConfig();\n this.storageStatePath = env.storageStatePath;\n const dir = path.dirname(this.storageStatePath);\n if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });\n }\n\n async login(credentials?: AuthCredentials): Promise<void> {\n const env = environment.getConfig();\n const creds = credentials ?? { username: env.authUsername, password: env.authPassword };\n logger.info(`Performing login for user: ${creds.username}`);\n\n const context = await this.browser.newContext();\n const page = await context.newPage();\n try {\n await page.goto(`${env.baseUrl}/login`, { timeout: env.navigationTimeout, waitUntil: 'domcontentloaded' });\n await this.performLoginActions(page, creds);\n await this.waitForLoginSuccess(page);\n await this.saveStorageState(context);\n logger.info('Login successful — storage state saved');\n } catch (err) {\n logger.error(`Login failed: ${String(err)}`);\n throw err;\n } finally {\n await context.close();\n }\n }\n\n /**\n * Customize this method for your application's login form.\n */\n private async performLoginActions(page: Page, credentials: AuthCredentials): Promise<void> {\n const env = environment.getConfig();\n await page.fill('input[name=\"username\"], input[type=\"email\"], #username', credentials.username, { timeout: env.actionTimeout });\n await page.fill('input[name=\"password\"], input[type=\"password\"], #password', credentials.password, { timeout: env.actionTimeout });\n await page.click('button[type=\"submit\"], button:has-text(\"Login\"), button:has-text(\"Sign in\")', { timeout: env.actionTimeout });\n logger.debug('Login form submitted');\n }\n\n /**\n * Customize this method to detect successful login in your application.\n */\n private async waitForLoginSuccess(page: Page): Promise<void> {\n const env = environment.getConfig();\n try {\n await Promise.race([\n page.waitForURL(/dashboard|home|app/, { timeout: env.authTimeout }),\n page.waitForSelector('[data-testid=\"user-menu\"], .user-profile, #dashboard', { timeout: env.authTimeout }),\n ]);\n } catch {\n if (page.url().includes('login')) throw new Error('Login failed — still on login page');\n throw new Error('Login success detection timed out');\n }\n }\n\n async saveStorageState(context: BrowserContext, customPath?: string): Promise<void> {\n const target = customPath ?? this.storageStatePath;\n await context.storageState({ path: target });\n logger.info(`Storage state saved: ${target}`);\n }\n\n async loadStorageState(customPath?: string): Promise<any> {\n const target = customPath ?? this.storageStatePath;\n if (!fs.existsSync(target)) { logger.warn(`Storage state not found: ${target}`); return null; }\n try {\n const state = JSON.parse(fs.readFileSync(target, 'utf-8'));\n logger.info(`Storage state loaded: ${target}`);\n return state;\n } catch (err) { logger.error(`Failed to load storage state: ${String(err)}`); return null; }\n }\n\n async isStorageStateValid(customPath?: string): Promise<boolean> {\n const target = customPath ?? this.storageStatePath;\n if (!fs.existsSync(target)) return false;\n try {\n const state = JSON.parse(fs.readFileSync(target, 'utf-8'));\n if (!state?.cookies?.length) return false;\n const now = Date.now();\n return state.cookies.some((c: any) => !c.expires || c.expires * 1_000 > now);\n } catch { return false; }\n }\n\n async createAuthenticatedContext(forceLogin = false): Promise<BrowserContext> {\n if (forceLogin || !(await this.isStorageStateValid())) {\n logger.info('Creating fresh login session');\n await this.login();\n }\n const state = await this.loadStorageState();\n const context = await this.browser.newContext({ storageState: state, viewport: { width: 1920, height: 1080 } });\n logger.info('Authenticated context created');\n return context;\n }\n\n async createAuthenticatedPage(forceLogin = false): Promise<Page> {\n const ctx = await this.createAuthenticatedContext(forceLogin);\n return ctx.newPage();\n }\n\n async verifyAuthentication(page: Page): Promise<boolean> {\n const indicators = [\n page.locator('[data-testid=\"user-menu\"]'),\n page.locator('.user-profile'),\n page.locator('#logout-button'),\n page.locator('button:has-text(\"Logout\")'),\n ];\n for (const loc of indicators) {\n try { await loc.waitFor({ timeout: 3_000 }); return true; } catch { /* try next */ }\n }\n if (page.url().includes('login')) { logger.warn('Not authenticated — on login page'); return false; }\n return false;\n }\n\n async logout(page: Page): Promise<void> {\n await page.click('button:has-text(\"Logout\"), #logout, [data-testid=\"logout\"]', { timeout: 5_000 });\n await page.waitForURL(/login/, { timeout: 10_000 });\n if (fs.existsSync(this.storageStatePath)) { fs.unlinkSync(this.storageStatePath); logger.info('Storage state cleared'); }\n }\n\n getStorageStatePath(): string { return this.storageStatePath; }\n setStorageStatePath(p: string): void { this.storageStatePath = p; }\n}\n",
59
59
  "src/utils/storage-state/AuthSetup.ts": "import { chromium } from '@playwright/test';\nimport { AuthManager } from './AuthManager';\nimport { logger } from '@utils/helpers/Logger';\nimport { environment } from '@config/environment';\n\n/**\n * AuthSetup — CLI script to pre-authenticate and save browser storage state.\n *\n * Run once before your test suite to avoid logging in on every scenario:\n * npm run auth:setup\n *\n * The saved storage-state file is loaded automatically by hooks.ts via the\n * `STORAGE_STATE_PATH` environment variable.\n */\nasync function setupAuth(): Promise<void> {\n const env = environment.getConfig();\n logger.info('Starting authentication setup');\n logger.info(`Environment : ${env.environment}`);\n logger.info(`Base URL : ${env.baseUrl}`);\n\n const browser = await chromium.launch({ headless: false });\n try {\n const authManager = new AuthManager(browser);\n await authManager.login();\n logger.info('✅ Authentication setup completed');\n logger.info(` Storage state: ${authManager.getStorageStatePath()}`);\n\n const isValid = await authManager.isStorageStateValid();\n if (isValid) {\n logger.info('✅ Storage state validated');\n } else {\n logger.error('❌ Storage state validation failed');\n process.exit(1);\n }\n } catch (err) {\n logger.error(`❌ Authentication setup failed: ${String(err)}`);\n process.exit(1);\n } finally {\n await browser.close();\n }\n}\n\nif (require.main === module) {\n setupAuth()\n .then(() => process.exit(0))\n .catch(err => { logger.error(`Unhandled error: ${String(err)}`); process.exit(1); });\n}\n\nexport { setupAuth };\n",
60
- "tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"commonjs\",\n \"lib\": [\"ES2022\"],\n \"outDir\": \"./dist\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"resolveJsonModule\": true,\n \"moduleResolution\": \"node\",\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"noImplicitReturns\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"allowSyntheticDefaultImports\": true,\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"types\": [\"node\", \"@types/node\"],\n \"rootDir\": \"src\",\n \"baseUrl\": \".\",\n \"paths\": {\n \"@hooks/*\": [\"src/hooks/*\"],\n \"@pages/*\": [\"src/pages/*\"],\n \"@utils/*\": [\"src/utils/*\"],\n \"@test/*\": [\"src/test/*\"],\n \"@features/*\": [\"src/test/features/*\"],\n \"@steps/*\": [\"src/test/steps/*\"],\n \"@locators/*\": [\"src/locators/*\"],\n \"@fixtures/*\": [\"src/fixtures/*\"]\n }\n },\n \"include\": [\n \"src/**/*\",\n \"src/test/features/**/*\",\n \"src/test/steps/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\", \"dist\", \"test-results\", \"src/templates\"],\n \"ts-node\": {\n \"require\": [\"tsconfig-paths/register\"],\n \"transpileOnly\": true,\n \"files\": true,\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n }\n}\n",
60
+ "tsconfig.json": "{\n \"compilerOptions\": {\n \"target\": \"ES2022\",\n \"module\": \"commonjs\",\n \"lib\": [\"ES2022\", \"dom\"],\n \"outDir\": \"./dist\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"forceConsistentCasingInFileNames\": true,\n \"resolveJsonModule\": true,\n \"moduleResolution\": \"node\",\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"noUnusedLocals\": true,\n \"noUnusedParameters\": true,\n \"noImplicitReturns\": true,\n \"noFallthroughCasesInSwitch\": true,\n \"allowSyntheticDefaultImports\": true,\n \"experimentalDecorators\": true,\n \"emitDecoratorMetadata\": true,\n \"types\": [\"node\", \"@types/node\"],\n \"rootDir\": \"src\",\n \"baseUrl\": \".\",\n \"paths\": {\n \"@config/*\": [\"src/config/*\"],\n \"@hooks/*\": [\"src/hooks/*\"],\n \"@pages/*\": [\"src/pages/*\"],\n \"@utils/*\": [\"src/utils/*\"],\n \"@test/*\": [\"src/test/*\"],\n \"@features/*\": [\"src/test/features/*\"],\n \"@steps/*\": [\"src/test/steps/*\"],\n \"@locators/*\": [\"src/locators/*\"],\n \"@fixtures/*\": [\"src/fixtures/*\"]\n }\n },\n \"include\": [\n \"src/**/*\",\n \"src/test/features/**/*\",\n \"src/test/steps/**/*.ts\"\n ],\n \"exclude\": [\"node_modules\", \"dist\", \"test-results\", \"src/templates\"],\n \"ts-node\": {\n \"require\": [\"tsconfig-paths/register\"],\n \"transpileOnly\": true,\n \"files\": true,\n \"compilerOptions\": {\n \"module\": \"commonjs\"\n }\n }\n}\n",
61
61
  }
62
62
 
63
63
  # fmt: on