// REQ-USR-001 / REQ-USR-002: 用户单据 E2E 关键旅程(Playwright,headless,page.route 桩后端) // 注:均经 SPA 内导航(登录 → 用户列表 → 新增/双击行)进入单据,避免 page.goto 全量刷新丢失 Redux 登录态。 import { test, expect, type Page } from '@playwright/test'; function ok(data: unknown) { return JSON.stringify({ code: 0, message: 'success', data }); } function err(code: number, message = '业务错误') { return JSON.stringify({ code, message, data: null }); } const EMPLOYEES = [{ iIncrement: 3, sEmployeeName: '张三', sEmployeeNo: 'zs' }]; const PERMISSIONS = [ { iIncrement: 1, sPermissionName: '默认显示', sPermissionCategory: '基础' }, { iIncrement: 2, sPermissionName: '高级查看', sPermissionCategory: '基础' }, ]; function makeUser(id: number, name: string) { return { id, sUserName: name, 员工名: '张三', sUserNo: 'zs', 部门: null, sUserType: '普通用户', sLanguage: '中文', iIsVoid: 0, tLastLoginDate: null, sCreator: 'admin', tCreateDate: '2026-01-01T00:00:00', }; } async function stubAuth(page: Page) { await page.route('**/api/usr/companies', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok([{ id: 1, sCompanyName: '甲公司', sVersion: '标准版' }]) }); }); await page.route('**/api/usr/login', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ token: 'tk-e2e', user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' } }), }); }); } async function stubLookups(page: Page) { await page.route('**/api/usr/employees**', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok(EMPLOYEES) }); }); await page.route('**/api/usr/permissions**', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok(PERMISSIONS) }); }); } async function loginAndGotoList(page: Page) { await page.goto('/login'); await page.getByPlaceholder('请输入你的用户名').fill('admin'); await page.getByPlaceholder('请输入你的密码').fill('secret'); await expect(page.getByText('甲公司(标准版)')).toBeVisible(); await page.getByRole('button', { name: /登\s*录/ }).click(); await expect(page).toHaveURL(/\/$/); await page.getByRole('button', { name: '用户列表' }).click(); await expect(page).toHaveURL(/\/usr\/users$/); } test.describe('用户单据关键旅程', () => { test('create user and return to list', async ({ page }) => { await stubAuth(page); await stubLookups(page); await page.route('**/api/usr/users', async (route) => { if (route.request().method() === 'POST') { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ id: 9 }) }); } else { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) }); } }); await loginAndGotoList(page); await page.getByTestId('btn-add').click(); await expect(page).toHaveURL(/\/usr\/users\/new$/); await expect(page.getByTestId('userdetail-page')).toBeVisible(); await page.getByTestId('field-username').fill('zhangsan'); await page.getByTestId('field-userno').fill('zs'); await page.getByTestId('select-language').locator('.ant-select-selector').click(); await page.getByText('中文', { exact: true }).click(); await page.getByTestId('perm-check-1').check(); const reqPromise = page.waitForRequest((req) => req.url().includes('/api/usr/users') && req.method() === 'POST'); await page.getByTestId('btn-save').click(); const req = await reqPromise; expect(req.postData() ?? '').toContain('zhangsan'); await expect(page.getByText('用户创建成功')).toBeVisible(); await expect(page).toHaveURL(/\/usr\/users$/); }); test('edit user prefill then save', async ({ page }) => { await stubAuth(page); await stubLookups(page); await page.route('**/api/usr/users/7', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ id: 7 }) }); // PUT }); await page.route('**/api/usr/users**', async (route) => { // 列表(首屏)+ 预填等于匹配(GET) if (route.request().method() === 'GET') { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [makeUser(7, 'zhangsan')], total: 1, pageNum: 1, pageSize: 10 }) }); } else { await route.fallback(); } }); await loginAndGotoList(page); await page.getByText('zhangsan').dblclick(); await expect(page).toHaveURL(/\/usr\/users\/7$/); await expect(page.getByTestId('field-username')).toHaveValue('zhangsan'); await expect(page.getByTestId('field-username')).toBeDisabled(); await page.getByTestId('select-language').locator('.ant-select-selector').click(); await page.getByText('英文', { exact: true }).click(); const reqPromise = page.waitForRequest((req) => req.url().includes('/api/usr/users/7') && req.method() === 'PUT'); await page.getByTestId('btn-save').click(); await reqPromise; await expect(page.getByText('保存成功')).toBeVisible(); await expect(page).toHaveURL(/\/usr\/users$/); }); test('username conflict shows inline error', async ({ page }) => { await stubAuth(page); await stubLookups(page); await page.route('**/api/usr/users', async (route) => { if (route.request().method() === 'POST') { await route.fulfill({ status: 200, contentType: 'application/json', body: err(40901, '用户名已存在') }); } else { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) }); } }); await loginAndGotoList(page); await page.getByTestId('btn-add').click(); await page.getByTestId('field-username').fill('zhangsan'); await page.getByTestId('field-userno').fill('zs'); await page.getByTestId('select-language').locator('.ant-select-selector').click(); await page.getByText('中文', { exact: true }).click(); await page.getByTestId('btn-save').click(); await expect(page.getByText('用户名已存在,请更换')).toBeVisible(); }); test('load error shows retry', async ({ page }) => { await stubAuth(page); await page.route('**/api/usr/users', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) }); }); await page.route('**/api/usr/employees**', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok(EMPLOYEES) }); }); let permFail = true; await page.route('**/api/usr/permissions**', async (route) => { if (permFail) { await route.fulfill({ status: 500, contentType: 'application/json', body: '{}' }); } else { await route.fulfill({ status: 200, contentType: 'application/json', body: ok(PERMISSIONS) }); } }); await loginAndGotoList(page); await page.getByTestId('btn-add').click(); await expect(page.getByTestId('userdetail-loaderror')).toBeVisible(); permFail = false; await page.getByTestId('userdetail-loaderror').getByRole('button', { name: '点击重试' }).click(); await expect(page.getByTestId('userdetail-page')).toBeVisible(); }); test('placeholder tabs/buttons are inert', async ({ page }) => { await stubAuth(page); await stubLookups(page); await page.route('**/api/usr/users', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) }); }); await loginAndGotoList(page); await page.getByTestId('btn-add').click(); await expect(page.getByTestId('userdetail-page')).toBeVisible(); await expect(page.getByRole('tab', { name: '客户查看权限' })).toHaveAttribute('aria-disabled', 'true'); await page.getByTestId('btn-ph-删除').click(); await expect(page.getByText('功能开发中')).toBeVisible(); }); });