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-manage/list') // 验证页面标题 (Bug #4537: 页面标题从 "LSS 管理" 改为 "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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') // 填入搜索条件 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-manage/list') // 等待表格加载 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 列表菜单项 (Bug #4537: 菜单标题从 "LSS 管理" 改为 "LSS 列表") await page.getByText('LSS 列表').first().click() // 验证跳转到 LSS 管理页面 await expect(page).toHaveURL(/\/lss-manage\/list/) 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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') // 等待表格加载 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-manage/list') 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-manage/list') 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-manage\/list/) }) /** * 测试点击"新增 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() } }) }) test.describe('LSS管理 - Bug修复验证测试', () => { // 登录辅助函数 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 }) } /** * Bug #4535: 重置按钮需要清除所有搜索条件(包括排序状态) */ test('Bug #4535 - 重置按钮清除所有搜索条件包括排序状态', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 await page.waitForTimeout(1000) // 填入搜索条件 await page.getByPlaceholder('LSS ID').fill('test-id') await page.getByPlaceholder('名称').fill('test-name') // 点击表头进行排序 const lssIdHeader = page.locator('th:has-text("LSS ID")') await lssIdHeader.click() await page.waitForTimeout(300) // 验证排序图标出现 await expect(lssIdHeader.locator('.ascending, .descending, .caret-wrapper')).toBeVisible() // 点击重置 await page.getByRole('button', { name: 'Reset' }).click() await page.waitForTimeout(300) // 验证搜索条件已清空 await expect(page.getByPlaceholder('LSS ID')).toHaveValue('') await expect(page.getByPlaceholder('名称')).toHaveValue('') // 验证排序状态已重置(表头不再显示排序方向) // 排序图标应该恢复到默认状态(无 ascending 或 descending 类) const sortIcon = lssIdHeader.locator('.ascending') const hasAscending = await sortIcon.count() const sortIconDesc = lssIdHeader.locator('.descending') const hasDescending = await sortIconDesc.count() // 排序应该被清除 expect(hasAscending + hasDescending).toBe(0) }) /** * Bug #4536: 心跳状态显示为英文(active/hold/dead)而非中文 */ test('Bug #4536 - 心跳状态显示为英文格式', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 await page.waitForTimeout(1000) // 获取心跳列的内容 const heartbeatColumn = page.locator('tbody tr').first().locator('td').nth(4) const heartbeatText = await heartbeatColumn.textContent() // 验证心跳状态是英文格式(active/hold/dead),而非中文(活跃/待机/离线) // 心跳列格式: "status | time" 例如 "active | 2024-01-26 10:00:00" expect(heartbeatText).toMatch(/active|hold|dead/i) // 不应该包含中文状态 expect(heartbeatText).not.toMatch(/活跃|待机|离线/) }) /** * Bug #4537: 页面标题从"LSS 管理"改为"LSS 列表" */ test('Bug #4537 - 页面标题显示为"LSS 列表"', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待页面加载 await page.waitForTimeout(500) // 验证侧边栏菜单显示"LSS 列表" await expect(page.locator('.el-menu-item.is-active, .el-sub-menu.is-active').first()).toContainText('LSS 列表') // 验证页面内容区域标题(如果有面包屑或页面标题) // 注意:根据实际UI调整选择器 const pageTitle = page.locator('text=LSS 列表').first() await expect(pageTitle).toBeVisible() }) /** * Bug #4538: 分页默认显示15条/页 */ test('Bug #4538 - 分页默认显示15条/页', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 await page.waitForTimeout(1000) // 验证分页选择器的默认值是15 const pageSizeSelector = page.locator( '.el-pagination .el-select .el-input__inner, .el-pagination .el-select-v2__placeholder' ) const pageSizeText = (await pageSizeSelector.first().textContent()) || (await pageSizeSelector.first().inputValue()) // 验证默认每页显示15条 expect(pageSizeText).toContain('15') }) /** * Bug #4538: 分页选项包含15 */ test('Bug #4538 - 分页选项包含15条/页选项', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 await page.waitForTimeout(1000) // 点击分页选择器打开下拉菜单 const pageSizeSelector = page.locator('.el-pagination .el-select') await pageSizeSelector.click() await page.waitForTimeout(300) // 验证下拉菜单中包含15选项 const dropdown = page.locator('.el-select-dropdown, .el-popper') await expect(dropdown.locator('text=15')).toBeVisible() // 验证选项顺序正确: 10, 15, 20, 50, 100 const options = dropdown.locator('.el-select-dropdown__item') const optionsText = await options.allTextContents() // 验证包含所有期望的选项 expect(optionsText.some((t) => t.includes('10'))).toBeTruthy() expect(optionsText.some((t) => t.includes('15'))).toBeTruthy() expect(optionsText.some((t) => t.includes('20'))).toBeTruthy() expect(optionsText.some((t) => t.includes('50'))).toBeTruthy() expect(optionsText.some((t) => t.includes('100'))).toBeTruthy() }) /** * Bug #4540: 查询按钮使用蓝底(409EFF)白字 */ test('Bug #4540 - 查询按钮使用蓝底白字', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待页面加载 await page.waitForTimeout(500) // 获取查询按钮 const searchButton = page.locator('[data-id="btn-search"]') await expect(searchButton).toBeVisible() // 验证按钮有 el-button--primary 类(蓝色按钮) await expect(searchButton).toHaveClass(/el-button--primary/) // 验证按钮背景色接近 #409EFF const bgColor = await searchButton.evaluate((el) => { return window.getComputedStyle(el).backgroundColor }) // #409EFF 的 RGB 值为 rgb(64, 158, 255) expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/) }) /** * Bug #4541: 重置按钮使用灰底(909399)白字 */ test('Bug #4541 - 重置按钮使用灰底白字', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待页面加载 await page.waitForTimeout(500) // 获取重置按钮 const resetButton = page.locator('[data-id="btn-reset"]') await expect(resetButton).toBeVisible() // 验证按钮有 el-button--info 类(灰色按钮) await expect(resetButton).toHaveClass(/el-button--info/) // 验证按钮背景色接近 #909399 const bgColor = await resetButton.evaluate((el) => { return window.getComputedStyle(el).backgroundColor }) // #909399 的 RGB 值为 rgb(144, 147, 153) expect(bgColor).toMatch(/rgb\(144,\s*147,\s*153\)|rgba\(144,\s*147,\s*153/) }) /** * Bug #4542: 分页当前页背景色使用蓝底(409EFF)白字 */ test('Bug #4542 - 分页当前页使用蓝底白字', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 await page.waitForTimeout(1000) // 获取分页组件中当前激活的页码按钮 const activePager = page.locator('.el-pagination .el-pager .is-active, .el-pagination .el-pager .active') await expect(activePager).toBeVisible() // 验证当前页背景色接近 #409EFF const bgColor = await activePager.evaluate((el) => { return window.getComputedStyle(el).backgroundColor }) // #409EFF 的 RGB 值为 rgb(64, 158, 255) expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/) }) /** * Bug #4557: 摄像头列表状态下拉框显示 active/hold/dead */ test('Bug #4557 - 摄像头列表状态下拉框显示英文选项', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待表格加载 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) // 点击状态下拉框 const statusSelect = drawer.locator('.camera-toolbar .el-select').first() await statusSelect.click() await page.waitForTimeout(300) // 验证下拉选项包含 active/hold/dead 而非中文 const dropdown = page.locator('.el-select-dropdown, .el-popper').last() const options = dropdown.locator('.el-select-dropdown__item') const optionsText = await options.allTextContents() // 验证包含英文选项 expect(optionsText.some((t) => t.includes('active'))).toBeTruthy() expect(optionsText.some((t) => t.includes('hold'))).toBeTruthy() expect(optionsText.some((t) => t.includes('dead'))).toBeTruthy() // 验证不包含中文状态 expect(optionsText.some((t) => t.includes('在线'))).toBeFalsy() expect(optionsText.some((t) => t.includes('离线'))).toBeFalsy() }) /** * Bug #4558: 摄像头列表没有数据时表头要显示 */ test('Bug #4558 - 摄像头列表无数据时显示表头', async ({ page }) => { await login(page) // Mock 摄像头列表 API 返回空数据 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: [], total: 0 } }) }) }) await page.goto('/lss-manage/list') 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) // 验证表头可见(即使没有数据) await expect(drawer.locator('th:has-text("设备ID")')).toBeVisible() await expect(drawer.locator('th:has-text("名称")')).toBeVisible() await expect(drawer.locator('th:has-text("状态")')).toBeVisible() // 验证空状态提示显示 await expect(drawer.locator('.el-empty')).toBeVisible() }) /** * Bug #4593: 删除提示显示正确的设备名称而非undefined */ test('Bug #4593 - 删除提示显示正确的设备名称', async ({ page }) => { await login(page) // Mock 摄像头列表 API 返回有数据 const testCameraName = '测试摄像头001' 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_TEST_001', cameraName: testCameraName, lssId: 'LSS_001', status: 'active', streamSn: 'STREAM_001' } ], total: 1 } }) }) }) await page.goto('/lss-manage/list') 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) // 点击删除按钮 const deleteButton = drawer .locator('tbody tr') .first() .locator('button[type="button"]') .filter({ hasText: '' }) .last() // 使用 Icon 选择器更精确 const deleteIcon = drawer.locator('tbody tr').first().locator('.iconify--mdi[data-icon*="delete"]').first() if ((await deleteIcon.count()) > 0) { await deleteIcon.click() } else { // 备选方案:点击最后一个按钮(删除按钮) await drawer.locator('tbody tr').first().locator('button').last().click() } // 等待确认对话框 const messageBox = page.locator('.el-message-box') await expect(messageBox).toBeVisible({ timeout: 5000 }) // 验证对话框中显示正确的设备名称,而非 "undefined" const messageText = await messageBox.locator('.el-message-box__message').textContent() expect(messageText).toContain(testCameraName) expect(messageText).not.toContain('undefined') // 关闭对话框 await messageBox.locator('button:has-text("取消")').click() }) /** * Bug #4569: 编辑摄像头标题改为"摄像头详情" */ test('Bug #4569 - 编辑摄像头抽屉标题显示"摄像头详情"', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') 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) // 点击摄像头编辑按钮 const cameraEditButton = drawer.locator('tbody tr').first().locator('button').first() if ((await cameraEditButton.count()) > 0) { await cameraEditButton.click() // 等待摄像头详情抽屉打开 const cameraDrawer = page.locator('.el-drawer').filter({ hasText: '摄像头详情' }) await expect(cameraDrawer).toBeVisible({ timeout: 5000 }) // 验证标题是"摄像头详情"而非"编辑摄像头" await expect(cameraDrawer.locator('.el-drawer__header, .el-drawer__title')).toContainText('摄像头详情') } }) /** * Bug #4570: 摄像头详情增加"添加时间"字段 */ test('Bug #4570 - 摄像头详情显示添加时间', async ({ page }) => { await login(page) // Mock 摄像头详情 API 返回带有 createdAt 的数据 await page.route('**/admin/camera/get*', async (route) => { await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ success: true, errCode: 0, data: { id: 1, cameraId: 'CAM_TEST_001', cameraName: '测试摄像头', lssId: 'LSS_001', status: 'active', vendorName: 'hikvision', model: 'DS-2CD2043G0-I', createdAt: '2024-01-15T10:30:00Z' } }) }) }) await page.goto('/lss-manage/list') 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) // 点击摄像头编辑按钮 const cameraEditButton = drawer.locator('tbody tr').first().locator('button').first() if ((await cameraEditButton.count()) > 0) { await cameraEditButton.click() // 等待摄像头详情抽屉打开 const cameraDrawer = page.locator('.el-drawer').filter({ hasText: '摄像头详情' }) await expect(cameraDrawer).toBeVisible({ timeout: 5000 }) // 验证有"添加时间"字段 await expect(cameraDrawer.locator('label:has-text("添加时间")')).toBeVisible() // 验证添加时间值显示正确格式 const timeValue = cameraDrawer.locator('label:has-text("添加时间")').locator('..').locator('.form-value') const timeText = await timeValue.textContent() // 验证时间格式或包含日期 expect(timeText).toMatch(/\d{4}-\d{2}-\d{2}|\-/) } }) /** * Bug #4545: 按钮大小统一,圆角框 */ test('Bug #4545 - 按钮有统一的圆角', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') // 等待页面加载 await page.waitForTimeout(500) // 获取查询按钮 const searchButton = page.locator('[data-id="btn-search"]') await expect(searchButton).toBeVisible() // 验证按钮有圆角 const borderRadius = await searchButton.evaluate((el) => { return window.getComputedStyle(el).borderRadius }) // 验证圆角值不为0 expect(borderRadius).not.toBe('0px') expect(parseInt(borderRadius)).toBeGreaterThanOrEqual(4) }) /** * Bug #4549: LSS列表作为「LSS 管理」的子菜单 */ test('Bug #4549 - LSS列表作为LSS管理的子菜单', async ({ page }) => { await login(page) // 等待侧边栏加载 await page.waitForTimeout(500) // 验证"LSS 管理"父菜单存在 const lssManageMenu = page.locator('.layout__nav-item--parent').filter({ hasText: 'LSS 管理' }) await expect(lssManageMenu).toBeVisible() // 点击展开子菜单 await lssManageMenu.click() await page.waitForTimeout(300) // 验证"LSS 列表"子菜单存在 const lssListMenu = page.locator('.layout__nav-item--child').filter({ hasText: 'LSS 列表' }) await expect(lssListMenu).toBeVisible() // 点击子菜单导航到页面 await lssListMenu.click() await expect(page).toHaveURL(/\/lss-manage\/list/) }) /** * Bug #4554 & #4555: 摄像头列表搜索字段 */ test('Bug #4554/#4555 - 摄像头列表有设备ID和设备名称搜索字段', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') 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) // 验证有"设备ID"搜索框 await expect(drawer.getByPlaceholder('设备ID')).toBeVisible() // 验证有"名称"搜索框 await expect(drawer.getByPlaceholder('名称')).toBeVisible() // 验证没有"IP / 设备ID / 名称"混合搜索框 const mixedSearch = drawer.getByPlaceholder('IP / 设备ID / 名称') await expect(mixedSearch).not.toBeVisible() }) /** * Bug #4562: 摄像头列表有分页功能 */ test('Bug #4562 - 摄像头列表有分页功能', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') 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) // 验证分页组件存在 await expect(drawer.locator('.el-pagination')).toBeVisible() }) /** * Bug #4567: List厂商显示和详情页一样 */ test('Bug #4567 - 厂商列使用vendorName字段', async ({ page }) => { await login(page) // Mock 摄像头列表 API 返回带有 vendorName 的数据 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_001', cameraName: '测试摄像头', lssId: 'LSS_001', status: 'active', vendorName: 'hikvision', model: 'DS-2CD2T45' } ], total: 1 } }) }) }) await page.goto('/lss-manage/list') 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) // 验证厂商列显示正确的格式化名称 const vendorCell = drawer.locator('tbody tr').first().locator('td').nth(5) const vendorText = await vendorCell.textContent() expect(vendorText).toContain('HIKVISION') }) /** * Bug #4588: 参数配置更新按钮蓝底白字 */ test('Bug #4588 - 参数配置更新按钮使用蓝底白字', async ({ page }) => { await login(page) await page.goto('/lss-manage/list') 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) // 点击参数配置"查看"按钮 const viewButton = drawer.locator('tbody tr').first().locator('button:has-text("查看")').first() if ((await viewButton.count()) > 0) { await viewButton.click() // 等待参数配置抽屉打开 const paramsDrawer = page.locator('.el-drawer').filter({ hasText: '参数配置' }) await expect(paramsDrawer).toBeVisible({ timeout: 5000 }) // 验证更新按钮是蓝色 const updateButton = paramsDrawer.locator('button:has-text("更新")') await expect(updateButton).toHaveClass(/el-button--primary/) } }) /** * Bug #4594: 删除确认对话框确定按钮蓝底白字 */ test('Bug #4594 - 删除确认对话框确定按钮蓝底白字', async ({ page }) => { await login(page) // Mock 摄像头列表 API 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_001', cameraName: '测试', status: 'active' }], total: 1 } }) }) }) await page.goto('/lss-manage/list') 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) // 点击删除按钮 await drawer.locator('tbody tr').first().locator('button').last().click() // 等待确认对话框 const messageBox = page.locator('.el-message-box') await expect(messageBox).toBeVisible({ timeout: 5000 }) // 验证确定按钮是蓝色 const confirmButton = messageBox.locator('button.el-button--primary') await expect(confirmButton).toBeVisible() const bgColor = await confirmButton.evaluate((el) => { return window.getComputedStyle(el).backgroundColor }) // #409EFF 的 RGB 值为 rgb(64, 158, 255) expect(bgColor).toMatch(/rgb\(64,\s*158,\s*255\)|rgba\(64,\s*158,\s*255/) // 关闭对话框 await messageBox.locator('button:has-text("取消")').click() }) })