Integrations

使用 Espresso 和 CaptchaAI 进行 Android CAPTCHA 测试

使用 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 密钥并设置求解器后端。

相关指南:

该文章已禁用评论。

相关文章

Use Cases 使用验证码处理自动提交表单
使用验证码处理自动提交表单的实用指南,包含现实场景、工作流程建议以及使用 Captcha AI 的可行步骤。

使用验证码处理自动提交表单的实用指南,包含现实场景、工作流程建议以及使用 Captcha AI 的可行步骤。

Apr 20, 2026
Use Cases 持续集成测试中的验证码处理
持续集成测试中的验证码处理实用指南,包含现实场景、工作流程建议和使用 Captcha AI 的可操作步骤。

持续集成测试中的验证码处理实用指南,包含现实场景、工作流程建议和使用 Captcha AI 的可操作步骤。

May 03, 2026
Use Cases 注册流程测试中的验证码处理
注册流程测试中的验证码处理实用指南,包含现实场景、工作流程建议以及使用 Captcha AI 的可操作步骤。

注册流程测试中的验证码处理实用指南,包含现实场景、工作流程建议以及使用 Captcha AI 的可操作步骤。

May 04, 2026