Use Cases

QA 自动化测试中的验证码识别方案

验证码是 QA 流水线里最容易被忽略的阻塞点:注册、登录或下单流程一旦挂了 reCAPTCHA,回归测试往往靠人肉点验证码,或者干脆跳过。把 CaptchaAI 接入测试脚本,就能让这类流程重新纳入自动化测试范围,不用改动被测页面本身。


哪些 QA 场景离不开验证码自动识别

下面几类测试最常被验证码卡住,也是接入 CaptchaAI 收益最明显的场景:

  • 回归测试——确认部署后表单依然可用
  • 端到端测试——跑通完整用户旅程
  • 负载测试——大规模模拟真实验证码流量
  • 跨浏览器测试——验证组件在不同浏览器下正常渲染
  • 无障碍测试——覆盖残障用户的替代交互流程

举例:某跨境电商团队的预发布环境同时挂载 reCAPTCHA v2(海外用户)和 GeeTest(极验,国内落地页)。reCAPTCHA 依赖 Google 托管脚本,境内网络访问常超时,团队因此把用例拆进独立 CI job。


接入前先算清楚:验证码测试的成本怎么控制

验证码识别本身有成本,套件一大容易被忽略。开始写代码前先理清控制点:

  1. 用 staging 环境测试——难度通常更低
  2. 按计划运行,而非每次 push——降低调用成本
  3. 确认 flaky 的用例缓存结果——避免重复识别
  4. 用环境变量本地跳过——节省开发成本
  5. 验证码用例集中放进专用 CI job——单独控制成本

这些手段可以叠加——先按计划跑,稳定后再引入缓存和标签拆分。


用 Pytest 封装可复用的验证码识别逻辑

把验证码识别逻辑封装成 helper 类,测试用例只管拿 token。CaptchaTestHelper 通过 in.php / res.php 完成提交与轮询,再用 fixture 注入测试类:

import pytest
import requests
import time


class CaptchaTestHelper:
    """Helper for solving CAPTCHAs in test environments."""

    def __init__(self, api_key):
        self.api_key = api_key

    def solve_recaptcha(self, sitekey, pageurl, timeout=120):
        """Solve reCAPTCHA and return token."""
        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "userrecaptcha",
            "googlekey": sitekey,
            "pageurl": pageurl,
            "json": 1,
        }, timeout=30)
        result = resp.json()
        assert result.get("status") == 1, f"Submit failed: {result}"
        task_id = result["request"]

        deadline = time.time() + timeout
        time.sleep(10)

        while time.time() < deadline:
            resp = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key, "action": "get",
                "id": task_id, "json": 1,
            }, timeout=15)
            data = resp.json()

            if data.get("status") == 1:
                return data["request"]
            if data["request"] != "CAPCHA_NOT_READY":
                raise RuntimeError(f"Solve error: {data['request']}")
            time.sleep(5)

        raise TimeoutError("CAPTCHA solve timeout")


@pytest.fixture(scope="session")
def captcha_helper():
    """Provide CaptchaAI helper for test session."""
    import os
    api_key = os.environ.get("CAPTCHAAI_API_KEY")
    if not api_key:
        pytest.skip("CAPTCHAAI_API_KEY not set")
    return CaptchaTestHelper(api_key)


class TestLoginFlow:
    """Test login flow behind reCAPTCHA."""

    SITEKEY = "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
    LOGIN_URL = "https://staging.example.com/qa-login"

    def test_login_with_valid_credentials(self, captcha_helper):
        """Verify login succeeds with valid creds and solved CAPTCHA."""
        token = captcha_helper.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)
        assert token and len(token) > 100

        resp = requests.post(self.LOGIN_URL, data={
            "username": "test_user",
            "password": "test_pass",
            "g-recaptcha-response": token,
        })
        assert resp.status_code == 200
        assert "Welcome" in resp.text

    def test_login_with_invalid_credentials(self, captcha_helper):
        """Verify login fails gracefully with bad creds but valid CAPTCHA."""
        token = captcha_helper.solve_recaptcha(self.SITEKEY, self.LOGIN_URL)

        resp = requests.post(self.LOGIN_URL, data={
            "username": "wrong_user",
            "password": "wrong_pass",
            "g-recaptcha-response": token,
        })
        assert resp.status_code in (200, 401)
        assert "Invalid" in resp.text or "error" in resp.text.lower()

    def test_login_without_captcha_fails(self):
        """Verify login rejects submissions without CAPTCHA."""
        resp = requests.post(self.LOGIN_URL, data={
            "username": "test_user",
            "password": "test_pass",
        })
        assert resp.status_code in (400, 403, 422)

TestLoginFlow 覆盖三种情况:凭证和验证码都对、验证码对但凭证错、缺验证码字段,确认登录逻辑和验证码校验互不干扰。


用 Selenium 跑通带验证码的端到端流程

端到端测试面对真实渲染的验证码组件。下面的模式从 DOM 读取 data-sitekey,调用 CaptchaAI 解决后把 token 写回表单,再触发 data-callback

import pytest
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


@pytest.fixture
def browser():
    """Create browser for testing."""
    options = webdriver.ChromeOptions()
    options.add_argument("--window-size=1920,1080")
    driver = webdriver.Chrome(options=options)
    yield driver
    driver.quit()


class TestRegistrationFlow:
    """Test registration form with CAPTCHA."""

    REG_URL = "https://staging.example.com/register"

    def test_registration_form_submits(self, browser, captcha_helper):
        """Full registration flow with CAPTCHA solving."""
        browser.get(self.REG_URL)

        # Fill form
        browser.find_element(By.ID, "email").send_keys("[email protected]")
        browser.find_element(By.ID, "password").send_keys("SecurePass123!")
        browser.find_element(By.ID, "confirm_password").send_keys("SecurePass123!")

        # Extract sitekey from page
        captcha_div = browser.find_element(By.CSS_SELECTOR, ".g-recaptcha")
        sitekey = captcha_div.get_attribute("data-sitekey")

        # Solve via API
        token = captcha_helper.solve_recaptcha(sitekey, browser.current_url)

        # Inject token
        browser.execute_script("""
            document.querySelector('[name="g-recaptcha-response"]').value = arguments[0];
        """, token)

        # Trigger callback if needed
        callback = captcha_div.get_attribute("data-callback")
        if callback:
            browser.execute_script(f"window['{callback}'](arguments[0]);", token)

        # Submit
        browser.find_element(By.CSS_SELECTOR, "button[type=submit]").click()

        # Verify success
        WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".success-message"))
        )

    def test_captcha_renders_on_page(self, browser):
        """Verify CAPTCHA widget loads on registration page."""
        browser.get(self.REG_URL)
        captcha = WebDriverWait(browser, 10).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".g-recaptcha, iframe[src*='recaptcha']"))
        )
        assert captcha.is_displayed()

test_captcha_renders_on_page 单独验证组件能否加载——先挂了就是页面问题,不用怀疑识别逻辑。


给验证码测试单独打标签,控制流水线范围

验证码识别有网络往返,耗时几秒到几十秒,混进主干套件会拖慢反馈。用 pytest 自定义 marker 把用例单独标出来:

# conftest.py
import os

# Mark tests that need CAPTCHA solving
def pytest_configure(config):
    config.addinivalue_line(
        "markers", "captcha: tests requiring CAPTCHA solving (may be slow)"
    )


# pytest.ini or pyproject.toml
"""
[tool.pytest.ini_options]
markers = [
    "captcha: tests requiring CAPTCHA solving (may be slow)",
]
"""

只跑验证码用例:

pytest -m captcha -v

跳过验证码,走快速通道:

pytest -m "not captcha" -v

接入前的落地检查

接入 CaptchaAI 前,建议先自查这几项:

检查项 目的
Helper 是否用 fixture 注入 减少重复轮询代码
是否用 marker 分组 主干套件不被拖慢
单元测试是否已 mock 验证码 识别只留给集成/端到端层
是否确认 staging 验证码难度 避免与生产环境不一致

确认后再合入主干 CI。


常见问题

运行验证码测试大概要花多少钱?

reCAPTCHA v2 单次识别约 $0.003,50 个用例的套件跑一次约 $0.15,按每天一次算,月成本约 $4.50,可直接编进 CI 预算。

验证码识别超时或失败,会不会拖出一堆"假失败"的用例?

会,这是常见的 flaky 来源。建议设置合理超时(示例 120 秒),失败时抛明确错误,并对确认是网络抖动的用例缓存结果。

要不要给验证码测试单独建一条 CI 流水线?

建议单独建:验证码识别耗时比单元测试高一个量级。用 captcha marker 分组,主干只跑 not captcha,验证码用例走独立 job。

单元测试该不该直接调用验证码识别 API?

不建议:单元测试应 mock 验证码返回值;只有集成测试和端到端测试对接真实环境时,才需要调用 CaptchaAI 拿真实 token。

预发布环境里同时有 reCAPTCHA 和 GeeTest,测试脚本要分别处理吗?

是的:reCAPTCHA v2 提交 googlekey + pageurl,GeeTest v3 提交 gtchallenge 等参数,返回字段也不同。项目里通常按类型各封装一个 helper,公用轮询逻辑。


相关指南


自动化你的 QA 流程——把 CaptchaAI 接入你的测试流水线

该文章已禁用评论。