使用 Espresso 构建的 Android UI 测试经常会遇到 WebView 中的验证码 - 登录页面、注册表单或显示 reCAPTCHA v2 的嵌入式支付流程。CaptchaAI提供编程解决方案,以便您的自动化测试套件可以端到端运行,而无需手动验证码交互。
本指南展示了如何在 Espresso 测试期间检测 Android WebView 中的验证码,通过后端服务解决它们,并将token 提交回页面。
真实场景
您的 Android 应用程序在 WebView 中加载第三方结账页面。允许付款前页面会显示reCAPTCHA v2。在 Espresso 仪器测试期间,此 CAPTCHA 会阻止结帐验证测试。
环境: Android Studio、Kotlin、Espresso、AndroidX 测试、CaptchaAI API、Python 后端。
第 1 步:在应用程序中创建测试助手
添加一个仅调试帮助器,可以在应用程序的 WebView 内评估 JavaScript:
// CaptchaTestHelper.kt — debug source set only
package com.example.app.testing
import android.webkit.JavascriptInterface
import android.webkit.WebView
import kotlinx.coroutines.*
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
class CaptchaTestHelper(private val webView: WebView) {
private var detectedSitekey: String? = null
private var detectedPageUrl: String? = null
private var solvedToken: String? = null
@JavascriptInterface
fun onCaptchaDetected(sitekey: String, pageurl: String) {
detectedSitekey = sitekey
detectedPageUrl = pageurl
}
fun detectCaptcha() {
webView.post {
webView.evaluateJavascript("""
(function() {
var el = document.querySelector('.g-recaptcha');
if (el) {
CaptchaHelper.onCaptchaDetected(
el.getAttribute('data-sitekey'),
window.location.href
);
return 'found';
}
return 'not_found';
})();
""", null)
}
}
suspend fun solveAndInject(): Boolean = withContext(Dispatchers.IO) {
val sitekey = detectedSitekey ?: return@withContext false
val pageurl = detectedPageUrl ?: return@withContext false
// Call backend solver
val client = OkHttpClient.Builder()
.callTimeout(java.time.Duration.ofMinutes(3))
.build()
val body = JSONObject().apply {
put("captchaType", "recaptcha_v2")
put("sitekey", sitekey)
put("pageurl", pageurl)
}.toString().toRequestBody("application/json".toMediaType())
val request = Request.Builder()
.url("http://10.0.2.2:3000/api/solve-captcha") // Host loopback for emulator
.post(body)
.build()
val response = client.newCall(request).execute()
val json = JSONObject(response.body?.string() ?: "")
val token = json.optString("token", "")
if (token.isEmpty()) return@withContext false
solvedToken = token
// Inject token on main thread
withContext(Dispatchers.Main) {
webView.evaluateJavascript("""
document.getElementById('g-recaptcha-response').value = '$token';
try {
var clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach(function(k) {
Object.keys(clients[k]).forEach(function(j) {
if (clients[k][j] && clients[k][j].callback) {
clients[k][j].callback('$token');
}
});
});
} catch(e) {}
""", null)
}
return@withContext true
}
companion object {
fun attach(webView: WebView): CaptchaTestHelper {
val helper = CaptchaTestHelper(webView)
webView.addJavascriptInterface(helper, "CaptchaHelper")
return helper
}
}
}
第2步:后端求解器服务
在测试执行期间在您的开发计算机上运行此 Python 求解器:
# android_test_solver.py
import os
import time
import requests
from flask import Flask, request, jsonify
app = Flask(__name__)
API_KEY = os.environ.get("CAPTCHAAI_API_KEY", "YOUR_API_KEY")
@app.route("/api/solve-captcha", methods=["POST"])
def solve():
data = request.json
# Submit to CaptchaAI
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": data["sitekey"],
"pageurl": data["pageurl"],
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
return jsonify({"error": result.get("request")}), 400
task_id = result["request"]
# Poll for result
for _ in range(30):
time.sleep(5)
poll = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id, "json": "1",
})
poll_result = poll.json()
if poll_result.get("status") == 1:
return jsonify({"token": poll_result["request"]})
if poll_result.get("request") != "CAPCHA_NOT_READY":
return jsonify({"error": poll_result["request"]}), 400
return jsonify({"error": "Timeout"}), 408
if __name__ == "__main__":
app.run(host="0.0.0.0", port=3000)
第 3 步:使用验证码处理进行 Espresso 测试
// CheckoutCaptchaTest.kt
package com.example.app
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.espresso.web.sugar.Web.onWebView
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class CheckoutCaptchaTest {
@get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java)
@Test
fun testCheckoutWithCaptcha() {
// Navigate to checkout
onView(withId(R.id.checkout_button)).perform(click())
// Wait for WebView to load
Thread.sleep(5000)
// Access the WebView and attach helper
activityRule.scenario.onActivity { activity ->
val webView = activity.findViewById<android.webkit.WebView>(R.id.webview)
val helper = CaptchaTestHelper.attach(webView)
helper.detectCaptcha()
// Wait for detection
Thread.sleep(2000)
// Solve and inject
runBlocking {
val solved = helper.solveAndInject()
assert(solved) { "CAPTCHA should be solved successfully" }
}
}
// Continue with form submission after token 提交
Thread.sleep(1000)
// Verify checkout completed
onView(withText("Order Confirmed")).check(
androidx.test.espresso.assertion.ViewAssertions.matches(isDisplayed())
)
}
}
故障排除
| 问题 | 原因 | 处理方式 |
|---|---|---|
10.0.2.2 无法访问 |
不使用Android模拟器 | 使用物理设备的实际主机IP; 10.0.2.2 是模拟器特定的 |
evaluateJavascript 回调为空 |
WebView 未完全加载 | 在评估之前添加 WebViewClient.onPageFinished() 侦听器 |
addJavascriptInterface 不工作 |
JavaScript 已禁用 | 致电webView.settings.javaScriptEnabled = true |
| 网络请求被明文策略阻止 | Android 9+ 上的 HTTP 到本地主机 | 在AndroidManifest.xml中添加android:usesCleartextTraffic="true"(仅调试) |
常问问题
Espresso 可以直接与 WebView 内容交互吗?
Espresso 具有用于基本 WebView 交互的 onWebView(),但它无法评估任意 JavaScript。您需要 WebView API 中的 evaluateJavascript() 来进行验证码处理。
这适用于 CI 的真实设备吗?
是的。将 10.0.2.2 替换为运行解算器后端的机器的实际 IP。确保设备可以通过网络到达后端。
如何防止测试助手交付生产?
将测试助手放入 src/debug/java/ 源集中。 Android 构建变体会自动从发布版本中排除调试源。
Android 应用程序中的 reCAPTCHA Enterprise 怎么样?
方法类似,但您需要 Enterprise sitekey,并且可能需要传递其他参数,例如 enterprise: 1 到 CaptchaAI。
相关文章
- 如何使用Api解决Recaptcha V2回调
- 构建自动化测试管道 Captchaai
- Recaptcha V2 Turnstile同一站点处理
下一步
自动化您的 Android CAPTCHA 测试 –获取您的 CaptchaAI API 密钥并设置求解器后端。
相关指南:
- 使用 XCUITest 进行 iOS 自动化验证码处理
- 使用 Appium 进行移动应用自动化中的验证码处理
- 从页面源中提取 reCAPTCHA 参数