|
- import type { APIRequestContext, Locator, Page } from '@playwright/test'
-
- import { attachJson, captureScreenshot, expect, runPrefix, test } from './fixtures'
- import {
- adminAccessToken,
- chooseSelectOption,
- confirmMessageBox,
- envelopeData,
- fillFormItem,
- loginAsAdmin,
- tableRowByText,
- visibleDialog,
- } from './helpers'
-
- test.describe.configure({ mode: 'serial' })
- test.use({ trace: 'off', video: 'off' })
-
- const suffix = runPrefix.replace(/[^A-Za-z0-9]/g, '').slice(-14).toLowerCase()
- const data = {
- departmentName: `${runPrefix}-测试部门`,
- departmentCode: `E2E_${suffix.toUpperCase()}`.slice(0, 32),
- roleName: `${runPrefix}-测试角色`,
- roleCode: `e2e_${suffix}`.slice(0, 48),
- username: `e2e_${suffix}`.slice(0, 32),
- displayName: `${runPrefix}-学员`,
- editedDisplayName: `${runPrefix}-已编辑学员`,
- initialPassword: `Init@${suffix.slice(-6)}Aa1`,
- finalPassword: `Final@${suffix.slice(-5)}Bb2`,
- }
-
- const auth = (token: string) => ({ Authorization: `Bearer ${token}` })
- const record = (value: unknown) => value && typeof value === 'object' ? value as Record<string, unknown> : {}
- const records = (value: unknown) => {
- const root = record(envelopeData(value))
- const items = root.records ?? root.items
- return Array.isArray(items) ? items.map(record) : Array.isArray(envelopeData(value)) ? (envelopeData(value) as unknown[]).map(record) : []
- }
-
- async function json(response: Awaited<ReturnType<APIRequestContext['get']>>) {
- return response.json().catch(() => ({}))
- }
-
- async function cleanupCreatedData(request: APIRequestContext, token: string) {
- const result: Record<string, string> = {}
- const userList = await request.get('/api/auth/v1/users', { headers: auth(token), params: { keyword: data.username, size: 100 } })
- const user = records(await json(userList)).find((item) => String(item.username) === data.username)
- if (user) {
- const response = await request.delete(`/api/auth/v1/users/${String(user.id)}`, {
- headers: auth(token), data: { version: Number(user.version ?? user.dataVersion ?? 0) },
- })
- result.user = `${response.status()}`
- }
-
- const roleList = await request.get('/api/auth/v1/roles', { headers: auth(token), params: { keyword: data.roleCode, size: 100 } })
- const role = records(await json(roleList)).find((item) => String(item.code) === data.roleCode)
- if (role) {
- const response = await request.delete(`/api/auth/v1/roles/${String(role.id)}`, {
- headers: auth(token), data: { version: Number(role.version ?? role.dataVersion ?? 0) },
- })
- result.role = `${response.status()}`
- }
-
- const departmentsResponse = await request.get('/api/auth/v1/departments', { headers: auth(token) })
- const departmentValue = envelopeData(await json(departmentsResponse))
- const departments = Array.isArray(departmentValue) ? departmentValue.map(record) : []
- const department = departments.find((item) => String(item.code) === data.departmentCode)
- if (department) {
- const response = await request.post(`/api/auth/v1/departments/${String(department.id)}/migrate-and-delete`, {
- headers: auth(token), data: { version: Number(department.version ?? department.dataVersion ?? 0) },
- })
- result.department = `${response.status()}`
- }
- return result
- }
-
- async function fillInputByPlaceholder(page: Page, placeholder: string, value: string) {
- const input = page.getByPlaceholder(placeholder)
- await expect(input).toBeVisible()
- await input.fill(value)
- }
-
- async function logout(page: Page) {
- await page.locator('.user-menu-trigger').click()
- await page.getByRole('menuitem', { name: /退出登录/ }).click()
- await confirmMessageBox(page, '退出登录')
- await expect(page).toHaveURL(/\/login(?:\?|$)/)
- }
-
- async function selectStudentIdentity(page: Page) {
- await page.locator('[data-role-code="student"]').click()
- const picker = page.locator('.login-identity-picker')
- await expect(picker).toBeVisible()
- await picker.click()
- const option = page.locator('.login-identity-popper .el-select-dropdown__item').filter({ hasText: data.editedDisplayName }).first()
- await expect(option).toBeVisible()
- await option.click()
- }
-
- test('IAM 真实生命周期:部门、角色、用户、授权、首次改密和逆序清理', async ({ page, request }, testInfo) => {
- test.slow()
- const token = await adminAccessToken(request)
- await cleanupCreatedData(request, token)
- await loginAsAdmin(page)
- let primaryError: unknown
- let cleanup: Record<string, string> = {}
-
- try {
- await test.step('通过部门管理页面创建并持久化测试部门', async () => {
- await page.goto('/organization/departments')
- await page.getByRole('button', { name: '新增部门', exact: true }).click()
- const dialog = visibleDialog(page)
- await fillFormItem(dialog, '部门名称', data.departmentName)
- await fillFormItem(dialog, '部门编码', data.departmentCode)
- await fillFormItem(dialog, '部门职责', `${runPrefix} E2E 自动化部门,运行结束后删除`)
- await dialog.getByRole('button', { name: '创建部门' }).click()
- await fillInputByPlaceholder(page, '请输入部门、编码或负责人', data.departmentCode)
- await page.getByRole('button', { name: '搜索', exact: true }).click()
- await expect(tableRowByText(page, data.departmentName)).toBeVisible()
- await captureScreenshot(page, testInfo, '01-department-created')
- })
-
- await test.step('通过角色管理页面创建、编辑并持久化自定义角色', async () => {
- await page.goto('/organization/roles')
- await page.getByRole('button', { name: '新增角色', exact: true }).click()
- const dialog = visibleDialog(page)
- await fillFormItem(dialog, '角色名称', data.roleName)
- await fillFormItem(dialog, '角色编码', data.roleCode)
- await fillFormItem(dialog, '界面简称', '测')
- await fillFormItem(dialog, '角色说明', `${runPrefix} 创建测试`)
- await dialog.getByRole('button', { name: '创建角色' }).click()
- await fillInputByPlaceholder(page, '请输入角色名称、编码或说明', data.roleCode)
- await page.getByRole('button', { name: '搜索', exact: true }).click()
- const row = tableRowByText(page, data.roleName)
- await expect(row).toBeVisible()
- await row.getByRole('button', { name: '编辑' }).click()
- const editDialog = visibleDialog(page)
- await fillFormItem(editDialog, '角色说明', `${runPrefix} 已编辑并保存`)
- await editDialog.getByRole('button', { name: '保存修改' }).click()
- await expect(tableRowByText(page, '已编辑并保存')).toBeVisible()
- await captureScreenshot(page, testInfo, '02-role-created-edited')
- })
-
- await test.step('在权限页给临时角色保存一项真实权限', async () => {
- await page.goto('/organization/permissions')
- const roleButton = page.locator('.role-selector > button').filter({ hasText: data.roleName })
- await expect(roleButton).toBeVisible()
- await roleButton.click()
- const uncheckedPermission = page.locator('.permission-item[aria-checked="false"]').first()
- await expect(uncheckedPermission).toBeVisible()
- await uncheckedPermission.click()
- const save = page.getByRole('button', { name: '保存权限配置' })
- await expect(save).toBeEnabled()
- await save.click()
- await expect(page.getByText(/权限已保存并立即生效/).last()).toBeVisible()
- await captureScreenshot(page, testInfo, '03-permission-saved')
- })
-
- await test.step('通过用户管理页面创建学员账号并编辑姓名', async () => {
- await page.goto('/organization/users')
- await page.getByRole('button', { name: '新增用户', exact: true }).click()
- const dialog = visibleDialog(page)
- await fillFormItem(dialog, '姓名', data.displayName)
- await fillFormItem(dialog, '登录账号', data.username)
- await chooseSelectOption(page, dialog, '所属部门', data.departmentName)
- await fillFormItem(dialog, '初始密码', data.initialPassword)
- const studentRole = dialog.locator('.el-checkbox').filter({ hasText: '学员' }).first()
- await expect(studentRole).toBeVisible()
- await studentRole.click()
- await chooseSelectOption(page, dialog, '登录默认身份', '学员')
- await dialog.getByRole('button', { name: '创建用户' }).click()
- await fillInputByPlaceholder(page, '请输入姓名、账号、部门或角色', data.username)
- await page.getByRole('button', { name: '搜索', exact: true }).click()
- const row = tableRowByText(page, data.username)
- await expect(row).toBeVisible()
- await row.getByRole('button', { name: '编辑' }).click()
- const editDialog = visibleDialog(page)
- await fillFormItem(editDialog, '姓名', data.editedDisplayName)
- await editDialog.getByRole('button', { name: '保存修改' }).click()
- await expect(tableRowByText(page, data.editedDisplayName)).toBeVisible()
- await captureScreenshot(page, testInfo, '04-user-created-edited')
- })
-
- await test.step('学员按身份选择登录并完成首次强制改密', async () => {
- await logout(page)
- await selectStudentIdentity(page)
- await page.locator('input[name="password"]').fill(data.initialPassword)
- await page.getByRole('button', { name: '登录系统' }).click()
- await expect(page).toHaveURL(/\/change-password(?:\?|$)/)
- await expect(page.getByRole('heading', { name: '设置新密码' })).toBeVisible()
- await page.getByPlaceholder('请输入新密码').fill(data.finalPassword)
- await page.getByPlaceholder('请再次输入').fill(data.finalPassword)
- await captureScreenshot(page, testInfo, '05-initial-password-form', [page.locator('input[type="password"]')])
- await page.getByRole('button', { name: '保存并进入平台' }).click()
- await expect(page).toHaveURL(/\/overview(?:\?|$)/)
- await expect(page.locator('.user-menu-trigger')).toContainText(data.editedDisplayName)
- await captureScreenshot(page, testInfo, '06-student-login-success')
-
- await page.goto('/organization/users')
- await expect(page).toHaveURL(/\/overview\?forbidden=1$/)
- await expect(page.getByRole('heading', { name: '用户管理' })).toHaveCount(0)
- await captureScreenshot(page, testInfo, '07-student-system-route-forbidden')
-
- await logout(page)
- await loginAsAdmin(page)
- })
-
- await test.step('列表筛选、停启入口及重置密码两种模式可以交互', async () => {
- await page.goto('/organization/users')
- await fillInputByPlaceholder(page, '请输入姓名、账号、部门或角色', data.username)
- await page.getByRole('button', { name: '搜索', exact: true }).click()
- const row = tableRowByText(page, data.username)
- await row.getByRole('button', { name: '重置密码' }).click()
- const dialog = visibleDialog(page)
- await expect(dialog.getByRole('radio', { name: /系统重置/ })).toBeChecked()
- await dialog.getByText('手动重置', { exact: true }).click()
- await expect(dialog.getByPlaceholder('请输入符合全部规则的新密码')).toBeVisible()
- await captureScreenshot(page, testInfo, '07-password-reset-modes', [dialog.locator('input[type="password"]')])
- await dialog.getByRole('button', { name: '取消' }).click()
- })
- } catch (error) {
- primaryError = error
- } finally {
- cleanup = await cleanupCreatedData(request, token).catch((error) => ({ cleanupError: String(error) }))
- await attachJson(testInfo, 'cleanup-result', cleanup)
- }
-
- if (primaryError) throw primaryError
- expect(cleanup.user, '用户应完成清理').toMatch(/^2/)
- expect(cleanup.role, '角色应完成清理').toMatch(/^2/)
- expect(cleanup.department, '部门应完成清理').toMatch(/^2/)
- })
|