/** * E2E 测试 — 移动端适配验证。 * * 覆盖 2-task-uiux.md 中 P6#47-50 任务的移动端适配: * - 投资人端 Sidebar 折叠为抽屉导航 * - 移动端 Header 显示菜单按钮 * - 抽屉导航可打开/关闭 * * 前置条件:后端 http://localhost:8000 + 前端 http://localhost:3000 已启动。 */ import { test, expect, type Page } from "@playwright/test"; const API_BASE = "http://localhost:8000/api/v1"; const TEST_EMAIL = `e2e_mobile_${Date.now()}@example.com`; const TEST_PASSWORD = "password123"; /** 注册投资人用户并登录。 */ async function loginAsInvestor(page: Page) { await fetch(`${API_BASE}/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: TEST_EMAIL, password: TEST_PASSWORD, name: "E2E移动端用户", tenant_name: "E2E移动端机构", role: "investor", }), }); await page.goto("/login"); await page.fill('input[id="email"]', TEST_EMAIL); await page.fill('input[id="password"]', TEST_PASSWORD); await page.click('button[type="submit"]'); } test.describe("移动端适配", () => { test.use({ viewport: { width: 375, height: 667 } }); test("移动端应显示顶部 Header 和菜单按钮", async ({ page }: { page: Page }) => { await loginAsInvestor(page); // 移动端 Header 应可见 const header = page.locator("header.md\\:hidden"); await expect(header).toBeVisible(); // 菜单按钮应可见 await expect(header.locator("button[aria-label='打开导航菜单']")).toBeVisible(); }); test("点击菜单按钮应打开抽屉导航", async ({ page }: { page: Page }) => { await loginAsInvestor(page); // 点击菜单按钮 await page.locator("button[aria-label='打开导航菜单']").click(); // 抽屉导航应可见 const drawer = page.locator(".fixed.inset-0.z-40"); await expect(drawer).toBeVisible({ timeout: 5000 }); // 抽屉内应包含导航项 await expect(drawer.locator("text=今日").first()).toBeVisible(); await expect(drawer.locator("text=组合").first()).toBeVisible(); }); test("点击关闭按钮应关闭抽屉导航", async ({ page }: { page: Page }) => { await loginAsInvestor(page); // 打开抽屉 await page.locator("button[aria-label='打开导航菜单']").click(); const drawer = page.locator(".fixed.inset-0.z-40"); await expect(drawer).toBeVisible(); // 点击关闭按钮 await page.locator("button[aria-label='关闭导航菜单']").click(); // 抽屉应消失 await expect(drawer).not.toBeVisible(); }); test("移动端 Sidebar 应隐藏", async ({ page }: { page: Page }) => { await loginAsInvestor(page); // 桌面端 Sidebar 在移动端应隐藏 const sidebar = page.locator("aside.hidden.md\\:block"); await expect(sidebar).toBeHidden(); }); test("移动端创始人底部导航应可见", async ({ page }: { page: Page }) => { // 注册创始人用户 const founderEmail = `e2e_mobile_founder_${Date.now()}@example.com`; await fetch(`${API_BASE}/auth/register`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: founderEmail, password: TEST_PASSWORD, name: "E2E移动端创始人", tenant_name: "E2E移动端创始人机构", role: "founder", }), }); await page.goto("/login"); await page.fill('input[id="email"]', founderEmail); await page.fill('input[id="password"]', TEST_PASSWORD); await page.click('button[type="submit"]'); // 创始人用户登录后可能重定向到投资人端,需显式导航 await page.goto('/founder'); // 底部导航应可见 const bottomNav = page.locator("nav.fixed.bottom-0"); await expect(bottomNav).toBeVisible(); // 应包含 6 个导航项 await expect(bottomNav.locator("text=今日")).toBeVisible(); await expect(bottomNav.locator("text=经营")).toBeVisible(); }); });