DevOps & Scaling

GitHub Actions + CaptchaAI:CI/CD 验证码测试

流水线跑到验证码保护的登录页或表单页,要么卡死,要么直接标红——这是很多团队 GitHub Actions 里绕不开的坑。解法并不复杂:让 CaptchaAI 的 API 替测试脚本把验证码解出来,拿到 token 后继续走正常的断言逻辑,全程不需要人工点验证码。

下面这套 workflow 配置已经在生产环境跑了一段时间,可以直接拿去改成自己的测试套件。


第一步:配置 GitHub Actions 工作流

触发条件

先定义触发条件:push 到 main、每个 PR,以及每周一早上 6 点的定时任务。定时任务确保测试不依赖某次代码变更触发。

Workflow 文件

# .github/workflows/captcha-tests.yml
name: CAPTCHA Integration Tests

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:

    - cron: "0 6 * * 1"  # Weekly Monday 6 AM

jobs:
  captcha-tests:
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:

      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install requests pytest

      - name: Run CAPTCHA integration tests
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
        run: pytest tests/test_captcha.py -v --tb=short

自建 runner 部署在国内网络环境的话,pip install 可以加镜像参数,比如 -i https://pypi.tuna.tsinghua.edu.cn/simple,省下排队下载的时间;官方托管 runner 在海外,通常不需要这一步。


第二步:把 API Key 存成 GitHub Secret

为什么用 Secret

密钥不能出现在代码或日志里,走仓库的 Secret 功能:

  1. 进入 Settings → Secrets and variables → Actions
  2. 点击 New repository secret
  3. Name 填 CAPTCHAAI_KEY
  4. Value 填你的 CaptchaAI API Key
  5. 点击 Add secret

第三步:编写验证码测试脚本

脚本要做什么

测试脚本做三件事:提交任务、轮询结果、校验 token。solve_recaptcha() 把提交和轮询封装成一个函数,超时上限 90 秒;三个测试用例分别覆盖识别流程本身、账户余额是否够用、以及 API Key 是否有效——余额和密钥检查跑得快,值得每次 PR 都执行:

# tests/test_captcha.py
import os
import time
import pytest
import requests


API_KEY = os.environ.get("CAPTCHAAI_KEY")
BASE_URL = "https://ocr.captchaai.com"


def solve_recaptcha(site_key, page_url, timeout=90):
    """Solve reCAPTCHA v2 via CaptchaAI."""
    resp = requests.post(f"{BASE_URL}/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": site_key,
        "pageurl": page_url,
        "json": 1,
    }, timeout=30)
    result = resp.json()
    assert result.get("status") == 1, f"Submit failed: {result}"

    task_id = result["request"]
    start = time.time()

    while time.time() - start < timeout:
        time.sleep(5)
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        }, timeout=15)
        data = resp.json()
        if data["request"] != "CAPCHA_NOT_READY":
            assert data.get("status") == 1, f"Solve failed: {data}"
            return data["request"]

    pytest.fail("CAPTCHA solve timed out")


@pytest.mark.skipif(not API_KEY, reason="CAPTCHAAI_KEY not set")
class TestCaptchaIntegration:
    """Integration tests for CAPTCHA-protected flows."""

    def test_recaptcha_v2_solve(self):
        """Verify CaptchaAI can solve reCAPTCHA v2."""
        token = solve_recaptcha(
            site_key="6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
            page_url="https://www.google.com/recaptcha/api2/demo",
        )
        assert len(token) > 100
        assert token.isascii()

    def test_balance_sufficient(self):
        """Ensure account balance is enough for test suite."""
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "getbalance",
            "json": 1,
        })
        balance = float(resp.json()["request"])
        assert balance > 0.50, f"Low balance: ${balance}"

    def test_api_key_valid(self):
        """Verify API key is accepted."""
        resp = requests.get(f"{BASE_URL}/res.php", params={
            "key": API_KEY,
            "action": "getbalance",
            "json": 1,
        })
        result = resp.json()
        assert result.get("status") == 1, f"Invalid key: {result}"

矩阵测试:一次覆盖多种验证码类型

常见组合:

  • reCAPTCHA v2
  • Turnstile
  • 图片验证码

不用写三份重复的 workflow,用 strategy.matrix 并行跑就行,fail-fast: false 保证一种类型失败不会拖垮其他类型的测试:

jobs:
  captcha-matrix:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        captcha-type: [recaptcha-v2, turnstile, image]
      fail-fast: false

    steps:

      - uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: pip install requests pytest

      - name: Run ${{ matrix.captcha-type }} tests
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
          CAPTCHA_TYPE: ${{ matrix.captcha-type }}
        run: pytest tests/test_${{ matrix.captcha-type }}.py -v

缓存测试结果,减少重复识别

验证码识别是要花钱的操作,测试代码没变就没必要每次都重新解一遍。用 actions/cache 把上一次跑通的标记缓存下来,命中就跳过:


      - name: Cache test results
        uses: actions/cache@v4
        with:
          path: .test-cache
          key: captcha-tests-${{ hashFiles('tests/**') }}

      - name: Skip if cached
        id: check-cache
        run: |
          if [ -f .test-cache/passed ]; then
            echo "skip=true" >> $GITHUB_OUTPUT
          fi

      - name: Run tests
        if: steps.check-cache.outputs.skip != 'true'
        env:
          CAPTCHAAI_KEY: ${{ secrets.CAPTCHAAI_KEY }}
        run: |
          pytest tests/test_captcha.py -v
          mkdir -p .test-cache && touch .test-cache/passed

测试失败时自动推送 Slack 通知

流水线红了,与其挨个翻日志,不如直接把失败原因和 run 链接推到 Slack 群里,团队第一时间就能看到:


      - name: Notify on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "CAPTCHA tests failed on ${{ github.ref }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "CAPTCHA tests *failed* on `${{ github.ref }}`\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View run>"
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

常见问题

在 CI 里跑验证码测试要花多少钱?

一次跑 3–5 个验证码的测试套件,单次运行成本大约 $0.01–0.03。如果只做每周一次的定时全量测试,一个月的识别成本大概在 $0.10–0.15;日常 PR 只做余额和密钥校验,几乎不产生识别费用。

GitHub Actions 的 runner 访问 Google reCAPTCHA 域名会不会超时?

官方托管 runner 一般不会,它们本身在海外网络里。自建 runner 部署在国内的话延迟会更高,建议把测试超时适当放宽,或把验证码识别这类任务放到官方托管 runner 上跑。

验证码测试失败,先查密钥还是查网络?

先看 test_api_key_valid:这个用例失败说明是密钥问题,跟网络无关;如果只有 test_recaptcha_v2_solve 超时而余额、密钥两项都通过,多半是网络延迟或识别耗时波动,加大 timeout 再重跑一次即可。

可以搬到 GitLab CI 或 CircleCI 上用吗?

可以。Python 测试代码本身跟 CI 平台无关,GitLab CI、CircleCI、Jenkins 都能直接复用,需要改的只是 workflow 的 YAML 语法,测试逻辑一行都不用动。


常见故障排查

问题 原因 处理方式
测试跳过,提示 “CAPTCHAAI_KEY 未设置” 仓库 Secret 没配 在 Settings 里补上 CAPTCHAAI_KEY
CI 里超时,本地却能跑通 Runner 网络延迟高于本地 把 timeout 调到 120 秒左右
余额检查失败 Secret 值里混入了空格或换行 重新粘贴一遍密钥,去掉首尾空白
工作流从不触发 分支或 trigger 配置写错 检查 YAML 里的 on:

官方托管 runner 在海外,访问 Google 的 reCAPTCHA 服务通常没有障碍;自建 runner 若机房在国内,访问境外域名的延迟会明显更高,甚至偶发超时——这正是上表第二行最常见的成因,先加大 timeout,再排查密钥或余额。


相关指南


把验证码测试交给自动化,流水线才能一直保持绿色——立即使用 CaptchaAI,接入你的 CI/CD。

该文章已禁用评论。