userdetail.spec.ts
8.15 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// 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();
});
});