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(''); } }
问题根源
- 未等待页面更新完成:首次点击后调用
this.delay(5000)但未加await,代码直接继续执行,此时页面可能还未完成更新,获取的按钮是旧DOM中的失效元素。 - 未重新等待元素就绪:页面更新后,新的“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
相关产品推荐
相关产品推荐

