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

举报
霍格沃兹测试开发学社 发表于 2026/01/15 14:45:41 2026/01/15
【摘要】 验证码(CAPTCHA)一直是自动化测试中最让人头疼的环节之一。每次碰到那些扭曲的文字、点选图片的挑战,自动化脚本就像撞上了一堵墙。我负责的电商项目最近就卡在了登录自动化这个环节——那个该死的滑动验证码让我们的回归测试屡屡失败。经过几周的实战踩坑和方案对比,我总结出几种用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%稳定,执行速度快。
缺点:仅限测试环境,生产环境模拟不了完整流程。


方案二:半自动方案——人工介入一次,重复使用凭证

对于无法关闭验证码但又不频繁变更的场景,这个方案很实用。

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,
      json1
    });
    
    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', { timeout5000 });
      
      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), // 加入微小垂直抖动
      { steps1 }
    );
    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;
      }
    }
  }
}

最佳实践建议

根据我们项目的经验,我推荐以下策略:

  1. 分层处理策略

    // 策略优先级:禁用 > 缓存 > 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('无法处理验证码');
      }
    }
  2. 验证码监控

    // 记录验证码出现频率,用于优化测试策略
    const captchaStats = {
      totalAttempts0,
      captchaShown0,
      successRate0,
      lastCaptchaTimenull
    };
  3. 环境感知配置

    // 根据环境选择不同策略
    const CAPTCHA_CONFIG = {
      development: { strategy'disable' },
      staging: { strategy'cached_token' },
      production: { strategy'mixed'fallback'ocr' }
    };

总结

验证码的处理没有一劳永逸的方案,但通过组合策略,我们基本能保证自动化测试的稳定性。我们团队目前的方案是:测试环境完全禁用,预发环境使用缓存令牌,只有少量的生产环境监控脚本会使用OCR服务。

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

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

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。