From b226aa0db7fbecb8fff92476537adc0ca3e16a4f Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期三, 09 九月 2026 13:25:57 +0800
Subject: [PATCH] 统一测试三类数据字段并修正记录编号与时长
---
tests/test-console-data-import-browser.mjs | 143 ++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 133 insertions(+), 10 deletions(-)
diff --git a/tests/test-console-data-import-browser.mjs b/tests/test-console-data-import-browser.mjs
index 0c17c18..a5537a3 100644
--- a/tests/test-console-data-import-browser.mjs
+++ b/tests/test-console-data-import-browser.mjs
@@ -2,28 +2,31 @@
import { readFile, mkdir } from 'node:fs/promises'
import { chromium, expect } from '@playwright/test'
import XLSX from 'xlsx'
-import { consoleImportColumns } from '../src/utils/consoleDataImport.ts'
+import { consoleImportColumns, createConsoleImportWorkbook } from '../src/utils/consoleDataImport.ts'
// 拦截构建产物和 API 完成页面验收,不启动业务服务或连接共享数据库。
const origin = 'http://single-sheet-import.test'
-const output = '../.scratch/simulator-and-data-import-v1/single-sheet-browser'
+const output = '../.scratch/console-excel-field-alignment/browser'
await mkdir(output, { recursive: true })
const browser = await chromium.launch({ channel: 'chrome', headless: true })
const calls = []
+const liveCalls = []
+let workbookRows = null
let result = { importedCount: 1, skippedCount: 0 }
const detail = { testId: 1, testName: '甲方数据导入验收', testStatus: 'COMPLETED', receptionStatus: 'ENDED',
thresholds: [], logicStepMappings: [], batteries: [{ testBatteryId: 1, batteryNo: 'BAT-1', groupName: '演示电池组', fields: [], collectorChannels: [], binding: { channelId: 1 } }] }
function standardFile(type, invalid = false) {
const fields = consoleImportColumns[type]
- const row = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: '2023-05-08 11:42:11', cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17, capacity: 8.5122, temperature: 25, positivePotential: 1.5, negativePotential: -0.1 }
+ const row = { recordNo: '17', batteryNo: 'BAT-1', recordedAt: '2023-05-08 11:42:11', cycleNo: 1, stepNo: 1, stepName: '恒流放电', sequenceId: 17, relativeTimeSeconds: '1:02:32.125', stepDuration: '1:02:32.125', capacity: 8.5122, temperature: 25, positivePotential: 1.5, negativePotential: -0.1, chargeMedianVoltage: 0, dischargeMedianVoltage: 1.1709 }
const rows = [Object.values(fields), Object.keys(fields).map(field => row[field] ?? '')]
if (invalid) {
while (rows.length < 19) rows.push([])
rows.push(Object.keys(fields).map(field => field === 'cycleNo' ? 0 : row[field] ?? ''))
}
- const workbook = XLSX.utils.book_new()
- XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet(rows), { REALTIME: '实时', STEP: '工步', CYCLE: '循环' }[type])
+ const workbook = createConsoleImportWorkbook(type)
+ workbook.Sheets[workbook.SheetNames[0]] = XLSX.utils.aoa_to_sheet(rows)
+ if (type !== 'REALTIME') XLSX.utils.sheet_add_aoa(workbook.Sheets.record, [Object.keys(consoleImportColumns.REALTIME).map(field => row[field] ?? '')], { origin: 'A2' })
return { name: '单页数据.xlsx', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', buffer: XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) }
}
@@ -37,21 +40,28 @@
if (path.startsWith('/assets/')) return route.fulfill({ body: await readFile(`dist${path}`), contentType: path.endsWith('.css') ? 'text/css' : 'application/javascript' })
if (path.endsWith('/console-data/actions')) {
calls.push(route.request().postDataJSON())
- return route.fulfill({ json: { code: 200, actionId: 1, ...result } })
+ if (calls.at(-1).dataType === 'ALL') workbookRows = JSON.parse(calls.at(-1).changesSummary)
+ return route.fulfill({ json: { code: 200, actionId: result.importedCount ? 1 : null,
+ actionIds: result.importedCount ? calls.at(-1).dataType === 'ALL' ? [1, 2, 3] : [1] : [], ...result } })
}
- if (path.endsWith('/live-state')) return route.fulfill({ json: { code: 200, data: { ...detail, sources: [], exceptions: [] } } })
- if (/\/data\/(records|steps|cycles)$/.test(path)) return route.fulfill({ json: { code: 200, data: [] } })
+ if (path.endsWith('/live-state')) { liveCalls.push(path); return route.fulfill({ json: { code: 200, data: { ...detail, sources: [], exceptions: [] } } }) }
+ if (/\/data\/(records|steps|cycles)$/.test(path)) {
+ const type = { records: 'REALTIME', steps: 'STEP', cycles: 'CYCLE' }[path.split('/').at(-1)]
+ const data = workbookRows?.[type]?.slice(0, 1).map(row => ({ ...row, sourceType: type === 'REALTIME' ? 'SNAPSHOT' : undefined, deviceTime: row.recordedAt, recordId: 1, stepRecordId: 1, cycleRecordId: 1, stableIdentity: `${type.toLowerCase()}:1` })) || []
+ return route.fulfill({ json: { code: 200, data } })
+ }
if (path.endsWith('/system/test/1')) return route.fulfill({ json: { code: 200, data: detail } })
if (path.endsWith('/getInfo')) return route.fulfill({ json: { code: 200, user: { userName: 'admin' }, roles: ['admin'] } })
return route.fulfill({ body: await readFile('dist/index.html'), contentType: 'text/html' })
})
for (const type of ['REALTIME', 'STEP', 'CYCLE']) {
+ await page.setViewportSize({ width: 1440, height: 1000 })
await page.goto(`${origin}/test-list/1/console?tab=data-list&dataType=${type}`)
const downloadEvent = page.waitForEvent('download')
await page.getByRole('button', { name: '下载模板', exact: true }).click()
const download = await downloadEvent
const workbook = XLSX.read(await readFile(await download.path()))
- assert.equal(workbook.SheetNames.length, 1)
+ assert.equal(workbook.SheetNames.length, type === 'REALTIME' ? 1 : 2)
assert.deepEqual(XLSX.utils.sheet_to_json(workbook.Sheets[workbook.SheetNames[0]], { header: 1 })[0], Object.values(consoleImportColumns[type]))
await page.goto(`${origin}/test-list/1/console/data/import?dataType=${type}`)
await expect(page.locator('.status-notice')).toHaveCount(0)
@@ -75,6 +85,12 @@
}
}
}
+ if (process.env.PARTY_EXCEL) {
+ const imported = JSON.parse(calls.find(call => call.dataType === 'STEP').changesSummary)
+ const source = XLSX.utils.sheet_to_json(XLSX.readFile(process.env.PARTY_EXCEL).Sheets.step)
+ assert.deepEqual(imported.map(row => [row.chargeMedianVoltage, row.dischargeMedianVoltage]),
+ source.map(row => [row['充电中值电压(V)'], row['放电中值电压(V)']]))
+ }
await page.goto(`${origin}/test-list/1/console/data/import?dataType=REALTIME`)
await page.locator('input[type=file]').setInputFiles(standardFile('REALTIME', true))
await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeDisabled()
@@ -92,8 +108,115 @@
await page.locator('.el-message-box').getByRole('button', { name: '确定', exact: true }).click()
await expect(page.locator('.result-stage')).toContainText('重复跳过1 条')
await page.screenshot({ path: `${output}/result-390.png`, fullPage: true })
+
+ function workbookFile(mode = 'valid') {
+ const workbook = createConsoleImportWorkbook()
+ for (const [index, type] of ['REALTIME', 'STEP', 'CYCLE'].entries()) {
+ const single = XLSX.read(standardFile(type).buffer)
+ workbook.Sheets[workbook.SheetNames[index]] = single.Sheets[single.SheetNames[0]]
+ }
+ if (mode === 'missing') { workbook.SheetNames.pop(); delete workbook.Sheets['循环'] }
+ if (mode === 'invalid') XLSX.utils.sheet_add_aoa(workbook.Sheets['循环'], [['错误数值']], { origin: 'F20' })
+ if (mode === 'identity') XLSX.utils.sheet_add_aoa(workbook.Sheets['循环'], [['OTHER']], { origin: 'B2' })
+ return { name: '三 Sheet.xlsx', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', buffer: XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) }
+ }
+ await page.setViewportSize({ width: 1440, height: 1000 })
+ await page.goto(`${origin}/test-list/1/console?tab=data-list`)
+ await page.getByRole('button', { name: '三 Sheet 一键导入', exact: true }).click()
+ const templateEvent = page.waitForEvent('download')
+ await page.getByRole('button', { name: '下载三 Sheet 模板', exact: true }).click()
+ const template = XLSX.read(await readFile(await (await templateEvent).path()))
+ assert.deepEqual(template.SheetNames, ['实时', '工步', '循环'])
+ for (const [index, type] of ['REALTIME', 'STEP', 'CYCLE'].entries()) assert.deepEqual(XLSX.utils.sheet_to_json(template.Sheets[template.SheetNames[index]], { header: 1 })[0], Object.values(consoleImportColumns[type]))
+ const beforeInvalid = calls.length
+ for (const mode of ['missing', 'invalid', 'identity']) {
+ await page.goto(`${origin}/test-list/1/console/data/import?mode=workbook`)
+ await page.locator('input[type=file]').setInputFiles(workbookFile(mode))
+ await expect(page.locator('.sheet-summary tbody tr')).toHaveCount(3)
+ await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeDisabled()
+ await expect(page.locator('.el-alert')).toContainText('循环')
+ const errorDownload = page.waitForEvent('download')
+ await page.getByRole('button', { name: '下载全部错误', exact: true }).click()
+ const errorsWorkbook = XLSX.read(await readFile(await (await errorDownload).path()))
+ assert.equal(XLSX.utils.sheet_to_json(errorsWorkbook.Sheets['失败数据'])[0]['工作表'], '循环')
+ }
+ assert.equal(calls.length, beforeInvalid)
+ await page.goto(`${origin}/test-list/1/console/data/import?mode=workbook`)
+ const beforeLive = liveCalls.length
+ result = { importedCount: process.env.PARTY_EXCEL ? 7767 : 3, skippedCount: 0 }
+ await page.locator('input[type=file]').setInputFiles(process.env.PARTY_EXCEL || workbookFile())
+ await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeEnabled()
+ for (const label of ['实时', '工步', '循环']) {
+ await page.getByRole('tab', { name: label, exact: true }).click()
+ await expect(page.locator('.preview-table-wrap tbody tr')).not.toHaveCount(0)
+ }
+ await page.screenshot({ path: `${output}/workbook-preview-1440.png`, fullPage: true })
+ await page.setViewportSize({ width: 390, height: 844 })
+ await expect(page.locator('.app-main')).toHaveCSS('margin-left', '0px')
+ await page.screenshot({ path: `${output}/workbook-preview-390.png`, fullPage: true, animations: 'disabled' })
+ assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
+ await page.getByRole('button', { name: '确认导入', exact: true }).click()
+ await page.locator('.el-message-box input').fill('三个 Sheet 共用原因')
+ await page.locator('.el-message-box').getByRole('button', { name: '确定', exact: true }).click()
+ await expect(page.locator('.result-stage')).toContainText('数据修订记录:#1、#2、#3')
+ await expect(page.locator('.result-stage')).not.toContainText('导入批次')
+ assert.equal(calls.length, beforeInvalid + 1)
+ assert.equal(calls.at(-1).dataType, 'ALL')
+ assert.equal(calls.at(-1).reason, '三个 Sheet 共用原因')
+ assert.deepEqual(Object.keys(workbookRows), ['REALTIME', 'STEP', 'CYCLE'])
+ assert.deepEqual(Object.values(workbookRows).map(rows => rows.length), process.env.PARTY_EXCEL ? [7605, 129, 33] : [1, 1, 1])
+ assert.equal(liveCalls.length, beforeLive)
+ await page.screenshot({ path: `${output}/workbook-result-390.png`, fullPage: true })
+ for (const type of ['REALTIME', 'STEP', 'CYCLE']) {
+ await page.goto(`${origin}/test-list/1/console?tab=data-list&dataType=${type}`)
+ await expect(page.locator('.data-toolbar')).toBeVisible()
+ await expect(page.locator('.table-wrap tbody')).toContainText('BAT-1')
+ await page.reload()
+ await expect(page.locator('.table-wrap tbody')).toContainText('BAT-1')
+ const expectedHeaders = ['记录编号', '电池', ...XLSX.utils.sheet_to_json(XLSX.readFile(process.env.PARTY_EXCEL || (await import('node:path')).resolve('../outputs/ticket03-excel/277#(5)(1)_补充三项测试值.xls')).Sheets[{ REALTIME: 'record', STEP: 'step', CYCLE: 'cycle' }[type]], { header: 1 })[0]]
+ assert.deepEqual((await page.locator('.table-wrap th').allTextContents()).map(value => value.trim()).slice(1, -1), expectedHeaders)
+ await expect(page.locator('.record-code').first()).toHaveText(type.toLowerCase() + ':1')
+ await page.screenshot({ path: `${output}/list-${type.toLowerCase()}-390.png`, fullPage: true })
+ await page.setViewportSize({ width: 1440, height: 1000 })
+ await page.screenshot({ path: `${output}/list-${type.toLowerCase()}.png`, fullPage: true, animations: 'disabled' })
+ const downloadEvent = page.waitForEvent('download')
+ await page.getByRole('button', { name: '下载', exact: true }).click()
+ const csvText = await readFile(await (await downloadEvent).path(), 'utf8')
+ const csvRows = XLSX.utils.sheet_to_json(XLSX.read(csvText.replace(/^\uFEFF/, ''), { type: 'string', raw: true }).Sheets.Sheet1, { header: 1, raw: true })
+ assert.deepEqual(csvRows[0], expectedHeaders)
+ assert.equal(csvRows[1][0], type.toLowerCase() + ':1')
+ if (type === 'REALTIME') {
+ assert.equal(csvRows[1][6], process.env.PARTY_EXCEL ? '0:00:00.000' : '1:02:32.125')
+ assert.match(csvRows[1][11], /^2023-05-08 11:42:11/)
+ }
+ if (type === 'STEP') {
+ assert.equal(csvRows[1][5], process.env.PARTY_EXCEL ? '1:02:32.000' : '1:02:32.125')
+ await expect(page.locator('.table-wrap thead')).toContainText('充电中值电压(V)')
+ await expect(page.locator('.table-wrap thead')).toContainText('放电中值电压(V)')
+ await expect(page.locator('.table-wrap tbody')).toContainText('0.0000')
+ await expect(page.locator('.table-wrap tbody')).toContainText('1.1709')
+ const csvEvent = page.waitForEvent('download')
+ await page.getByRole('button', { name: '下载', exact: true }).click()
+ const csv = await readFile(await (await csvEvent).path(), 'utf8')
+ assert.match(csv, /充电中值电压\(V\)/)
+ assert.match(csv, /放电中值电压\(V\)/)
+ assert.match(csv, /"0\.0000","1\.1709"/)
+ assert.doesNotMatch(csv, /峰值|谷值/)
+ await page.locator('.row-actions').first().getByRole('button', { name: '编辑', exact: true }).click()
+ const durationInput = page.locator('.single-form-grid label').filter({ hasText: '工步时间(h:min:s.ms)' }).locator('input')
+ await expect(durationInput).toHaveValue(process.env.PARTY_EXCEL ? '1:02:32.000' : '1:02:32.125')
+ await durationInput.fill('1:02:33.250')
+ await page.locator('.remark-field textarea').fill('核对时长编辑与记录编号')
+ const edited = page.waitForResponse(response => response.url().endsWith('/console-data/actions'))
+ await page.getByRole('button', { name: '提交修改', exact: true }).click()
+ await edited
+ assert.equal(calls.at(-1).operationType, 'EDIT')
+ assert.deepEqual(JSON.parse(calls.at(-1).identitySummary), ['step:1'])
+ assert.deepEqual(JSON.parse(calls.at(-1).overrides[0].fieldValues), { stepDuration: '3753.25' })
+ }
+ }
assert.deepEqual(errors, [])
- console.log('Ticket03 浏览器:三个独立模板、三子页提交、整页错误阻断、重复结果与窄屏通过')
+ console.log('浏览器:独立及三 Sheet 模板、预览校验、全部修订编号、子页刷新与窄屏通过;导入未请求实时监控')
} finally {
await browser.close()
}
--
Gitblit v1.7.1