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') async function main() { const directory = '/Volumes/Extreme SSD/yawan-storage/weknora/acceptance' const credentials = JSON.parse(fs.readFileSync(path.join(directory, 'super-admin-login.json'), 'utf8')) const login = await fetch('http://127.0.0.1:18080/api/v1/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: credentials.email, password: credentials.password }), }) assert.equal(login.status, 200) const auth = await login.json() const browser = await chromium.launch({ headless: true, channel: 'chrome' }) const checks = [], errors = [], apiFailures = [] const check = (name, passed) => { checks.push({ name, passed: !!passed }); assert.ok(passed, name) } let page 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) page = await context.newPage() page.on('pageerror', error => errors.push(error.message)) page.on('response', response => { const url = new URL(response.url()) if (url.pathname.startsWith('/api/') && response.status() >= 400) apiFailures.push({ path: url.pathname, status: response.status() }) }) await page.goto('http://127.0.0.1:15173/platform/agents') await page.locator('.user-button').waitFor() const routeRegistered = await page.evaluate(() => { const router = document.querySelector('#app').__vue_app__.config.globalProperties.$router return router.getRoutes().some(route => route.path === '/platform/help') }) check('manual_route_registered', routeRegistered) await page.locator('.user-button').click() check('upstream_github_menu_removed', await page.locator('.user-dropdown').getByText('GitHub', { exact: true }).count() === 0) await page.locator('.user-dropdown').getByText('操作手册', { exact: true }).click() await page.locator('.help-page').waitFor() await page.locator('.user-dropdown').waitFor({ state: 'hidden' }) check('manual_menu_opens_internal_page', page.url().includes('/platform/help')) check('admin_sections_visible_for_admin', await page.locator('.help-admin-label').count() === 7) check('all_manual_sections_render', await page.locator('.help-section').count() === 22) await page.screenshot({ path: path.join(directory, 'help-desktop.png'), animations: 'disabled' }) await page.locator('.help-directory a[href="#agents"]').click() await page.waitForURL('**/platform/help#agents') check('directory_updates_anchor', page.url().endsWith('#agents')) check('agent_creation_steps_present', await page.locator('#agents').getByText('创建 Agent', { exact: true }).isVisible()) await page.getByRole('textbox', { name: '搜索操作手册' }).fill('二维码') check('search_filters_sections', await page.locator('.help-section').count() === 1) check('search_finds_wechat', await page.locator('#wechat').count() === 1) await page.getByRole('textbox', { name: '搜索操作手册' }).fill('not-found-example-916') check('empty_search_state', await page.locator('.help-empty').isVisible()) await page.getByRole('button', { name: '清除搜索', exact: true }).click() await page.setViewportSize({ width: 390, height: 844 }) await page.evaluate(() => document.querySelector('.help-page').scrollIntoView({ block: 'start' })) check('mobile_directory_collapses', await page.locator('.help-directory nav').isHidden()) await page.getByRole('button', { name: '手册目录', exact: true }).click() check('mobile_directory_opens', await page.locator('.help-directory nav').isVisible()) await page.locator('.help-directory a[href="#content-tasks"]').click() await page.waitForURL('**/platform/help#content-tasks') check('mobile_directory_closes_after_selection', await page.locator('.help-directory nav').isHidden()) await page.screenshot({ path: path.join(directory, 'help-mobile.png'), animations: 'disabled' }) const mobileLayout = await page.evaluate(() => ({ width: innerWidth, scrollWidth: document.documentElement.scrollWidth, overflow: [...document.querySelectorAll('body *')].map(element => ({ name: element.tagName, class: element.className, rect: element.getBoundingClientRect().toJSON() })).filter(item => item.rect.right > innerWidth + 1 && item.rect.width > 0).slice(0, 12) })) console.log(JSON.stringify({ mobile_layout: mobileLayout })) check('mobile_no_horizontal_overflow', mobileLayout.scrollWidth <= mobileLayout.width + 1) await page.evaluate(async () => { const { useAuthStore } = await import('/src/stores/auth.ts') const store = useAuthStore() store.user = { ...store.user, is_system_admin: false, can_access_all_tenants: false } store.memberships = store.memberships.map(membership => ({ ...membership, role: 'viewer' })) }) await page.waitForFunction(() => document.querySelectorAll('.help-admin-label').length === 0) check('admin_sections_hidden_for_viewer_ui', await page.locator('.help-section[id^="admin-"]').count() === 0) await page.getByRole('textbox', { name: '搜索操作手册' }).fill('数据库迁移错误') check('admin_content_excluded_from_viewer_search', await page.locator('.help-empty').isVisible()) check('no_page_errors', errors.length === 0) const report = { checks, permission_test: 'UI store role transition only; no backend mutation or authorization claim', errors, api_failures: apiFailures } fs.writeFileSync(path.join(directory, 'help-browser-20260916.json'), JSON.stringify(report, null, 2)) console.log(JSON.stringify(report, null, 2)) } catch (error) { if (page) await page.screenshot({ path: path.join(directory, 'help-test-failure.png'), animations: 'disabled' }).catch(() => {}) console.log(JSON.stringify({ checks, errors, page_url: page?.url() })) throw error } finally { await browser.close() } } main().catch(error => { console.error(error.message); process.exitCode = 1 })