霍格沃兹测试开发学社

《Python测试开发进阶训练营》(随到随学!)
2023年第2期《Python全栈开发与自动化测试班》(开班在即)
报名联系weixin/qq:2314507862

Playwright处理验证码的自动化解决方案

关注 霍格沃兹测试学院公众号,回复「资料」, 领取人工智能测试开发技术合集

验证码(CAPTCHA)一直是自动化测试中最让人头疼的环节之一。每次碰到那些扭曲的文字、点选图片的挑战,自动化脚本就像撞上了一堵墙。我负责的电商项目最近就卡在了登录自动化这个环节——那个该死的滑动验证码让我们的回归测试屡屡失败。

经过几周的实战踩坑和方案对比,我总结出几种用Playwright处理验证码的可行方案。这些方案各有适用场景,没有绝对的“银弹”,但足够帮你绕过大多数验证码障碍。

方案一:最直接的方式——测试环境关闭验证码
如果你们公司有测试环境的管理权限,这是最干净利落的解决方案。

// 在测试环境中,通过修改配置或调用管理接口禁用验证码
asyncfunction disableCaptchaInTestEnv(page) {
// 方式1:如果有管理后台接口
await page.goto('http://test-admin.example.com/features');
await page.click('#toggle-captcha');

// 方式2:通过设置测试用户白名单
await page.evaluate(() => {
localStorage.setItem('bypass_captcha', 'true');
});

// 方式3:修改hosts或使用mock服务(需运维配合)
console.log('验证码已在测试环境关闭');
}
优点:零成本,100%稳定,执行速度快。
缺点:仅限测试环境,生产环境模拟不了完整流程。

Playwright mcp技术学习交流群
伙伴们,对AI测试、大模型评测、质量保障感兴趣吗?我们建了一个 「Playwright mcp技术学习交流群」,专门用来探讨相关技术、分享资料、互通有无。无论你是正在实践还是好奇探索,都欢迎扫码加入,一起抱团成长!期待与你交流!👇

image

方案二:半自动方案——人工介入一次,重复使用凭证
对于无法关闭验证码但又不频繁变更的场景,这个方案很实用。

const fs = require('fs');
const path = require('path');

class CaptchaHandler {
constructor() {
this.tokenFile = path.join(__dirname, '.auth_token');
}

async handleCaptcha(page) {
// 检查是否有缓存的登录凭证
if (fs.existsSync(this.tokenFile)) {
const token = fs.readFileSync(this.tokenFile, 'utf8');
awaitthis.useCachedToken(page, token);
returntrue;
}

// 首次需要人工处理
console.log('请手动完成验证码验证...');

// Playwright会暂停,等待人工操作
await page.pause();  // 这是关键!手动完成后按回车继续

// 保存获取到的凭证(如cookie、token)
const cookies = await page.context().cookies();
const authToken = cookies.find(c => c.name === 'auth_token');

if (authToken) {
  fs.writeFileSync(this.tokenFile, authToken.value);
  console.log('凭证已保存,后续测试将自动使用');
}

returntrue;

}

async useCachedToken(page, token) {
// 使用缓存的token设置cookie
await page.context().addCookies([{
name: 'auth_token',
value: token,
domain: 'your-domain.com',
path: '/'
}]);

// 刷新页面使cookie生效
await page.reload();

}
}

// 使用示例
const handler = new CaptchaHandler();
await handler.handleCaptcha(page);
await page.goto('https://your-app.com/dashboard');
优点:平衡了自动化与可靠性,只需人工介入一次。
缺点:凭证过期后需要重新人工处理。

方案三:全自动方案——第三方OCR服务
当需要完全自动化且验证码不算太复杂时,可以考虑OCR方案。

const axios = require('axios');
const fs = require('fs');

asyncfunction solveCaptchaWithOCR(page) {
// 1. 定位并截图验证码元素
const captchaElement = await page.$('.captcha-image');
const screenshotPath = 'captcha.png';
await captchaElement.screenshot({ path: screenshotPath });

// 2. 读取图片并编码为base64
const imageBuffer = fs.readFileSync(screenshotPath);
const base64Image = imageBuffer.toString('base64');

try {
// 3. 调用OCR API(这里以2Captcha为例,需注册获取API key)
const apiKey = process.env.CAPTCHA_API_KEY;
const response = await axios.post('https://2captcha.com/in.php', {
key: apiKey,
method: 'base64',
body: base64Image,
json: 1
});

const requestId = response.data.request;

// 4. 轮询获取结果
let result = null;
for (let i = 0; i < 30; i++) {
  await page.waitForTimeout(2000);
  
  const checkResponse = await axios.get(
    `https://2captcha.com/res.php?key=${apiKey}&action=get&id=${requestId}&json=1`
  );
  
  if (checkResponse.data.status === 1) {
    result = checkResponse.data.request;
    break;
  }
}

if (!result) thrownewError('OCR识别超时');

// 5. 输入识别结果
await page.fill('#captcha-input', result);
await page.click('#submit-btn');

returntrue;

} catch (error) {
console.error('OCR识别失败:', error.message);
// 失败时保存截图供后续分析
fs.renameSync(screenshotPath, failed_${Date.now()}.png);
returnfalse;
}
}
费用提示:2Captcha每1000次识别约$1-3,具体看复杂度。对于大型测试套件,这是笔不小的开销。

方案四:智能等待与重试机制
有时候验证码的出现是有条件的,可以通过优化测试逻辑来减少触发。

async function smartLogin(page, username, password) {
const MAX_RETRIES = 3;

for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
await page.goto('https://example.com/login');

  // 填写登录表单
  await page.fill('#username', username);
  await page.fill('#password', password);
  
  // 检测验证码是否出现
  const captchaVisible = await page.isVisible('.captcha-container');
  
  if (captchaVisible) {
    console.log(`第${attempt}次尝试出现验证码,尝试绕过...`);
    
    // 尝试刷新验证码(有时新的验证码更简单)
    await page.click('.refresh-captcha');
    await page.waitForTimeout(1000);
    
    // 这里可以集成上述的任一解决方案
    // await solveCaptchaWithOCR(page);
    
    // 或者使用备用账号
    if (attempt > 1) {
      await page.fill('#username', `${username}_backup`);
    }
  }
  
  // 提交登录
  await page.click('#login-btn');
  
  // 等待登录成功标志
  await page.waitForSelector('.user-dashboard', { timeout: 5000 });
  
  console.log('登录成功!');
  returntrue;
  
} catch (error) {
  console.log(`第${attempt}次登录尝试失败: ${error.message}`);
  
  if (attempt === MAX_RETRIES) {
    thrownewError(`登录失败,已重试${MAX_RETRIES}次`);
  }
  
  // 等待一段时间后重试
  await page.waitForTimeout(2000);
}

}
}
方案五:针对特定类型验证码的专项处理
滑动验证码处理
async function handleSlideCaptcha(page) {
const slider = await page.$('.slider');
const sliderBox = await slider.boundingBox();
const target = await page.$('.slider-target');
const targetBox = await target.boundingBox();

// 计算需要滑动的距离
const slideDistance = targetBox.x - sliderBox.x;

// 模拟人类滑动(先快后慢)
await slider.hover();
await page.mouse.down();

// 分段滑动,模拟真实轨迹
const steps = 10;
const stepDistance = slideDistance / steps;

for (let i = 0; i < steps; i++) {
// 越靠近目标越慢
const speed = 50 + Math.random() * 100 - i * 10;
await page.mouse.move(
sliderBox.x + stepDistance * (i + 1),
sliderBox.y + (Math.random() * 10 - 5), // 加入微小垂直抖动
{ steps: 1 }
);
await page.waitForTimeout(speed);
}

await page.mouse.up();
}
点选文字验证码(简单版)
async function handleClickCaptcha(page) {
// 获取需要点击的文字
const promptText = await page.$eval('.captcha-prompt', el => el.textContent);
const wordsToClick = promptText.match(/点击【(.*?)】/)[1].split('');

// 获取所有可点击的文字元素
const charElements = await page.$$('.captcha-char');

for (const char of wordsToClick) {
for (const element of charElements) {
const text = await element.textContent();
if (text === char) {
// 随机延迟后点击,模拟人类反应时间
await page.waitForTimeout(300 + Math.random() * 500);
await element.click();
break;
}
}
}
}
最佳实践建议
根据我们项目的经验,我推荐以下策略:

分层处理策略:

// 策略优先级:禁用 > 缓存 > OCR > 重试
class CaptchaStrategy {
async solve(page) {
if (awaitthis.tryDisableCaptcha(page)) return;
if (awaitthis.tryCachedToken(page)) return;
if (awaitthis.tryOCR(page)) return;
if (awaitthis.tryAlternativeAccount(page)) return;

// 最后手段:标记测试失败并保存截图
awaitthis.saveDebugInfo(page);
thrownewError('无法处理验证码');

}
}
验证码监控:

// 记录验证码出现频率,用于优化测试策略
const captchaStats = {
totalAttempts: 0,
captchaShown: 0,
successRate: 0,
lastCaptchaTime: null
};
环境感知配置:

// 根据环境选择不同策略
const CAPTCHA_CONFIG = {
development: { strategy: 'disable' },
staging: { strategy: 'cached_token' },
production: { strategy: 'mixed', fallback: 'ocr' }
};
总结
验证码的处理没有一劳永逸的方案,但通过组合策略,我们基本能保证自动化测试的稳定性。我们团队目前的方案是:测试环境完全禁用,预发环境使用缓存令牌,只有少量的生产环境监控脚本会使用OCR服务。

最后提醒一点:尊重网站的验证码机制。这些措施旨在提升测试效率,而不是滥用或攻击服务。对于特别复杂的验证码(如行为验证),与其花费大量精力破解,不如考虑与开发团队协商,为自动化测试提供专门的测试接口或令牌。

如果你有更好的验证码处理方案,欢迎在评论区分享——毕竟,每个项目的验证码实现都可能不一样,多交流才能少踩坑。

推荐学习
自动化智能体与测试用例生成课程,限时免费,机会难得。扫码报名,参与直播,希望您在这场课程中收获满满,开启智能自动化测试的新篇章!

image

posted @ 2026-01-15 14:22  霍格沃兹测试开发学社  阅读(0)  评论(0)    收藏  举报