Các thử nghiệm giao diện người dùng Android được xây dựng bằng Espresso thường gặp CAPTCHA bên trong WebView - trang đăng nhập, biểu mẫu đăng ký hoặc luồng thanh toán được nhúng hiển thị reCAPTCHA v2.CaptchaAIcung cấp giải pháp theo chương trình để bộ kiểm tra tự động của bạn có thể chạy từ đầu đến cuối mà không cần tương tác CAPTCHA thủ công.
Hướng dẫn này trình bày cách phát hiện CAPTCHA trong Android WebView trong quá trình kiểm tra Espresso, giải quyết chúng thông qua dịch vụ phụ trợ và đưa lại mã thông báo vào trang.
Kịch bản thế giới thực
Ứng dụng Android của bạn tải trang thanh toán của bên thứ ba trong WebView. Trang trình bày reCAPTCHA v2 trước khi cho phép thanh toán. Trong quá trình kiểm tra thiết bị của Espresso, CAPTCHA này sẽ chặn kiểm tra xác minh thanh toán.
Môi trường: Android Studio, Kotlin, Espresso, AndroidX Test, API CaptchaAI, chương trình phụ trợ Python.
Bước 1: Tạo Trình trợ giúp kiểm tra trong ứng dụng
Thêm trình trợ giúp chỉ gỡ lỗi có thể đánh giá JavaScript bên trong WebView của ứng dụng:
// 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
}
}
}
Bước 2: Dịch vụ bộ giải phụ trợ
Chạy bộ giải Python này trên máy phát triển của bạn trong quá trình thực hiện kiểm tra:
# 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)
Bước 3: Kiểm tra Espresso bằng cách xử lý CAPTCHA
// 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 injection
Thread.sleep(1000)
// Verify checkout completed
onView(withText("Order Confirmed")).check(
androidx.test.espresso.assertion.ViewAssertions.matches(isDisplayed())
)
}
}
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
10.0.2.2 không thể truy cập được |
Không sử dụng Trình giả lập Android | Sử dụng IP máy chủ thực tế cho các thiết bị vật lý; 10.0.2.2 dành riêng cho trình giả lập |
Cuộc gọi lại evaluateJavascript là null |
WebView chưa được tải đầy đủ | Thêm trình nghe WebViewClient.onPageFinished() trước khi đánh giá |
addJavascriptInterface không hoạt động |
Đã tắt JavaScript | Gọi webView.settings.javaScriptEnabled = true |
| Yêu cầu mạng bị chặn bởi chính sách Cleartext | HTTP sang localhost trên Android 9+ | Thêm android:usesCleartextTraffic="true" vào AndroidManifest.xml (chỉ gỡ lỗi) |
Câu hỏi thường gặp
Espresso có thể tương tác trực tiếp với nội dung WebView không?
Espresso có onWebView() cho các tương tác WebView cơ bản nhưng không thể đánh giá JavaScript tùy ý. Bạn cần evaluateJavascript() từ API WebView để xử lý CAPTCHA.
Tính năng này có hoạt động trên các thiết bị thực dành cho CI không?
Vâng. Thay thế 10.0.2.2 bằng IP thực của máy đang chạy chương trình phụ trợ bộ giải. Đảm bảo thiết bị có thể tiếp cận phần phụ trợ qua mạng.
Làm cách nào để ngăn người trợ giúp kiểm tra chuyển sang sản xuất?
Đặt các trình trợ giúp kiểm tra vào bộ nguồn src/debug/java/. Các biến thể bản dựng Android tự động loại trừ các nguồn gỡ lỗi khỏi bản dựng bản phát hành.
Còn reCAPTCHA Enterprise trong ứng dụng Android thì sao?
Cách tiếp cận tương tự nhưng bạn cần có khóa trang Doanh nghiệp và có thể cần chuyển các tham số bổ sung như enterprise: 1 cho CaptchaAI.
bài viết liên quan
- Cách giải quyết cuộc gọi lại Recaptcha V2 bằng Api
- Xây dựng quy trình thử nghiệm tự động Captchaai
- Xử lý cửa quay Recaptcha V2 trên cùng một trang web
Các bước tiếp theo
Tự động kiểm tra CAPTCHA trên Android của bạn —lấy khóa API CaptchaAI của bạnvà thiết lập phần phụ trợ của bộ giải.
Hướng dẫn liên quan:
- Xử lý CAPTCHA tự động hóa iOS với XCUITest
- Xử lý CAPTCHA trong Tự động hóa ứng dụng di động với Appium
- Trích xuất tham số reCAPTCHA từ nguồn trang