const assert = require('node:assert/strict') const fs = require('node:fs') const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright') const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance' const origin = process.env.VERIFY_FRONTEND_ORIGIN || 'http://127.0.0.1:15173' const apiOrigin = process.env.VERIFY_API_ORIGIN || 'http://127.0.0.1:18080' const outputName = process.env.VERIFY_REPORT_NAME || 'navigation-parity-checks' assert.match(outputName, /^[a-zA-Z0-9_-]+$/) const startedAt = new Date().toISOString() const checks = [], failures = [] let browser function writeReport(complete) { const report = { started_at: startedAt, checked_at: new Date().toISOString(), origin, api_origin: apiOrigin, checks, failures, passed: complete && checks.length === 21 && checks.every(c => c.passed) && failures.length === 0 } fs.writeFileSync(`${directory}/${outputName}.json`, JSON.stringify(report, null, 2)) return report } async function main() { writeReport(false) const credentials = JSON.parse(fs.readFileSync(`${directory}/super-admin-login.json`, 'utf8')) const response = await fetch(`${apiOrigin}/api/v1/auth/login`, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({email: credentials.email, password: credentials.password}), signal: AbortSignal.timeout(30000)}) assert.equal(response.status, 200) const auth = await response.json() browser = await chromium.launch({headless: true, channel: 'chrome'}) try { const context = await browser.newContext({viewport: {width: 1440, height: 1000}}) await context.addInitScript(a => { const values = {weknora_user: JSON.stringify({...a.user, tenant_id: String(a.user.tenant_id)}), weknora_tenant: JSON.stringify({...a.active_tenant, id: '10059'}), weknora_token: a.token, weknora_refresh_token: a.refresh_token, weknora_memberships: JSON.stringify(a.memberships)} for (const [key, value] of Object.entries(values)) localStorage.setItem(key, value) localStorage.setItem('weknora:new-user-guide-done:v1', '1') }, auth) const routes = ['/platform/workbench/content?tab=tasks', '/platform/workbench/content?tab=maps', '/platform/workbench/flows', '/platform/digital-humans', '/platform/knowledge-bases', '/platform/agents', '/platform/agents?tab=skills', '/platform/agents?tab=memory', '/platform/workbench/access', '/platform/workbench/resource-versions', '/platform/help', ...['balance', 'usage', 'audit', 'system'].map(tab => `/platform/workbench/operations/${tab}`), ...['models', 'members', 'memory', 'system-audit-log', 'runtime-queues', 'tenant'].map(section => `/platform/settings?section=${section}`)] for (const route of routes) { const page = await context.newPage(), failures = [], started = Date.now(), requests = [], requestedPaths = [], failedRequests = [] const pendingRequests = new Map() page.on('request', request => { requestedPaths.push(new URL(request.url()).pathname); pendingRequests.set(request, Date.now()) }) page.on('pageerror', () => failures.push('javascript_error')) page.on('response', r => { if (r.status() >= 400 && new URL(r.url()).pathname.startsWith('/api/')) failures.push(`HTTP ${r.status()} ${new URL(r.url()).pathname}`) }) page.on('requestfinished', request => { pendingRequests.delete(request) const timing = request.timing() if (timing.responseEnd >= 1000) requests.push({ path: new URL(request.url()).pathname, duration_ms: timing.responseEnd, first_byte_ms: timing.responseStart }) }) page.on('requestfailed', request => { pendingRequests.delete(request) const error = request.failure()?.errorText || '' failedRequests.push({ path: new URL(request.url()).pathname, error }) if (error !== 'net::ERR_ABORTED') failures.push(`Request failed: ${new URL(request.url()).pathname} ${error}`) }) try { await page.goto(`${origin}${route}`) await page.waitForLoadState('networkidle', {timeout: 15000}) assert.ok(new URL(page.url()).pathname !== '/login') assert.ok((await page.locator('body').innerText()).length > 100) const section = new URL(route, origin).searchParams.get('section') if (section === 'models' || section === 'members') { assert.ok(await page.locator(section === 'models' ? '.model-settings' : '.tenant-members').isVisible(), 'Selected settings section must render') assert.ok(!requestedPaths.some(path => /\/(?:IntegrationSettingsSection|SandboxSkillsPanel|AgentStreamDisplay)-[^/]+\.js$/.test(path)), 'Unopened settings must not load integration or skill modules') } assert.equal(await page.locator('[data-guide="nav-workbench/resource-versions"]').count(), 0) if (route.endsWith('/resource-versions')) { assert.notEqual(new URL(page.url()).pathname, '/platform/workbench/resource-versions', 'Retired resource versions must redirect') } if (route.endsWith('/operations/usage')) { assert.equal(await page.getByRole('cell', {name: 'document_summary', exact: true}).count(), 0) await page.screenshot({path: `${directory}/${outputName}-desktop.png`, animations: 'disabled'}) } } catch (e) { failures.push(`${e.name}: ${e.message}`) } checks.push({route, duration_ms: Date.now() - started, passed: failures.length === 0, failures, pending_requests: [...pendingRequests].map(([request, requestedAt]) => ({ path: new URL(request.url()).pathname, duration_ms: Date.now() - requestedAt })), failed_requests: failedRequests, slow_requests: requests.sort((a, b) => b.duration_ms - a.duration_ms).slice(0, 10)}) await page.close() writeReport(false) console.log(JSON.stringify(checks.at(-1))) } } finally { await browser.close().catch(error => failures.push(`Browser cleanup: ${error.name}`)) browser = undefined } } main().catch(error => { failures.push(`${error.name}: ${error.message}`) }).finally(async () => { if (browser) await browser.close().catch(error => failures.push(`Browser cleanup: ${error.name}`)) const report = writeReport(true) console.log(JSON.stringify(report)) if (!report.passed) process.exitCode = 1 })