/** * cli:run-ui-test — execute.ts * * Reads manifest + users, runs each test via dev-browser-driver, returns * an aggregated report. NO persistence here — the Studio runner consumes * the envelope and calls uiTestService for DB rows. * * Auto-correction is also out of scope for this CLI — it's the Studio * runner's responsibility (Vague 7). The CLI runs each test EXACTLY ONCE. */ import path from 'node:path'; import { promises as fs } from 'node:fs'; import { runDevBrowserTest, type DriverTestSpec, type DriverTestUser, } from './lib/dev-browser-driver.js'; import type { RunUiTestInput, RunUiTestResult, RunUiTestReport } from './types.js'; interface ManifestFile { generatedAt: string; module: string; appCode: string; tests: Array<{ id: string; scenario: DriverTestSpec['scenario']; page: string; role: string; fixture?: Record; expectations: DriverTestSpec['expectations']; }>; } interface UsersFile { generatedAt: string; module: string; appCode: string; users: Array<{ role: string; email: string; password: string; applicationCode: string; roleName: string; }>; } export async function execute(spec: RunUiTestInput): Promise { const manifestAbs = path.resolve(spec.projectPath, spec.manifestPath); const usersAbs = path.resolve(spec.projectPath, spec.usersPath); const manifest: ManifestFile = JSON.parse(await fs.readFile(manifestAbs, 'utf8')); const usersFile: UsersFile = JSON.parse(await fs.readFile(usersAbs, 'utf8')); const usersByRole = new Map(); for (const u of usersFile.users) { usersByRole.set(u.role, { role: u.role, email: u.email, password: u.password }); } // Filter tests by --test-id and --module if provided. let tests = manifest.tests; if (spec.testId) tests = tests.filter((t) => t.id === spec.testId); if (spec.module && spec.module !== manifest.module) { return { module: manifest.module, appCode: manifest.appCode, totalTests: 0, passed: 0, failed: 0, durationMs: 0, results: [], }; } const startedAt = Date.now(); const results: RunUiTestResult[] = []; for (const t of tests) { const user = usersByRole.get(t.role); if (!user) { results.push({ testId: t.id, scenario: t.scenario, page: t.page, role: t.role, status: 'fail', reason: `driver-error: no test user seeded for role "${t.role}"`, durationMs: 0, capture: { exitCode: null }, }); continue; } const driverResult = await runDevBrowserTest( { id: t.id, scenario: t.scenario, page: t.page, role: t.role, fixture: t.fixture, expectations: t.expectations, }, user, { frontendUrl: spec.frontendUrl, apiUrl: spec.apiUrl, captureDir: spec.captureDir, timeoutMs: spec.timeoutMs, cwd: spec.projectPath, }, ); results.push({ testId: t.id, scenario: t.scenario, page: t.page, role: t.role, status: driverResult.status, reason: driverResult.reason, durationMs: driverResult.durationMs, capture: driverResult.capture, }); } const passed = results.filter((r) => r.status === 'pass').length; const failed = results.length - passed; return { module: manifest.module, appCode: manifest.appCode, totalTests: results.length, passed, failed, durationMs: Date.now() - startedAt, results, }; }