如何使用Playwright下载无.pdf后缀的生成式PDF文件?
问题分析
你的代码卡在等待下载事件,核心原因是无头模式下Chromium的下载行为与非无头模式存在差异,同时弹窗页面的元素加载等待、事件监听逻辑可能存在时序问题。手动操作能成功说明业务流程没问题,只需调整自动化环境配置和等待逻辑即可解决。
解决方案
1. 配置无头模式的下载权限与路径
在创建浏览器上下文时,明确指定下载路径并授予下载权限,避免无头模式下的默认行为限制:
const context = await browser.newContext({ acceptDownloads: true, downloadsPath: downloadPath, // 直接指定下载目录 permissions: ['downloads'] // 授予下载权限 });
2. 禁用Chromium内置PDF查看器
无头模式下Chromium默认会用内置查看器打开PDF,而非触发下载,需通过启动参数强制下载行为:
const browser = await chromium.launch({ headless: true, args: [ '--disable-pdf-viewer', // 禁用内置PDF查看器 '--allow-downloads-without-user-activation', // 允许自动化触发下载 '--download-restrictions=0' // 解除下载限制 ] });
3. 优化事件监听与页面等待逻辑
通过Promise.all并行监听弹窗和下载事件,避免时序偏差;同时添加页面加载等待,确保弹窗内元素可交互:
// 并行监听弹窗和弹窗内的下载事件 const [page1, download] = await Promise.all([ page.waitForEvent('popup'), (async () => { const popup = await page.waitForEvent('popup'); await popup.waitForLoadState('networkidle'); // 等待弹窗页面完全加载 return popup.waitForEvent('download'); })() ]); // 触发PDF导出弹窗 await page.frameLocator('frame[name="link"]').getByRole('link', { name: 'Export PDF...' }).click(); // 等待下载按钮可见后再点击 await page1.getByRole('button', { name: 'Submit' }).waitFor({ state: 'visible' }); await page1.getByRole('button', { name: 'Submit' }).click();
完整优化代码
const { chromium } = require('playwright'); const path = require('path'); (async () => { const downloadPath = path.join(__dirname, 'edt'); const browser = await chromium.launch({ headless: true, args: [ '--disable-pdf-viewer', '--allow-downloads-without-user-activation', '--download-restrictions=0' ] }); const context = await browser.newContext({ acceptDownloads: true, downloadsPath: downloadPath, permissions: ['downloads'] }); const page = await context.newPage(); // 登录流程 await page.goto('https://planif.eppe.ui/jsp/custom/eppee/easyMyPlanning.jsp'); await page.getByLabel('Identifiant :').fill('myusername'); await page.getByLabel('Mot de passe :').fill('mysecretpassword'); await page.getByLabel('Mot de passe :').press('Enter'); // 选择选项并提交 await page.getByRole('combobox').selectOption('10'); await page.getByRole('button', { name: 'Submit' }).click(); await page.locator('.footer > a').click(); // 并行监听弹窗和下载事件 const [page1, download] = await Promise.all([ page.waitForEvent('popup'), (async () => { const popup = await page.waitForEvent('popup'); await popup.waitForLoadState('networkidle'); return popup.waitForEvent('download'); })() ]); // 触发PDF导出弹窗 await page.frameLocator('frame[name="link"]').getByRole('link', { name: 'Export PDF...' }).click(); // 点击下载按钮 await page1.getByRole('button', { name: 'Submit' }).waitFor({ state: 'visible' }); await page1.getByRole('button', { name: 'Submit' }).click(); // 保存下载文件 await download.saveAs(path.join(downloadPath, 'edt.pdf')); await browser.close(); })();
关键说明
- 无头模式参数直接改变了Chromium对PDF的处理逻辑,强制触发下载而非内置查看;
- 并行事件监听避免了先等待弹窗再监听下载的时序漏洞;
- 页面加载等待确保弹窗内的下载按钮完全就绪,不会出现点击无效的情况。
内容的提问来源于stack exchange,提问作者lolozen
相关产品推荐
相关产品推荐

