| | |
| | | await expect(page.locator('.reception-state')).toHaveText('接收中') |
| | | await expect.poll(async () => (await persistedRows(page, testId, 'records')).length, { timeout: 90_000 }).toBeGreaterThan(1) |
| | | await expect(page.locator('.cell-card .temperature')).not.toHaveText('-') |
| | | await expect(page.locator('.alarm-list')).toContainText('测试预警异常') |
| | | await expect(page.locator('.alarm-list')).toContainText('采集器 · 温度超限') |
| | | await expect(page.locator('.alarm-list')).toContainText('当前值:48.5 ℃ · 预警阈值:0 ~ 20 ℃') |
| | | |
| | | await page.getByRole('button', { name: '暂停接收', exact: true }).click() |
| | | await page.getByRole('button', { name: '暂停接收', exact: true }).last().click() |
| | |
| | | await page.getByRole('button', { name: '继续接收', exact: true }).click() |
| | | await page.getByRole('button', { name: '继续接收', exact: true }).last().click() |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | await expect.poll(async () => (await persistedRows(page, testId, 'records')).length).toBeGreaterThan(pausedCount) |
| | | // 继续接收仍按正式分钟采样,不要求在页面轮询的二十秒内入库。 |
| | | await expect.poll(async () => (await persistedRows(page, testId, 'records')).length, { timeout: 90_000 }).toBeGreaterThan(pausedCount) |
| | | await expect.poll(async () => Math.max(0, ...(await persistedRows(page, testId, 'records')).map(row => Number(row.sequenceId) || 0))) |
| | | .toBeGreaterThan(pausedCabinetSequence) |
| | | }) |
| | | |
| | | await test.step('重新登录、后端重启和 SSE 重连后恢复状态与事实', async () => { |
| | | await page.getByRole('button', { name: '工步图表', exact: true }).click() |
| | | await page.locator('.detail-meta input').fill('Ticket06 最新测试详情') |
| | | await page.getByRole('button', { name: '保存详情', exact: true }).click() |
| | | await expect(page.getByText('测试详情已保存', { exact: true })).toBeVisible() |
| | | await page.getByRole('button', { name: '退出当前账号' }).click() |
| | | await page.getByRole('button', { name: '退出', exact: true }).click() |
| | | await login(page) |
| | | await page.goto(`/test-list/${testId}/console`) |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | |
| | | await stack.restartBackend() |
| | | await expect.poll(async () => { |
| | | try { |
| | | const result = await apiJson<ApiResult<{ testStatus: string }>>(page, `/system/test/${testId}/live-state`) |
| | | return result.data?.testStatus |
| | | } catch { |
| | | return '' |
| | | } |
| | | }, { timeout: 90_000 }).toBe('RUNNING') |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | await page.reload() |
| | | await page.getByRole('button', { name: '工步图表', exact: true }).click() |
| | | await expect(page.locator('.detail-meta input')).toHaveValue('Ticket06 最新测试详情') |
| | | expect((await persistedRows(page, testId, 'records')).length).toBeGreaterThan(0) |
| | | }) |
| | | |
| | | await test.step('停止立即进入完成态,异常事实仍可人工关闭追溯', async () => { |
| | | const warningsBeforeStop = await apiJson<{ code: number; rows: WarningRecord[] }>(page, |
| | | `/system/test-warning-exceptions/list?testId=${testId}&status=OPEN`) |
| | | expect(warningsBeforeStop.rows.length).toBeGreaterThan(0) |
| | | expect(warningsBeforeStop.rows[0].occurrenceCount).toBeGreaterThan(0) |
| | | expect(JSON.parse(warningsBeforeStop.rows[0].ruleSnapshot).maxValue).toBe(20) |
| | | |
| | | await page.getByRole('button', { name: '实时监控', exact: true }).click() |
| | | await page.route(`**/dev-api/system/test/${testId}/reception/end`, async route => { |
| | | await new Promise(resolve => setTimeout(resolve, 700)) |
| | | await route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ code: 500, msg: '模拟停止失败' }) }) |
| | | }) |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).click() |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).last().click() |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | await expect(page.getByRole('alert').filter({ hasText: '模拟停止失败' })).toBeVisible() |
| | | await expect(page.locator('.project-meta')).toContainText('检测中') |
| | | await page.unroute(`**/dev-api/system/test/${testId}/reception/end`) |
| | | |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).click() |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).last().click() |
| | | await expect(page.locator('.running-state')).toHaveText('检测完成') |
| | | await expect(page.locator('.reception-state')).toHaveText('已结束接收') |
| | | await expect(page.locator('.alarm-empty')).toContainText('暂无异常') |
| | | await expect(page.locator('.cell-card .temperature')).toHaveText('-') |
| | | |
| | | const stoppedCount = (await persistedRows(page, testId, 'records')).length |
| | | await page.waitForTimeout(1_500) |
| | | expect((await persistedRows(page, testId, 'records')).length).toBe(stoppedCount) |
| | | |
| | | await page.goto('/exception') |
| | | const exceptionRow = page.locator('tbody tr').filter({ hasText: names.test }).first() |
| | | await expect(exceptionRow).toContainText('待处理') |
| | | await exceptionRow.getByRole('button', { name: '处理', exact: true }).click() |
| | | await page.locator('.el-message-box__input textarea').fill('Ticket06 人工确认并关闭') |
| | | await page.getByRole('button', { name: '完成处理', exact: true }).click() |
| | | await expect(exceptionRow).toContainText('已处理') |
| | | |
| | | const warningsAfterClose = await apiJson<{ code: number; rows: WarningRecord[] }>(page, |
| | | `/system/test-warning-exceptions/list?testId=${testId}`) |
| | | expect(warningsAfterClose.rows.some(item => item.exceptionStatus === 'CLOSED')).toBe(true) |
| | | }) |
| | | |
| | | await test.step('三类当前有效数据可修订并形成统一修订记录', async () => { |
| | | // 模拟器没有工步或循环历史;通过已有正式导入动作准备供修订的事实。 |
| | | // 模拟器没有工步或循环历史;检测完成后才通过正式导入准备供修订的事实。 |
| | | await page.goto(`/test-list/${testId}/console`) |
| | | for (const kind of ['STEP', 'CYCLE']) { |
| | | const row = { recordNo: `INITIAL-${kind}`, batteryNo: names.battery, recordedAt: Date.now(), |
| | | cycleNo: 1, stepNo: 1, stepName: '恒流充电', sequenceId: 1, |
| | |
| | | await expect(page.getByText(/电池组 F 不可用/)).toHaveCount(0) |
| | | }) |
| | | |
| | | await test.step('重新登录、后端重启和 SSE 重连后恢复状态与事实', async () => { |
| | | await page.getByRole('button', { name: '退出当前账号' }).click() |
| | | await page.getByRole('button', { name: '退出', exact: true }).click() |
| | | await login(page) |
| | | await page.goto(`/test-list/${testId}/console`) |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | |
| | | await test.step('完成态重启后保留详情和三类修订事实', async () => { |
| | | await stack.restartBackend() |
| | | await expect.poll(async () => { |
| | | try { |
| | | const result = await apiJson<ApiResult<{ testStatus: string }>>(page, `/system/test/${testId}/live-state`) |
| | | return result.data?.testStatus |
| | | } catch { |
| | | return '' |
| | | } |
| | | }, { timeout: 90_000 }).toBe('RUNNING') |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | await page.reload() |
| | | await page.goto(`/test-list/${testId}/console`) |
| | | await expect(page.locator('.running-state')).toHaveText('检测完成') |
| | | await expect(page.locator('.reception-state')).toHaveText('已结束接收') |
| | | await page.getByRole('button', { name: '工步图表', exact: true }).click() |
| | | await expect(page.locator('.detail-meta input')).toHaveValue('Ticket06 最新测试详情') |
| | | expect((await persistedRows(page, testId, 'records')).length).toBeGreaterThan(0) |
| | | expect((await persistedRows(page, testId, 'steps')).some(row => Number(row.endVoltage) === 3.2222)).toBe(true) |
| | | expect((await persistedRows(page, testId, 'cycles')).some(row => Number(row.dischargeCapacity) === 4)).toBe(true) |
| | | }) |
| | | |
| | | await test.step('停止立即进入完成态,异常事实仍可人工关闭追溯', async () => { |
| | | const warningsBeforeStop = await apiJson<{ code: number; rows: WarningRecord[] }>(page, |
| | | `/system/test-warning-exceptions/list?testId=${testId}&status=OPEN`) |
| | | expect(warningsBeforeStop.rows.length).toBeGreaterThan(0) |
| | | expect(warningsBeforeStop.rows[0].occurrenceCount).toBeGreaterThan(0) |
| | | expect(JSON.parse(warningsBeforeStop.rows[0].ruleSnapshot).maxValue).toBe(20) |
| | | |
| | | await page.getByRole('button', { name: '实时监控', exact: true }).click() |
| | | await page.route(`**/dev-api/system/test/${testId}/reception/end`, async route => { |
| | | await new Promise(resolve => setTimeout(resolve, 700)) |
| | | await route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ code: 500, msg: '模拟停止失败' }) }) |
| | | }) |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).click() |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).last().click() |
| | | await expect(page.locator('.running-state')).toHaveText('检测中') |
| | | await expect(page.getByRole('alert').filter({ hasText: '模拟停止失败' })).toBeVisible() |
| | | await expect(page.locator('.project-meta')).toContainText('检测中') |
| | | await page.unroute(`**/dev-api/system/test/${testId}/reception/end`) |
| | | |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).click() |
| | | await page.getByRole('button', { name: '停止测试', exact: true }).last().click() |
| | | await expect(page.locator('.running-state')).toHaveText('检测完成') |
| | | await expect(page.locator('.reception-state')).toHaveText('已结束接收') |
| | | await expect(page.locator('.alarm-empty')).toContainText('暂无异常') |
| | | await expect(page.locator('.cell-card .temperature')).toHaveText('-') |
| | | |
| | | const stoppedCount = (await persistedRows(page, testId, 'records')).length |
| | | await page.waitForTimeout(1_500) |
| | | expect((await persistedRows(page, testId, 'records')).length).toBe(stoppedCount) |
| | | |
| | | await page.goto('/exception') |
| | | const exceptionRow = page.locator('tbody tr').filter({ hasText: names.test }).first() |
| | | await expect(exceptionRow).toContainText('待处理') |
| | | await exceptionRow.getByRole('button', { name: '处理', exact: true }).click() |
| | | await page.locator('.el-message-box__input textarea').fill('Ticket06 人工确认并关闭') |
| | | await page.getByRole('button', { name: '完成处理', exact: true }).click() |
| | | await expect(exceptionRow).toContainText('已处理') |
| | | |
| | | const warningsAfterClose = await apiJson<{ code: number; rows: WarningRecord[] }>(page, |
| | | `/system/test-warning-exceptions/list?testId=${testId}`) |
| | | expect(warningsAfterClose.rows.some(item => item.exceptionStatus === 'CLOSED')).toBe(true) |
| | | }) |
| | | |
| | | await test.step('三类导入直接落表,修改当前 F 后已有测试读取新值', async () => { |
| | |
| | | }) |
| | | expect(result.code).toBe(200) |
| | | const table = `t_test_${kind.toLowerCase()}_record` |
| | | expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} WHERE test_id=${testId} AND source_identity='IMPORT'`)).toBe(kind === 'REALTIME' ? '1' : '2') |
| | | expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} WHERE test_id=${testId} AND source_record_id IN ('INITIAL-${kind}', '${row.recordNo}')`)).toBe(kind === 'REALTIME' ? '1' : '2') |
| | | // 正式导入使用该电池绑定的完整通道身份,IMPORT 只是修订动作类型。 |
| | | expect(stack.queryDatabase(`SELECT COUNT(*) FROM ${table} r JOIN t_test_channel_binding b ON b.binding_id=r.binding_id |
| | | JOIN t_detection_channel c ON c.channel_id=b.channel_id WHERE r.test_id=${testId} AND r.source_record_id='${row.recordNo}' |
| | | AND r.source_identity=CONCAT(c.adapter_code,':',c.devtype,':',c.devid,':',c.subdevid,':',c.chlid)`)).toBe('1') |
| | | const endpoint = kind === 'REALTIME' ? 'records' : kind === 'STEP' ? 'steps' : 'cycles' |
| | | expect((await persistedRows(page, testId, endpoint)).some(item => item.recordNo === row.recordNo)).toBe(true) |
| | | if (kind === 'REALTIME') expect(stack.queryDatabase(`SELECT cabinet_sequence_id FROM ${table} WHERE test_id=${testId} AND source_identity='IMPORT'`)).toBe('1') |
| | | if (kind === 'REALTIME') expect(stack.queryDatabase(`SELECT cabinet_sequence_id FROM ${table} WHERE test_id=${testId} AND source_record_id='${row.recordNo}'`)).toBe('1') |
| | | } |
| | | const detail = await apiJson<ApiResult<{ batteries: Array<{ groupId: number }> }>>(page, `/system/test/${testId}`) |
| | | const groupId = detail.data.batteries[0]!.groupId |
| | |
| | | |
| | | await test.step('设备协议日志只包含发现和实时查询', async () => { |
| | | await page.goto(`${stack.backendUrl}/dev/bts-simulator.html`) |
| | | await expect(page.locator('#requests .request-command').first()).toBeVisible() |
| | | const commands = await page.locator('#requests .request-command').allTextContents() |
| | | expect(commands.length).toBeGreaterThan(0) |
| | | expect(commands.every(command => ['discover', 'observe', 'cabinet-realtime', 'collector-realtime', 'getdevinfo', 'inquire'].includes(command))).toBe(true) |