You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Puppeteer无头浏览器API调用时弹窗账号密码填充不稳定问题

Puppeteer 操作magicul.io登录偶现填充失败问题

问题详情

  • 使用Puppeteer无头模式启动浏览器,操作magicul.io网站完成登录流程:点击登录按钮弹出新窗口,在弹窗内填充账号密码,登录完成后弹窗关闭
  • 异常表现:单独运行脚本无问题,通过API调用时偶现失败,有时执行到打印“Find Inputs”后停止,无法填入账号密码
  • 测试说明:可使用任意账号密码进行测试

原代码

const express = require('express');
const dotenv = require('dotenv');
const cors = require('cors');
const puppeteer = require('puppeteer');
dotenv.config({ path: '.env' });
global.__appRootDir = __dirname;

const app = express();

app.use(cors());
app.use(express.json({ limit: '50mb' }));

const PORT = process.env.PORT ?? '3059'
const HOST = process.env.HOST ?? 'localhost';

app.get('/', (req, res) => {
    res.send('server is responding')
})

app.post('/conversion', conversion)

const conversion = async (req, res) => {

    try {
        const browser = await puppeteer.launch({
            headless: true
        });
        const page = await browser.newPage();

        await page.goto(process.env.SITE_URL, { waitUntil: 'domcontentloaded' });

        await page.waitForSelector('button[id=\"login\"]');
        await page.click('button[id=\"login\"]');

        console.log("Popup Opened");

        const newPagePromise = new Promise(x => browser.once('targetcreated', target => x(target.page())));
        const popup = await newPagePromise;

        console.log("Find Inputs");
        const usernameInput = await popup.waitForSelector('input[id=\"username\"]', { timeout: 30000 });
        await usernameInput.type(process.env.USERNAME)

        await popup.keyboard.press('Enter')

        const passwordInput = await popup.waitForSelector('input[id=\"password\"]', { timeout: 30000 });
        await passwordInput.type(process.env.PASSWORD)

        console.log("Form  Filled");
        await popup.keyboard.press('Enter')

        // Create a promise that resolves when the popup page is closed
        const popupClosedPromise = new Promise((resolve) => {
            popup.once('close', () => {
                resolve()
            })
        })

        // Wait for the popup page to close
        await popupClosedPromise
        console.log("popup closed");

        console.log('login done')

        // next process - works perfectly.


    } catch (error) {
        console.error(error)
        return res.status(500).json({ message: error.message })
    }
}

// listen for requests
app.listen(PORT, HOST, () => {
    console.log(`Server is listening on port ${HOST}:${PORT}`);
});

解决方案

1. 修复弹窗捕获的竞态问题

原代码使用browser.once('targetcreated')可能错过弹窗创建时机,改为先检查已存在的目标,再监听新目标:

// 替换原弹窗捕获逻辑
await page.click('button[id="login"]');
console.log("Popup Opened");

let popup;
// 先检查是否已存在弹窗目标
const existingTargets = await browser.targets();
const popupTarget = existingTargets.find(target => 
  target.type() === 'page' && target.url().includes('magicul.io/login')
);

if (popupTarget) {
  popup = await popupTarget.page();
} else {
  // 监听新目标创建,确保不会错过
  popup = await new Promise(resolve => {
    const handleTargetCreated = target => {
      if (target.type() === 'page' && target.url().includes('magicul.io/login')) {
        browser.removeListener('targetcreated', handleTargetCreated);
        resolve(target.page());
      }
    };
    browser.on('targetcreated', handleTargetCreated);
  });
}

2. 优化页面等待策略

无头模式下网络加载不稳定,调整页面加载等待条件,并确保弹窗页面完全加载:

// 修改主页面跳转的等待条件
await page.goto(process.env.SITE_URL, { waitUntil: 'networkidle2' });

// 获取弹窗后,等待弹窗页面加载完成
await popup.waitForNavigation({ waitUntil: 'networkidle2' });

3. 确保输入框可交互后再填充

使用waitForFunction验证输入框处于可见且可交互状态,避免元素未就绪导致填充失败:

// 替换原账号输入框等待代码
await popup.waitForFunction(
  () => {
    const input = document.getElementById('username');
    return input && input.offsetParent !== null && !input.disabled;
  },
  { timeout: 30000 }
);
const usernameInput = await popup.$('input[id="username"]');
await usernameInput.type(process.env.USERNAME);

// 替换原密码输入框等待代码
await popup.waitForFunction(
  () => {
    const input = document.getElementById('password');
    return input && input.offsetParent !== null && !input.disabled;
  },
  { timeout: 30000 }
);
const passwordInput = await popup.$('input[id="password"]');
await passwordInput.type(process.env.PASSWORD);

4. 移除不可靠的键盘切换逻辑

原代码用Enter键切换到密码框,改为直接定位密码框,避免页面逻辑变化导致失效:

// 移除这一行:await popup.keyboard.press('Enter')
// 直接使用上面的密码框等待和填充逻辑即可

5. 优化无头浏览器配置

添加必要参数模拟真实浏览器环境,避免被网站识别或布局异常:

const browser = await puppeteer.launch({
  headless: 'new', // 使用新版无头模式,兼容性更好
  args: [
    '--no-sandbox',
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',
    '--disable-gpu',
    '--window-size=1920,1080', // 设置标准窗口大小,避免响应式布局影响元素定位
    '--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'
  ]
});

内容的提问来源于stack exchange,提问作者Saloni Saraiya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 04:34:55