import { expect, test, type Page } from '@playwright/test'
|
import { Ticket04Stack } from './support/ticket04-stack'
|
|
const stack = new Ticket04Stack()
|
test.use({ actionTimeout: 20_000 })
|
test.beforeAll(async () => {
|
await stack.start()
|
stack.executeIsolatedSql(`
|
INSERT INTO t_battery_group(group_id,group_name,model) VALUES(9201,'拼接打印组','型号');
|
INSERT INTO t_test_type(test_type_id,test_type_name) VALUES(9201,'容量');
|
INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
|
VALUES(9201,'来源甲',1,1,9201,'隔离验收','COMPLETED','ENDED'),(9202,'来源乙',1,1,9201,'隔离验收','COMPLETED','ENDED');
|
INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id)
|
VALUES(9201,9201,9201,'电池01',9201),(9202,9202,9201,'电池01',9202);
|
`)
|
})
|
test.afterAll(async () => stack.stop())
|
|
async function login(page: Page, username = 'admin') {
|
if (page.url().startsWith('http')) await page.evaluate(() => localStorage.clear())
|
await page.goto('/login')
|
await page.locator('input[autocomplete="username"]').fill(username)
|
await page.locator('input[autocomplete="current-password"]').fill(username === 'admin' ? 'admin123' : 'Report123!')
|
await page.getByRole('button', { name: '登录', exact: true }).click()
|
await expect(page).toHaveURL(/\/dashboard$/)
|
}
|
async function api(page: Page, path: string, method = 'GET', data?: unknown, code = 200) {
|
const token = await page.evaluate(() => localStorage.getItem('battery_detection_auth_token'))
|
const body = await (await page.request.fetch(`${stack.backendUrl}/system/${path}`, { method, data, headers: { Authorization: `Bearer ${token}` } })).json()
|
expect(body.code, `${path}: ${body.msg}`).toBe(code)
|
return body
|
}
|
|
test('指定保存版本独立重开、删源打印、未保存及窗口失败反馈', async ({ page }) => {
|
await login(page)
|
const payload = { name: '拼接打印V1', description: '说明 <script>不可执行</script>', selections: [{ testId: 9201, testBatteryId: 9201 }, { testId: 9202, testBatteryId: 9202 }], settings: { batteryFields: [], specialFields: ['人工备注'], detailFieldValues: { 人工备注: { 电池01: '0' } }, metrics: ['finalVoltage1', 'energyEfficiency'], seriesColors: ['#123456'] } }
|
const v1 = (await api(page, 'splice-report', 'POST', payload)).data
|
const v2 = (await api(page, `splice-report/${v1.id}/versions`, 'POST', { ...payload, name: '拼接打印V2', baseVersionNo: 1, changeSummary: '更新说明' })).data
|
await page.goto(`/report-splice/${v1.id}/edit?mode=view&version=1`)
|
await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V1')
|
await expect(page.locator('.splice-snapshot')).toContainText('来源甲')
|
await page.evaluate(() => { window.open = () => null })
|
await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
|
await expect(page.getByText('打印窗口打开失败,请允许浏览器弹出窗口后重试', { exact: true })).toBeVisible()
|
await api(page, 'test/9201', 'DELETE')
|
await api(page, 'test/9202', 'DELETE')
|
await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
|
for (const saved of [v1, v2]) {
|
expect((await api(page, `splice-report/${v1.id}/versions/${saved.versionNo}/print`)).data).toEqual(saved)
|
await page.goto(`/report-splice/${v1.id}/edit?mode=view&version=${saved.versionNo}`)
|
await expect(page.locator('.splice-snapshot h1')).toHaveText(saved.name)
|
const popupPromise = page.waitForEvent('popup')
|
await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
|
const popup = await popupPromise
|
await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
|
await expect(popup.locator('.splice-snapshot')).toContainText('来源甲')
|
await expect(popup.locator('.splice-note')).toHaveText(saved.description)
|
expect(await popup.locator('.splice-snapshot').innerText()).toEqual(await page.locator('.splice-snapshot').innerText())
|
await popup.close()
|
}
|
await page.goto(`/report-splice/${v1.id}/edit`)
|
await page.getByPlaceholder('请输入拼接报表名称').fill('未保存名称')
|
await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
|
await expect(page.getByText('有未保存修改,请先保存新版本后再打印', { exact: true })).toBeVisible()
|
await page.goto('/report-splice/new')
|
await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
|
await expect(page.getByText('请先保存报表后再打印', { exact: true })).toBeVisible()
|
})
|
|
test('打印权限独立校验,查看用户不能直接获取打印版本', async ({ page }) => {
|
await login(page)
|
const saved = (await api(page, 'splice-report/list')).rows[0]
|
const permissions = (await api(page, 'menu/permissionList')).data.flatMap((module: any) => module.pages).find((item: any) => item.pageName === '拼接报表').permissions
|
for (const [username, action] of [['splice_reader', '查看'], ['splice_exporter', '导出']]) {
|
const menuIds = permissions.filter((item: any) => item.menuName === action).map((item: any) => item.menuId)
|
expect(menuIds).toHaveLength(1)
|
await api(page, 'role', 'POST', { roleName: username, roleKey: 'common', roleSort: 1, status: '0', menuIds })
|
const role = (await api(page, `role/list?roleName=${username}`)).rows[0]
|
await api(page, 'user', 'POST', { userName: username, nickName: username, password: 'Report123!', status: '0', roleIds: [role.roleId] })
|
}
|
await login(page, 'splice_reader')
|
await api(page, `splice-report/${saved.id}/versions/1/print`, 'GET', undefined, 403)
|
await page.goto(`/report-splice/${saved.id}/edit?mode=view`)
|
await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V2')
|
await expect(page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })).toHaveCount(0)
|
await page.getByRole('button', { name: '历史版本', exact: true }).click()
|
await page.locator('.el-dialog tr').filter({ hasText: '首次保存' }).getByRole('button', { name: '查看', exact: true }).click()
|
await expect(page.locator('.splice-snapshot h1')).toHaveText('拼接打印V1')
|
await login(page, 'splice_exporter')
|
await api(page, `splice-report/${saved.id}/versions/1/print`)
|
await api(page, `splice-report/${saved.id}`, 'GET', undefined, 403)
|
await api(page, `splice-report/${saved.id}/versions/999/print`, 'GET', undefined, 500)
|
})
|
|
test('长分段真实统计、单位缺失和桌面窄屏多页打印', async ({ page }) => {
|
await login(page)
|
const cycleNos = [1, 3, ...Array.from({ length: 48 }, (_, index) => index + 5)]
|
stack.executeIsolatedSql(`
|
INSERT INTO t_test(test_id,test_name,creator_id,principal_id,test_type_id,test_purpose,test_status,reception_status)
|
VALUES(9211,'长分段甲',1,1,9201,'隔离验收','COMPLETED','ENDED'),(9212,'长分段乙',1,1,9201,'隔离验收','COMPLETED','ENDED');
|
INSERT INTO t_test_battery(test_battery_id,test_id,group_id,battery_no,collector_id)
|
VALUES(9211,9211,9201,'电池长名称01',9211),(9212,9212,9201,'电池长名称01',9212);
|
`)
|
for (let start = 0; start < cycleNos.length; start += 10) {
|
const batch = cycleNos.slice(start, start + 10)
|
stack.executeIsolatedSql(`
|
INSERT INTO t_test_cycle_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,dedupe_key,adapter_timezone_id,device_time_utc,device_time_raw,server_received_at_utc,cycle_no,charge_energy_wh,discharge_energy_wh,content_sha256)
|
VALUES ${[9211, 9212].flatMap(id => batch.map(no => `(${id},${id},${id},'splice','splice:${id}','${no}','${no}','Asia/Hong_Kong','2026-09-01 00:00:00','2026-09-01 08:00:00','2026-09-01 00:00:01',${no},${no === 3 ? '0' : '0.1361'},0.1694,UNHEX(SHA2('${id}:${no}',256)))`)).join(',')};
|
INSERT INTO t_test_step_record(test_id,test_battery_id,binding_id,adapter_code,source_identity,source_record_id,dedupe_key,adapter_timezone_id,device_time_utc,device_time_raw,server_received_at_utc,cycle_no,step_no,voltage_v,content_sha256)
|
VALUES ${[9211, 9212].flatMap(id => batch.map(no => `(${id},${id},${id},'splice','splice:${id}','${no}','${no}','Asia/Hong_Kong','2026-09-01 00:00:00','2026-09-01 08:00:00','2026-09-01 00:00:01',${no},1,${no === 3 ? 'NULL' : no === 1 ? '0' : '-0.2'},UNHEX(SHA2('${id}:${no}',256)))`)).join(',')};
|
`)
|
}
|
const saved = (await api(page, 'splice-report', 'POST', { name: '长分段拼接打印', description: '长说明'.repeat(150), selections: [{ testId: 9211, testBatteryId: 9211 }, { testId: 9212, testBatteryId: 9212 }], settings: { batteryFields: ['字段 (mAh)'], specialFields: ['备注'], detailFieldValues: { '字段 (mAh)': { 电池长名称01: '8512.2' }, 备注: { 电池长名称01: '' } }, metrics: ['finalVoltage1', 'energyEfficiency'], seriesColors: ['#123456'] } })).data
|
expect(saved.segments[0].statistics['1'].finalVoltage1).toBe(0)
|
expect(saved.segments[0].statistics['1'].energyEfficiency).toBeCloseTo(124.46730345)
|
expect(saved.segments[0].statistics['3'].finalVoltage1 ?? null).toBeNull()
|
await page.goto(`/report-splice/${saved.id}/edit?mode=view`)
|
await expect(page.locator('.splice-chart')).toHaveCount(6)
|
await expect(page.locator('.splice-statistics').first()).toContainText('124.4673')
|
await expect(page.locator('.splice-statistics').first()).toContainText('缺失')
|
for (const width of [1440, 390]) {
|
await page.setViewportSize({ width, height: 900 })
|
await expect.poll(() => page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true)
|
await expect(page.getByRole('button', { name: '打印 / 另存 PDF', exact: true })).toBeInViewport()
|
await page.locator('.splice-chart').first().scrollIntoViewIfNeeded()
|
await page.screenshot({ path: `tests/e2e/.artifacts/splice-saved-${width}.png` })
|
}
|
await page.context().addInitScript(() => { window.print = () => { document.body.dataset.printRequested = 'true' } })
|
const popupPromise = page.waitForEvent('popup')
|
await page.getByRole('button', { name: '打印 / 另存 PDF', exact: true }).click()
|
const popup = await popupPromise
|
await expect(popup.locator('body')).toHaveAttribute('data-print-requested', 'true')
|
await expect(popup.locator('.splice-chart')).toHaveCount(6)
|
await expect(popup.locator('.splice-chart svg path[stroke="#123456"]').first()).toBeAttached()
|
expect(await popup.locator('.splice-snapshot').innerText()).toEqual(await page.locator('.splice-snapshot').innerText())
|
await popup.emulateMedia({ media: 'print' })
|
await popup.screenshot({ path: 'tests/e2e/.artifacts/splice-print-long.png', fullPage: true })
|
await popup.pdf({ path: 'tests/e2e/.artifacts/splice-print-long.pdf', preferCSSPageSize: true, printBackground: true })
|
await popup.close()
|
expect((await api(page, `splice-report/${saved.id}/versions`)).data).toHaveLength(1)
|
})
|