91 lines
7.2 KiB
JavaScript
91 lines
7.2 KiB
JavaScript
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' }
|
|
const datasetSource = { id: 'source-1', name: '行业资讯', kind: 'rss', url: 'https://example.com/feed', iconUrl: '', description: '行业动态', enabled: true, builtIn: false, lastSyncedAt: null, itemCount: 1, createdAt: '2026-07-22T10:00:00Z', updatedAt: '2026-07-22T10:00:00Z' }
|
|
const datasetItem = { id: 'item-1', sourceId: 'source-1', title: 'AI 工作台趋势', summary: '新的协作方式正在改变项目管理。', content: '这是来自数据源的探索内容,可在 Web 工作台继续管理数据源和同步任务。', url: '', imageUrl: '', status: 'unread', starred: false, publishedAt: '2026-07-22T10:00:00Z', createdAt: '2026-07-22T10:00:00Z', updatedAt: '2026-07-22T10:00:00Z' }
|
|
|
|
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 === '/api/v1/dataset-sources') return route.fulfill({ json: [datasetSource] })
|
|
if (path === '/api/v1/dataset-items') return route.fulfill({ json: { items: [datasetItem], total: 1, limit: 50, offset: 0 } })
|
|
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')
|
|
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-current span')?.textContent,
|
|
navCount: document.querySelectorAll('.channel-rail button').length,
|
|
statCount: document.querySelectorAll('.stat-card').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.getByRole('button', { name: '探索' }).click()
|
|
await page.waitForSelector('.explore-detail')
|
|
const exploreSources = await page.locator('.explore-source-strip button').count()
|
|
const exploreDetail = await page.locator('.explore-detail').count()
|
|
await page.screenshot({ path: 'test-results/app-explore.png', fullPage: true })
|
|
await page.locator('.project-current').click()
|
|
await page.getByRole('option', { name: '森林项目' }).click()
|
|
await page.waitForSelector('.channel-rail')
|
|
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()
|
|
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 !== 0) failures.push(`all-projects view must hide the right rail, got ${home.navCount}`)
|
|
if (home.statCount !== 4) failures.push(`expected four summary cards, got ${home.statCount}`)
|
|
if (home.pending !== 1 || home.completed !== 0) failures.push(`all-project task sections incorrect: ${JSON.stringify(home)}`)
|
|
if (taskModal !== 1) failures.push('task editor did not open')
|
|
if (exploreSources !== 2 || exploreDetail !== 1) failures.push(`explore reader did not render: ${JSON.stringify({ exploreSources, exploreDetail })}`)
|
|
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, exploreSources, exploreDetail, profileMenu, errors }, null, 2))
|
|
if (failures.length) throw new Error(failures.join('\n'))
|