lss.spec.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813
  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')
  32. // 验证页面标题
  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')
  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')
  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')
  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')
  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 管理菜单项
  139. await page.getByText('LSS 管理').first().click()
  140. // 验证跳转到 LSS 管理页面
  141. await expect(page).toHaveURL(/\/lss/)
  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')
  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')
  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')
  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')
  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')
  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')
  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')
  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. })