Commit eaaf80596ed0fe0c67b7f9b0c8c6cd6ab15a56e8

Authored by zichun
1 parent db8f9d9f

test(usr): 用户单据 E2E 关键旅程 REQ-USR-001 REQ-USR-002

frontend/tests/e2e/userdetail.spec.ts 0 → 100644
  1 +// REQ-USR-001 / REQ-USR-002: 用户单据 E2E 关键旅程(Playwright,headless,page.route 桩后端)
  2 +// 注:均经 SPA 内导航(登录 → 用户列表 → 新增/双击行)进入单据,避免 page.goto 全量刷新丢失 Redux 登录态。
  3 +import { test, expect, type Page } from '@playwright/test';
  4 +
  5 +function ok(data: unknown) {
  6 + return JSON.stringify({ code: 0, message: 'success', data });
  7 +}
  8 +
  9 +function err(code: number, message = '业务错误') {
  10 + return JSON.stringify({ code, message, data: null });
  11 +}
  12 +
  13 +const EMPLOYEES = [{ iIncrement: 3, sEmployeeName: '张三', sEmployeeNo: 'zs' }];
  14 +const PERMISSIONS = [
  15 + { iIncrement: 1, sPermissionName: '默认显示', sPermissionCategory: '基础' },
  16 + { iIncrement: 2, sPermissionName: '高级查看', sPermissionCategory: '基础' },
  17 +];
  18 +
  19 +function makeUser(id: number, name: string) {
  20 + return {
  21 + id,
  22 + sUserName: name,
  23 + 员工名: '张三',
  24 + sUserNo: 'zs',
  25 + 部门: null,
  26 + sUserType: '普通用户',
  27 + sLanguage: '中文',
  28 + iIsVoid: 0,
  29 + tLastLoginDate: null,
  30 + sCreator: 'admin',
  31 + tCreateDate: '2026-01-01T00:00:00',
  32 + };
  33 +}
  34 +
  35 +async function stubAuth(page: Page) {
  36 + await page.route('**/api/usr/companies', async (route) => {
  37 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok([{ id: 1, sCompanyName: '甲公司', sVersion: '标准版' }]) });
  38 + });
  39 + await page.route('**/api/usr/login', async (route) => {
  40 + await route.fulfill({
  41 + status: 200,
  42 + contentType: 'application/json',
  43 + body: ok({ token: 'tk-e2e', user: { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' } }),
  44 + });
  45 + });
  46 +}
  47 +
  48 +async function stubLookups(page: Page) {
  49 + await page.route('**/api/usr/employees**', async (route) => {
  50 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok(EMPLOYEES) });
  51 + });
  52 + await page.route('**/api/usr/permissions**', async (route) => {
  53 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok(PERMISSIONS) });
  54 + });
  55 +}
  56 +
  57 +async function loginAndGotoList(page: Page) {
  58 + await page.goto('/login');
  59 + await page.getByPlaceholder('请输入你的用户名').fill('admin');
  60 + await page.getByPlaceholder('请输入你的密码').fill('secret');
  61 + await expect(page.getByText('甲公司(标准版)')).toBeVisible();
  62 + await page.getByRole('button', { name: /登\s*录/ }).click();
  63 + await expect(page).toHaveURL(/\/$/);
  64 + await page.getByRole('button', { name: '用户列表' }).click();
  65 + await expect(page).toHaveURL(/\/usr\/users$/);
  66 +}
  67 +
  68 +test.describe('用户单据关键旅程', () => {
  69 + test('create user and return to list', async ({ page }) => {
  70 + await stubAuth(page);
  71 + await stubLookups(page);
  72 + await page.route('**/api/usr/users', async (route) => {
  73 + if (route.request().method() === 'POST') {
  74 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ id: 9 }) });
  75 + } else {
  76 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) });
  77 + }
  78 + });
  79 + await loginAndGotoList(page);
  80 + await page.getByTestId('btn-add').click();
  81 + await expect(page).toHaveURL(/\/usr\/users\/new$/);
  82 + await expect(page.getByTestId('userdetail-page')).toBeVisible();
  83 + await page.getByTestId('field-username').fill('zhangsan');
  84 + await page.getByTestId('field-userno').fill('zs');
  85 + await page.getByTestId('select-language').locator('.ant-select-selector').click();
  86 + await page.getByText('中文', { exact: true }).click();
  87 + await page.getByTestId('perm-check-1').check();
  88 +
  89 + const reqPromise = page.waitForRequest((req) => req.url().includes('/api/usr/users') && req.method() === 'POST');
  90 + await page.getByTestId('btn-save').click();
  91 + const req = await reqPromise;
  92 + expect(req.postData() ?? '').toContain('zhangsan');
  93 + await expect(page.getByText('用户创建成功')).toBeVisible();
  94 + await expect(page).toHaveURL(/\/usr\/users$/);
  95 + });
  96 +
  97 + test('edit user prefill then save', async ({ page }) => {
  98 + await stubAuth(page);
  99 + await stubLookups(page);
  100 + await page.route('**/api/usr/users/7', async (route) => {
  101 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ id: 7 }) }); // PUT
  102 + });
  103 + await page.route('**/api/usr/users**', async (route) => {
  104 + // 列表(首屏)+ 预填等于匹配(GET)
  105 + if (route.request().method() === 'GET') {
  106 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [makeUser(7, 'zhangsan')], total: 1, pageNum: 1, pageSize: 10 }) });
  107 + } else {
  108 + await route.fallback();
  109 + }
  110 + });
  111 + await loginAndGotoList(page);
  112 + await page.getByText('zhangsan').dblclick();
  113 + await expect(page).toHaveURL(/\/usr\/users\/7$/);
  114 + await expect(page.getByTestId('field-username')).toHaveValue('zhangsan');
  115 + await expect(page.getByTestId('field-username')).toBeDisabled();
  116 + await page.getByTestId('select-language').locator('.ant-select-selector').click();
  117 + await page.getByText('英文', { exact: true }).click();
  118 +
  119 + const reqPromise = page.waitForRequest((req) => req.url().includes('/api/usr/users/7') && req.method() === 'PUT');
  120 + await page.getByTestId('btn-save').click();
  121 + await reqPromise;
  122 + await expect(page.getByText('保存成功')).toBeVisible();
  123 + await expect(page).toHaveURL(/\/usr\/users$/);
  124 + });
  125 +
  126 + test('username conflict shows inline error', async ({ page }) => {
  127 + await stubAuth(page);
  128 + await stubLookups(page);
  129 + await page.route('**/api/usr/users', async (route) => {
  130 + if (route.request().method() === 'POST') {
  131 + await route.fulfill({ status: 200, contentType: 'application/json', body: err(40901, '用户名已存在') });
  132 + } else {
  133 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) });
  134 + }
  135 + });
  136 + await loginAndGotoList(page);
  137 + await page.getByTestId('btn-add').click();
  138 + await page.getByTestId('field-username').fill('zhangsan');
  139 + await page.getByTestId('field-userno').fill('zs');
  140 + await page.getByTestId('select-language').locator('.ant-select-selector').click();
  141 + await page.getByText('中文', { exact: true }).click();
  142 + await page.getByTestId('btn-save').click();
  143 + await expect(page.getByText('用户名已存在,请更换')).toBeVisible();
  144 + });
  145 +
  146 + test('load error shows retry', async ({ page }) => {
  147 + await stubAuth(page);
  148 + await page.route('**/api/usr/users', async (route) => {
  149 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) });
  150 + });
  151 + await page.route('**/api/usr/employees**', async (route) => {
  152 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok(EMPLOYEES) });
  153 + });
  154 + let permFail = true;
  155 + await page.route('**/api/usr/permissions**', async (route) => {
  156 + if (permFail) {
  157 + await route.fulfill({ status: 500, contentType: 'application/json', body: '{}' });
  158 + } else {
  159 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok(PERMISSIONS) });
  160 + }
  161 + });
  162 + await loginAndGotoList(page);
  163 + await page.getByTestId('btn-add').click();
  164 + await expect(page.getByTestId('userdetail-loaderror')).toBeVisible();
  165 + permFail = false;
  166 + await page.getByTestId('userdetail-loaderror').getByRole('button', { name: '点击重试' }).click();
  167 + await expect(page.getByTestId('userdetail-page')).toBeVisible();
  168 + });
  169 +
  170 + test('placeholder tabs/buttons are inert', async ({ page }) => {
  171 + await stubAuth(page);
  172 + await stubLookups(page);
  173 + await page.route('**/api/usr/users', async (route) => {
  174 + await route.fulfill({ status: 200, contentType: 'application/json', body: ok({ records: [], total: 0, pageNum: 1, pageSize: 10 }) });
  175 + });
  176 + await loginAndGotoList(page);
  177 + await page.getByTestId('btn-add').click();
  178 + await expect(page.getByTestId('userdetail-page')).toBeVisible();
  179 + await expect(page.getByRole('tab', { name: '客户查看权限' })).toHaveAttribute('aria-disabled', 'true');
  180 + await page.getByTestId('btn-ph-删除').click();
  181 + await expect(page.getByText('功能开发中')).toBeVisible();
  182 + });
  183 +});