This commit is contained in:
fuenmao
2026-10-19 16:47:24 +08:00
parent e977a988b2
commit e3727d40bd
14 changed files with 171 additions and 32 deletions
+2
View File
@@ -76,6 +76,7 @@ try{
await page.getByRole('button',{name:'导入 Excel',exact:true}).click()
await expect(page.getByText('已选择 3 条',{exact:true})).toBeVisible()
await expect(page.getByText('待修改',{exact:true})).toBeVisible()
await page.locator('.group-review-heading').click()
const group=page.getByRole('checkbox',{name:'选择分组:某某学校 / 初二年级 / 秋季学期服务费',exact:true})
await group.uncheck();await expect(page.getByText('已选择 2 条',{exact:true})).toBeVisible()
await expect(page.getByRole('checkbox',{name:'选择分组:某某学校',exact:true})).toHaveJSProperty('indeterminate',true)
@@ -149,6 +150,7 @@ try{
await page.getByRole('button',{name:'收据工作台',exact:true}).click()
await page.locator('.panel-heading [data-task="import"]').click()
await expect(page.getByText('已选择 60 条',{exact:true})).toBeVisible()
await page.locator('.group-review-heading').click()
await expect(page.locator('.group-tree-root > .receipt-group-node')).toHaveCount(50)
await page.getByRole('button',{name:'显示更多分组(还有 10 项)',exact:true}).click()
await expect(page.locator('.group-tree-root > .receipt-group-node')).toHaveCount(60)
+69 -21
View File
@@ -1,39 +1,87 @@
import { _electron as electron,expect } from '@playwright/test'
import { listPackage } from '@electron/asar'
import ExcelJS from 'exceljs'
import { PNG } from 'pngjs'
import fs from 'node:fs/promises'
import path from 'node:path'
import {PDFDocument} from 'pdf-lib'
const profile=path.join(process.cwd(),'.qa','packaged-profile-1.0.1')
await fs.mkdir(profile,{recursive:true})
const env={...process.env};delete env.ELECTRON_RUN_AS_NODE
const root=process.cwd(),version=JSON.parse(await fs.readFile('package.json','utf8')).version
await fs.mkdir(path.join(root,'.qa'),{recursive:true})
const qa=await fs.mkdtemp(path.join(root,'.qa','packaged-'+version+'-'))
const profile=path.join(qa,'profile'),sealFolder=path.join(qa,'seals'),output=path.join(qa,'output')
for(const folder of [profile,sealFolder,output])await fs.mkdir(folder,{recursive:true})
const image=new PNG({width:120,height:120})
for(let y=0;y<120;y++)for(let x=0;x<120;x++){
const i=(y*120+x)*4,d=Math.hypot(x-60,y-60)
image.data[i]=195;image.data[i+1]=30;image.data[i+2]=35;image.data[i+3]=d>45&&d<50?255:0
}
await fs.writeFile(path.join(sealFolder,'测试印章.png'),PNG.sync.write(image))
const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据')
sheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额'])
for(const [school,name,amount] of [['甲学校','张三',3500],['乙学校','赵六',2500],['甲学校','李四',3000],['甲学校','王五',2000]])sheet.addRow(['2026-10-19',school,'高一',name,'秋季学期服务费',amount])
const excelFile=path.join(qa,'收据测试数据.xlsx');await book.xlsx.writeFile(excelFile)
const archive=listPackage(path.join(root,'release','win-unpacked','resources','app.asar'))
expect(archive.some(file=>/[\\/](?:\.local-data|\.qa|\.vs|node_modules)(?:[\\/]|$)/.test(file))).toBe(false)
expect(archive.some(file=>/\.(?:sqlite|map)$/i.test(file))).toBe(false)
expect(archive.some(file=>/[\\/]brand-[^\\/]+\.png$/.test(file))).toBe(true)
const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL
let app
const errors=[]
try{
app=await electron.launch({executablePath:path.join(process.cwd(),'release','win-unpacked','收据工坊.exe'),args:['--smoke-test','--user-data-dir='+profile],env,timeout:60000})
app=await electron.launch({executablePath:path.join(root,'release','win-unpacked','收据工坊.exe'),args:['--smoke-test','--user-data-dir='+profile],env,timeout:60000})
const page=await app.firstWindow();page.setDefaultTimeout(20000)
page.on('pageerror',error=>errors.push(error.message))
await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible({timeout:30000})
await expect(page.getByText('把表格交给收据工坊')).toBeVisible({timeout:30000})
const boot=await page.evaluate(()=>window.receipts.bootstrap())
expect(boot.version).toBe(version)
expect(boot.dataDirectory).toBe(profile)
expect(boot.nextNumber).toBe('SJ00000001')
const sealFolder=path.join(process.cwd(),'.qa','fixtures','seals'),output=path.join(process.cwd(),'.qa','packaged-output')
await fs.mkdir(output,{recursive:true})
await app.evaluate(({shell})=>{shell.openPath=async()=>''})
expect(boot.historyTotal).toBe(0);expect(boot.settings.historyPageSize).toBe(5)
await expect.poll(()=>page.locator('.brand-mark').evaluate(img=>img.complete&&img.naturalWidth===200)).toBe(true)
await page.getByRole('button',{name:'A4 打印排版',exact:true}).click()
await expect(page.frameLocator('iframe').locator('.receipt')).toHaveCount(2)
await page.getByLabel('打印每页张数').selectOption('1')
await expect(page.frameLocator('iframe').locator('.receipt')).toHaveCount(1)
await page.getByLabel('打印每页张数').selectOption('2')
await expect(page.frameLocator('iframe').locator('.receipt')).toHaveCount(2)
expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe(boot.nextNumber)
await app.evaluate(({shell,dialog},file)=>{shell.openPath=async()=>'';dialog.showOpenDialog=async()=>({canceled:false,filePaths:[file]})},excelFile)
await page.evaluate(async({output,sealFolder})=>{const b=await window.receipts.bootstrap();await window.receipts.saveSettings({...b.settings,outputDirectory:output,sealDirectory:sealFolder,numberPrefix:'PACK'})},{output,sealFolder})
const seals=await page.evaluate(()=>window.receipts.listSeals());expect(seals.images.length).toBeGreaterThan(0)
await app.evaluate(({dialog},file)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[file]})},path.join(process.cwd(),'.qa','fixtures','收据测试数据.xlsx'))
const draft=await page.evaluate(()=>window.receipts.importExcel())
expect(draft.rows[0].receipt.customer).toBe('某某学校 初二年级 张三')
expect(draft.rows[0].receipt.school).toBe('某某学校')
expect(draft.rows[0].receipt.grade).toBe('初二年级')
expect(draft.rows[0].receipt.studentName).toBe('张三')
await page.evaluate(async({id,sealId})=>window.receipts.exportReceipts({draftId:id,rowIndices:[0],sealId,options:{pdf:true,png:true,print:true,receiptsPerPage:2,cutLine:true}}),{id:draft.id,sealId:seals.images[0].id})
await expect.poll(async()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]?.status),{timeout:90000}).toBe('completed')
const seals=await page.evaluate(()=>window.receipts.listSeals());expect(seals.images).toHaveLength(1)
await page.evaluate(async sealId=>{const b=await window.receipts.bootstrap();await window.receipts.saveSettings({...b.settings,defaultSealId:sealId})},seals.images[0].id)
await page.reload()
await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible()
await page.getByRole('button',{name:'导入 Excel',exact:true}).click()
await expect(page.getByText('已选择 4 条',{exact:true})).toBeVisible()
await expect(page.locator('.group-review')).toHaveJSProperty('open',false)
await page.getByRole('button',{name:'A4 打印排版',exact:true}).click()
await expect(page.frameLocator('iframe').locator('.receipt')).toHaveCount(2)
await expect(page.frameLocator('iframe').getByText('甲学校 高一 李四',{exact:true})).toBeVisible()
await page.getByLabel('单张 PNG',{exact:true}).check()
await page.getByRole('button',{name:/生成 4 张收据/}).click()
await expect.poll(async()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]?.status),{timeout:120000,intervals:[500,1000]}).toBe('completed')
const batch=(await page.evaluate(()=>window.receipts.listBatches()))[0]
expect(batch.count).toBe(4);expect(batch.firstNumber).toBe('PACK00000001');expect(batch.lastNumber).toBe('PACK00000004')
const files=await fs.readdir(batch.outputDirectory,{recursive:true})
expect(files.filter(n=>n.endsWith('.png'))).toHaveLength(1)
expect(files.filter(n=>n.endsWith('.png'))).toHaveLength(4)
expect(files.filter(n=>n.endsWith('.pdf'))).toHaveLength(6)
const png=await fs.readFile(path.join(batch.outputDirectory,files.find(n=>n.endsWith('.png'))))
expect(png.readUInt32BE(png.indexOf('pHYs')+4)).toBe(11811)
const pdf=await PDFDocument.load(await fs.readFile(path.join(batch.outputDirectory,files.find(n=>path.basename(n).startsWith('A4')))))
expect(pdf.getPageCount()).toBe(1)
await fs.writeFile('.qa/package-result.json',JSON.stringify({passed:true,platform:'Windows 10 x64',packaged:true,renderedPdf:true,renderedPng:true,dpi:300,profile},null,2))
console.log('Packaged application verified: standalone startup, Excel, seals, PDF and PNG.')
const pages={}
for(const [school,count] of [['甲学校',2],['乙学校',1]]){
const file=path.join(school,'高一','秋季学期服务费','A4打印版_每页2张.pdf')
expect(files).toContain(file)
const pdf=await PDFDocument.load(await fs.readFile(path.join(batch.outputDirectory,file)))
expect(pdf.getPageCount()).toBe(count);pages[school]=count
expect(Math.abs(pdf.getPage(0).getWidth()-595.28)).toBeLessThan(1)
}
await page.getByRole('button',{name:/生成记录/}).click()
await expect(page.locator('.history-table tbody tr')).toHaveCount(1)
expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('PACK00000005')
expect(errors).toEqual([])
const result={passed:true,version,packaged:true,cleanArchive:true,defaultGroupCollapsed:true,liveA4Preview:true,receipts:4,renderedPdf:true,renderedPng:true,printPages:pages,dpi:300,profile,errors}
await fs.writeFile(path.join(qa,'result.json'),JSON.stringify(result,null,2))
await fs.writeFile(path.join(root,'.qa','package-result.json'),JSON.stringify(result,null,2))
console.log('Packaged application verified: '+version+'. Artifacts: '+qa)
}finally{if(app)await app.close().catch(()=>{})}