live-stream.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  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. async function login(page: Page) {
  7. await page.goto('/login')
  8. await page.evaluate(() => {
  9. localStorage.clear()
  10. document.cookie.split(';').forEach((c) => {
  11. document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
  12. })
  13. })
  14. await page.reload()
  15. await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
  16. await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
  17. await page.getByRole('button', { name: '登录' }).click()
  18. await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
  19. }
  20. test.describe('LiveStream 管理 - 搜索功能测试', () => {
  21. /**
  22. * 按 Stream SN 搜索
  23. */
  24. test('按 Stream SN 搜索 - 验证参数传递', async ({ page }) => {
  25. await login(page)
  26. await page.goto('/live-stream')
  27. // 等待表格加载
  28. await page.waitForSelector('tbody tr', { timeout: 10000 })
  29. // 设置 API 拦截来验证参数
  30. let requestBody: any = null
  31. await page.route('**/admin/live-stream/list', async (route) => {
  32. const request = route.request()
  33. if (request.method() === 'POST') {
  34. requestBody = request.postDataJSON()
  35. }
  36. await route.continue()
  37. })
  38. // 在 stream sn 搜索框输入
  39. await page.getByPlaceholder('stream sn').fill('stream_123')
  40. // 点击查询
  41. await page.getByRole('button', { name: '查询' }).click()
  42. await page.waitForTimeout(1000)
  43. // 验证请求参数中包含 streamSn
  44. expect(requestBody).not.toBeNull()
  45. expect(requestBody.streamSn).toBe('stream_123')
  46. })
  47. /**
  48. * 按 Name 搜索
  49. */
  50. test('按 Name 搜索 - 验证参数传递', async ({ page }) => {
  51. await login(page)
  52. await page.goto('/live-stream')
  53. // 等待表格加载
  54. await page.waitForSelector('tbody tr', { timeout: 10000 })
  55. // 设置 API 拦截
  56. let requestBody: any = null
  57. await page.route('**/admin/live-stream/list', async (route) => {
  58. const request = route.request()
  59. if (request.method() === 'POST') {
  60. requestBody = request.postDataJSON()
  61. }
  62. await route.continue()
  63. })
  64. // 在 name 搜索框输入
  65. await page.getByPlaceholder('name').fill('测试流')
  66. // 点击查询
  67. await page.getByRole('button', { name: '查询' }).click()
  68. await page.waitForTimeout(1000)
  69. // 验证请求参数中包含 name
  70. expect(requestBody).not.toBeNull()
  71. expect(requestBody.name).toBe('测试流')
  72. })
  73. /**
  74. * 按 LSS 搜索
  75. */
  76. test('按 LSS 搜索 - 验证参数传递', async ({ page }) => {
  77. await login(page)
  78. await page.goto('/live-stream')
  79. // 等待表格加载
  80. await page.waitForSelector('tbody tr', { timeout: 10000 })
  81. // 设置 API 拦截
  82. let requestBody: any = null
  83. await page.route('**/admin/live-stream/list', async (route) => {
  84. const request = route.request()
  85. if (request.method() === 'POST') {
  86. requestBody = request.postDataJSON()
  87. }
  88. await route.continue()
  89. })
  90. // 点击 LSS 下拉框
  91. await page.locator('.el-select').filter({ hasText: 'LSS' }).click()
  92. await page.waitForTimeout(300)
  93. // 选择第一个 LSS 选项(如果有)
  94. const firstOption = page.locator('.el-select-dropdown__item').first()
  95. if (await firstOption.isVisible()) {
  96. const lssValue = await firstOption.textContent()
  97. await firstOption.click()
  98. // 点击查询
  99. await page.getByRole('button', { name: '查询' }).click()
  100. await page.waitForTimeout(1000)
  101. // 验证请求参数中包含 lssId
  102. expect(requestBody).not.toBeNull()
  103. expect(requestBody.lssId).toBe(lssValue?.trim())
  104. }
  105. })
  106. /**
  107. * 按设备ID搜索
  108. */
  109. test('按设备ID搜索 - 验证参数传递', async ({ page }) => {
  110. await login(page)
  111. await page.goto('/live-stream')
  112. // 等待表格加载
  113. await page.waitForSelector('tbody tr', { timeout: 10000 })
  114. // 设置 API 拦截
  115. let requestBody: any = null
  116. await page.route('**/admin/live-stream/list', async (route) => {
  117. const request = route.request()
  118. if (request.method() === 'POST') {
  119. requestBody = request.postDataJSON()
  120. }
  121. await route.continue()
  122. })
  123. // 在设备ID搜索框输入
  124. await page.getByPlaceholder('设备ID').fill('EEE1')
  125. // 点击查询
  126. await page.getByRole('button', { name: '查询' }).click()
  127. await page.waitForTimeout(1000)
  128. // 验证请求参数中包含 cameraId
  129. expect(requestBody).not.toBeNull()
  130. expect(requestBody.cameraId).toBe('EEE1')
  131. })
  132. /**
  133. * 组合搜索 - 所有字段
  134. */
  135. test('组合搜索 - 多字段同时搜索', async ({ page }) => {
  136. await login(page)
  137. await page.goto('/live-stream')
  138. // 等待表格加载
  139. await page.waitForSelector('tbody tr', { timeout: 10000 })
  140. // 设置 API 拦截
  141. let requestBody: any = null
  142. await page.route('**/admin/live-stream/list', async (route) => {
  143. const request = route.request()
  144. if (request.method() === 'POST') {
  145. requestBody = request.postDataJSON()
  146. }
  147. await route.continue()
  148. })
  149. // 填入所有搜索条件
  150. await page.getByPlaceholder('stream sn').fill('stream_001')
  151. await page.getByPlaceholder('name').fill('测试')
  152. await page.getByPlaceholder('设备ID').fill('EEE1')
  153. // 点击查询
  154. await page.getByRole('button', { name: '查询' }).click()
  155. await page.waitForTimeout(1000)
  156. // 验证请求参数包含所有字段
  157. expect(requestBody).not.toBeNull()
  158. expect(requestBody.streamSn).toBe('stream_001')
  159. expect(requestBody.name).toBe('测试')
  160. expect(requestBody.cameraId).toBe('EEE1')
  161. })
  162. /**
  163. * 重置搜索条件
  164. */
  165. test('重置搜索条件 - 清空所有输入', async ({ page }) => {
  166. await login(page)
  167. await page.goto('/live-stream')
  168. // 填入搜索条件
  169. await page.getByPlaceholder('stream sn').fill('test-sn')
  170. await page.getByPlaceholder('name').fill('test-name')
  171. await page.getByPlaceholder('设备ID').fill('test-device')
  172. // 点击重置
  173. await page.getByRole('button', { name: '重置' }).click()
  174. await page.waitForTimeout(300)
  175. // 验证搜索条件已清空
  176. await expect(page.getByPlaceholder('stream sn')).toHaveValue('')
  177. await expect(page.getByPlaceholder('name')).toHaveValue('')
  178. await expect(page.getByPlaceholder('设备ID')).toHaveValue('')
  179. })
  180. })
  181. test.describe('LiveStream 管理 - BUG 回归测试', () => {
  182. /**
  183. * BUG 回归测试:按设备ID搜索应该只返回匹配的记录
  184. *
  185. * 问题描述:
  186. * - 前端已修复:searchForm.cameraId 参数现在会正确传递给 API
  187. * - 后端待修复:API 目前未实现 cameraId 过滤,返回所有数据
  188. *
  189. * 预期行为:
  190. * - 搜索设备ID "EEE1" 应该只返回 1 条记录
  191. * - 该记录的设备ID列应该显示 "EEE1"
  192. *
  193. * 当前状态:测试会失败,等待后端实现过滤
  194. */
  195. test('BUG: 按设备ID搜索 EEE1 应该只返回 1 条匹配记录', async ({ page }) => {
  196. await login(page)
  197. await page.goto('/live-stream')
  198. // 等待表格加载
  199. await page.waitForSelector('tbody tr', { timeout: 10000 })
  200. await page.waitForTimeout(500)
  201. // 在设备ID搜索框输入 "EEE1"
  202. await page.getByPlaceholder('设备ID').fill('EEE1')
  203. // 点击查询按钮
  204. await page.getByRole('button', { name: '查询' }).click()
  205. // 等待搜索结果加载
  206. await page.waitForTimeout(1000)
  207. // 验证:应该只有 1 条记录
  208. const tableRows = page.locator('tbody tr')
  209. const rowCount = await tableRows.count()
  210. expect(rowCount).toBe(1)
  211. // 验证:该记录的设备ID应该是 "EEE1"
  212. const firstRowDeviceId = await tableRows.first().locator('td').nth(3).textContent()
  213. expect(firstRowDeviceId?.trim()).toBe('EEE1')
  214. // 验证:分页显示 Total 1
  215. await expect(page.locator('.el-pagination')).toContainText('Total 1')
  216. })
  217. })
  218. test.describe('LiveStream 管理 - 页面功能测试', () => {
  219. test('LiveStream 管理页面正确显示', async ({ page }) => {
  220. await login(page)
  221. await page.goto('/live-stream')
  222. // 验证页面标题
  223. await expect(page.locator('text=LiveStream 管理')).toBeVisible()
  224. // 验证搜索表单元素
  225. await expect(page.getByPlaceholder('stream sn')).toBeVisible()
  226. await expect(page.getByPlaceholder('name')).toBeVisible()
  227. await expect(page.getByPlaceholder('设备ID')).toBeVisible()
  228. await expect(page.getByRole('button', { name: '查询' })).toBeVisible()
  229. await expect(page.getByRole('button', { name: '重置' })).toBeVisible()
  230. await expect(page.getByRole('button', { name: '新增' })).toBeVisible()
  231. // 验证表头
  232. await expect(page.locator('th:has-text("Stream SN"), th:has-text("stream sn")')).toBeVisible()
  233. await expect(page.locator('th:has-text("Name"), th:has-text("名称")')).toBeVisible()
  234. await expect(page.locator('th:has-text("LSS")')).toBeVisible()
  235. await expect(page.locator('th:has-text("Device ID"), th:has-text("设备ID")')).toBeVisible()
  236. })
  237. test('打开新增 LiveStream 抽屉', async ({ page }) => {
  238. await login(page)
  239. await page.goto('/live-stream')
  240. // 点击新增按钮
  241. await page.getByRole('button', { name: '新增' }).click()
  242. // 验证抽屉打开
  243. const drawer = page.locator('.el-drawer').filter({ hasText: '新增 Live Stream' })
  244. await expect(drawer).toBeVisible({ timeout: 5000 })
  245. // 验证表单元素
  246. await expect(drawer.locator('label:has-text("名称")')).toBeVisible()
  247. await expect(drawer.locator('label:has-text("LSS 节点")')).toBeVisible()
  248. await expect(drawer.locator('label:has-text("摄像头")')).toBeVisible()
  249. // 关闭抽屉
  250. await drawer.getByRole('button', { name: '取消' }).click()
  251. await expect(drawer).not.toBeVisible({ timeout: 5000 })
  252. })
  253. test('分页功能正常', async ({ page }) => {
  254. await login(page)
  255. await page.goto('/live-stream')
  256. // 等待表格加载
  257. await page.waitForTimeout(1000)
  258. // 验证分页组件存在
  259. const pagination = page.locator('.el-pagination')
  260. await expect(pagination).toBeVisible()
  261. // 验证 Total 显示
  262. await expect(pagination.locator('text=/Total \\d+/')).toBeVisible()
  263. })
  264. test('从侧边栏导航到 LiveStream 管理', async ({ page }) => {
  265. await login(page)
  266. // 点击侧边栏 LiveStream 管理菜单项
  267. await page.getByText('LiveStream 管理').first().click()
  268. // 验证跳转到 LiveStream 管理页面
  269. await expect(page).toHaveURL(/\/live-stream/)
  270. await expect(page.locator('text=LiveStream 管理')).toBeVisible()
  271. })
  272. })