lss.spec.ts 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768
  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 TEST_DATA = {
  7. name: '東京テスト環境',
  8. address: '東京都渋谷区神宮前1-2-3',
  9. // 用于恢复的原始数据
  10. originalName: '初台測試環境',
  11. originalAddress: ''
  12. }
  13. test.describe('LSS管理 CRUD 测试', () => {
  14. // 登录辅助函数
  15. async function login(page: Page) {
  16. await page.goto('/login')
  17. await page.evaluate(() => {
  18. localStorage.clear()
  19. document.cookie.split(';').forEach((c) => {
  20. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  21. })
  22. })
  23. await page.reload()
  24. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  25. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  26. await page.getByRole('button', { name: '登录' }).click()
  27. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  28. }
  29. test('LSS管理页面正确显示', async ({ page }) => {
  30. await login(page)
  31. await page.goto('/lss-manage/list')
  32. // 验证页面标题 (Bug #4537: 页面标题从 "LSS 管理" 改为 "LSS 列表")
  33. await expect(page.locator('text=LSS 列表')).toBeVisible()
  34. // 验证搜索表单元素
  35. await expect(page.getByPlaceholder('LSS ID')).toBeVisible()
  36. await expect(page.getByPlaceholder('名称')).toBeVisible()
  37. await expect(page.getByRole('button', { name: 'Search' })).toBeVisible()
  38. await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible()
  39. // 验证表头
  40. await expect(page.locator('th:has-text("LSS ID")')).toBeVisible()
  41. await expect(page.locator('th:has-text("Name")')).toBeVisible()
  42. await expect(page.locator('th:has-text("Address")')).toBeVisible()
  43. await expect(page.locator('th:has-text("IP")')).toBeVisible()
  44. await expect(page.locator('th:has-text("Actions")')).toBeVisible()
  45. })
  46. test('编辑LSS节点 - 修改Name和Address', async ({ page }) => {
  47. await login(page)
  48. await page.goto('/lss-manage/list')
  49. // 等待表格加载
  50. await page.waitForTimeout(1000)
  51. // 找到数据行中的编辑按钮(Actions列第一个按钮)
  52. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  53. await expect(editButton).toBeVisible({ timeout: 10000 })
  54. await editButton.click()
  55. // 验证编辑对话框打开
  56. const dialog = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'LSS详情' })
  57. await expect(dialog).toBeVisible({ timeout: 5000 })
  58. // 修改 Name 字段
  59. const nameInput = dialog
  60. .locator('input')
  61. .filter({ hasText: /名称|Name/ })
  62. .or(dialog.locator('label:has-text("Name")').locator('..').locator('input'))
  63. .first()
  64. // 使用更通用的方式定位 Name 输入框
  65. const nameField = dialog.getByPlaceholder('请输入名称')
  66. await nameField.clear()
  67. await nameField.fill(TEST_DATA.name)
  68. // 修改 Address 字段
  69. const addressField = dialog.getByPlaceholder('请输入地址')
  70. await addressField.clear()
  71. await addressField.fill(TEST_DATA.address)
  72. // 点击 Update 按钮保存
  73. await dialog.getByRole('button', { name: 'Update' }).click()
  74. // 等待对话框关闭
  75. await expect(dialog).not.toBeVisible({ timeout: 10000 })
  76. // 验证列表中数据已更新
  77. await page.waitForTimeout(500)
  78. const firstRow = page.locator('tbody tr').first()
  79. await expect(firstRow).toContainText(TEST_DATA.name)
  80. await expect(firstRow).toContainText(TEST_DATA.address)
  81. })
  82. test('查询LSS节点', async ({ page }) => {
  83. await login(page)
  84. await page.goto('/lss-manage/list')
  85. // 等待表格加载
  86. await page.waitForTimeout(1000)
  87. // 在名称搜索框输入关键词
  88. await page.getByPlaceholder('名称').fill('東京')
  89. // 点击搜索
  90. await page.getByRole('button', { name: 'Search' }).click()
  91. await page.waitForTimeout(500)
  92. // 验证搜索结果
  93. const tableRows = page.locator('tbody tr')
  94. const rowCount = await tableRows.count()
  95. if (rowCount > 0) {
  96. // 验证每行都包含搜索关键词
  97. for (let i = 0; i < rowCount; i++) {
  98. const row = tableRows.nth(i)
  99. const rowText = await row.textContent()
  100. expect(rowText).toContain('東京')
  101. }
  102. }
  103. })
  104. test('重置搜索条件', async ({ page }) => {
  105. await login(page)
  106. await page.goto('/lss-manage/list')
  107. // 填入搜索条件
  108. await page.getByPlaceholder('LSS ID').fill('test-id')
  109. await page.getByPlaceholder('名称').fill('test-name')
  110. // 点击重置
  111. await page.getByRole('button', { name: 'Reset' }).click()
  112. await page.waitForTimeout(300)
  113. // 验证搜索条件已清空
  114. await expect(page.getByPlaceholder('LSS ID')).toHaveValue('')
  115. await expect(page.getByPlaceholder('名称')).toHaveValue('')
  116. })
  117. test('查看LSS节点设备列表', async ({ page }) => {
  118. await login(page)
  119. await page.goto('/lss-manage/list')
  120. // 等待表格加载
  121. await page.waitForTimeout(1000)
  122. // 点击 Device List 列的按钮
  123. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  124. await expect(deviceListButton).toBeVisible({ timeout: 10000 })
  125. await deviceListButton.click()
  126. // 验证设备列表面板打开
  127. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: '设备列表' })
  128. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  129. // 验证设备列表表头
  130. await expect(devicePanel.locator('th:has-text("设备ID")')).toBeVisible()
  131. await expect(devicePanel.locator('th:has-text("名称")')).toBeVisible()
  132. // 关闭面板
  133. await devicePanel.locator('button[aria-label*="Close"], .el-drawer__close-btn, .el-dialog__close').first().click()
  134. await expect(devicePanel).not.toBeVisible({ timeout: 5000 })
  135. })
  136. test('从侧边栏导航到LSS管理', async ({ page }) => {
  137. await login(page)
  138. // 点击侧边栏 LSS 列表菜单项 (Bug #4537: 菜单标题从 "LSS 管理" 改为 "LSS 列表")
  139. await page.getByText('LSS 列表').first().click()
  140. // 验证跳转到 LSS 管理页面
  141. await expect(page).toHaveURL(/\/lss-manage\/list/)
  142. await expect(page.locator('text=LSS 列表')).toBeVisible()
  143. })
  144. })
  145. test.describe('LSS管理 - CodeEditor 组件测试 (JSON模式)', () => {
  146. // 登录辅助函数
  147. async function login(page: Page) {
  148. await page.goto('/login')
  149. await page.evaluate(() => {
  150. localStorage.clear()
  151. document.cookie.split(';').forEach((c) => {
  152. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  153. })
  154. })
  155. await page.reload()
  156. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  157. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  158. await page.getByRole('button', { name: '登录' }).click()
  159. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  160. }
  161. /**
  162. * 测试 CodeEditor JSON 模式 - 头部显示
  163. */
  164. test('CodeEditor JSON模式 - 验证头部显示JSON标签和按钮', async ({ page }) => {
  165. await login(page)
  166. await page.goto('/lss-manage/list')
  167. // 等待表格加载
  168. await page.waitForTimeout(1000)
  169. // 点击 Device List 按钮打开设备列表
  170. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  171. await expect(deviceListButton).toBeVisible({ timeout: 10000 })
  172. await deviceListButton.click()
  173. // 等待设备列表面板打开
  174. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  175. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  176. // 点击 Parameter Configuration 的 View 按钮
  177. const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
  178. await expect(viewButton).toBeVisible({ timeout: 5000 })
  179. await viewButton.click()
  180. // 等待参数配置抽屉打开
  181. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  182. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  183. // 验证 CodeEditor 头部显示 JSON 标签
  184. const codeEditor = paramsDrawer.locator('.code-editor')
  185. await expect(codeEditor).toBeVisible()
  186. await expect(codeEditor.locator('.editor-header')).toBeVisible()
  187. await expect(codeEditor.locator('.file-type')).toContainText('JSON')
  188. // 验证 Copy 按钮存在
  189. await expect(codeEditor.locator('button:has-text("复制"), button:has-text("Copy")')).toBeVisible()
  190. // 验证 格式化 按钮存在 (JSON模式特有)
  191. await expect(codeEditor.locator('button:has-text("格式化")')).toBeVisible()
  192. })
  193. /**
  194. * 测试 CodeEditor JSON 模式 - 复制功能
  195. */
  196. test('CodeEditor JSON模式 - 复制按钮功能', async ({ page }) => {
  197. await login(page)
  198. await page.goto('/lss-manage/list')
  199. // 等待表格加载
  200. await page.waitForTimeout(1000)
  201. // 点击 Device List 按钮
  202. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  203. await deviceListButton.click()
  204. // 等待设备列表面板打开
  205. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  206. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  207. // 点击 Parameter Configuration 的 View 按钮
  208. const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
  209. await viewButton.click()
  210. // 等待参数配置抽屉打开
  211. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  212. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  213. // 点击复制按钮
  214. const copyButton = paramsDrawer.locator(
  215. '.code-editor button:has-text("复制"), .code-editor button:has-text("Copy")'
  216. )
  217. await copyButton.click()
  218. // 验证复制成功提示
  219. await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 3000 })
  220. })
  221. /**
  222. * 测试 CodeEditor JSON 模式 - 格式化功能
  223. */
  224. test('CodeEditor JSON模式 - 格式化按钮功能', async ({ page }) => {
  225. await login(page)
  226. await page.goto('/lss-manage/list')
  227. // 等待表格加载
  228. await page.waitForTimeout(1000)
  229. // 点击 Device List 按钮
  230. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  231. await deviceListButton.click()
  232. // 等待设备列表面板打开
  233. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  234. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  235. // 点击 Parameter Configuration 的 View 按钮
  236. const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
  237. await viewButton.click()
  238. // 等待参数配置抽屉打开
  239. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  240. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  241. // 验证格式化按钮可用(如果内容是有效JSON)
  242. const formatButton = paramsDrawer.locator('.code-editor button:has-text("格式化")')
  243. await expect(formatButton).toBeVisible()
  244. // 点击格式化按钮(如果按钮未禁用)
  245. const isDisabled = await formatButton.isDisabled()
  246. if (!isDisabled) {
  247. await formatButton.click()
  248. // 格式化后内容应该保持有效
  249. await page.waitForTimeout(300)
  250. }
  251. })
  252. /**
  253. * 测试 CodeEditor JSON 模式 - 无效JSON显示错误提示
  254. */
  255. test('CodeEditor JSON模式 - 无效JSON显示错误提示', async ({ page }) => {
  256. await login(page)
  257. await page.goto('/lss-manage/list')
  258. // 等待表格加载
  259. await page.waitForTimeout(1000)
  260. // 点击 Device List 按钮
  261. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  262. await deviceListButton.click()
  263. // 等待设备列表面板打开
  264. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  265. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  266. // 点击 Parameter Configuration 的 View 按钮
  267. const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
  268. await viewButton.click()
  269. // 等待参数配置抽屉打开
  270. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  271. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  272. // 获取编辑器并输入无效JSON
  273. const codeEditor = paramsDrawer.locator('.code-editor')
  274. const editorContent = codeEditor.locator('.cm-content')
  275. // 清空并输入无效JSON
  276. await editorContent.click()
  277. await page.keyboard.press('Meta+a')
  278. await page.keyboard.type('{ invalid json }')
  279. // 验证错误提示显示
  280. await expect(codeEditor.locator('.validation-error')).toBeVisible({ timeout: 3000 })
  281. await expect(codeEditor.locator('.validation-error')).toContainText('JSON 格式错误')
  282. // 验证格式化按钮被禁用
  283. const formatButton = codeEditor.locator('button:has-text("格式化")')
  284. await expect(formatButton).toBeDisabled()
  285. })
  286. /**
  287. * 测试 CodeEditor JSON 模式 - 更新内容并验证保存成功
  288. */
  289. test('CodeEditor JSON模式 - 更新参数配置并验证保存成功', async ({ page }) => {
  290. await login(page)
  291. await page.goto('/lss-manage/list')
  292. // 等待表格加载
  293. await page.waitForTimeout(1000)
  294. // 点击 Device List 按钮
  295. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  296. await deviceListButton.click()
  297. // 等待设备列表面板打开
  298. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  299. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  300. // 点击 Parameter Configuration 的 View 按钮
  301. const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
  302. await viewButton.click()
  303. // 等待参数配置抽屉打开
  304. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  305. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  306. // 获取当前编辑器内容
  307. const codeEditor = paramsDrawer.locator('.code-editor')
  308. const editorContent = codeEditor.locator('.cm-content')
  309. // 生成唯一标识用于验证更新
  310. const timestamp = Date.now()
  311. const testValue = `"testUpdate": "${timestamp}"`
  312. // 修改JSON内容 - 在现有JSON中添加测试字段
  313. await editorContent.click()
  314. await page.keyboard.press('Meta+a')
  315. // 输入新的有效JSON内容
  316. await page.keyboard.type(`{\n ${testValue},\n "ip": "192.168.0.64"\n}`)
  317. // 点击更新按钮
  318. const updateButton = paramsDrawer.locator('button:has-text("更新"), button:has-text("Update")')
  319. await updateButton.click()
  320. // 等待更新成功提示
  321. await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 })
  322. // 等待抽屉关闭
  323. await expect(paramsDrawer).not.toBeVisible({ timeout: 5000 })
  324. // 重新打开参数配置抽屉验证内容已保存
  325. await page.waitForTimeout(500)
  326. await viewButton.click()
  327. // 等待抽屉重新打开
  328. const paramsDrawerReopened = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  329. await expect(paramsDrawerReopened).toBeVisible({ timeout: 5000 })
  330. // 验证内容包含我们添加的测试字段
  331. const editorContentReopened = paramsDrawerReopened.locator('.cm-content')
  332. await expect(editorContentReopened).toContainText(timestamp.toString())
  333. })
  334. /**
  335. * 测试 CodeEditor JSON 模式 - 运行参数更新并验证保存成功
  336. */
  337. test('CodeEditor JSON模式 - 更新运行参数并验证保存成功', async ({ page }) => {
  338. await login(page)
  339. await page.goto('/lss-manage/list')
  340. // 等待表格加载
  341. await page.waitForTimeout(1000)
  342. // 点击 Device List 按钮
  343. const deviceListButton = page.locator('tbody tr').first().locator('button').first()
  344. await deviceListButton.click()
  345. // 等待设备列表面板打开
  346. const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
  347. await expect(devicePanel).toBeVisible({ timeout: 5000 })
  348. // 点击 Run Parameters 的 View 按钮(第二个 View 按钮)
  349. const viewButtons = devicePanel.locator('tbody tr').first().locator('button:has-text("View")')
  350. const runParamsViewButton = viewButtons.nth(1)
  351. await expect(runParamsViewButton).toBeVisible({ timeout: 5000 })
  352. await runParamsViewButton.click()
  353. // 等待运行参数抽屉打开
  354. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '运行参数' })
  355. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  356. // 获取编辑器
  357. const codeEditor = paramsDrawer.locator('.code-editor')
  358. const editorContent = codeEditor.locator('.cm-content')
  359. // 生成唯一标识用于验证更新
  360. const timestamp = Date.now()
  361. const testValue = `"runtimeTest": "${timestamp}"`
  362. // 修改JSON内容
  363. await editorContent.click()
  364. await page.keyboard.press('Meta+a')
  365. await page.keyboard.type(`{\n ${testValue}\n}`)
  366. // 点击更新按钮
  367. const updateButton = paramsDrawer.locator('button:has-text("更新"), button:has-text("Update")')
  368. await updateButton.click()
  369. // 等待更新成功提示
  370. await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 })
  371. // 等待抽屉关闭
  372. await expect(paramsDrawer).not.toBeVisible({ timeout: 5000 })
  373. // 重新打开运行参数抽屉验证内容已保存
  374. await page.waitForTimeout(500)
  375. await runParamsViewButton.click()
  376. // 等待抽屉重新打开
  377. const paramsDrawerReopened = page.locator('.el-drawer').filter({ hasText: '运行参数' })
  378. await expect(paramsDrawerReopened).toBeVisible({ timeout: 5000 })
  379. // 验证内容包含我们添加的测试字段
  380. const editorContentReopened = paramsDrawerReopened.locator('.cm-content')
  381. await expect(editorContentReopened).toContainText(timestamp.toString())
  382. })
  383. })
  384. test.describe('LSS管理 - 摄像头列表搜索测试', () => {
  385. // 登录辅助函数
  386. async function login(page: Page) {
  387. await page.goto('/login')
  388. await page.evaluate(() => {
  389. localStorage.clear()
  390. document.cookie.split(';').forEach((c) => {
  391. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  392. })
  393. })
  394. await page.reload()
  395. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  396. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  397. await page.getByRole('button', { name: '登录' }).click()
  398. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  399. }
  400. // 打开摄像头列表辅助函数
  401. async function openCameraList(page: Page) {
  402. await page.goto('/lss-manage/list')
  403. await page.waitForTimeout(1000)
  404. // 点击 Device List 按钮打开设备列表 - 点击编辑按钮进入 LSS 编辑抽屉
  405. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  406. await expect(editButton).toBeVisible({ timeout: 10000 })
  407. await editButton.click()
  408. // 等待 LSS 编辑抽屉打开
  409. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  410. await expect(drawer).toBeVisible({ timeout: 5000 })
  411. // 点击"摄像头列表" Tab
  412. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  413. await page.waitForTimeout(500)
  414. return drawer
  415. }
  416. /**
  417. * 测试摄像头搜索表单元素显示
  418. */
  419. test('摄像头列表 - 验证搜索表单元素显示', async ({ page }) => {
  420. await login(page)
  421. const drawer = await openCameraList(page)
  422. const toolbar = drawer.locator('.camera-toolbar')
  423. // 验证 Device ID 搜索框存在
  424. await expect(toolbar.getByPlaceholder('设备ID')).toBeVisible()
  425. // 验证 Name 搜索框存在
  426. await expect(toolbar.getByPlaceholder('名称')).toBeVisible()
  427. // 验证 Status 下拉框存在
  428. await expect(toolbar.locator('.el-select').first()).toBeVisible()
  429. // 验证查询按钮存在
  430. await expect(toolbar.getByRole('button', { name: /查询|Search/ })).toBeVisible()
  431. // 验证重置按钮存在
  432. await expect(toolbar.getByRole('button', { name: /重置|Reset/ })).toBeVisible()
  433. // 验证新增按钮存在
  434. await expect(toolbar.getByRole('button', { name: /新增|Add/ })).toBeVisible()
  435. })
  436. /**
  437. * 测试按设备ID搜索
  438. */
  439. test('摄像头列表 - 按设备ID搜索', async ({ page }) => {
  440. await login(page)
  441. const drawer = await openCameraList(page)
  442. const toolbar = drawer.locator('.camera-toolbar')
  443. // 等待表格数据加载
  444. await page.waitForTimeout(500)
  445. // 获取搜索前的数据数量
  446. const initialRowCount = await drawer.locator('.tab-content-wrapper tbody tr').count()
  447. // 使用固定的设备ID搜索
  448. const searchId = 'CT-IP100'
  449. // 输入设备ID搜索
  450. await toolbar.getByPlaceholder('设备ID').fill(searchId)
  451. // 点击搜索
  452. await toolbar.getByRole('button', { name: /查询|Search/ }).click()
  453. await page.waitForTimeout(500)
  454. // 验证搜索条件已应用
  455. await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue(searchId)
  456. // 验证搜索结果
  457. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  458. const rowCount = await tableRows.count()
  459. // 搜索结果应该有数据
  460. expect(rowCount).toBeGreaterThan(0)
  461. // 如果初始数据大于搜索结果,说明搜索生效了
  462. if (initialRowCount > 1) {
  463. expect(rowCount).toBeLessThanOrEqual(initialRowCount)
  464. }
  465. // 验证搜索结果中包含搜索关键词
  466. const firstRowDeviceId = await tableRows.first().locator('td').first().textContent()
  467. expect(firstRowDeviceId?.toUpperCase()).toContain('CT')
  468. })
  469. /**
  470. * 测试按名称搜索
  471. */
  472. test('摄像头列表 - 按名称搜索', async ({ page }) => {
  473. await login(page)
  474. const drawer = await openCameraList(page)
  475. const toolbar = drawer.locator('.camera-toolbar')
  476. // 等待表格数据加载
  477. await page.waitForTimeout(500)
  478. // 获取搜索前的数据数量
  479. const initialRowCount = await drawer.locator('.tab-content-wrapper tbody tr').count()
  480. // 使用固定的名称搜索
  481. const searchName = '初台64'
  482. // 输入名称搜索
  483. await toolbar.getByPlaceholder('名称').fill(searchName)
  484. // 点击搜索
  485. await toolbar.getByRole('button', { name: /查询|Search/ }).click()
  486. await page.waitForTimeout(500)
  487. // 验证搜索条件已应用
  488. await expect(toolbar.getByPlaceholder('名称')).toHaveValue(searchName)
  489. // 验证搜索结果
  490. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  491. const rowCount = await tableRows.count()
  492. // 搜索结果应该有数据
  493. expect(rowCount).toBeGreaterThan(0)
  494. // 如果初始数据大于搜索结果,说明搜索生效了
  495. if (initialRowCount > 1) {
  496. expect(rowCount).toBeLessThanOrEqual(initialRowCount)
  497. }
  498. // 验证搜索结果中包含搜索关键词
  499. const firstRowName = await tableRows.first().locator('td').nth(1).textContent()
  500. expect(firstRowName).toContain('初台')
  501. })
  502. /**
  503. * 测试组合搜索 - 设备ID + 名称
  504. */
  505. test('摄像头列表 - 组合搜索设备ID和名称', async ({ page }) => {
  506. await login(page)
  507. const drawer = await openCameraList(page)
  508. const toolbar = drawer.locator('.camera-toolbar')
  509. // 等待表格数据加载
  510. await page.waitForTimeout(500)
  511. // 输入设备ID
  512. await toolbar.getByPlaceholder('设备ID').fill('CT')
  513. // 输入名称
  514. await toolbar.getByPlaceholder('名称').fill('初台')
  515. // 点击搜索
  516. await toolbar.getByRole('button', { name: /查询|Search/ }).click()
  517. await page.waitForTimeout(500)
  518. // 验证搜索条件已应用(输入框保留值)
  519. await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('CT')
  520. await expect(toolbar.getByPlaceholder('名称')).toHaveValue('初台')
  521. })
  522. /**
  523. * 测试重置搜索条件
  524. */
  525. test('摄像头列表 - 重置搜索条件', async ({ page }) => {
  526. await login(page)
  527. const drawer = await openCameraList(page)
  528. const toolbar = drawer.locator('.camera-toolbar')
  529. // 填入搜索条件
  530. await toolbar.getByPlaceholder('设备ID').fill('test-device-id')
  531. await toolbar.getByPlaceholder('名称').fill('test-camera-name')
  532. // 验证输入值
  533. await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('test-device-id')
  534. await expect(toolbar.getByPlaceholder('名称')).toHaveValue('test-camera-name')
  535. // 点击重置
  536. await toolbar.getByRole('button', { name: /重置|Reset/ }).click()
  537. await page.waitForTimeout(300)
  538. // 验证搜索条件已清空
  539. await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('')
  540. await expect(toolbar.getByPlaceholder('名称')).toHaveValue('')
  541. })
  542. /**
  543. * 测试搜索无结果情况
  544. */
  545. test('摄像头列表 - 搜索无结果显示空状态', async ({ page }) => {
  546. await login(page)
  547. const drawer = await openCameraList(page)
  548. const toolbar = drawer.locator('.camera-toolbar')
  549. // 输入一个不存在的设备ID
  550. await toolbar.getByPlaceholder('设备ID').fill('non-existent-device-id-xyz123')
  551. // 点击搜索
  552. await toolbar.getByRole('button', { name: /查询|Search/ }).click()
  553. await page.waitForTimeout(500)
  554. // 验证显示空状态或表格无数据
  555. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  556. const rowCount = await tableRows.count()
  557. if (rowCount === 0) {
  558. // 验证显示空状态组件
  559. await expect(drawer.locator('.el-empty')).toBeVisible()
  560. }
  561. })
  562. /**
  563. * 测试按Enter键搜索
  564. */
  565. test('摄像头列表 - 按Enter键触发搜索', async ({ page }) => {
  566. await login(page)
  567. const drawer = await openCameraList(page)
  568. const toolbar = drawer.locator('.camera-toolbar')
  569. // 等待表格数据加载
  570. await page.waitForTimeout(500)
  571. // 在设备ID输入框输入并按Enter
  572. const deviceIdInput = toolbar.getByPlaceholder('设备ID')
  573. await deviceIdInput.fill('CT-IP100')
  574. await deviceIdInput.press('Enter')
  575. await page.waitForTimeout(500)
  576. // 验证搜索已执行(检查输入值保留)
  577. await expect(deviceIdInput).toHaveValue('CT-IP100')
  578. // 验证搜索结果有数据
  579. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  580. const rowCount = await tableRows.count()
  581. expect(rowCount).toBeGreaterThan(0)
  582. })
  583. /**
  584. * 测试名称输入框按Enter键搜索
  585. */
  586. test('摄像头列表 - 名称输入框按Enter键触发搜索', async ({ page }) => {
  587. await login(page)
  588. const drawer = await openCameraList(page)
  589. const toolbar = drawer.locator('.camera-toolbar')
  590. // 等待表格数据加载
  591. await page.waitForTimeout(500)
  592. // 在名称输入框输入并按Enter
  593. const nameInput = toolbar.getByPlaceholder('名称')
  594. await nameInput.fill('初台64')
  595. await nameInput.press('Enter')
  596. await page.waitForTimeout(500)
  597. // 验证搜索已执行(检查输入值保留)
  598. await expect(nameInput).toHaveValue('初台64')
  599. // 验证搜索结果有数据
  600. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  601. const rowCount = await tableRows.count()
  602. expect(rowCount).toBeGreaterThan(0)
  603. })
  604. /**
  605. * 测试表格列显示正确
  606. */
  607. test('摄像头列表 - 验证表格列显示', async ({ page }) => {
  608. await login(page)
  609. const drawer = await openCameraList(page)
  610. // 验证表头列(中英文)
  611. await expect(drawer.locator('th:has-text("设备ID"), th:has-text("Device ID")')).toBeVisible()
  612. await expect(drawer.locator('th:has-text("名称"), th:has-text("Name")')).toBeVisible()
  613. await expect(drawer.locator('th:has-text("状态"), th:has-text("Status")')).toBeVisible()
  614. await expect(drawer.locator('th:has-text("厂商"), th:has-text("Vendor")')).toBeVisible()
  615. await expect(drawer.locator('th:has-text("型号"), th:has-text("Model")')).toBeVisible()
  616. })
  617. /**
  618. * 测试设备总数显示
  619. */
  620. test('摄像头列表 - 验证设备总数显示', async ({ page }) => {
  621. await login(page)
  622. const drawer = await openCameraList(page)
  623. // 等待表格数据加载
  624. await page.waitForTimeout(500)
  625. // 获取表格行数
  626. const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
  627. const rowCount = await tableRows.count()
  628. if (rowCount > 0) {
  629. // 验证总数显示(例如 "共 2 个设备" 或 "Total 2 Device")
  630. await expect(drawer.locator('.camera-count')).toBeVisible()
  631. }
  632. })
  633. })
  634. test.describe('LSS管理 - 摄像头未创建 Live Stream 对话框测试', () => {
  635. // 登录辅助函数
  636. async function login(page: Page) {
  637. await page.goto('/login')
  638. await page.evaluate(() => {
  639. localStorage.clear()
  640. document.cookie.split(';').forEach((c) => {
  641. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  642. })
  643. })
  644. await page.reload()
  645. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  646. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  647. await page.getByRole('button', { name: '登录' }).click()
  648. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  649. }
  650. // 打开摄像头列表辅助函数
  651. async function openCameraList(page: Page) {
  652. await page.goto('/lss-manage/list')
  653. await page.waitForTimeout(1000)
  654. // 点击编辑按钮进入 LSS 编辑抽屉
  655. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  656. await expect(editButton).toBeVisible({ timeout: 10000 })
  657. await editButton.click()
  658. // 等待 LSS 编辑抽屉打开
  659. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  660. await expect(drawer).toBeVisible({ timeout: 5000 })
  661. // 点击"摄像头列表" Tab
  662. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  663. await page.waitForTimeout(500)
  664. return drawer
  665. }
  666. /**
  667. * 测试点击无 streamSn 的摄像头时显示对话框
  668. */
  669. test('点击无 streamSn 的摄像头时显示"尚未建立 Live Stream"对话框', async ({ page }) => {
  670. await login(page)
  671. // Mock 摄像头列表 API 返回无 streamSn 的数据
  672. await page.route('**/admin/camera/list*', async (route) => {
  673. await route.fulfill({
  674. status: 200,
  675. contentType: 'application/json',
  676. body: JSON.stringify({
  677. success: true,
  678. errCode: 0,
  679. data: {
  680. list: [
  681. {
  682. id: 1,
  683. cameraId: 'CAM_NO_STREAM',
  684. cameraName: '无推流摄像头',
  685. lssId: 'LSS_001',
  686. status: 'active',
  687. streamSn: null // 没有 streamSn
  688. }
  689. ],
  690. total: 1
  691. }
  692. })
  693. })
  694. })
  695. const drawer = await openCameraList(page)
  696. // 等待表格数据加载
  697. await page.waitForTimeout(500)
  698. // 点击摄像头控制按钮(crosshairs-btn)
  699. const crosshairsBtn = drawer.locator('.crosshairs-btn').first()
  700. await expect(crosshairsBtn).toBeVisible({ timeout: 5000 })
  701. await crosshairsBtn.click()
  702. // 验证对话框显示
  703. const messageBox = page.locator('.el-message-box')
  704. await expect(messageBox).toBeVisible({ timeout: 5000 })
  705. // 验证对话框标题
  706. await expect(messageBox.locator('.el-message-box__title')).toContainText('尚未建立 Live Stream')
  707. // 验证对话框内容
  708. await expect(messageBox.locator('.el-message-box__message')).toContainText('请先新增 Live Stream')
  709. // 验证按钮存在
  710. await expect(messageBox.locator('button:has-text("新增 Live Stream")')).toBeVisible()
  711. await expect(messageBox.locator('button:has-text("取消")')).toBeVisible()
  712. })
  713. /**
  714. * 测试点击"取消"按钮关闭对话框
  715. */
  716. test('点击"取消"按钮关闭对话框', async ({ page }) => {
  717. await login(page)
  718. // Mock 摄像头列表 API 返回无 streamSn 的数据
  719. await page.route('**/admin/camera/list*', async (route) => {
  720. await route.fulfill({
  721. status: 200,
  722. contentType: 'application/json',
  723. body: JSON.stringify({
  724. success: true,
  725. errCode: 0,
  726. data: {
  727. list: [
  728. {
  729. id: 1,
  730. cameraId: 'CAM_NO_STREAM',
  731. cameraName: '无推流摄像头',
  732. lssId: 'LSS_001',
  733. status: 'active',
  734. streamSn: null
  735. }
  736. ],
  737. total: 1
  738. }
  739. })
  740. })
  741. })
  742. const drawer = await openCameraList(page)
  743. await page.waitForTimeout(500)
  744. // 点击摄像头控制按钮
  745. const crosshairsBtn = drawer.locator('.crosshairs-btn').first()
  746. await crosshairsBtn.click()
  747. // 等待对话框显示
  748. const messageBox = page.locator('.el-message-box')
  749. await expect(messageBox).toBeVisible({ timeout: 5000 })
  750. // 点击取消按钮
  751. await messageBox.locator('button:has-text("取消")').click()
  752. // 验证对话框关闭
  753. await expect(messageBox).not.toBeVisible({ timeout: 3000 })
  754. // 验证仍在 LSS 页面
  755. await expect(page).toHaveURL(/\/lss-manage\/list/)
  756. })
  757. /**
  758. * 测试点击"新增 Live Stream"按钮跳转到创建页面
  759. */
  760. test('点击"新增 Live Stream"按钮跳转到 live-stream 创建页面', async ({ page }) => {
  761. await login(page)
  762. const drawer = await openCameraList(page)
  763. await page.waitForTimeout(500)
  764. // 找到没有 streamSn 的摄像头(crosshairs-btn 没有 active 类的)
  765. const inactiveCrosshairsBtn = drawer.locator('.crosshairs-btn:not(.active)').first()
  766. // 如果存在无 streamSn 的摄像头
  767. if ((await inactiveCrosshairsBtn.count()) > 0) {
  768. await inactiveCrosshairsBtn.click()
  769. // 等待对话框显示
  770. const messageBox = page.locator('.el-message-box')
  771. await expect(messageBox).toBeVisible({ timeout: 5000 })
  772. // 点击"新增 Live Stream"按钮
  773. await messageBox.locator('button:has-text("新增 Live Stream")').click()
  774. // 验证跳转到 live-stream 页面并带有 action=create 参数
  775. await expect(page).toHaveURL(/\/live-stream\?cameraId=.*&action=create/, { timeout: 5000 })
  776. // 验证新增抽屉自动打开
  777. const liveStreamDrawer = page.locator('.el-drawer').filter({ hasText: '新增 Live Stream' })
  778. await expect(liveStreamDrawer).toBeVisible({ timeout: 5000 })
  779. } else {
  780. // 如果没有无 streamSn 的摄像头,跳过测试
  781. test.skip()
  782. }
  783. })
  784. /**
  785. * 测试有 streamSn 的摄像头直接跳转(不显示对话框)
  786. */
  787. test('有 streamSn 的摄像头直接跳转到 live-stream 页面', async ({ page }) => {
  788. await login(page)
  789. const drawer = await openCameraList(page)
  790. await page.waitForTimeout(500)
  791. // 找到有 streamSn 的摄像头(crosshairs-btn 有 active 类的)
  792. const activeCrosshairsBtn = drawer.locator('.crosshairs-btn.active').first()
  793. // 如果存在有 streamSn 的摄像头
  794. if ((await activeCrosshairsBtn.count()) > 0) {
  795. await activeCrosshairsBtn.click()
  796. // 等待一小段时间检查是否有对话框
  797. await page.waitForTimeout(500)
  798. // 验证没有显示对话框,直接跳转
  799. const messageBox = page.locator('.el-message-box')
  800. // 验证跳转到 live-stream 页面(不带 action=create)
  801. await expect(page).toHaveURL(/\/live-stream\?cameraId=/, { timeout: 5000 })
  802. await expect(page).not.toHaveURL(/action=create/)
  803. } else {
  804. // 如果没有有 streamSn 的摄像头,跳过测试
  805. test.skip()
  806. }
  807. })
  808. })
  809. test.describe('LSS管理 - Bug修复验证测试', () => {
  810. // 登录辅助函数
  811. async function login(page: Page) {
  812. await page.goto('/login')
  813. await page.evaluate(() => {
  814. localStorage.clear()
  815. document.cookie.split(';').forEach((c) => {
  816. document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
  817. })
  818. })
  819. await page.reload()
  820. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  821. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  822. await page.getByRole('button', { name: '登录' }).click()
  823. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  824. }
  825. /**
  826. * Bug #4535: 重置按钮需要清除所有搜索条件(包括排序状态)
  827. */
  828. test('Bug #4535 - 重置按钮清除所有搜索条件包括排序状态', async ({ page }) => {
  829. await login(page)
  830. await page.goto('/lss-manage/list')
  831. // 等待表格加载
  832. await page.waitForTimeout(1000)
  833. // 填入搜索条件
  834. await page.getByPlaceholder('LSS ID').fill('test-id')
  835. await page.getByPlaceholder('名称').fill('test-name')
  836. // 点击表头进行排序
  837. const lssIdHeader = page.locator('th:has-text("LSS ID")')
  838. await lssIdHeader.click()
  839. await page.waitForTimeout(300)
  840. // 验证排序图标出现
  841. await expect(lssIdHeader.locator('.ascending, .descending, .caret-wrapper')).toBeVisible()
  842. // 点击重置
  843. await page.getByRole('button', { name: 'Reset' }).click()
  844. await page.waitForTimeout(300)
  845. // 验证搜索条件已清空
  846. await expect(page.getByPlaceholder('LSS ID')).toHaveValue('')
  847. await expect(page.getByPlaceholder('名称')).toHaveValue('')
  848. // 验证排序状态已重置(表头不再显示排序方向)
  849. // 排序图标应该恢复到默认状态(无 ascending 或 descending 类)
  850. const sortIcon = lssIdHeader.locator('.ascending')
  851. const hasAscending = await sortIcon.count()
  852. const sortIconDesc = lssIdHeader.locator('.descending')
  853. const hasDescending = await sortIconDesc.count()
  854. // 排序应该被清除
  855. expect(hasAscending + hasDescending).toBe(0)
  856. })
  857. /**
  858. * Bug #4536: 心跳状态显示为英文(active/hold/dead)而非中文
  859. */
  860. test('Bug #4536 - 心跳状态显示为英文格式', async ({ page }) => {
  861. await login(page)
  862. await page.goto('/lss-manage/list')
  863. // 等待表格加载
  864. await page.waitForTimeout(1000)
  865. // 获取心跳列的内容
  866. const heartbeatColumn = page.locator('tbody tr').first().locator('td').nth(4)
  867. const heartbeatText = await heartbeatColumn.textContent()
  868. // 验证心跳状态是英文格式(active/hold/dead),而非中文(活跃/待机/离线)
  869. // 心跳列格式: "status | time" 例如 "active | 2024-01-26 10:00:00"
  870. expect(heartbeatText).toMatch(/active|hold|dead/i)
  871. // 不应该包含中文状态
  872. expect(heartbeatText).not.toMatch(/活跃|待机|离线/)
  873. })
  874. /**
  875. * Bug #4537: 页面标题从"LSS 管理"改为"LSS 列表"
  876. */
  877. test('Bug #4537 - 页面标题显示为"LSS 列表"', async ({ page }) => {
  878. await login(page)
  879. await page.goto('/lss-manage/list')
  880. // 等待页面加载
  881. await page.waitForTimeout(500)
  882. // 验证侧边栏菜单显示"LSS 列表"
  883. await expect(page.locator('.el-menu-item.is-active, .el-sub-menu.is-active').first()).toContainText('LSS 列表')
  884. // 验证页面内容区域标题(如果有面包屑或页面标题)
  885. // 注意:根据实际UI调整选择器
  886. const pageTitle = page.locator('text=LSS 列表').first()
  887. await expect(pageTitle).toBeVisible()
  888. })
  889. /**
  890. * Bug #4538: 分页默认显示15条/页
  891. */
  892. test('Bug #4538 - 分页默认显示15条/页', async ({ page }) => {
  893. await login(page)
  894. await page.goto('/lss-manage/list')
  895. // 等待表格加载
  896. await page.waitForTimeout(1000)
  897. // 验证分页选择器的默认值是15
  898. const pageSizeSelector = page.locator(
  899. '.el-pagination .el-select .el-input__inner, .el-pagination .el-select-v2__placeholder'
  900. )
  901. const pageSizeText = (await pageSizeSelector.first().textContent()) || (await pageSizeSelector.first().inputValue())
  902. // 验证默认每页显示15条
  903. expect(pageSizeText).toContain('15')
  904. })
  905. /**
  906. * Bug #4538: 分页选项包含15
  907. */
  908. test('Bug #4538 - 分页选项包含15条/页选项', async ({ page }) => {
  909. await login(page)
  910. await page.goto('/lss-manage/list')
  911. // 等待表格加载
  912. await page.waitForTimeout(1000)
  913. // 点击分页选择器打开下拉菜单
  914. const pageSizeSelector = page.locator('.el-pagination .el-select')
  915. await pageSizeSelector.click()
  916. await page.waitForTimeout(300)
  917. // 验证下拉菜单中包含15选项
  918. const dropdown = page.locator('.el-select-dropdown, .el-popper')
  919. await expect(dropdown.locator('text=15')).toBeVisible()
  920. // 验证选项顺序正确: 10, 15, 20, 50, 100
  921. const options = dropdown.locator('.el-select-dropdown__item')
  922. const optionsText = await options.allTextContents()
  923. // 验证包含所有期望的选项
  924. expect(optionsText.some((t) => t.includes('10'))).toBeTruthy()
  925. expect(optionsText.some((t) => t.includes('15'))).toBeTruthy()
  926. expect(optionsText.some((t) => t.includes('20'))).toBeTruthy()
  927. expect(optionsText.some((t) => t.includes('50'))).toBeTruthy()
  928. expect(optionsText.some((t) => t.includes('100'))).toBeTruthy()
  929. })
  930. /**
  931. * Bug #4540: 查询按钮使用蓝底(409EFF)白字
  932. */
  933. test('Bug #4540 - 查询按钮使用蓝底白字', async ({ page }) => {
  934. await login(page)
  935. await page.goto('/lss-manage/list')
  936. // 等待页面加载
  937. await page.waitForTimeout(500)
  938. // 获取查询按钮
  939. const searchButton = page.locator('[data-id="btn-search"]')
  940. await expect(searchButton).toBeVisible()
  941. // 验证按钮有 el-button--primary 类(蓝色按钮)
  942. await expect(searchButton).toHaveClass(/el-button--primary/)
  943. // 验证按钮背景色接近 #409EFF
  944. const bgColor = await searchButton.evaluate((el) => {
  945. return window.getComputedStyle(el).backgroundColor
  946. })
  947. // #409EFF 的 RGB 值为 rgb(64, 158, 255)
  948. expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/)
  949. })
  950. /**
  951. * Bug #4541: 重置按钮使用灰底(909399)白字
  952. */
  953. test('Bug #4541 - 重置按钮使用灰底白字', async ({ page }) => {
  954. await login(page)
  955. await page.goto('/lss-manage/list')
  956. // 等待页面加载
  957. await page.waitForTimeout(500)
  958. // 获取重置按钮
  959. const resetButton = page.locator('[data-id="btn-reset"]')
  960. await expect(resetButton).toBeVisible()
  961. // 验证按钮有 el-button--info 类(灰色按钮)
  962. await expect(resetButton).toHaveClass(/el-button--info/)
  963. // 验证按钮背景色接近 #909399
  964. const bgColor = await resetButton.evaluate((el) => {
  965. return window.getComputedStyle(el).backgroundColor
  966. })
  967. // #909399 的 RGB 值为 rgb(144, 147, 153)
  968. expect(bgColor).toMatch(/rgb\(144,\s*147,\s*153\)|rgba\(144,\s*147,\s*153/)
  969. })
  970. /**
  971. * Bug #4542: 分页当前页背景色使用蓝底(409EFF)白字
  972. */
  973. test('Bug #4542 - 分页当前页使用蓝底白字', async ({ page }) => {
  974. await login(page)
  975. await page.goto('/lss-manage/list')
  976. // 等待表格加载
  977. await page.waitForTimeout(1000)
  978. // 获取分页组件中当前激活的页码按钮
  979. const activePager = page.locator('.el-pagination .el-pager .is-active, .el-pagination .el-pager .active')
  980. await expect(activePager).toBeVisible()
  981. // 验证当前页背景色接近 #409EFF
  982. const bgColor = await activePager.evaluate((el) => {
  983. return window.getComputedStyle(el).backgroundColor
  984. })
  985. // #409EFF 的 RGB 值为 rgb(64, 158, 255)
  986. expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/)
  987. })
  988. /**
  989. * Bug #4557: 摄像头列表状态下拉框显示 active/hold/dead
  990. */
  991. test('Bug #4557 - 摄像头列表状态下拉框显示英文选项', async ({ page }) => {
  992. await login(page)
  993. await page.goto('/lss-manage/list')
  994. // 等待表格加载
  995. await page.waitForTimeout(1000)
  996. // 点击编辑按钮进入 LSS 编辑抽屉
  997. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  998. await expect(editButton).toBeVisible({ timeout: 10000 })
  999. await editButton.click()
  1000. // 等待 LSS 编辑抽屉打开
  1001. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1002. await expect(drawer).toBeVisible({ timeout: 5000 })
  1003. // 点击"摄像头列表" Tab
  1004. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1005. await page.waitForTimeout(500)
  1006. // 点击状态下拉框
  1007. const statusSelect = drawer.locator('.camera-toolbar .el-select').first()
  1008. await statusSelect.click()
  1009. await page.waitForTimeout(300)
  1010. // 验证下拉选项包含 active/hold/dead 而非中文
  1011. const dropdown = page.locator('.el-select-dropdown, .el-popper').last()
  1012. const options = dropdown.locator('.el-select-dropdown__item')
  1013. const optionsText = await options.allTextContents()
  1014. // 验证包含英文选项
  1015. expect(optionsText.some((t) => t.includes('active'))).toBeTruthy()
  1016. expect(optionsText.some((t) => t.includes('hold'))).toBeTruthy()
  1017. expect(optionsText.some((t) => t.includes('dead'))).toBeTruthy()
  1018. // 验证不包含中文状态
  1019. expect(optionsText.some((t) => t.includes('在线'))).toBeFalsy()
  1020. expect(optionsText.some((t) => t.includes('离线'))).toBeFalsy()
  1021. })
  1022. /**
  1023. * Bug #4558: 摄像头列表没有数据时表头要显示
  1024. */
  1025. test('Bug #4558 - 摄像头列表无数据时显示表头', async ({ page }) => {
  1026. await login(page)
  1027. // Mock 摄像头列表 API 返回空数据
  1028. await page.route('**/admin/camera/list*', async (route) => {
  1029. await route.fulfill({
  1030. status: 200,
  1031. contentType: 'application/json',
  1032. body: JSON.stringify({
  1033. success: true,
  1034. errCode: 0,
  1035. data: {
  1036. list: [],
  1037. total: 0
  1038. }
  1039. })
  1040. })
  1041. })
  1042. await page.goto('/lss-manage/list')
  1043. await page.waitForTimeout(1000)
  1044. // 点击编辑按钮进入 LSS 编辑抽屉
  1045. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1046. await expect(editButton).toBeVisible({ timeout: 10000 })
  1047. await editButton.click()
  1048. // 等待 LSS 编辑抽屉打开
  1049. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1050. await expect(drawer).toBeVisible({ timeout: 5000 })
  1051. // 点击"摄像头列表" Tab
  1052. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1053. await page.waitForTimeout(500)
  1054. // 验证表头可见(即使没有数据)
  1055. await expect(drawer.locator('th:has-text("设备ID")')).toBeVisible()
  1056. await expect(drawer.locator('th:has-text("名称")')).toBeVisible()
  1057. await expect(drawer.locator('th:has-text("状态")')).toBeVisible()
  1058. // 验证空状态提示显示
  1059. await expect(drawer.locator('.el-empty')).toBeVisible()
  1060. })
  1061. /**
  1062. * Bug #4593: 删除提示显示正确的设备名称而非undefined
  1063. */
  1064. test('Bug #4593 - 删除提示显示正确的设备名称', async ({ page }) => {
  1065. await login(page)
  1066. // Mock 摄像头列表 API 返回有数据
  1067. const testCameraName = '测试摄像头001'
  1068. await page.route('**/admin/camera/list*', async (route) => {
  1069. await route.fulfill({
  1070. status: 200,
  1071. contentType: 'application/json',
  1072. body: JSON.stringify({
  1073. success: true,
  1074. errCode: 0,
  1075. data: {
  1076. list: [
  1077. {
  1078. id: 1,
  1079. cameraId: 'CAM_TEST_001',
  1080. cameraName: testCameraName,
  1081. lssId: 'LSS_001',
  1082. status: 'active',
  1083. streamSn: 'STREAM_001'
  1084. }
  1085. ],
  1086. total: 1
  1087. }
  1088. })
  1089. })
  1090. })
  1091. await page.goto('/lss-manage/list')
  1092. await page.waitForTimeout(1000)
  1093. // 点击编辑按钮进入 LSS 编辑抽屉
  1094. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1095. await expect(editButton).toBeVisible({ timeout: 10000 })
  1096. await editButton.click()
  1097. // 等待 LSS 编辑抽屉打开
  1098. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1099. await expect(drawer).toBeVisible({ timeout: 5000 })
  1100. // 点击"摄像头列表" Tab
  1101. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1102. await page.waitForTimeout(500)
  1103. // 点击删除按钮
  1104. const deleteButton = drawer
  1105. .locator('tbody tr')
  1106. .first()
  1107. .locator('button[type="button"]')
  1108. .filter({ hasText: '' })
  1109. .last()
  1110. // 使用 Icon 选择器更精确
  1111. const deleteIcon = drawer.locator('tbody tr').first().locator('.iconify--mdi[data-icon*="delete"]').first()
  1112. if ((await deleteIcon.count()) > 0) {
  1113. await deleteIcon.click()
  1114. } else {
  1115. // 备选方案:点击最后一个按钮(删除按钮)
  1116. await drawer.locator('tbody tr').first().locator('button').last().click()
  1117. }
  1118. // 等待确认对话框
  1119. const messageBox = page.locator('.el-message-box')
  1120. await expect(messageBox).toBeVisible({ timeout: 5000 })
  1121. // 验证对话框中显示正确的设备名称,而非 "undefined"
  1122. const messageText = await messageBox.locator('.el-message-box__message').textContent()
  1123. expect(messageText).toContain(testCameraName)
  1124. expect(messageText).not.toContain('undefined')
  1125. // 关闭对话框
  1126. await messageBox.locator('button:has-text("取消")').click()
  1127. })
  1128. /**
  1129. * Bug #4569: 编辑摄像头标题改为"摄像头详情"
  1130. */
  1131. test('Bug #4569 - 编辑摄像头抽屉标题显示"摄像头详情"', async ({ page }) => {
  1132. await login(page)
  1133. await page.goto('/lss-manage/list')
  1134. await page.waitForTimeout(1000)
  1135. // 点击编辑按钮进入 LSS 编辑抽屉
  1136. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1137. await expect(editButton).toBeVisible({ timeout: 10000 })
  1138. await editButton.click()
  1139. // 等待 LSS 编辑抽屉打开
  1140. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1141. await expect(drawer).toBeVisible({ timeout: 5000 })
  1142. // 点击"摄像头列表" Tab
  1143. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1144. await page.waitForTimeout(500)
  1145. // 点击摄像头编辑按钮
  1146. const cameraEditButton = drawer.locator('tbody tr').first().locator('button').first()
  1147. if ((await cameraEditButton.count()) > 0) {
  1148. await cameraEditButton.click()
  1149. // 等待摄像头详情抽屉打开
  1150. const cameraDrawer = page.locator('.el-drawer').filter({ hasText: '摄像头详情' })
  1151. await expect(cameraDrawer).toBeVisible({ timeout: 5000 })
  1152. // 验证标题是"摄像头详情"而非"编辑摄像头"
  1153. await expect(cameraDrawer.locator('.el-drawer__header, .el-drawer__title')).toContainText('摄像头详情')
  1154. }
  1155. })
  1156. /**
  1157. * Bug #4570: 摄像头详情增加"添加时间"字段
  1158. */
  1159. test('Bug #4570 - 摄像头详情显示添加时间', async ({ page }) => {
  1160. await login(page)
  1161. // Mock 摄像头详情 API 返回带有 createdAt 的数据
  1162. await page.route('**/admin/camera/get*', async (route) => {
  1163. await route.fulfill({
  1164. status: 200,
  1165. contentType: 'application/json',
  1166. body: JSON.stringify({
  1167. success: true,
  1168. errCode: 0,
  1169. data: {
  1170. id: 1,
  1171. cameraId: 'CAM_TEST_001',
  1172. cameraName: '测试摄像头',
  1173. lssId: 'LSS_001',
  1174. status: 'active',
  1175. vendorName: 'hikvision',
  1176. model: 'DS-2CD2043G0-I',
  1177. createdAt: '2024-01-15T10:30:00Z'
  1178. }
  1179. })
  1180. })
  1181. })
  1182. await page.goto('/lss-manage/list')
  1183. await page.waitForTimeout(1000)
  1184. // 点击编辑按钮进入 LSS 编辑抽屉
  1185. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1186. await expect(editButton).toBeVisible({ timeout: 10000 })
  1187. await editButton.click()
  1188. // 等待 LSS 编辑抽屉打开
  1189. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1190. await expect(drawer).toBeVisible({ timeout: 5000 })
  1191. // 点击"摄像头列表" Tab
  1192. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1193. await page.waitForTimeout(500)
  1194. // 点击摄像头编辑按钮
  1195. const cameraEditButton = drawer.locator('tbody tr').first().locator('button').first()
  1196. if ((await cameraEditButton.count()) > 0) {
  1197. await cameraEditButton.click()
  1198. // 等待摄像头详情抽屉打开
  1199. const cameraDrawer = page.locator('.el-drawer').filter({ hasText: '摄像头详情' })
  1200. await expect(cameraDrawer).toBeVisible({ timeout: 5000 })
  1201. // 验证有"添加时间"字段
  1202. await expect(cameraDrawer.locator('label:has-text("添加时间")')).toBeVisible()
  1203. // 验证添加时间值显示正确格式
  1204. const timeValue = cameraDrawer.locator('label:has-text("添加时间")').locator('..').locator('.form-value')
  1205. const timeText = await timeValue.textContent()
  1206. // 验证时间格式或包含日期
  1207. expect(timeText).toMatch(/\d{4}-\d{2}-\d{2}|\-/)
  1208. }
  1209. })
  1210. /**
  1211. * Bug #4545: 按钮大小统一,圆角框
  1212. */
  1213. test('Bug #4545 - 按钮有统一的圆角', async ({ page }) => {
  1214. await login(page)
  1215. await page.goto('/lss-manage/list')
  1216. // 等待页面加载
  1217. await page.waitForTimeout(500)
  1218. // 获取查询按钮
  1219. const searchButton = page.locator('[data-id="btn-search"]')
  1220. await expect(searchButton).toBeVisible()
  1221. // 验证按钮有圆角
  1222. const borderRadius = await searchButton.evaluate((el) => {
  1223. return window.getComputedStyle(el).borderRadius
  1224. })
  1225. // 验证圆角值不为0
  1226. expect(borderRadius).not.toBe('0px')
  1227. expect(parseInt(borderRadius)).toBeGreaterThanOrEqual(4)
  1228. })
  1229. /**
  1230. * Bug #4549: LSS列表作为「LSS 管理」的子菜单
  1231. */
  1232. test('Bug #4549 - LSS列表作为LSS管理的子菜单', async ({ page }) => {
  1233. await login(page)
  1234. // 等待侧边栏加载
  1235. await page.waitForTimeout(500)
  1236. // 验证"LSS 管理"父菜单存在
  1237. const lssManageMenu = page.locator('.layout__nav-item--parent').filter({ hasText: 'LSS 管理' })
  1238. await expect(lssManageMenu).toBeVisible()
  1239. // 点击展开子菜单
  1240. await lssManageMenu.click()
  1241. await page.waitForTimeout(300)
  1242. // 验证"LSS 列表"子菜单存在
  1243. const lssListMenu = page.locator('.layout__nav-item--child').filter({ hasText: 'LSS 列表' })
  1244. await expect(lssListMenu).toBeVisible()
  1245. // 点击子菜单导航到页面
  1246. await lssListMenu.click()
  1247. await expect(page).toHaveURL(/\/lss-manage\/list/)
  1248. })
  1249. /**
  1250. * Bug #4554 & #4555: 摄像头列表搜索字段
  1251. */
  1252. test('Bug #4554/#4555 - 摄像头列表有设备ID和设备名称搜索字段', async ({ page }) => {
  1253. await login(page)
  1254. await page.goto('/lss-manage/list')
  1255. await page.waitForTimeout(1000)
  1256. // 点击编辑按钮进入 LSS 编辑抽屉
  1257. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1258. await expect(editButton).toBeVisible({ timeout: 10000 })
  1259. await editButton.click()
  1260. // 等待 LSS 编辑抽屉打开
  1261. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1262. await expect(drawer).toBeVisible({ timeout: 5000 })
  1263. // 点击"摄像头列表" Tab
  1264. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1265. await page.waitForTimeout(500)
  1266. // 验证有"设备ID"搜索框
  1267. await expect(drawer.getByPlaceholder('设备ID')).toBeVisible()
  1268. // 验证有"名称"搜索框
  1269. await expect(drawer.getByPlaceholder('名称')).toBeVisible()
  1270. // 验证没有"IP / 设备ID / 名称"混合搜索框
  1271. const mixedSearch = drawer.getByPlaceholder('IP / 设备ID / 名称')
  1272. await expect(mixedSearch).not.toBeVisible()
  1273. })
  1274. /**
  1275. * Bug #4562: 摄像头列表有分页功能
  1276. */
  1277. test('Bug #4562 - 摄像头列表有分页功能', async ({ page }) => {
  1278. await login(page)
  1279. await page.goto('/lss-manage/list')
  1280. await page.waitForTimeout(1000)
  1281. // 点击编辑按钮进入 LSS 编辑抽屉
  1282. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1283. await expect(editButton).toBeVisible({ timeout: 10000 })
  1284. await editButton.click()
  1285. // 等待 LSS 编辑抽屉打开
  1286. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1287. await expect(drawer).toBeVisible({ timeout: 5000 })
  1288. // 点击"摄像头列表" Tab
  1289. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1290. await page.waitForTimeout(500)
  1291. // 验证分页组件存在
  1292. await expect(drawer.locator('.el-pagination')).toBeVisible()
  1293. })
  1294. /**
  1295. * Bug #4567: List厂商显示和详情页一样
  1296. */
  1297. test('Bug #4567 - 厂商列使用vendorName字段', async ({ page }) => {
  1298. await login(page)
  1299. // Mock 摄像头列表 API 返回带有 vendorName 的数据
  1300. await page.route('**/admin/camera/list*', async (route) => {
  1301. await route.fulfill({
  1302. status: 200,
  1303. contentType: 'application/json',
  1304. body: JSON.stringify({
  1305. success: true,
  1306. errCode: 0,
  1307. data: {
  1308. list: [
  1309. {
  1310. id: 1,
  1311. cameraId: 'CAM_001',
  1312. cameraName: '测试摄像头',
  1313. lssId: 'LSS_001',
  1314. status: 'active',
  1315. vendorName: 'hikvision',
  1316. model: 'DS-2CD2T45'
  1317. }
  1318. ],
  1319. total: 1
  1320. }
  1321. })
  1322. })
  1323. })
  1324. await page.goto('/lss-manage/list')
  1325. await page.waitForTimeout(1000)
  1326. // 点击编辑按钮进入 LSS 编辑抽屉
  1327. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1328. await expect(editButton).toBeVisible({ timeout: 10000 })
  1329. await editButton.click()
  1330. // 等待 LSS 编辑抽屉打开
  1331. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1332. await expect(drawer).toBeVisible({ timeout: 5000 })
  1333. // 点击"摄像头列表" Tab
  1334. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1335. await page.waitForTimeout(500)
  1336. // 验证厂商列显示正确的格式化名称
  1337. const vendorCell = drawer.locator('tbody tr').first().locator('td').nth(5)
  1338. const vendorText = await vendorCell.textContent()
  1339. expect(vendorText).toContain('HIKVISION')
  1340. })
  1341. /**
  1342. * Bug #4588: 参数配置更新按钮蓝底白字
  1343. */
  1344. test('Bug #4588 - 参数配置更新按钮使用蓝底白字', async ({ page }) => {
  1345. await login(page)
  1346. await page.goto('/lss-manage/list')
  1347. await page.waitForTimeout(1000)
  1348. // 点击编辑按钮进入 LSS 编辑抽屉
  1349. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1350. await expect(editButton).toBeVisible({ timeout: 10000 })
  1351. await editButton.click()
  1352. // 等待 LSS 编辑抽屉打开
  1353. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1354. await expect(drawer).toBeVisible({ timeout: 5000 })
  1355. // 点击"摄像头列表" Tab
  1356. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1357. await page.waitForTimeout(500)
  1358. // 点击参数配置"查看"按钮
  1359. const viewButton = drawer.locator('tbody tr').first().locator('button:has-text("查看")').first()
  1360. if ((await viewButton.count()) > 0) {
  1361. await viewButton.click()
  1362. // 等待参数配置抽屉打开
  1363. const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
  1364. await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
  1365. // 验证更新按钮是蓝色
  1366. const updateButton = paramsDrawer.locator('button:has-text("更新")')
  1367. await expect(updateButton).toHaveClass(/el-button--primary/)
  1368. }
  1369. })
  1370. /**
  1371. * Bug #4594: 删除确认对话框确定按钮蓝底白字
  1372. */
  1373. test('Bug #4594 - 删除确认对话框确定按钮蓝底白字', async ({ page }) => {
  1374. await login(page)
  1375. // Mock 摄像头列表 API
  1376. await page.route('**/admin/camera/list*', async (route) => {
  1377. await route.fulfill({
  1378. status: 200,
  1379. contentType: 'application/json',
  1380. body: JSON.stringify({
  1381. success: true,
  1382. errCode: 0,
  1383. data: {
  1384. list: [{ id: 1, cameraId: 'CAM_001', cameraName: '测试', status: 'active' }],
  1385. total: 1
  1386. }
  1387. })
  1388. })
  1389. })
  1390. await page.goto('/lss-manage/list')
  1391. await page.waitForTimeout(1000)
  1392. // 点击编辑按钮进入 LSS 编辑抽屉
  1393. const editButton = page.locator('tbody tr').first().locator('button').nth(1)
  1394. await expect(editButton).toBeVisible({ timeout: 10000 })
  1395. await editButton.click()
  1396. // 等待 LSS 编辑抽屉打开
  1397. const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
  1398. await expect(drawer).toBeVisible({ timeout: 5000 })
  1399. // 点击"摄像头列表" Tab
  1400. await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
  1401. await page.waitForTimeout(500)
  1402. // 点击删除按钮
  1403. await drawer.locator('tbody tr').first().locator('button').last().click()
  1404. // 等待确认对话框
  1405. const messageBox = page.locator('.el-message-box')
  1406. await expect(messageBox).toBeVisible({ timeout: 5000 })
  1407. // 验证确定按钮是蓝色
  1408. const confirmButton = messageBox.locator('button.el-button--primary')
  1409. await expect(confirmButton).toBeVisible()
  1410. const bgColor = await confirmButton.evaluate((el) => {
  1411. return window.getComputedStyle(el).backgroundColor
  1412. })
  1413. // #409EFF 的 RGB 值为 rgb(64, 158, 255)
  1414. expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/)
  1415. // 关闭对话框
  1416. await messageBox.locator('button:has-text("取消")').click()
  1417. })
  1418. })