From 2900cc7caa1247b6e14f78879c9714ff4c048c8c Mon Sep 17 00:00:00 2001 From: zichun Date: Mon, 1 Jun 2026 17:44:50 +0800 Subject: [PATCH] test(usr): 用户列表 E2E 关键旅程 REQ-USR-003 --- frontend/tests/e2e/userlist.spec.ts | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+), 0 deletions(-) create mode 100644 frontend/tests/e2e/userlist.spec.ts diff --git a/frontend/tests/e2e/userlist.spec.ts b/frontend/tests/e2e/userlist.spec.ts new file mode 100644 index 0000000..8df7393 --- /dev/null +++ b/frontend/tests/e2e/userlist.spec.ts @@ -0,0 +1,182 @@ +import { test, expect, type Page } from '@playwright/test'; + +// 桩用户列表响应工厂 +function usersBody(records: unknown[], total: number, pageNum = 1, pageSize = 10) { + return JSON.stringify({ + code: 0, + message: 'success', + data: { records, total, pageNum, pageSize }, + }); +} + +function makeUser(id: number, name: string, over: Record = {}) { + return { + id, + sUserName: name, + 员工名: null, + sUserNo: `U00${id}`, + 部门: '技术部', + sUserType: '普通用户', + sLanguage: '中文', + iIsVoid: 0, + tLastLoginDate: null, + sCreator: 'admin', + tCreateDate: '2024-01-01T00:00:00', + ...over, + }; +} + +// 桩登录 / 版本下拉(沿用 shell.spec.ts 模式)。用户列表由各用例单独桩。 +async function stubAuth(page: Page) { + await page.route('**/api/usr/companies', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + code: 0, + message: 'success', + data: [{ id: 1, sCompanyName: '甲公司', sVersion: '标准版' }], + }), + }); + }); + await page.route('**/api/usr/login', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + code: 0, + message: 'success', + data: { + token: 'tk-e2e', + user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' }, + }, + }), + }); + }); +} + +async function login(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(/\/$/); +} + +async function gotoUserList(page: Page) { + await page.getByRole('button', { name: '用户列表' }).click(); + await expect(page).toHaveURL(/\/usr\/users$/); +} + +test.describe('用户列表关键旅程', () => { + test('enter user list renders rows', async ({ page }) => { + await stubAuth(page); + await page.route('**/api/usr/users**', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([makeUser(1, '李雷'), makeUser(2, '韩梅梅')], 2), + }); + }); + await login(page); + await gotoUserList(page); + await expect(page.getByText('李雷')).toBeVisible(); + await expect(page.getByText('共 2 条记录')).toBeVisible(); + }); + + test('empty result shows 暂无匹配的用户', async ({ page }) => { + await stubAuth(page); + await page.route('**/api/usr/users**', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([], 0), + }); + }); + await login(page); + await gotoUserList(page); + await expect(page.getByText('暂无匹配的用户')).toBeVisible(); + }); + + test('search by value triggers query', async ({ page }) => { + await stubAuth(page); + await page.route('**/api/usr/users**', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([makeUser(3, '王搜索')], 1), + }); + }); + await login(page); + await gotoUserList(page); + const input = page.getByTestId('filter-query-value').locator('input'); + await input.fill('王'); + const reqPromise = page.waitForRequest((req) => + req.url().includes('/api/usr/users') && req.url().includes('queryValue'), + ); + await page.getByTestId('btn-search').click(); + const req = await reqPromise; + expect(decodeURIComponent(req.url())).toContain('queryValue=王'); + await expect(page.getByText('王搜索')).toBeVisible(); + }); + + test('pagination next page refetches with pageNum=2', async ({ page }) => { + await stubAuth(page); + await page.route('**/api/usr/users**', async (route) => { + const url = route.request().url(); + const pageNum = url.includes('pageNum=2') ? 2 : 1; + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([makeUser(pageNum, `行${pageNum}`)], 30, pageNum, 10), + }); + }); + await login(page); + await gotoUserList(page); + await expect(page.getByText('行1')).toBeVisible(); + const reqPromise = page.waitForRequest((req) => + req.url().includes('/api/usr/users') && req.url().includes('pageNum=2'), + ); + await page.getByTitle('下一页').click(); + await reqPromise; + await expect(page.getByText('行2')).toBeVisible(); + }); + + test('double click row navigates to user detail', async ({ page }) => { + await stubAuth(page); + await page.route('**/api/usr/users**', async (route) => { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([makeUser(88, '详情用户')], 1), + }); + }); + await login(page); + await gotoUserList(page); + await page.getByText('详情用户').dblclick(); + await expect(page).toHaveURL(/\/usr\/users\/88$/); + }); + + test('error response shows retry then recovers', async ({ page }) => { + await stubAuth(page); + let fail = true; + await page.route('**/api/usr/users**', async (route) => { + if (fail) { + await route.fulfill({ status: 500, contentType: 'application/json', body: '{}' }); + } else { + await route.fulfill({ + status: 200, + contentType: 'application/json', + body: usersBody([makeUser(1, '恢复用户')], 1), + }); + } + }); + await login(page); + await gotoUserList(page); + await expect(page.getByText('加载失败,点击重试')).toBeVisible(); + fail = false; + await page.getByTestId('userlist-error').getByRole('button', { name: '点击重试' }).click(); + await expect(page.getByText('恢复用户')).toBeVisible(); + }); +}); -- libgit2 0.22.2