system-role.spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { test, expect, type Page } from '@playwright/test'
  2. // 测试账号配置
  3. const TEST_USERNAME = process.env.TEST_USERNAME || 'admin'
  4. const TEST_PASSWORD = process.env.TEST_PASSWORD || '123456'
  5. // 生成唯一的角色编码
  6. const generateRoleCode = () => `TEST_${Date.now()}`
  7. // data-id 选择器辅助函数
  8. const byDataId = (id: string) => `[data-id="${id}"]`
  9. test.describe('系统角色管理 CRUD 测试', () => {
  10. // 登录辅助函数
  11. async function login(page: Page) {
  12. await page.goto('/login')
  13. await page.evaluate(() => {
  14. localStorage.clear()
  15. document.cookie.split(';').forEach((c) => {
  16. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  17. })
  18. })
  19. await page.reload()
  20. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  21. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  22. await page.getByRole('button', { name: '登录' }).click()
  23. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  24. }
  25. test('角色管理页面正确显示', async ({ page }) => {
  26. await login(page)
  27. await page.goto('/system/role')
  28. // 验证页面元素
  29. await expect(page.locator(byDataId('btn-add'))).toBeVisible()
  30. await expect(page.locator(byDataId('btn-search'))).toBeVisible()
  31. await expect(page.locator(byDataId('btn-reset'))).toBeVisible()
  32. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  33. // 验证表头
  34. await expect(page.locator('thead').getByText('ID')).toBeVisible()
  35. await expect(page.locator('thead').getByText('角色名称')).toBeVisible()
  36. await expect(page.locator('thead').getByText('角色编码')).toBeVisible()
  37. await expect(page.locator('thead').getByText('用户数')).toBeVisible()
  38. await expect(page.locator('thead').getByText('状态')).toBeVisible()
  39. await expect(page.locator('thead').getByText('描述')).toBeVisible()
  40. })
  41. test('查询和重置功能', async ({ page }) => {
  42. await login(page)
  43. await page.goto('/system/role')
  44. // 等待表格加载
  45. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  46. // 输入搜索关键词
  47. await page.locator(byDataId('search-keyword')).fill('ADMIN')
  48. // 点击查询按钮
  49. await page.locator(byDataId('btn-search')).click()
  50. await page.waitForTimeout(500)
  51. // 表格应该仍然可见
  52. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  53. // 点击重置按钮
  54. await page.locator(byDataId('btn-reset')).click()
  55. await page.waitForTimeout(500)
  56. // 验证输入框被清空
  57. await expect(page.locator(byDataId('search-keyword'))).toHaveValue('')
  58. // 表格应该仍然可见
  59. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  60. })
  61. test('新增角色完整流程', async ({ page }) => {
  62. await login(page)
  63. await page.goto('/system/role')
  64. const roleCode = generateRoleCode()
  65. const roleName = `测试角色_${Date.now()}`
  66. // 点击新增按钮
  67. await page.locator(byDataId('btn-add')).click()
  68. // 验证抽屉打开
  69. const drawer = page.locator(byDataId('role-drawer'))
  70. await expect(drawer).toBeVisible()
  71. await expect(page.locator('.el-drawer__title')).toContainText('新增角色')
  72. // 填写表单
  73. await drawer.locator(byDataId('input-name')).fill(roleName)
  74. await drawer.locator(byDataId('input-code')).fill(roleCode)
  75. await drawer.locator(byDataId('input-description')).fill('E2E测试创建的角色')
  76. // 等待提交按钮可用后点击
  77. const submitBtn = page.locator(byDataId('btn-submit'))
  78. await expect(submitBtn).toBeEnabled()
  79. await submitBtn.click()
  80. // 等待成功消息或抽屉关闭
  81. const successPromise = page
  82. .locator('.el-message--success')
  83. .waitFor({ timeout: 10000 })
  84. .catch(() => null)
  85. const drawerClosePromise = expect(drawer).not.toBeVisible({ timeout: 10000 })
  86. await Promise.race([successPromise, drawerClosePromise])
  87. // 如果抽屉关闭了,说明操作已完成
  88. await expect(drawer).not.toBeVisible({ timeout: 5000 })
  89. })
  90. test('编辑角色功能', async ({ page }) => {
  91. await login(page)
  92. await page.goto('/system/role')
  93. // 等待表格加载
  94. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  95. await page.waitForTimeout(1000)
  96. // 点击第一行的编辑按钮
  97. const editBtn = page.locator(byDataId('btn-edit')).first()
  98. // 等待数据加载
  99. await expect(editBtn).toBeVisible({ timeout: 10000 })
  100. await editBtn.click()
  101. // 验证抽屉打开
  102. const drawer = page.locator(byDataId('role-drawer'))
  103. await expect(drawer).toBeVisible()
  104. await expect(page.locator('.el-drawer__title')).toContainText('编辑角色')
  105. // 修改名称
  106. const nameInput = drawer.locator(byDataId('input-name'))
  107. await nameInput.clear()
  108. await nameInput.fill(`编辑测试角色_${Date.now()}`)
  109. // 修改描述
  110. const descInput = drawer.locator(byDataId('input-description'))
  111. await descInput.clear()
  112. await descInput.fill('E2E编辑测试')
  113. // 等待提交按钮可用后点击
  114. const submitBtn = page.locator(byDataId('btn-submit'))
  115. await expect(submitBtn).toBeEnabled()
  116. await submitBtn.click()
  117. // 验证抽屉关闭
  118. await expect(drawer).not.toBeVisible({ timeout: 15000 })
  119. })
  120. test('删除角色功能', async ({ page }) => {
  121. await login(page)
  122. await page.goto('/system/role')
  123. // 等待表格加载
  124. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  125. await page.waitForTimeout(1000)
  126. // 找到一个可删除的角色(非 ADMIN)的删除按钮
  127. const deleteBtn = page.locator(`${byDataId('btn-delete')}:not([disabled])`).first()
  128. // 如果没有可删除的角色,跳过此测试
  129. const count = await deleteBtn.count()
  130. if (count === 0) {
  131. test.skip()
  132. return
  133. }
  134. await deleteBtn.click()
  135. // 确认删除对话框出现
  136. await expect(page.locator('.el-message-box')).toBeVisible()
  137. await page.locator('.el-message-box').getByRole('button', { name: '确定' }).click()
  138. // 等待确认对话框关闭
  139. await expect(page.locator('.el-message-box')).not.toBeVisible({ timeout: 10000 })
  140. // 验证表格仍然可见
  141. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  142. })
  143. test('新增角色表单验证', async ({ page }) => {
  144. await login(page)
  145. await page.goto('/system/role')
  146. // 点击新增按钮
  147. await page.locator(byDataId('btn-add')).click()
  148. await expect(page.locator(byDataId('role-drawer'))).toBeVisible()
  149. // 直接点击确定,不填写任何内容
  150. await page.locator(byDataId('btn-submit')).click()
  151. // 验证显示验证错误
  152. const drawer = page.locator(byDataId('role-drawer'))
  153. await expect(drawer.getByText('请输入角色名称')).toBeVisible()
  154. await expect(drawer.getByText('请输入角色编码')).toBeVisible()
  155. })
  156. test('角色编码格式验证', async ({ page }) => {
  157. await login(page)
  158. await page.goto('/system/role')
  159. // 点击新增按钮
  160. await page.locator(byDataId('btn-add')).click()
  161. const drawer = page.locator(byDataId('role-drawer'))
  162. await expect(drawer).toBeVisible()
  163. // 填写角色名称
  164. await drawer.locator(byDataId('input-name')).fill('测试角色')
  165. // 填写不合法的角色编码(小写字母)
  166. await drawer.locator(byDataId('input-code')).fill('test_role')
  167. // 点击提交
  168. await page.locator(byDataId('btn-submit')).click()
  169. // 验证显示格式错误
  170. await expect(drawer.getByText(/大写字母/)).toBeVisible({ timeout: 5000 })
  171. })
  172. test('取消新增角色', async ({ page }) => {
  173. await login(page)
  174. await page.goto('/system/role')
  175. // 点击新增按钮
  176. await page.locator(byDataId('btn-add')).click()
  177. const drawer = page.locator(byDataId('role-drawer'))
  178. await expect(drawer).toBeVisible()
  179. // 填写部分内容
  180. await drawer.locator(byDataId('input-name')).fill('测试取消')
  181. await drawer.locator(byDataId('input-code')).fill('TEST_CANCEL')
  182. // 点击取消
  183. await page.locator(byDataId('btn-cancel')).click()
  184. // 验证抽屉关闭
  185. await expect(drawer).not.toBeVisible()
  186. })
  187. test('从侧边栏导航到角色管理', async ({ page }) => {
  188. await login(page)
  189. // 展开系统管理菜单
  190. await page.getByText('系统管理').first().click()
  191. await page.waitForTimeout(300)
  192. // 点击角色管理菜单项
  193. await page.getByText('角色管理').first().click()
  194. // 验证跳转到角色管理页面
  195. await expect(page).toHaveURL(/\/system\/role/)
  196. await expect(page.locator(byDataId('btn-add'))).toBeVisible()
  197. })
  198. test('状态筛选功能', async ({ page }) => {
  199. await login(page)
  200. await page.goto('/system/role')
  201. // 等待表格加载
  202. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  203. // 选择启用状态
  204. await page.locator(byDataId('search-status')).click()
  205. await page.getByText('启用').click()
  206. // 点击查询
  207. await page.locator(byDataId('btn-search')).click()
  208. await page.waitForTimeout(500)
  209. // 表格应该仍然可见
  210. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  211. // 选择禁用状态
  212. await page.locator(byDataId('search-status')).click()
  213. await page.getByText('禁用').click()
  214. // 点击查询
  215. await page.locator(byDataId('btn-search')).click()
  216. await page.waitForTimeout(500)
  217. // 表格应该仍然可见
  218. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  219. })
  220. test('ADMIN 角色不能删除', async ({ page }) => {
  221. await login(page)
  222. await page.goto('/system/role')
  223. // 等待表格加载
  224. await expect(page.locator(byDataId('role-table'))).toBeVisible()
  225. await page.waitForTimeout(1000)
  226. // 找到 ADMIN 角色行的删除按钮
  227. const adminRow = page.locator('tr').filter({ hasText: 'ADMIN' })
  228. const adminDeleteBtn = adminRow.locator(byDataId('btn-delete'))
  229. // 如果找到了 ADMIN 角色,验证其删除按钮是禁用的
  230. const count = await adminDeleteBtn.count()
  231. if (count > 0) {
  232. await expect(adminDeleteBtn).toBeDisabled()
  233. }
  234. })
  235. })