流水线跑到验证码保护的登录页或表单页,要么卡死,要么直接标红——这是很多团队 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 功能:
- 进入 Settings → Secrets and variables → Actions
- 点击 New repository secret
- Name 填
CAPTCHAAI_KEY - Value 填你的 CaptchaAI API Key
- 点击 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。