Files

102 lines
6.9 KiB
JavaScript
Raw Permalink Normal View History

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 reportName = process.env.VERIFY_REPORT_NAME || 'public-first-entry-loading'
assert.match(reportName, /^[a-zA-Z0-9_-]+$/)
const started_at = new Date().toISOString()
const checks = [], failures = [], timings = {}, requests = []
let browser
function check(name, value) {
checks.push({ name, passed: !!value })
assert.ok(value, name)
}
async function main() {
const credentials = JSON.parse(fs.readFileSync(path.join(root, 'acceptance/super-admin-login.json'), 'utf8'))
browser = await chromium.launch({ headless: true, channel: 'chrome' })
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
await page.addInitScript(() => {
localStorage.setItem('locale', 'zh-CN')
for (const key of ['weknora:new-user-guide-done:v1', 'weknora:contextual-guide-kb-list:v2', 'weknora:contextual-guide-kb-create:v3', 'weknora:contextual-guide-tenant-models:v1']) localStorage.setItem(key, '1')
})
page.on('pageerror', error => failures.push(error.message))
page.on('request', request => requests.push({ path: new URL(request.url()).pathname, method: request.method() }))
await page.goto(`${origin}/login`)
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"]').click()
await page.waitForURL('**/platform/knowledge-bases', { timeout: 30000 })
timings.login_to_route_ms = Date.now() - loginStarted
await page.locator('[data-guide="kb-list-create"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle', { timeout: 30000 })
check('cold_form_login_completes_within_30_seconds', timings.login_to_route_ms < 30000)
check('hidden_settings_module_not_requested_on_entry', !requests.some(item => /\/Settings-[^/]+\.js$/.test(item.path)))
check('hidden_knowledge_editor_not_requested_on_entry', !requests.some(item => /\/KnowledgeBaseEditorModal-[^/]+\.js$/.test(item.path)))
check('chat_agent_list_not_prefetched_on_knowledge_entry', !requests.some(item => item.path === '/api/v1/agents'))
check('chat_search_providers_not_prefetched_on_knowledge_entry', !requests.some(item => item.path === '/api/v1/web-search-providers'))
const editorStarted = Date.now()
await page.locator('[data-guide="kb-list-create"]').click()
const editor = page.locator('.settings-overlay').filter({ has: page.locator('[data-guide="kb-editor-sidebar"]') })
await editor.waitFor({ state: 'visible' })
await editor.locator('.editor-initializing').waitFor({ state: 'hidden' })
const name = editor.locator('[data-guide="kb-create-name"] input')
await name.fill('Unsaved loading verification')
timings.editor_ready_ms = Date.now() - editorStarted
check('first_open_editor_initializes_and_accepts_input', await name.inputValue() === 'Unsaved loading verification')
check('editor_module_requested_only_when_opened', requests.some(item => /\/KnowledgeBaseEditorModal-[^/]+\.js$/.test(item.path)))
await page.locator('.t-message').first().waitFor({ state: 'hidden' })
await page.locator('.t-notification').first().waitFor({ state: 'hidden' })
await page.screenshot({ path: path.join(root, `acceptance/${reportName}-editor-desktop.png`) })
await editor.locator('.close-btn').click()
await editor.waitFor({ state: 'hidden' })
await page.locator('[data-guide="kb-list-create"]').click()
await editor.waitFor({ state: 'visible' })
await editor.locator('.editor-initializing').waitFor({ state: 'hidden' })
check('reopened_editor_does_not_reuse_discarded_draft', await name.inputValue() === '')
for (const width of [390, 320]) {
await page.setViewportSize({ width, height: 844 })
await page.screenshot({ path: path.join(root, `acceptance/${reportName}-editor-mobile-${width}.png`) })
check(`editor_mobile_${width}_no_page_overflow`, await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 2))
check(`editor_mobile_${width}_form_uses_available_width_without_clipping`, await editor.evaluate(element => {
const content = element.querySelector('.content-wrapper')
const input = element.querySelector('[data-guide="kb-create-name"]')
if (!content || !input) return false
const bounds = content.getBoundingClientRect(), field = input.getBoundingClientRect()
return bounds.width >= innerWidth - 40 && content.scrollWidth <= content.clientWidth + 2
&& field.width >= 250 && field.left >= bounds.left && field.right <= bounds.right
}))
check(`editor_mobile_${width}_footer_buttons_visible`, await editor.locator('.settings-footer-actions').evaluate(element => {
const bounds = element.getBoundingClientRect()
return bounds.left >= 0 && bounds.right <= innerWidth && bounds.bottom <= innerHeight && bounds.top > 0
}))
}
await editor.locator('.close-btn').click()
await editor.waitFor({ state: 'hidden' })
await page.setViewportSize({ width: 1440, height: 1000 })
await page.locator('[data-guide="user-menu"]').click()
await page.locator('.menu-item').filter({ hasText: '全部设置' }).click()
await page.waitForURL('**/platform/settings**')
await page.locator('.settings-overlay').waitFor({ state: 'visible' })
check('settings_menu_opens_one_settings_dialog', await page.locator('.settings-overlay').count() === 1)
check('settings_module_requested_when_opened', requests.some(item => /\/Settings-[^/]+\.js$/.test(item.path)))
const chatRequestStart = requests.length
await page.goto(`${origin}/platform/creatChat`)
await page.waitForLoadState('networkidle', { timeout: 30000 })
check('chat_route_still_loads_its_agents_and_models', ['/api/v1/agents', '/api/v1/models'].every(path => requests.slice(chatRequestStart).some(item => item.path === path)))
check('chat_route_loads_without_redirect', new URL(page.url()).pathname === '/platform/creatChat')
check('no_business_resource_writes', !requests.some(item => ['POST', 'PUT', 'PATCH', 'DELETE'].includes(item.method) && item.path.startsWith('/api/') && !item.path.startsWith('/api/v1/auth/')))
check('no_page_errors', failures.length === 0)
}
main().catch(error => { failures.push(error.message) }).finally(async () => {
if (browser) await browser.close()
const report = { started_at, checked_at: new Date().toISOString(), origin, checks, timings, failures, passed: checks.length === 20 && checks.every(item => item.passed) && failures.length === 0 }
fs.writeFileSync(path.join(root, `acceptance/${reportName}.json`), JSON.stringify(report, null, 2))
console.log(JSON.stringify(report))
if (!report.passed) process.exitCode = 1
})