const startedAt = new Date().toISOString() const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const { chromium } = require('/Users/macclaw/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright') const root = path.resolve(__dirname, '../..') const origin = process.env.VERIFY_FRONTEND_ORIGIN || 'https://agent.pducv.cn' const outputName = process.env.VERIFY_REPORT_NAME || 'public-release-smoke' assert.match(outputName, /^[a-zA-Z0-9_-]+$/) const checks = [], failures = [], expectedDenials = [], pageErrors = [] const timings = {}, pendingRequests = new Map(), slowRequests = [], failedRequests = [] const mapRequests = [] const runtimeConfigRequests = [] let measuringMapEntry = false let browser, page function check(name, value) { checks.push({ name, passed: !!value }) assert.ok(value, name) } async function main() { const release = JSON.parse(fs.readFileSync(path.join(root, 'public-release.json'), 'utf8')) const res = await fetch(`${origin}/release.json`, { signal: AbortSignal.timeout(30000) }) check('immutable_release_matches_origin', res.ok && (await res.json()).release === release.release) const html = await fetch(`${origin}/login`, { signal: AbortSignal.timeout(30000) }) check('html_revalidates_after_updates', html.ok && /no-cache/.test(html.headers.get('cache-control'))) const body = await html.text() check('main_runtime_config_is_inline', body.includes('')) const embed = await fetch(`${origin}/embed/release-verification`, { signal: AbortSignal.timeout(30000) }) const embedBody = await embed.text() check('embed_runtime_config_is_inline', embed.ok && embedBody.includes('')) const assets = [...body.matchAll(/(?:src|href)="(\/assets\/[^"]+\.(?:js|css))"/g)].map(match => match[1]) check('production_build_has_assets', assets.length > 0) for (const asset of assets) { const response = await fetch(origin + asset, { signal: AbortSignal.timeout(30000) }) check(`static_asset_available:${asset}`, response.ok && /immutable/.test(response.headers.get('cache-control'))) await response.arrayBuffer() } const protectedResponse = await fetch(`${origin}/api/v1/access/me`, { signal: AbortSignal.timeout(30000) }) check('api_requires_auth_and_does_not_cache', protectedResponse.status === 401 && /no-store/.test(protectedResponse.headers.get('cache-control'))) const credentials = JSON.parse(fs.readFileSync(path.join(root, 'acceptance/super-admin-login.json'), 'utf8')) browser = await chromium.launch({ headless: true, channel: 'chrome' }) page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }) await page.addInitScript(() => localStorage.setItem('weknora:new-user-guide-done:v1', '1')) page.on('pageerror', error => pageErrors.push(error.stack || error.message)) page.on('request', request => { pendingRequests.set(request, Date.now()) const url = new URL(request.url()) if (url.pathname === '/config.js') runtimeConfigRequests.push(url.pathname) if (measuringMapEntry && request.method() === 'GET' && url.pathname === '/api/v1/workbench/resources/training-maps') mapRequests.push(url.pathname + url.search) }) page.on('requestfinished', request => { const started = pendingRequests.get(request) pendingRequests.delete(request) const duration = started === undefined ? 0 : Date.now() - started if (duration >= 3000) slowRequests.push({ path: new URL(request.url()).pathname, duration_ms: duration }) }) page.on('requestfailed', request => { pendingRequests.delete(request) const failure = { path: new URL(request.url()).pathname, error: request.failure()?.errorText || '' } failedRequests.push(failure) // Full-page navigation intentionally cancels the preceding page's polling. if (failure.error !== 'net::ERR_ABORTED') pageErrors.push(`request failed: ${failure.path} ${failure.error}`) }) page.on('response', response => { const pathname = new URL(response.url()).pathname // Standard deployments explicitly refuse the login page's Lite bootstrap probe. if (pathname === '/api/v1/auth/auto-setup' && response.status() === 403) { expectedDenials.push({ path: pathname, status: 403, reason: 'Lite-only bootstrap denied in standard edition' }) return } if (response.status() >= 400 && /\/(?:assets|api)\//.test(pathname)) { pageErrors.push(`${response.status()} ${pathname}`) } }) await page.goto(`${origin}/login`) check('login_has_no_upstream_official_links', await page.locator('a[href*="github.com"], a[href*="weknora.com"]').count() === 0) await page.locator('input[autocomplete="username"]').fill(credentials.username) await page.locator('input[type="password"]').first().fill(credentials.password) const loginStarted = Date.now() await page.locator('button[type="submit"]').first().click() await page.waitForURL('**/platform/**', { timeout: 30000 }) timings.login_to_route_ms = Date.now() - loginStarted await page.waitForLoadState('networkidle', { timeout: 30000 }) check('real_public_form_login', true) const mapsStarted = Date.now() measuringMapEntry = true await page.goto(`${origin}/admin#/admin/training-maps`) await page.waitForURL('**/platform/workbench/content?tab=maps', { timeout: 30000 }) await page.waitForLoadState('networkidle', { timeout: 30000 }) timings.training_maps_ready_ms = Date.now() - mapsStarted measuringMapEntry = false check('old_training_map_bookmark_redirects', true) check('training_maps_entry_does_not_fetch_duplicate_pages', mapRequests.length > 0 && new Set(mapRequests).size === mapRequests.length) check('training_maps_entry_uses_lightweight_lists', mapRequests.every(url => new URL(url, origin).searchParams.get('view') === 'summary')) await page.locator('.workbench-detail h2').waitFor({ state: 'visible' }) const firstSelectedName = await page.locator('.workbench-resource.is-selected strong').innerText() check('selected_map_loads_complete_detail', await page.locator('.workbench-detail h2').innerText() === firstSelectedName) if (await page.locator('.workbench-resource').count() > 1) { const next = page.locator('.workbench-resource').nth(1) const nextName = await next.locator('strong').innerText() await next.click() await page.locator('.workbench-detail h2').filter({ hasText: nextName }).waitFor({ state: 'visible' }) check('selecting_another_map_loads_matching_detail', await page.locator('.workbench-detail h2').innerText() === nextName) await page.getByRole('button', { name: '刷新', exact: true }).click() await page.locator('.workbench-detail h2').filter({ hasText: nextName }).waitFor({ state: 'visible' }) check('refresh_keeps_selected_map_detail', await page.locator('.workbench-detail h2').innerText() === nextName) } await page.screenshot({ path: path.join(root, `acceptance/${outputName}-desktop.png`), animations: 'disabled' }) if (process.env.VERIFY_PC_ONLY !== '1') { await page.setViewportSize({ width: 390, height: 844 }) await page.screenshot({ path: path.join(root, `acceptance/${outputName}-mobile.png`), animations: 'disabled' }) check('mobile_page_does_not_overflow', await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2)) } await page.goto(`${origin}/admin#/admin/digital-humans`) await page.waitForURL('**/platform/digital-humans', { timeout: 30000 }) await page.waitForLoadState('networkidle', { timeout: 30000 }) check('old_digital_human_bookmark_redirects', true) check('no_unexpected_api_or_lazy_module_errors', pageErrors.length === 0) check('no_blocking_runtime_config_request', runtimeConfigRequests.length === 0) } main().catch(error => { failures.push(error.message); process.exitCode = 1 }).finally(async () => { const diagnostics = { path: page ? new URL(page.url()).pathname : null, slow_requests: slowRequests, failed_requests: failedRequests, training_map_requests: mapRequests, runtime_config_requests: runtimeConfigRequests, pending_requests: [...pendingRequests].map(([request, started]) => ({ path: new URL(request.url()).pathname, duration_ms: Date.now() - started })), } failures.push(...pageErrors) if (failures.length) process.exitCode = 1 if (browser) await browser.close() const report = { started_at: startedAt, checked_at: new Date().toISOString(), origin, pc_only: process.env.VERIFY_PC_ONLY === '1', checks, timings, diagnostics, expected_denials: expectedDenials, failures, passed: !failures.length } fs.writeFileSync(path.join(root, `acceptance/${outputName}.json`), JSON.stringify(report, null, 2)) console.log(JSON.stringify(report)) })