Files
agent/apps/desktop/scripts/visual-check.mjs

76 lines
5.4 KiB
JavaScript
Raw Normal View History

import { createServer } from 'vite'
import { chromium } from 'playwright'
const port = Number(process.env.MINI_VISUAL_PORT ?? 4180)
const server = await createServer({ root: process.cwd(), server: { host: '127.0.0.1', port, strictPort: true } })
await server.listen()
const browser = await chromium.launch({ headless: true })
const page = await browser.newPage({ viewport: { width: 420, height: 820 }, deviceScaleFactor: 1 })
const errors = []
page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()) })
const projectId = '019b0000-0000-7000-8000-000000000101'
const user = { email: 'demo@senlin.ai', displayName: '演示用户' }
const workspace = {
project: { id: projectId, name: '森林项目', identifier: 'forest', icon: 'folder', background: '#165DFF', description: '项目协作空间', initials: '森林', unreadCount: 0 },
channels: [], tags: [{ id: 'tag-product', name: '产品' }, { id: 'tag-design', name: '设计' }], recentSessions: [], inbox: [{ id: 'inbox-1', projectId, source: 'manual', title: '客户反馈', summary: '整理下周的反馈内容', status: 'open', tag: '产品', time: '2026-07-22T10:00:00Z' }],
tasks: [
{ id: 'task-1', projectId, title: '整理产品路线', summary: '确认下一阶段交付范围。', completed: false, owner: '张明', due: null, createdAt: '2026-07-22T09:00:00Z', completedAt: null, tagId: 'tag-product', tag: '产品' },
{ id: 'task-2', projectId, title: '完成迷你布局', summary: '验证窄屏下的导航和滚动。', completed: true, owner: '张明', due: null, createdAt: '2026-07-21T10:00:00Z', completedAt: '2026-07-22T10:00:00Z', tagId: 'tag-design', tag: '设计' },
],
aiSessions: [{ id: 'session-1', projectId, title: '梳理版本计划', summary: '下一步怎么安排', updatedAt: '2026-07-22T10:00:00Z', references: [] }],
documents: [{ id: 'doc-1', projectId, kind: 'markdown', name: '版本规划', extension: '.md', mimeType: 'text/markdown', updatedAt: '2026-07-22T10:00:00Z' }],
cronPlans: [{ id: 'cron-1', projectId, title: '每周复盘', schedule: '0 17 * * 5', nextRun: null, enabled: true, lastResult: '', owner: '张明' }],
}
const expert = { id: 'expert-1', slug: 'product-manager', category: 'product', categoryName: '产品', name: '产品经理', description: '负责需求分析', emoji: '📘', color: '#165DFF' }
await page.route('http://localhost:9150/api/v1/**', async (route) => {
const request = route.request(); const path = new URL(request.url()).pathname
if (path === '/api/v1/auth/login') return route.fulfill({ json: { token: 'app-token', user } })
if (path === '/api/v1/auth/me') return route.fulfill({ json: user })
if (path === '/api/v1/projects') return route.fulfill({ json: [workspace.project] })
if (path === `/api/v1/projects/${projectId}/workspace`) return route.fulfill({ json: workspace })
if (path === '/api/v1/ai-experts') return route.fulfill({ json: [expert] })
if (path.includes('/tasks/') && request.method() === 'PATCH') return route.fulfill({ json: {} })
return route.fulfill({ status: 404, json: { error: { code: 'not_found', message: '未配置的检查接口' } } })
})
await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle' })
await page.locator('.login-card input').nth(2).press('Enter')
await page.waitForSelector('.app-shell')
await page.waitForSelector('.task-row')
2026-07-25 20:14:58 +08:00
await page.screenshot({ path: 'test-results/app-home.png', fullPage: true })
const home = await page.evaluate(() => ({
width: document.querySelector('.app-shell')?.getBoundingClientRect().width,
selectedAll: document.querySelector('.project-pill.active span')?.textContent,
navCount: document.querySelectorAll('.channel-rail button').length,
pending: document.querySelectorAll('.task-row:not(.done)').length,
completed: document.querySelectorAll('.task-row.done').length,
}))
await page.locator('.task-row').first().click()
const taskModal = await page.getByText('编辑任务').count()
await page.getByRole('button', { name: '取消' }).click()
await page.locator('.channel-rail button[title="资料"]').click()
const documents = await page.locator('.detail-row').count()
await page.locator('.channel-rail button[title="AI"]').click()
await page.waitForSelector('.expert-chips')
const experts = await page.locator('.expert-chips span').count()
await page.locator('.profile-trigger').click()
await page.getByText('修改资料').waitFor()
const profileMenu = await page.getByText('修改资料').count()
2026-07-25 20:14:58 +08:00
await page.screenshot({ path: 'test-results/app-ai.png', fullPage: true })
await browser.close(); await server.close()
const failures = []
if (home.width !== 420) failures.push(`expected 420px shell, got ${home.width}`)
if (home.selectedAll !== '全部') failures.push(`default project scope must be 全部, got ${home.selectedAll}`)
if (home.navCount !== 6) failures.push(`expected six right rail menus, got ${home.navCount}`)
if (home.pending !== 1 || home.completed !== 1) failures.push(`task sections incorrect: ${JSON.stringify(home)}`)
if (taskModal !== 1) failures.push('task editor did not open')
if (documents !== 1) failures.push(`expected one document, got ${documents}`)
if (experts !== 1) failures.push(`expected one AI expert, got ${experts}`)
if (profileMenu !== 1) failures.push('profile menu did not open')
if (errors.length) failures.push(`console errors: ${errors.join('; ')}`)
console.log(JSON.stringify({ home, documents, experts, taskModal, profileMenu, errors }, null, 2))
if (failures.length) throw new Error(failures.join('\n'))