Files

90 lines
6.2 KiB
JavaScript
Raw Permalink Normal View History

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
})