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 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(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') 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).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(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 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(()=>{})}