import { _electron as electron,expect } from '@playwright/test' import electronPath from 'electron' import ExcelJS from 'exceljs' import { PNG } from 'pngjs' import { PDFDocument } from 'pdf-lib' import fs from 'node:fs/promises' import path from 'node:path' const root=process.cwd(),qa=path.join(root,'.qa','print-regression'),profile=path.join(qa,'profile'),output=path.join(qa,'output'),seals=path.join(qa,'seals') if(!qa.startsWith(root+path.sep))throw Error('Invalid QA path') await fs.rm(qa,{recursive:true,force:true}) await fs.mkdir(seals,{recursive:true});await fs.mkdir(output,{recursive:true}) const image=new PNG({width:256,height:256}) let seed=731 for(let i=0;i>>0 image.data[i]=130+(seed&127);image.data[i+1]=(seed>>>8)&63;image.data[i+2]=(seed>>>16)&63;image.data[i+3]=100+(seed>>>24)%156 } await fs.writeFile(path.join(seals,'批量打印测试章.png'),PNG.sync.write(image)) const book=new ExcelJS.Workbook(),sheet=book.addWorksheet('收据数据') sheet.addRow(['收款日期','学校','年级','姓名','收费项目','金额','出纳','复核']) for(let i=1;i<=41;i++)sheet.addRow(['2026-10-16','测试学校','高2028届','测试学生'+i,'测试收费项目',3500,'测试出纳','测试复核']) const excelFile=path.join(qa,'批量测试.xlsx');await book.xlsx.writeFile(excelFile) const env={...process.env};delete env.ELECTRON_RUN_AS_NODE;delete env.ELECTRON_RENDERER_URL let desktop try{ desktop=await electron.launch({executablePath:electronPath,args:['.','--test-mode','--user-data-dir='+profile],cwd:root,env,timeout:60000}) const page=await desktop.firstWindow();page.setDefaultTimeout(30000) await expect(page.getByRole('heading',{name:'收据工作台',exact:true})).toBeVisible() await expect.poll(()=>page.evaluate(async()=>!!(await window.receipts.bootstrap()).settings)).toBe(true) await desktop.evaluate(({dialog,shell},p)=>{dialog.showOpenDialog=async()=>({canceled:false,filePaths:[p.excelFile]});shell.openPath=async()=>''},{excelFile}) await page.evaluate(async p=>{ const boot=await window.receipts.bootstrap() await window.receipts.saveSettings({...boot.settings,outputDirectory:p.output,sealDirectory:p.seals,numberPrefix:'REG',exportPdf:false,exportPng:false,exportPrint:true}) },{output,seals}) const sealCharacters=await page.evaluate(async()=> (await window.receipts.listSeals()).images[0].dataUrl.length) expect(sealCharacters*40).toBeGreaterThan(2*1024*1024) const batchId=await page.evaluate(async()=>{ const draft=await window.receipts.importExcel(),seal=(await window.receipts.listSeals()).images[0] window.printTestProgress=[] window.receipts.onProgress(p=>window.printTestProgress.push({finished:p.finished,status:p.status,errorLength:p.errors.join('').length})) return window.receipts.exportReceipts({draftId:draft.id,rowIndices:draft.rows.map(r=>r.index),sealId:seal.id,options:{pdf:false,png:false,print:true,receiptsPerPage:2,cutLine:true}}) }) await expect.poll(()=>page.evaluate(async()=> (await window.receipts.listBatches())[0]),{timeout:120000,intervals:[500,1000]}).toMatchObject({status:process.argv.includes('--expect-failure')?'failed':'completed'}) const summary=await page.evaluate(async()=> (await window.receipts.listBatches())[0]) if(process.argv.includes('--expect-failure')){ expect(summary.message).toContain('ERR_INVALID_URL') console.log(JSON.stringify({reproduced:true,receiptCount:summary.count,messageCharacters:summary.message.length,imageCharactersInFirstChunk:sealCharacters*40})) }else{ const files=await fs.readdir(summary.outputDirectory,{recursive:true}) const pdf=await PDFDocument.load(await fs.readFile(path.join(summary.outputDirectory,files.find(f=>f.endsWith('.pdf'))))) expect(pdf.getPageCount()).toBe(21);expect(Math.abs(pdf.getPage(0).getWidth()-595.28)).toBeLessThan(1) const manifest=JSON.parse(await fs.readFile(path.join(summary.outputDirectory,'生成清单.json'),'utf8')) expect(manifest.errors).toEqual([]);expect(manifest.receipts).toHaveLength(41) expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('REG00000042') const firstDirectory=summary.outputDirectory await desktop.evaluate(({BrowserWindow})=>{ const original=BrowserWindow.prototype.loadURL BrowserWindow.prototype.receiptTestLoadURL=original BrowserWindow.prototype.loadURL=function(url,...args){ if(url.startsWith('receipt://print/'))return Promise.reject(new Error("ERR_INVALID_URL (-300) loading 'data:text/html;charset=utf-8,"+'sensitive'.repeat(300000)+"'")) return original.call(this,url,...args) } }) await page.evaluate(async id=>window.receipts.exportReceipts({batchId:id,options:{pdf:false,png:false,print:true,receiptsPerPage:2,cutLine:true}}),batchId) await expect.poll(()=>page.evaluate(async()=> (await window.receipts.listBatches())[0].status),{timeout:30000}).toBe('failed') await expect(page.getByRole('alert')).toContainText('ERR_INVALID_URL') expect((await page.getByRole('alert').innerText()).length).toBeLessThan(450) await expect(page.getByRole('status')).toHaveCount(0) const failed=(await page.evaluate(()=>window.receipts.listBatches()))[0] expect(failed.message.length).toBeLessThan(450);expect(failed.message).not.toContain('sensitive') await desktop.evaluate(({BrowserWindow})=>{BrowserWindow.prototype.loadURL=BrowserWindow.prototype.receiptTestLoadURL;delete BrowserWindow.prototype.receiptTestLoadURL}) await page.evaluate(async id=>window.receipts.exportReceipts({batchId:id,options:{pdf:true,png:true,print:true,receiptsPerPage:2,cutLine:true}}),batchId) await expect.poll(()=>page.evaluate(async old=>{const b=(await window.receipts.listBatches())[0];return b.outputDirectory!==old&&b.status==='completed'},firstDirectory),{timeout:180000,intervals:[1000]}).toBe(true) const retry=(await page.evaluate(()=>window.receipts.listBatches()))[0],retryFiles=await fs.readdir(retry.outputDirectory,{recursive:true}) expect(retryFiles.filter(f=>f.endsWith('.pdf'))).toHaveLength(42);expect(retryFiles.filter(f=>f.endsWith('.png'))).toHaveLength(41) expect((await page.evaluate(()=>window.receipts.bootstrap())).nextNumber).toBe('REG00000042') await fs.writeFile(path.join(qa,'result.json'),JSON.stringify({passed:true,receiptCount:41,printPages:pdf.getPageCount(),imageCharactersInFirstChunk:sealCharacters*40,retryPdfCount:42,retryPngCount:41,nextNumber:'REG00000042',shortErrorVerified:true,failureNoticeVerified:true,output:retry.outputDirectory},null,2)) console.log('Large stamp batch printing and original-number retry passed.') } }finally{if(desktop)await desktop.close().catch(()=>{})}