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

Puppeteer页面内容更新后无法二次点击同ID按钮问题

Puppeteer表单自动化二次点击同ID按钮失败问题

我正在学习Puppeteer,尝试为某站点实现表单填充自动化,脚本流程如下:

  • 访问目标站点
  • 点击“Create Temporary ID”按钮
  • 填写表单内容
  • 点击“Continue”按钮
  • 页面内容更新
  • 再次点击具有相同ID的“Continue”按钮

问题:首次点击同ID的“Continue”按钮正常,但页面内容更新后二次点击该按钮失败。

脚本代码:

import tesseract from 'node-tesseract-ocr';
import Puppeteer from 'puppeteer';

const config = {
  lang: 'eng', // default
  oem: 3,
  psm: 13,
};

export class AutomationController {
  public async CreateProfile(data: any) {
    try {
      const _data = {
        nationality: 'BRA',
        id: '0508228690083',
        title: 'Mr',
        firstName: 'Shaun',
        middleName: 'Maxwell',
        surname: 'Smith',
        dob: '15062001',
        gender: 'M',
        email: 'shaun@gmail.com',
        countryCode: '+27',
        mobileNumber: '0832567890',
      };

      const browser = await Puppeteer.launch({ headless: false });
      const page = await browser.newPage();
      await page.goto(
        'https://self-service.wits.ac.za/psc/csprodonl/UW_SELF_SERVICE/SA/c/VC_OA_LOGIN_MENU.VC_OA_LOGIN_FL.GBL',
        { waitUntil: 'networkidle2', timeout: 0 }
      );

      // Click create temp id button
      await page.waitForSelector('#VC_OA_LOGIN_WRK_REGISTER');
      await page.click('#VC_OA_LOGIN_WRK_REGISTER');

      // Nationality field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_COUNTRY');
      // await page.select('select[id="VC_OA_LOGIN_WRK_COUNTRY"]', 'AUS');

      // ID number field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_NATIONAL_ID');
      const idInput = await page.$('#VC_OA_LOGIN_WRK_NATIONAL_ID');
      await idInput?.type(_data.id);
      await this.delay(4000);

      // Name Title field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_NAME_PREFIX');
      await page.select(
        'select[id="VC_OA_LOGIN_WRK_NAME_PREFIX"]',
        _data.title
      );
      await this.delay(4000);

      // First name field
      await page.waitForSelector(
        '#win0divVC_OA_LOGIN_WRK_FIRST_NAMEctrl > #VC_OA_LOGIN_WRK_FIRST_NAME'
      ); // VC_OA_LOGIN_WRK_FIRST_NAME
      const firstNameInput = await page.$('#VC_OA_LOGIN_WRK_FIRST_NAME');
      await firstNameInput?.type(_data.firstName);
      await this.delay(4000);

      // Middle name field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_MIDDLE_NAME');
      const middleNameInput = await page.$('#VC_OA_LOGIN_WRK_MIDDLE_NAME');
      await middleNameInput?.type(_data.middleName);
      await this.delay(4000);

      // Surname field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_LAST_NAME');
      const surnameInput = await page.$('#VC_OA_LOGIN_WRK_LAST_NAME');
      await surnameInput?.type(_data.surname);
      await this.delay(4000);

      // Gender field
      await page.waitForSelector('#VC_OA_LOGIN_WRK_SEX');
      await page.select('select[id="VC_OA_LOGIN_WRK_SEX"]', _data.gender);
      await this.delay(4000);

      // Email field
      await page.waitForSelector(
        '#win0divVC_OA_LOGIN_WRK_EMAIL_ADDRctrl > #VC_OA_LOGIN_WRK_EMAIL_ADDR'
      );
      const emailInput = await page.$(
        '#win0divVC_OA_LOGIN_WRK_EMAIL_ADDRctrl > #VC_OA_LOGIN_WRK_EMAIL_ADDR'
      );
      await emailInput?.type(_data.email);
      await this.delay(4000);

      // Mobile number
      await page.waitForSelector('#VC_OA_LOGIN_WRK_VC_PHONE_CELL_SS');
      const mobileInput = await page.$('#VC_OA_LOGIN_WRK_VC_PHONE_CELL_SS');
      await mobileInput?.type(_data.mobileNumber);
      await this.delay(4000);

      await page.waitForSelector(
        '#win0divVC_SEC_WRK_HTML_AREA_02 > div > div > img'
      );

      // Bypass security check
      const imgs = await page.$$eval(
        '#win0divVC_SEC_WRK_HTML_AREA_02 > div > div > img',
        (imgs) => imgs.map((img) => img.getAttribute('src'))
      );
      const securityPassword = this.getCharacterAfterSecondUnderscore(imgs);

      await page.waitForSelector('#VC_OA_LOGIN_WRK_VC_SEC_CODE');
      const securityField = await page.$('#VC_OA_LOGIN_WRK_VC_SEC_CODE');
      await securityField?.type(securityPassword);

      await this.delay(4000);

      // Continue button
      await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB');
      const continueBtn = await page.$('#VC_OA_LOGIN_WRK_CONTINUE_PB');
      await continueBtn.click();

      this.delay(5000);
      // TODO - HANDLE IF SECURITY CHECK FAILED

      //THIS THE BUTTON THAT FAILS TO CLICK SECOND TIME
      // CONFIRM Details button
      const confirmBtn = await page.$('#VC_OA_LOGIN_WRK_CONTINUE_PB'); 
      await confirmBtn.click();
      console.log(`Automation.controller - 136`, 'DONE');

      await this.delay(4000);
    } catch (error) {
      console.log(`Automation.controller - 137`, error);
    }
  }

  private delay(time) {
    return new Promise(function (resolve) {
      setTimeout(resolve, time);
    });
  }

  private getCharacterAfterSecondUnderscore(strings: string[]): string {
    const results: string[] = [];

    for (const string of strings) {
      const splittedString = string.split('_');
      results.push(splittedString[2]);
    }

    return results.join('');
  }
}

问题根源

  1. 未等待页面更新完成:首次点击后调用this.delay(5000)但未加await,代码直接继续执行,此时页面可能还未完成更新,获取的按钮是旧DOM中的失效元素。
  2. 未重新等待元素就绪:页面更新后,新的“Continue”按钮虽然ID相同,但属于新的DOM节点,直接用page.$获取可能拿到已被移除的旧元素,或者元素还未渲染完成。

修复方案

修改二次点击按钮的代码,确保:

  • 用await等待延迟,让页面完成更新
  • 重新等待按钮元素可见后再点击

修改后的关键代码:

// Continue button
await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB');
const continueBtn = await page.$('#VC_OA_LOGIN_WRK_CONTINUE_PB');
await continueBtn.click();

// 必须加await,确保页面更新完成
await this.delay(5000);
// TODO - HANDLE IF SECURITY CHECK FAILED

// 重新等待按钮元素,确保是新DOM中的有效元素
await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB', { visible: true });
const confirmBtn = await page.$('#VC_OA_LOGIN_WRK_CONTINUE_PB'); 
await confirmBtn.click();
console.log(`Automation.controller - 136`, 'DONE');

额外优化建议

  • 尽量避免固定延迟,改用page.waitForNavigation或等待特定元素来判断页面状态,更可靠:
await continueBtn.click();
// 等待页面导航完成,替代固定延迟
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// 再等待确认按钮可见
await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB', { visible: true });
  • 简化代码,直接用page.click结合选择器等待:
// 首次点击
await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB');
await page.click('#VC_OA_LOGIN_WRK_CONTINUE_PB');

await this.delay(5000);

// 二次点击
await page.waitForSelector('#VC_OA_LOGIN_WRK_CONTINUE_PB', { visible: true });
await page.click('#VC_OA_LOGIN_WRK_CONTINUE_PB');

内容的提问来源于stack exchange,提问作者Ntshembo Hlongwane

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 14:04:56