import assert from 'node:assert/strict'; import { readFile, mkdir, writeFile } from 'node:fs/promises'; import { chromium } from 'playwright'; import { checkCustomer } from './customer-functional.mjs'; import { checkPlatform } from './platform-functional.mjs'; import { checkLayouts } from './layout-functional.mjs'; import { checkSessions } from './session-functional.mjs'; import { checkComponents } from './components-functional.mjs'; const baseUrl = process.env.FRONTEND_URL || 'http://127.0.0.1:18762'; const accounts = JSON.parse(await readFile(new URL('../../.work/frontend-accounts.json', import.meta.url), 'utf8')); const browser = await chromium.launch({ headless: true, executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE }); const results = []; const errors = []; let requests = 0; try { for (const [name, check] of [['customer', checkCustomer], ['platform', checkPlatform]]) { const context = await browser.newContext({ viewport: { width: 1440, height: 1000 } }); const page = await context.newPage(); page.setDefaultTimeout(15000); page.on('pageerror', error => errors.push(`${name}: ${error.message}`)); page.on('request', request => { if (/\/(openapi|admin)\//.test(request.url())) requests++; }); const checks = await check(page, baseUrl, accounts); results.push({ area: name, checks }); const palette = await page.locator(name === 'customer' ? '.mobile .content' : '.admin-sidebar').evaluate(element => getComputedStyle(element).backgroundColor); assert.equal(palette, name === 'customer' ? 'rgb(246, 248, 252)' : 'rgb(7, 17, 38)', `${name} 已确认背景色`); // 只检查公开 DOM 的布局,不读取组件内部状态,也不使用截图。 for (const width of [1440, 390]) { await page.setViewportSize({ width, height: 1000 }); const geometry = await page.locator('body').evaluate(element => ({ width: document.documentElement.clientWidth, scroll: element.scrollWidth })); assert.ok(geometry.scroll <= geometry.width + 1, `${name} ${width}px 页面横向溢出`); } await context.close(); } const layoutContext = await browser.newContext(); const layoutPage = await layoutContext.newPage(); layoutPage.on('pageerror', error => errors.push(`layout: ${error.message}`)); results.push({ area: 'layout', checks: await checkLayouts(layoutPage, baseUrl, accounts) }); await layoutContext.close(); const sessionContext = await browser.newContext(); const sessionPage = await sessionContext.newPage(); sessionPage.on('pageerror', error => errors.push(`session: ${error.message}`)); results.push({ area: 'session', checks: await checkSessions(sessionPage, baseUrl, accounts) }); await sessionContext.close(); const componentContext = await browser.newContext(); const componentPage = await componentContext.newPage(); componentPage.on('pageerror', error => errors.push(`components: ${error.message}`)); results.push({ area: 'components', checks: await checkComponents(componentPage, baseUrl) }); await componentContext.close(); assert.deepEqual(errors, []); await mkdir(new URL('../../.work/', import.meta.url), { recursive: true }); await writeFile(new URL('../../.work/frontend-functional.json', import.meta.url), JSON.stringify({ results, errors, requests, screenshots: 0, generatedModelRequests: 0 }, null, 2)); console.log(JSON.stringify({ areas: results.map(item => ({ area: item.area, passed: item.checks.length })), errors, requests, screenshots: 0 }, null, 2)); } finally { await browser.close(); } // 商家流程使用自己的真实登录与独立浏览器会话,与前面的消费者流程依次执行。 await import('./merchant-functional.mjs'); await import('./merchant-business.mjs'); await import('./entry-isolation.mjs'); await import('./platform-session.mjs'); await import('./login-rate-limit.mjs'); const merchant = JSON.parse(await readFile(new URL('../../.work/frontend-verification/merchant.json', import.meta.url), 'utf8')); const business = JSON.parse(await readFile(new URL('../../.work/frontend-verification/merchant-business.json', import.meta.url), 'utf8')); const extra = {}; for (const name of ['entry-isolation', 'platform-session', 'login-rate-limit']) extra[name] = JSON.parse(await readFile(new URL(`../../.work/frontend-verification/${name}.json`, import.meta.url), 'utf8')); await writeFile(new URL('../../.work/frontend-verification/full.json', import.meta.url), JSON.stringify({ date: new Date().toISOString(), results, merchant, business, extra, browserErrors: errors, screenshots: 0, generatedModelRequests: 0 }, null, 2)); console.log('全部前端黑盒与功能检查通过。');