集成指南

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

相关指南:

该文章已禁用评论。