React应用Puppeteer登录流程测试在无头模式下运行失败求解
问题原因及解决方案
核心原因
两种模式运行结果不一致,通常是以下几个因素导致:
- Puppeteer旧版
headless: true的无头模式与有头模式存在原生行为差异,包括UA标识、Cookie策略、渲染逻辑都不一致,Puppeteer v19+推出的headless: 'new'新版无头模式已完全对齐有头模式的行为。 - 默认视口尺寸差异:无头模式默认视口尺寸远小于普通桌面浏览器尺寸,可能导致元素未渲染在可视区域内,触发点击失效、选择器无法匹配的问题。
- User-Agent标识差异:旧版无头模式的UA会携带
HeadlessChrome字样,多数站点的安全策略、反爬规则会直接拦截该类请求,导致登录弹窗无法加载、请求被拒绝。 - 第三方Cookie限制:无头模式默认禁用第三方Cookie,若你的登录弹窗为OAuth第三方授权登录,会导致授权流程被中断。
- 弹出窗口拦截:无头模式默认可能阻止页面弹出新窗口,导致你监听的
popup事件始终无法触发,流程卡在等待弹窗的步骤超时。
修复方案
1. 优先使用新版无头模式
修改puppeteer.launch配置,替换旧版无头模式:
browser = await puppeteer.launch({ // 仅Puppeteer v19及以上版本支持,行为完全和有头模式一致 headless: 'new', slowMo: 50, args: [ // 禁用沙箱,避免Linux环境下启动失败 '--no-sandbox', '--disable-setuid-sandbox', // 允许弹出窗口 '--disable-popup-blocking', // 禁用第三方Cookie限制,适配第三方登录场景 '--disable-features=SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure' ] }) // 手动设置视口为常规桌面尺寸 await page.setViewport({ width: 1920, height: 1080 })
2. 替换User-Agent伪装普通浏览器
在页面加载前手动设置正常浏览器的UA,绕过站点拦截:
await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36') await page.goto("http://localhost:5000/test_site");
3. 优化弹窗获取逻辑
避免popup事件监听失效的问题,可直接通过浏览器页面列表获取弹窗:
await page.click("#login-button"); // 等待弹窗加载完成 await page.waitForTimeout(2000); // 获取所有打开的页面,最后一个即为新弹出的登录窗口 const allPages = await browser.pages(); const popup = allPages[allPages.length - 1];
4. 延长超时时间+添加日志排查
给所有waitForSelector方法添加自定义超时,同时在报错时截图定位问题:
test("Authentication Process by logging in", async () => { try { const username = 'test123@gmail.com'; const password = 'pw123'; await page.waitForSelector("#login-button", {timeout: 15000}); await page.click("#login-button"); await page.waitForTimeout(2000); const allPages = await browser.pages(); const popup = allPages[allPages.length - 1]; await popup.waitForSelector("#usernameOrEmail", {timeout: 15000}); await popup.type("#usernameOrEmail", username); await popup.waitForSelector("#password", {timeout: 15000}); await popup.type("#password", password); await popup.click("#submitButton"); await page.waitForSelector("#logout-button", {timeout: 15000}) const text = await page.$eval("#logout-button", (e) => e.textContent); expect(text).toBe('Logout'); } catch (err) { // 报错时截图,方便定位当前页面状态 await page.screenshot({path: 'test-error.png', fullPage: true}); throw err; } }, 20000) // 给单测试用例设置20s超时,避免默认10s不够用
内容的提问来源于stack exchange,提问作者Swissyodler
相关产品推荐
相关产品推荐

