结账测试脚本最容易卡在哪一步?答案通常是验证码——它可能出现在加购、登录、收货地址、支付、订单确认中的任意一个页面。这篇实战指南用 CaptchaAI API 演示具体怎么做:一套 Selenium 自动化框架,覆盖购物车到订单确认的完整链路,每一步先检查页面上是否有 reCAPTCHA 或 Cloudflare Turnstile,有就先识别再继续。
跨境电商 QA 团队的实际用法
跨境电商独立站测试团队的日常验收清单里,"结账链路走通"几乎是必查项——自建站或 Shopify 类站点前台大多接的是 reCAPTCHA v3 或 Cloudflare Turnstile,而不是国内站点常见的极验滑块。日常冒烟测试用 BASIC($15/月,5 线程)就够;大促前要并行跑多个国家的结账用例,升级到 STANDARD($30/月,15 线程)即可——解决次数不限,不用按次额外付费。
结账流程里,验证码通常卡在哪几个页面
- 加入购物车 — reCAPTCHA v3(不可见),防机器人下单
- 登录 / 访客结账 — reCAPTCHA v2,账号校验
- 收货地址 — Cloudflare Turnstile,限流保护
- 支付页面 — reCAPTCHA v2 / v3,防欺诈
- 订单确认 — reCAPTCHA v2,最终校验
触发条件常和请求频率、Cookie 状态挂钩,同一站点也可能今天没有、明天就多一道。因此"每步都检测"比只测已知环节更稳妥。
整体架构:五个页面,一次统一的检测动作
┌────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ Cart │───▶│ Shipping │───▶│ Payment │───▶│ Review │───▶│ Confirm │
│ │ │ Address │ │ Info │ │ Order │ │ │
└────────┘ └───────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
[CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?] [CAPTCHA?]
核心代码实现
自动化结账执行器
思路很直接:CaptchaSolver 负责调 in.php 提交任务、轮询 res.php 拿结果;CheckoutAutomator 每次进入新页面前先调用 _solve_if_captcha(),识别到 Turnstile 就写入 cf-turnstile-response 字段,识别到 reCAPTCHA 就写入 g-recaptcha-response 字段——字段名必须和页面表单一致。
import time
import requests
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
class CaptchaSolver:
BASE = "https://ocr.captchaai.com"
def __init__(self, api_key):
self.api_key = api_key
def solve(self, params, initial_wait=10):
params["key"] = self.api_key
params["json"] = 1
resp = requests.post(f"{self.BASE}/in.php", data=params).json()
if resp["status"] != 1:
raise Exception(resp["request"])
task_id = resp["request"]
time.sleep(initial_wait)
for _ in range(60):
result = requests.get(
f"{self.BASE}/res.php",
params={"key": self.api_key, "action": "get", "id": task_id, "json": 1},
).json()
if result["request"] == "CAPCHA_NOT_READY":
time.sleep(5)
continue
if result["status"] == 1:
return result["request"]
raise Exception(result["request"])
raise TimeoutError("Timed out")
class CheckoutAutomator:
def __init__(self, api_key):
self.solver = CaptchaSolver(api_key)
self.driver = webdriver.Chrome()
self.wait = WebDriverWait(self.driver, 15)
def _find(self, selector):
return self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
def _click(self, selector):
self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, selector))).click()
def _fill(self, selector, value):
el = self._find(selector)
el.clear()
el.send_keys(value)
def _solve_if_captcha(self):
"""Check for and solve any CAPTCHA on the current page."""
html = self.driver.page_source
page_url = self.driver.current_url
# Turnstile
turnstile = self.driver.find_elements(By.CSS_SELECTOR, ".cf-turnstile")
if turnstile:
sitekey = turnstile[0].get_attribute("data-sitekey")
token = self.solver.solve({
"method": "turnstile",
"sitekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=cf-turnstile-response]").value = "{token}";'
)
return "turnstile"
# reCAPTCHA
recaptcha = self.driver.find_elements(By.CSS_SELECTOR, "[data-sitekey]")
if recaptcha and "recaptcha" in html.lower():
sitekey = recaptcha[0].get_attribute("data-sitekey")
# Check if v3 (invisible)
if "grecaptcha.execute" in html:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
"version": "v3",
"action": "checkout",
}, initial_wait=20)
else:
token = self.solver.solve({
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": page_url,
})
self.driver.execute_script(
f'document.querySelector("[name=g-recaptcha-response]").value = "{token}";'
)
return "recaptcha"
return None
# --- Checkout Steps ---
def add_to_cart(self, product_url, quantity=1):
"""Step 1: Navigate to product and add to cart."""
self.driver.get(product_url)
time.sleep(2)
# Set quantity if field exists
qty_fields = self.driver.find_elements(By.CSS_SELECTOR, "input[name='quantity']")
if qty_fields:
qty_fields[0].clear()
qty_fields[0].send_keys(str(quantity))
self._solve_if_captcha()
self._click("[data-action='add-to-cart'], .add-to-cart, #add-to-cart")
time.sleep(2)
return True
def fill_shipping(self, address):
"""Step 2: Fill shipping address."""
self._solve_if_captcha()
field_map = {
"first_name": "#shipping-first-name, [name='firstName']",
"last_name": "#shipping-last-name, [name='lastName']",
"address": "#shipping-address, [name='address1']",
"city": "#shipping-city, [name='city']",
"state": "#shipping-state, [name='state']",
"zip": "#shipping-zip, [name='postalCode']",
"phone": "#shipping-phone, [name='phone']",
}
for field, selectors in field_map.items():
if field in address:
for selector in selectors.split(", "):
elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
if elements:
elements[0].clear()
elements[0].send_keys(address[field])
break
self._solve_if_captcha()
self._click("[data-step='shipping-submit'], .continue-to-payment")
time.sleep(2)
return True
def fill_payment(self, payment):
"""Step 3: Fill payment information."""
self._solve_if_captcha()
# Handle iframe for card fields (common pattern)
iframes = self.driver.find_elements(By.CSS_SELECTOR, "iframe[name*='card']")
if iframes:
self.driver.switch_to.frame(iframes[0])
self._fill("input[name='cardnumber']", payment["card_number"])
self.driver.switch_to.default_content()
if len(iframes) > 1:
self.driver.switch_to.frame(iframes[1])
self._fill("input[name='exp-date']", payment["expiry"])
self.driver.switch_to.default_content()
if len(iframes) > 2:
self.driver.switch_to.frame(iframes[2])
self._fill("input[name='cvc']", payment["cvv"])
self.driver.switch_to.default_content()
else:
# Direct fields
self._fill("[name='cardNumber'], #card-number", payment["card_number"])
self._fill("[name='expiry'], #card-expiry", payment["expiry"])
self._fill("[name='cvv'], #card-cvv", payment["cvv"])
self._solve_if_captcha()
self._click("[data-step='payment-submit'], .continue-to-review")
time.sleep(2)
return True
def confirm_order(self):
"""Step 4: Review and confirm order."""
self._solve_if_captcha()
self._click("[data-step='confirm'], .place-order, #place-order")
time.sleep(3)
# Check for confirmation
html = self.driver.page_source.lower()
if "order confirmed" in html or "thank you" in html or "confirmation" in html:
return True
return False
def run_checkout(self, product_url, address, payment, quantity=1):
"""Run complete checkout flow."""
steps = [
("Add to cart", lambda: self.add_to_cart(product_url, quantity)),
("Proceed to checkout", lambda: self._click(".checkout-btn, a[href*='checkout']")),
("Fill shipping", lambda: self.fill_shipping(address)),
("Fill payment", lambda: self.fill_payment(payment)),
("Confirm order", lambda: self.confirm_order()),
]
for step_name, step_fn in steps:
print(f"Step: {step_name}...")
try:
result = step_fn()
print(f" ✓ {step_name} complete")
except Exception as e:
print(f" ✗ {step_name} failed: {e}")
return False
return True
def close(self):
self.driver.quit()
完整调用示例
把 CheckoutAutomator 接入测试用例时,只需要传商品链接、收货地址和测试卡号,run_checkout() 会自动跑完加购到确认订单五步,每一步内部已经带了验证码检测:
automator = CheckoutAutomator("YOUR_API_KEY")
try:
success = automator.run_checkout(
product_url="https://store.example.com/product/widget-pro",
address={
"first_name": "Jane",
"last_name": "Smith",
"address": "123 Test Street",
"city": "San Francisco",
"state": "CA",
"zip": "94102",
"phone": "415-555-0100",
},
payment={
"card_number": "4111111111111111",
"expiry": "12/26",
"cvv": "123",
},
quantity=1,
)
print(f"Checkout {'succeeded' if success else 'failed'}")
finally:
automator.close()
常见问题
结账自动化测试会不会被平台风控标记为异常流量?
这取决于脚本本身的请求频率和 Cookie 一致性,和 CaptchaAI 的验证码识别环节无关。用真实浏览器内核、保持合理操作间隔即可。
一页式结账(one-page checkout)也要逐页检测吗?
不用,填完一页所有字段后,在最终提交前调用一次 _solve_if_captcha() 即可。
支付页 token 突然失效导致提交失败怎么办?
通常是识别完 token 后隔太久才提交。把 _solve_if_captcha() 挪到点击提交前的最后一步,中间不要再有其他等待。
结账流程里出现 GeeTest 验证码也能处理吗?
可以,CaptchaAI 支持 GeeTest v3(geetest 方法),逻辑和 Turnstile/reCAPTCHA 分支类似:识别挑战后把结果写回对应字段。
常见边缘场景处理
真实结账流程不会总是"一路绿灯",下面三种情况在测试中最常遇到。
校验失败后重新弹出验证码
有些站点在表单校验不通过时会重新渲染验证码,直接重试整个步骤即可——_solve_if_captcha() 会在重试里再跑一次:
def retry_step_with_captcha(self, step_fn, max_retries=2):
for attempt in range(max_retries + 1):
try:
return step_fn()
except Exception:
if attempt < max_retries:
self._solve_if_captcha()
continue
raise
只在会话首次请求出现的验证码
有些网站只在会话的第一次请求上出现验证码。用同一个 Selenium 会话贯穿整个流程,不要在步骤之间重启浏览器或清空 cookie。
动态渲染的表单字段
结账页面常用前端框架异步渲染表单。填表前先等一个确定会出现的元素加载完成,比固定 sleep 更可靠:
def wait_for_step(self, indicator_selector, timeout=15):
"""Wait for a step to fully load before proceeding."""
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.CSS_SELECTOR, indicator_selector))
)
常见故障排查
跑通整套流程后,下面这几类报错出现频率最高:
- 支付页验证码 token 被拒绝 — 原因:token 已过期;处理方式:提交前最后一刻再解决验证码。
- 卡号字段填不进去 — 原因:Stripe/Braintree 用 iframe 承载卡号输入框;处理方式:填充前先切换到对应 iframe。
- 结账页跳转回登录页 — 原因:会话已过期;处理方式:结账前补登录步骤,或检测到跳转后重新登录。
- 订单提交后没反应也不报错 — 原因:必填字段缺失;处理方式:每步之后检查页面错误提示。
相关指南
结账自动化测试——用 CaptchaAI 处理验证码。