yeg
2026-09-05 b650b5b15a84c29a3eeea3986d49436aca4b795c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import assert from 'node:assert/strict'
import { readFile, mkdir } from 'node:fs/promises'
import { chromium, expect } from '@playwright/test'
import XLSX from 'xlsx'
import { consoleImportColumns } from '../src/utils/consoleDataImport.ts'
 
// 拦截构建产物和 API 完成页面验收,不启动业务服务或连接共享数据库。
const origin = 'http://single-sheet-import.test'
const output = '../.scratch/simulator-and-data-import-v1/single-sheet-browser'
await mkdir(output, { recursive: true })
const browser = await chromium.launch({ channel: 'chrome', headless: true })
const calls = []
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 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])
  return { name: '单页数据.xlsx', mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', buffer: XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }) }
}
 
try {
  const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } })
  const errors = []
  page.on('pageerror', error => errors.push(error.message))
  await page.addInitScript(() => localStorage.setItem('battery_detection_auth_token', 'browser-test'))
  await page.route(`${origin}/**`, async route => {
    const path = new URL(route.request().url()).pathname
    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 (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('/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.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.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)
    await page.locator('input[type=file]').setInputFiles(process.env.PARTY_EXCEL || standardFile(type))
    await expect(page.getByRole('button', { name: '确认导入', exact: true })).toBeEnabled()
    if (type === 'REALTIME') await page.screenshot({ path: `${output}/preview-1440.png`, fullPage: true })
    await page.getByRole('button', { name: '确认导入', exact: true }).click()
    await page.locator('.el-message-box input').fill('甲方数据兼容验收')
    await page.locator('.el-message-box').getByRole('button', { name: '确定', exact: true }).click()
    await expect(page.locator('.result-stage')).toContainText('导入完成')
    assert.equal(calls.at(-1).dataType, type)
    const imported = JSON.parse(calls.at(-1).changesSummary)
    assert.equal(imported[0].batteryNo, 'BAT-1')
    if (process.env.PARTY_EXCEL) assert.equal(imported.length, { REALTIME: 7605, STEP: 129, CYCLE: 33 }[type])
  }
  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()
  await expect(page.locator('.el-alert')).toContainText('第20行,列「循环序号」')
  assert.equal(calls.length, 3)
  await page.setViewportSize({ width: 390, height: 844 })
  await expect(page.locator('.app-main')).toHaveCSS('margin-left', '0px')
  await page.screenshot({ path: `${output}/errors-390.png`, fullPage: true })
  assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth), false)
  result = { importedCount: 0, skippedCount: 1 }
  await page.goto(`${origin}/test-list/1/console/data/import?dataType=REALTIME`)
  await page.locator('input[type=file]').setInputFiles(standardFile('REALTIME'))
  await page.getByRole('button', { name: '确认导入', exact: true }).click()
  await page.locator('.el-message-box input').fill('重复导入核对')
  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 })
  assert.deepEqual(errors, [])
  console.log('Ticket03 浏览器:三个独立模板、三子页提交、整页错误阻断、重复结果与窄屏通过')
} finally {
  await browser.close()
}