test: add frontend visual regression coverage
ci / gitleaks (push) Successful in 9s
ci / docker (push) Successful in 4m4s

This commit is contained in:
2026-09-19 13:21:57 +08:00
parent cec0a4418c
commit 9c0dc3a31f
5 changed files with 55 additions and 2 deletions
+45
View File
@@ -0,0 +1,45 @@
import type { APIRequestContext, Page } from '@playwright/test'
import { expect, test } from './fixtures'
async function csrf(request: APIRequestContext) {
const state = await request.storageState()
return state.cookies.find(cookie => cookie.name === 'dodo_csrf')?.value ?? ''
}
async function createTask(request: APIRequestContext, baseURL: string, title: string, listId: string) {
const response = await request.post('/api/v1/tasks', {
data: { title, list_id: listId },
headers: { 'x-csrf-token': await csrf(request), origin: baseURL },
})
expect(response.ok(), await response.text()).toBeTruthy()
}
async function openInbox(page: Page) {
if ((await page.viewportSize())!.width <= 930) {
await page.locator('.topbar').getByRole('button', { name: /展开菜单|收起菜单/ }).click()
}
await page.locator('.sidebar').getByRole('button', { name: '收集箱', exact: true }).click()
}
test('inbox and task composer match approved visuals', async ({ page, request, baseURL }, testInfo) => {
test.skip(testInfo.project.name !== 'mobile-390', 'approved visual baselines are maintained for mobile-390 only')
await page.clock.install({ time: new Date('2026-09-19T05:00:00.000Z') })
const bootstrap = await request.get('/api/v1/bootstrap')
expect(bootstrap.ok()).toBeTruthy()
const inbox = (await bootstrap.json()).lists.find((item: { is_inbox: boolean }) => item.is_inbox)
expect(inbox).toBeTruthy()
await createTask(request, baseURL!, '整理本周工作记录', inbox.id)
await createTask(request, baseURL!, '检查 Dodo 备份', inbox.id)
await createTask(request, baseURL!, '周末买水果和牛奶', inbox.id)
await page.goto('/')
await openInbox(page)
await expect(page.locator('.task-row')).toHaveCount(3)
await expect(page).toHaveScreenshot('inbox.png', { fullPage: true })
await page.getByRole('button', { name: '添加任务' }).click()
await expect(page.getByRole('dialog', { name: /添加任务/ })).toBeVisible()
await expect(page).toHaveScreenshot('task-composer.png', { fullPage: true })
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+1 -1
View File
@@ -1 +1 @@
{"name":"dodo-frontend","private":true,"version":"0.1.0","type":"module","packageManager":"[email protected]","scripts":{"dev":"vite --host 0.0.0.0","build":"vue-tsc -b && vite build","test":"vitest run --exclude 'e2e/**'","test:e2e:mobile":"node scripts/playwright-mobile.mjs"},"dependencies":{"@vitejs/plugin-vue":"latest","class-variance-authority":"latest","clsx":"latest","lucide-vue-next":"^0.468.0","markdown-it":"^15.0.2","markdown-it-task-lists":"^2.1.1","reka-ui":"latest","tailwind-merge":"latest","vue":"latest","vue-router":"latest"},"devDependencies":{"@playwright/test":"^1.63.0","@tailwindcss/vite":"latest","@types/markdown-it":"^14.2.0","@types/node":"latest","fflate":"^0.8.3","jsdom":"^30.0.1","tailwindcss":"latest","typescript":"^5.7.2","vite":"latest","vitest":"latest","vue-tsc":"latest"},"pnpm":{"onlyBuiltDependencies":["vue-demi"]}}
{"name":"dodo-frontend","private":true,"version":"0.1.0","type":"module","packageManager":"[email protected]","scripts":{"dev":"vite --host 0.0.0.0","build":"vue-tsc -b && vite build","test":"vitest run --exclude 'e2e/**'","test:e2e:mobile":"node scripts/playwright-mobile.mjs","test:e2e:visual":"DODO_E2E_PROJECT=mobile-390 playwright test e2e/visual-regression.spec.ts --project mobile-390"},"dependencies":{"@vitejs/plugin-vue":"latest","class-variance-authority":"latest","clsx":"latest","lucide-vue-next":"^0.468.0","markdown-it":"^15.0.2","markdown-it-task-lists":"^2.1.1","reka-ui":"latest","tailwind-merge":"latest","vue":"latest","vue-router":"latest"},"devDependencies":{"@playwright/test":"^1.63.0","@tailwindcss/vite":"latest","@types/markdown-it":"^14.2.0","@types/node":"latest","fflate":"^0.8.3","jsdom":"^30.0.1","tailwindcss":"latest","typescript":"^5.7.2","vite":"latest","vitest":"latest","vue-tsc":"latest"},"pnpm":{"onlyBuiltDependencies":["vue-demi"]}}
+9 -1
View File
@@ -25,7 +25,15 @@ export default defineConfig({
fullyParallel: false,
workers: 1,
timeout: 45_000,
expect: { timeout: 8_000 },
expect: {
timeout: 8_000,
toHaveScreenshot: {
animations: 'disabled',
caret: 'hide',
maxDiffPixelRatio: 0.001,
},
},
snapshotPathTemplate: '{testDir}/{testFilePath}-snapshots/{arg}-{projectName}{ext}',
outputDir: path.join(runtimeRoot, 'test-results'),
reporter: [['line'], ['html', { outputFolder: path.join(runtimeRoot, 'report'), open: 'never' }]],
globalSetup: './e2e/global-setup.ts',