| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025 |
- import { test, expect, type Page } from '@playwright/test'
- // 测试账号配置
- const TEST_USERNAME = process.env.TEST_USERNAME || 'admin'
- const TEST_PASSWORD = process.env.TEST_PASSWORD || '123456'
- // 日本相关测试数据
- const TEST_DATA = {
- name: '東京テスト環境',
- address: '東京都渋谷区神宮前1-2-3',
- // 用于恢复的原始数据
- originalName: '初台測試環境',
- originalAddress: ''
- }
- test.describe('LSS管理 CRUD 测试', () => {
- // 登录辅助函数
- async function login(page: Page) {
- await page.goto('/login')
- await page.evaluate(() => {
- localStorage.clear()
- document.cookie.split(';').forEach((c) => {
- document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
- })
- })
- await page.reload()
- await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
- await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
- await page.getByRole('button', { name: '登录' }).click()
- await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
- }
- test('LSS管理页面正确显示', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 验证页面标题
- await expect(page.locator('text=LSS 管理')).toBeVisible()
- // 验证搜索表单元素
- await expect(page.getByPlaceholder('LSS ID')).toBeVisible()
- await expect(page.getByPlaceholder('名称')).toBeVisible()
- await expect(page.getByRole('button', { name: 'Search' })).toBeVisible()
- await expect(page.getByRole('button', { name: 'Reset' })).toBeVisible()
- // 验证表头
- await expect(page.locator('th:has-text("LSS ID")')).toBeVisible()
- await expect(page.locator('th:has-text("Name")')).toBeVisible()
- await expect(page.locator('th:has-text("Address")')).toBeVisible()
- await expect(page.locator('th:has-text("IP")')).toBeVisible()
- await expect(page.locator('th:has-text("Actions")')).toBeVisible()
- })
- test('编辑LSS节点 - 修改Name和Address', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 找到数据行中的编辑按钮(Actions列第一个按钮)
- const editButton = page.locator('tbody tr').first().locator('button').nth(1)
- await expect(editButton).toBeVisible({ timeout: 10000 })
- await editButton.click()
- // 验证编辑对话框打开
- const dialog = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'LSS详情' })
- await expect(dialog).toBeVisible({ timeout: 5000 })
- // 修改 Name 字段
- const nameInput = dialog
- .locator('input')
- .filter({ hasText: /名称|Name/ })
- .or(dialog.locator('label:has-text("Name")').locator('..').locator('input'))
- .first()
- // 使用更通用的方式定位 Name 输入框
- const nameField = dialog.getByPlaceholder('请输入名称')
- await nameField.clear()
- await nameField.fill(TEST_DATA.name)
- // 修改 Address 字段
- const addressField = dialog.getByPlaceholder('请输入地址')
- await addressField.clear()
- await addressField.fill(TEST_DATA.address)
- // 点击 Update 按钮保存
- await dialog.getByRole('button', { name: 'Update' }).click()
- // 等待对话框关闭
- await expect(dialog).not.toBeVisible({ timeout: 10000 })
- // 验证列表中数据已更新
- await page.waitForTimeout(500)
- const firstRow = page.locator('tbody tr').first()
- await expect(firstRow).toContainText(TEST_DATA.name)
- await expect(firstRow).toContainText(TEST_DATA.address)
- })
- test('查询LSS节点', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 在名称搜索框输入关键词
- await page.getByPlaceholder('名称').fill('東京')
- // 点击搜索
- await page.getByRole('button', { name: 'Search' }).click()
- await page.waitForTimeout(500)
- // 验证搜索结果
- const tableRows = page.locator('tbody tr')
- const rowCount = await tableRows.count()
- if (rowCount > 0) {
- // 验证每行都包含搜索关键词
- for (let i = 0; i < rowCount; i++) {
- const row = tableRows.nth(i)
- const rowText = await row.textContent()
- expect(rowText).toContain('東京')
- }
- }
- })
- test('重置搜索条件', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 填入搜索条件
- await page.getByPlaceholder('LSS ID').fill('test-id')
- await page.getByPlaceholder('名称').fill('test-name')
- // 点击重置
- await page.getByRole('button', { name: 'Reset' }).click()
- await page.waitForTimeout(300)
- // 验证搜索条件已清空
- await expect(page.getByPlaceholder('LSS ID')).toHaveValue('')
- await expect(page.getByPlaceholder('名称')).toHaveValue('')
- })
- test('查看LSS节点设备列表', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 列的按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await expect(deviceListButton).toBeVisible({ timeout: 10000 })
- await deviceListButton.click()
- // 验证设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: '设备列表' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 验证设备列表表头
- await expect(devicePanel.locator('th:has-text("设备ID")')).toBeVisible()
- await expect(devicePanel.locator('th:has-text("名称")')).toBeVisible()
- // 关闭面板
- await devicePanel.locator('button[aria-label*="Close"], .el-drawer__close-btn, .el-dialog__close').first().click()
- await expect(devicePanel).not.toBeVisible({ timeout: 5000 })
- })
- test('从侧边栏导航到LSS管理', async ({ page }) => {
- await login(page)
- // 点击侧边栏 LSS 管理菜单项
- await page.getByText('LSS 管理').first().click()
- // 验证跳转到 LSS 管理页面
- await expect(page).toHaveURL(/\/lss/)
- await expect(page.locator('text=LSS 管理')).toBeVisible()
- })
- })
- test.describe('LSS管理 - CodeEditor 组件测试 (JSON模式)', () => {
- // 登录辅助函数
- async function login(page: Page) {
- await page.goto('/login')
- await page.evaluate(() => {
- localStorage.clear()
- document.cookie.split(';').forEach((c) => {
- document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
- })
- })
- await page.reload()
- await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
- await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
- await page.getByRole('button', { name: '登录' }).click()
- await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
- }
- /**
- * 测试 CodeEditor JSON 模式 - 头部显示
- */
- test('CodeEditor JSON模式 - 验证头部显示JSON标签和按钮', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮打开设备列表
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await expect(deviceListButton).toBeVisible({ timeout: 10000 })
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Parameter Configuration 的 View 按钮
- const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
- await expect(viewButton).toBeVisible({ timeout: 5000 })
- await viewButton.click()
- // 等待参数配置抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 验证 CodeEditor 头部显示 JSON 标签
- const codeEditor = paramsDrawer.locator('.code-editor')
- await expect(codeEditor).toBeVisible()
- await expect(codeEditor.locator('.editor-header')).toBeVisible()
- await expect(codeEditor.locator('.file-type')).toContainText('JSON')
- // 验证 Copy 按钮存在
- await expect(codeEditor.locator('button:has-text("复制"), button:has-text("Copy")')).toBeVisible()
- // 验证 格式化 按钮存在 (JSON模式特有)
- await expect(codeEditor.locator('button:has-text("格式化")')).toBeVisible()
- })
- /**
- * 测试 CodeEditor JSON 模式 - 复制功能
- */
- test('CodeEditor JSON模式 - 复制按钮功能', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Parameter Configuration 的 View 按钮
- const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
- await viewButton.click()
- // 等待参数配置抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 点击复制按钮
- const copyButton = paramsDrawer.locator(
- '.code-editor button:has-text("复制"), .code-editor button:has-text("Copy")'
- )
- await copyButton.click()
- // 验证复制成功提示
- await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 3000 })
- })
- /**
- * 测试 CodeEditor JSON 模式 - 格式化功能
- */
- test('CodeEditor JSON模式 - 格式化按钮功能', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Parameter Configuration 的 View 按钮
- const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
- await viewButton.click()
- // 等待参数配置抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 验证格式化按钮可用(如果内容是有效JSON)
- const formatButton = paramsDrawer.locator('.code-editor button:has-text("格式化")')
- await expect(formatButton).toBeVisible()
- // 点击格式化按钮(如果按钮未禁用)
- const isDisabled = await formatButton.isDisabled()
- if (!isDisabled) {
- await formatButton.click()
- // 格式化后内容应该保持有效
- await page.waitForTimeout(300)
- }
- })
- /**
- * 测试 CodeEditor JSON 模式 - 无效JSON显示错误提示
- */
- test('CodeEditor JSON模式 - 无效JSON显示错误提示', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Parameter Configuration 的 View 按钮
- const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
- await viewButton.click()
- // 等待参数配置抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 获取编辑器并输入无效JSON
- const codeEditor = paramsDrawer.locator('.code-editor')
- const editorContent = codeEditor.locator('.cm-content')
- // 清空并输入无效JSON
- await editorContent.click()
- await page.keyboard.press('Meta+a')
- await page.keyboard.type('{ invalid json }')
- // 验证错误提示显示
- await expect(codeEditor.locator('.validation-error')).toBeVisible({ timeout: 3000 })
- await expect(codeEditor.locator('.validation-error')).toContainText('JSON 格式错误')
- // 验证格式化按钮被禁用
- const formatButton = codeEditor.locator('button:has-text("格式化")')
- await expect(formatButton).toBeDisabled()
- })
- /**
- * 测试 CodeEditor JSON 模式 - 更新内容并验证保存成功
- */
- test('CodeEditor JSON模式 - 更新参数配置并验证保存成功', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Parameter Configuration 的 View 按钮
- const viewButton = devicePanel.locator('tbody tr').first().locator('button:has-text("View")').first()
- await viewButton.click()
- // 等待参数配置抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 获取当前编辑器内容
- const codeEditor = paramsDrawer.locator('.code-editor')
- const editorContent = codeEditor.locator('.cm-content')
- // 生成唯一标识用于验证更新
- const timestamp = Date.now()
- const testValue = `"testUpdate": "${timestamp}"`
- // 修改JSON内容 - 在现有JSON中添加测试字段
- await editorContent.click()
- await page.keyboard.press('Meta+a')
- // 输入新的有效JSON内容
- await page.keyboard.type(`{\n ${testValue},\n "ip": "192.168.0.64"\n}`)
- // 点击更新按钮
- const updateButton = paramsDrawer.locator('button:has-text("更新"), button:has-text("Update")')
- await updateButton.click()
- // 等待更新成功提示
- await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 })
- // 等待抽屉关闭
- await expect(paramsDrawer).not.toBeVisible({ timeout: 5000 })
- // 重新打开参数配置抽屉验证内容已保存
- await page.waitForTimeout(500)
- await viewButton.click()
- // 等待抽屉重新打开
- const paramsDrawerReopened = page.locator('.el-drawer').filter({ hasText: '参数配置' })
- await expect(paramsDrawerReopened).toBeVisible({ timeout: 5000 })
- // 验证内容包含我们添加的测试字段
- const editorContentReopened = paramsDrawerReopened.locator('.cm-content')
- await expect(editorContentReopened).toContainText(timestamp.toString())
- })
- /**
- * 测试 CodeEditor JSON 模式 - 运行参数更新并验证保存成功
- */
- test('CodeEditor JSON模式 - 更新运行参数并验证保存成功', async ({ page }) => {
- await login(page)
- await page.goto('/lss')
- // 等待表格加载
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮
- const deviceListButton = page.locator('tbody tr').first().locator('button').first()
- await deviceListButton.click()
- // 等待设备列表面板打开
- const devicePanel = page.locator('.el-drawer, .el-dialog').filter({ hasText: 'Camera List' })
- await expect(devicePanel).toBeVisible({ timeout: 5000 })
- // 点击 Run Parameters 的 View 按钮(第二个 View 按钮)
- const viewButtons = devicePanel.locator('tbody tr').first().locator('button:has-text("View")')
- const runParamsViewButton = viewButtons.nth(1)
- await expect(runParamsViewButton).toBeVisible({ timeout: 5000 })
- await runParamsViewButton.click()
- // 等待运行参数抽屉打开
- const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '运行参数' })
- await expect(paramsDrawer).toBeVisible({ timeout: 5000 })
- // 获取编辑器
- const codeEditor = paramsDrawer.locator('.code-editor')
- const editorContent = codeEditor.locator('.cm-content')
- // 生成唯一标识用于验证更新
- const timestamp = Date.now()
- const testValue = `"runtimeTest": "${timestamp}"`
- // 修改JSON内容
- await editorContent.click()
- await page.keyboard.press('Meta+a')
- await page.keyboard.type(`{\n ${testValue}\n}`)
- // 点击更新按钮
- const updateButton = paramsDrawer.locator('button:has-text("更新"), button:has-text("Update")')
- await updateButton.click()
- // 等待更新成功提示
- await expect(page.locator('.el-message--success')).toBeVisible({ timeout: 5000 })
- // 等待抽屉关闭
- await expect(paramsDrawer).not.toBeVisible({ timeout: 5000 })
- // 重新打开运行参数抽屉验证内容已保存
- await page.waitForTimeout(500)
- await runParamsViewButton.click()
- // 等待抽屉重新打开
- const paramsDrawerReopened = page.locator('.el-drawer').filter({ hasText: '运行参数' })
- await expect(paramsDrawerReopened).toBeVisible({ timeout: 5000 })
- // 验证内容包含我们添加的测试字段
- const editorContentReopened = paramsDrawerReopened.locator('.cm-content')
- await expect(editorContentReopened).toContainText(timestamp.toString())
- })
- })
- test.describe('LSS管理 - 摄像头列表搜索测试', () => {
- // 登录辅助函数
- async function login(page: Page) {
- await page.goto('/login')
- await page.evaluate(() => {
- localStorage.clear()
- document.cookie.split(';').forEach((c) => {
- document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
- })
- })
- await page.reload()
- await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
- await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
- await page.getByRole('button', { name: '登录' }).click()
- await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
- }
- // 打开摄像头列表辅助函数
- async function openCameraList(page: Page) {
- await page.goto('/lss')
- await page.waitForTimeout(1000)
- // 点击 Device List 按钮打开设备列表 - 点击编辑按钮进入 LSS 编辑抽屉
- const editButton = page.locator('tbody tr').first().locator('button').nth(1)
- await expect(editButton).toBeVisible({ timeout: 10000 })
- await editButton.click()
- // 等待 LSS 编辑抽屉打开
- const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
- await expect(drawer).toBeVisible({ timeout: 5000 })
- // 点击"摄像头列表" Tab
- await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
- await page.waitForTimeout(500)
- return drawer
- }
- /**
- * 测试摄像头搜索表单元素显示
- */
- test('摄像头列表 - 验证搜索表单元素显示', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 验证 Device ID 搜索框存在
- await expect(toolbar.getByPlaceholder('设备ID')).toBeVisible()
- // 验证 Name 搜索框存在
- await expect(toolbar.getByPlaceholder('名称')).toBeVisible()
- // 验证 Status 下拉框存在
- await expect(toolbar.locator('.el-select').first()).toBeVisible()
- // 验证查询按钮存在
- await expect(toolbar.getByRole('button', { name: /查询|Search/ })).toBeVisible()
- // 验证重置按钮存在
- await expect(toolbar.getByRole('button', { name: /重置|Reset/ })).toBeVisible()
- // 验证新增按钮存在
- await expect(toolbar.getByRole('button', { name: /新增|Add/ })).toBeVisible()
- })
- /**
- * 测试按设备ID搜索
- */
- test('摄像头列表 - 按设备ID搜索', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 获取搜索前的数据数量
- const initialRowCount = await drawer.locator('.tab-content-wrapper tbody tr').count()
- // 使用固定的设备ID搜索
- const searchId = 'CT-IP100'
- // 输入设备ID搜索
- await toolbar.getByPlaceholder('设备ID').fill(searchId)
- // 点击搜索
- await toolbar.getByRole('button', { name: /查询|Search/ }).click()
- await page.waitForTimeout(500)
- // 验证搜索条件已应用
- await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue(searchId)
- // 验证搜索结果
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- // 搜索结果应该有数据
- expect(rowCount).toBeGreaterThan(0)
- // 如果初始数据大于搜索结果,说明搜索生效了
- if (initialRowCount > 1) {
- expect(rowCount).toBeLessThanOrEqual(initialRowCount)
- }
- // 验证搜索结果中包含搜索关键词
- const firstRowDeviceId = await tableRows.first().locator('td').first().textContent()
- expect(firstRowDeviceId?.toUpperCase()).toContain('CT')
- })
- /**
- * 测试按名称搜索
- */
- test('摄像头列表 - 按名称搜索', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 获取搜索前的数据数量
- const initialRowCount = await drawer.locator('.tab-content-wrapper tbody tr').count()
- // 使用固定的名称搜索
- const searchName = '初台64'
- // 输入名称搜索
- await toolbar.getByPlaceholder('名称').fill(searchName)
- // 点击搜索
- await toolbar.getByRole('button', { name: /查询|Search/ }).click()
- await page.waitForTimeout(500)
- // 验证搜索条件已应用
- await expect(toolbar.getByPlaceholder('名称')).toHaveValue(searchName)
- // 验证搜索结果
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- // 搜索结果应该有数据
- expect(rowCount).toBeGreaterThan(0)
- // 如果初始数据大于搜索结果,说明搜索生效了
- if (initialRowCount > 1) {
- expect(rowCount).toBeLessThanOrEqual(initialRowCount)
- }
- // 验证搜索结果中包含搜索关键词
- const firstRowName = await tableRows.first().locator('td').nth(1).textContent()
- expect(firstRowName).toContain('初台')
- })
- /**
- * 测试组合搜索 - 设备ID + 名称
- */
- test('摄像头列表 - 组合搜索设备ID和名称', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 输入设备ID
- await toolbar.getByPlaceholder('设备ID').fill('CT')
- // 输入名称
- await toolbar.getByPlaceholder('名称').fill('初台')
- // 点击搜索
- await toolbar.getByRole('button', { name: /查询|Search/ }).click()
- await page.waitForTimeout(500)
- // 验证搜索条件已应用(输入框保留值)
- await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('CT')
- await expect(toolbar.getByPlaceholder('名称')).toHaveValue('初台')
- })
- /**
- * 测试重置搜索条件
- */
- test('摄像头列表 - 重置搜索条件', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 填入搜索条件
- await toolbar.getByPlaceholder('设备ID').fill('test-device-id')
- await toolbar.getByPlaceholder('名称').fill('test-camera-name')
- // 验证输入值
- await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('test-device-id')
- await expect(toolbar.getByPlaceholder('名称')).toHaveValue('test-camera-name')
- // 点击重置
- await toolbar.getByRole('button', { name: /重置|Reset/ }).click()
- await page.waitForTimeout(300)
- // 验证搜索条件已清空
- await expect(toolbar.getByPlaceholder('设备ID')).toHaveValue('')
- await expect(toolbar.getByPlaceholder('名称')).toHaveValue('')
- })
- /**
- * 测试搜索无结果情况
- */
- test('摄像头列表 - 搜索无结果显示空状态', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 输入一个不存在的设备ID
- await toolbar.getByPlaceholder('设备ID').fill('non-existent-device-id-xyz123')
- // 点击搜索
- await toolbar.getByRole('button', { name: /查询|Search/ }).click()
- await page.waitForTimeout(500)
- // 验证显示空状态或表格无数据
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- if (rowCount === 0) {
- // 验证显示空状态组件
- await expect(drawer.locator('.el-empty')).toBeVisible()
- }
- })
- /**
- * 测试按Enter键搜索
- */
- test('摄像头列表 - 按Enter键触发搜索', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 在设备ID输入框输入并按Enter
- const deviceIdInput = toolbar.getByPlaceholder('设备ID')
- await deviceIdInput.fill('CT-IP100')
- await deviceIdInput.press('Enter')
- await page.waitForTimeout(500)
- // 验证搜索已执行(检查输入值保留)
- await expect(deviceIdInput).toHaveValue('CT-IP100')
- // 验证搜索结果有数据
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- expect(rowCount).toBeGreaterThan(0)
- })
- /**
- * 测试名称输入框按Enter键搜索
- */
- test('摄像头列表 - 名称输入框按Enter键触发搜索', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- const toolbar = drawer.locator('.camera-toolbar')
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 在名称输入框输入并按Enter
- const nameInput = toolbar.getByPlaceholder('名称')
- await nameInput.fill('初台64')
- await nameInput.press('Enter')
- await page.waitForTimeout(500)
- // 验证搜索已执行(检查输入值保留)
- await expect(nameInput).toHaveValue('初台64')
- // 验证搜索结果有数据
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- expect(rowCount).toBeGreaterThan(0)
- })
- /**
- * 测试表格列显示正确
- */
- test('摄像头列表 - 验证表格列显示', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- // 验证表头列(中英文)
- await expect(drawer.locator('th:has-text("设备ID"), th:has-text("Device ID")')).toBeVisible()
- await expect(drawer.locator('th:has-text("名称"), th:has-text("Name")')).toBeVisible()
- await expect(drawer.locator('th:has-text("状态"), th:has-text("Status")')).toBeVisible()
- await expect(drawer.locator('th:has-text("厂商"), th:has-text("Vendor")')).toBeVisible()
- await expect(drawer.locator('th:has-text("型号"), th:has-text("Model")')).toBeVisible()
- })
- /**
- * 测试设备总数显示
- */
- test('摄像头列表 - 验证设备总数显示', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 获取表格行数
- const tableRows = drawer.locator('.tab-content-wrapper tbody tr')
- const rowCount = await tableRows.count()
- if (rowCount > 0) {
- // 验证总数显示(例如 "共 2 个设备" 或 "Total 2 Device")
- await expect(drawer.locator('.camera-count')).toBeVisible()
- }
- })
- })
- test.describe('LSS管理 - 摄像头未创建 Live Stream 对话框测试', () => {
- // 登录辅助函数
- async function login(page: Page) {
- await page.goto('/login')
- await page.evaluate(() => {
- localStorage.clear()
- document.cookie.split(';').forEach((c) => {
- document.cookie = c.replace(/^ +/, '').replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`)
- })
- })
- await page.reload()
- await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
- await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
- await page.getByRole('button', { name: '登录' }).click()
- await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
- }
- // 打开摄像头列表辅助函数
- async function openCameraList(page: Page) {
- await page.goto('/lss')
- await page.waitForTimeout(1000)
- // 点击编辑按钮进入 LSS 编辑抽屉
- const editButton = page.locator('tbody tr').first().locator('button').nth(1)
- await expect(editButton).toBeVisible({ timeout: 10000 })
- await editButton.click()
- // 等待 LSS 编辑抽屉打开
- const drawer = page.locator('.el-drawer').filter({ hasText: 'LSS详情' })
- await expect(drawer).toBeVisible({ timeout: 5000 })
- // 点击"摄像头列表" Tab
- await drawer.locator('.el-tabs__item').filter({ hasText: '摄像头列表' }).click()
- await page.waitForTimeout(500)
- return drawer
- }
- /**
- * 测试点击无 streamSn 的摄像头时显示对话框
- */
- test('点击无 streamSn 的摄像头时显示"尚未建立 Live Stream"对话框', async ({ page }) => {
- await login(page)
- // Mock 摄像头列表 API 返回无 streamSn 的数据
- await page.route('**/admin/camera/list*', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({
- success: true,
- errCode: 0,
- data: {
- list: [
- {
- id: 1,
- cameraId: 'CAM_NO_STREAM',
- cameraName: '无推流摄像头',
- lssId: 'LSS_001',
- status: 'active',
- streamSn: null // 没有 streamSn
- }
- ],
- total: 1
- }
- })
- })
- })
- const drawer = await openCameraList(page)
- // 等待表格数据加载
- await page.waitForTimeout(500)
- // 点击摄像头控制按钮(crosshairs-btn)
- const crosshairsBtn = drawer.locator('.crosshairs-btn').first()
- await expect(crosshairsBtn).toBeVisible({ timeout: 5000 })
- await crosshairsBtn.click()
- // 验证对话框显示
- const messageBox = page.locator('.el-message-box')
- await expect(messageBox).toBeVisible({ timeout: 5000 })
- // 验证对话框标题
- await expect(messageBox.locator('.el-message-box__title')).toContainText('尚未建立 Live Stream')
- // 验证对话框内容
- await expect(messageBox.locator('.el-message-box__message')).toContainText('请先新增 Live Stream')
- // 验证按钮存在
- await expect(messageBox.locator('button:has-text("新增 Live Stream")')).toBeVisible()
- await expect(messageBox.locator('button:has-text("取消")')).toBeVisible()
- })
- /**
- * 测试点击"取消"按钮关闭对话框
- */
- test('点击"取消"按钮关闭对话框', async ({ page }) => {
- await login(page)
- // Mock 摄像头列表 API 返回无 streamSn 的数据
- await page.route('**/admin/camera/list*', async (route) => {
- await route.fulfill({
- status: 200,
- contentType: 'application/json',
- body: JSON.stringify({
- success: true,
- errCode: 0,
- data: {
- list: [
- {
- id: 1,
- cameraId: 'CAM_NO_STREAM',
- cameraName: '无推流摄像头',
- lssId: 'LSS_001',
- status: 'active',
- streamSn: null
- }
- ],
- total: 1
- }
- })
- })
- })
- const drawer = await openCameraList(page)
- await page.waitForTimeout(500)
- // 点击摄像头控制按钮
- const crosshairsBtn = drawer.locator('.crosshairs-btn').first()
- await crosshairsBtn.click()
- // 等待对话框显示
- const messageBox = page.locator('.el-message-box')
- await expect(messageBox).toBeVisible({ timeout: 5000 })
- // 点击取消按钮
- await messageBox.locator('button:has-text("取消")').click()
- // 验证对话框关闭
- await expect(messageBox).not.toBeVisible({ timeout: 3000 })
- // 验证仍在 LSS 页面
- await expect(page).toHaveURL(/\/lss/)
- })
- /**
- * 测试点击"新增 Live Stream"按钮跳转到创建页面
- */
- test('点击"新增 Live Stream"按钮跳转到 live-stream 创建页面', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- await page.waitForTimeout(500)
- // 找到没有 streamSn 的摄像头(crosshairs-btn 没有 active 类的)
- const inactiveCrosshairsBtn = drawer.locator('.crosshairs-btn:not(.active)').first()
- // 如果存在无 streamSn 的摄像头
- if ((await inactiveCrosshairsBtn.count()) > 0) {
- await inactiveCrosshairsBtn.click()
- // 等待对话框显示
- const messageBox = page.locator('.el-message-box')
- await expect(messageBox).toBeVisible({ timeout: 5000 })
- // 点击"新增 Live Stream"按钮
- await messageBox.locator('button:has-text("新增 Live Stream")').click()
- // 验证跳转到 live-stream 页面并带有 action=create 参数
- await expect(page).toHaveURL(/\/live-stream\?cameraId=.*&action=create/, { timeout: 5000 })
- // 验证新增抽屉自动打开
- const liveStreamDrawer = page.locator('.el-drawer').filter({ hasText: '新增 Live Stream' })
- await expect(liveStreamDrawer).toBeVisible({ timeout: 5000 })
- } else {
- // 如果没有无 streamSn 的摄像头,跳过测试
- test.skip()
- }
- })
- /**
- * 测试有 streamSn 的摄像头直接跳转(不显示对话框)
- */
- test('有 streamSn 的摄像头直接跳转到 live-stream 页面', async ({ page }) => {
- await login(page)
- const drawer = await openCameraList(page)
- await page.waitForTimeout(500)
- // 找到有 streamSn 的摄像头(crosshairs-btn 有 active 类的)
- const activeCrosshairsBtn = drawer.locator('.crosshairs-btn.active').first()
- // 如果存在有 streamSn 的摄像头
- if ((await activeCrosshairsBtn.count()) > 0) {
- await activeCrosshairsBtn.click()
- // 等待一小段时间检查是否有对话框
- await page.waitForTimeout(500)
- // 验证没有显示对话框,直接跳转
- const messageBox = page.locator('.el-message-box')
- // 验证跳转到 live-stream 页面(不带 action=create)
- await expect(page).toHaveURL(/\/live-stream\?cameraId=/, { timeout: 5000 })
- await expect(page).not.toHaveURL(/action=create/)
- } else {
- // 如果没有有 streamSn 的摄像头,跳过测试
- test.skip()
- }
- })
- })
|