如何通过Node JS/Webdriver.io/Chimp实现PDF打印及结果验证?
我来帮你解决这个测试@media print CSS的问题,结合Node.js、Chimp、Webdriver.io和Selenium,分两个核心部分拆解:生成与实际打印一致的PDF,以及验证PDF的正确性。
生成与实际打印一致的PDF(Chrome/Firefox)
要保证PDF和实际打印效果完全匹配,必须调用浏览器原生的打印渲染引擎生成文件,而非截图转PDF。下面分别给出Chrome和Firefox的实现方案:
Chrome 实现方案
Chrome的DevTools协议提供了Page.printToPDF命令,能直接触发浏览器的打印渲染流程,完美遵循@media print规则。在Webdriver.io/Chimp中可以通过cdp方法调用这个命令:
// 在Chimp/Webdriver.io测试中生成Chrome PDF async function generateChromePrintPDF(outputPath = 'print-result.pdf') { // 调用Chrome DevTools的printToPDF命令 const pdfResult = await browser.cdp('Page', 'printToPDF', { printBackground: true, // 关键:启用背景渲染,匹配实际打印(默认不开启) displayHeaderFooter: false, // 关闭默认页眉页脚(按需调整) mediaType: 'print', // 强制使用print媒体类型,确保@media print样式生效 paperWidth: 8.5, // 纸张宽度(英寸,A4可设为8.27) paperHeight: 11, // 纸张高度(英寸,A4可设为11.69) marginTop: 0, // 自定义边距(可选) marginBottom: 0, marginLeft: 0, marginRight: 0 }); // 将返回的base64数据写入文件 const fs = require('fs'); fs.writeFileSync(outputPath, Buffer.from(pdfResult.data, 'base64')); console.log(`PDF已生成:${outputPath}`); }
调用后生成的PDF,就是Chrome实际打印时的渲染结果,完全贴合你的@media print CSS配置。
Firefox 实现方案
Firefox需要先配置打印偏好,将默认打印机设为「保存到PDF」,再触发页面打印生成文件:
// 在Chimp/Webdriver.io测试中生成Firefox PDF async function generateFirefoxPrintPDF(outputPath = 'firefox-print-result.pdf') { // 设置Firefox打印偏好,静默保存为PDF await browser.setPreferences({ 'print.always_print_silent': true, // 静默打印,不弹出对话框 'print.printer_Mozilla_Save_to_PDF.print_to_file': true, // 保存到文件 'print.printer_Mozilla_Save_to_PDF.print_to_filename': outputPath, // 指定输出路径 'print.printer_Mozilla_Save_to_PDF.paper_size_type': '0', // 自定义纸张尺寸 'print.printer_Mozilla_Save_to_PDF.paper_width': 8.5, 'print.printer_Mozilla_Save_to_PDF.paper_height': 11, 'print.print_bgcolor': true, // 打印背景颜色 'print.print_bgimages': true // 打印背景图片 }); // 触发页面打印 await browser.executeScript(() => window.print()); // 等待文件生成(根据页面复杂度调整延迟时间) await browser.pause(2000); console.log(`PDF已生成:${outputPath}`); }
注意:Firefox的偏好设置可能随版本略有调整,建议使用绝对路径避免权限问题。
扫描PDF验证结果正确性
生成PDF后,我们可以从内容准确性和样式符合性两个维度验证,下面是几种实用方法:
1. 文本内容验证(基础)
用pdf-parse库提取PDF文本,检查关键内容的存在/缺失,对应@media print中元素的显示/隐藏规则:
const fs = require('fs'); const pdfParse = require('pdf-parse'); async function verifyPDFContent(pdfPath) { const pdfBuffer = fs.readFileSync(pdfPath); const { text } = await pdfParse(pdfBuffer); // 验证应该显示的内容存在 if (!text.includes('订单编号:20240501')) { throw new Error('PDF中缺失预期的打印内容'); } // 验证@media print中隐藏的内容不存在 if (text.includes('网站导航栏')) { throw new Error('PDF中包含不应显示的内容(导航栏)'); } console.log('文本内容验证通过'); }
2. 样式/布局验证(进阶)
如果需要验证布局(比如元素位置、分页是否正确),可以用pdfjs-dist解析PDF页面结构,检查元素的坐标和尺寸:
const fs = require('fs'); const pdfjsLib = require('pdfjs-dist'); async function verifyPDFLayout(pdfPath) { const pdfData = new Uint8Array(fs.readFileSync(pdfPath)); const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise; const page = await pdf.getPage(1); const { viewport } = page; const textContent = await page.getTextContent(); // 查找特定文本的位置,验证是否在预期区域 const targetText = '总计:¥100.00'; const targetItem = textContent.items.find(item => item.str === targetText); if (!targetItem) { throw new Error('未找到目标文本'); } // 转换为页面坐标(示例:验证文本是否在页面底部区域) const { x, y } = viewport.convertToViewportPoint(targetItem.transform[4], targetItem.transform[5]); if (y < 50) { // 假设底部50px以内是预期位置 console.log('布局验证通过:总计文本在页面底部'); } else { throw new Error('总计文本位置不符合预期'); } }
3. 视觉对比验证(精准)
如果需要完全匹配视觉效果,可以将PDF转换为图片,用resemblejs和预期的基准图片对比:
const pdfPoppler = require('pdf-poppler'); const resemble = require('resemblejs'); const fs = require('fs'); async function verifyPDFVisual(pdfPath, expectedImagePath) { // 将PDF第一页转换为PNG图片 const convertOptions = { format: 'png', out_dir: './temp', out_prefix: 'print-preview', page: 1 }; await pdfPoppler.convert(pdfPath, convertOptions); const generatedImagePath = './temp/print-preview-1.png'; // 对比生成图片和预期图片 return new Promise((resolve, reject) => { resemble(generatedImagePath) .compareTo(expectedImagePath) .ignoreMinor() // 忽略微小差异(如字体渲染的细微差别) .onComplete(data => { if (data.misMatchPercentage < 0.5) { // 允许0.5%以内的差异 console.log('视觉验证通过'); fs.unlinkSync(generatedImagePath); // 清理临时文件 resolve(); } else { reject(new Error(`视觉差异过大:${data.misMatchPercentage}%`)); } }); }); }
内容的提问来源于stack exchange,提问作者Ryan Shillington
相关产品推荐
相关产品推荐

