Pengujian UI Android yang dibuat dengan Espresso sering kali mengenai CAPTCHA di dalam WebViews – halaman login, formulir pendaftaran, atau alur pembayaran tersemat yang menampilkan reCAPTCHA v2.CaptchaAImemberikan penyelesaian terprogram sehingga rangkaian pengujian otomatis Anda dapat berjalan secara end-to-end tanpa interaksi CAPTCHA manual.
Panduan ini menunjukkan cara mendeteksi CAPTCHA di Android WebViews selama pengujian Espresso, menyelesaikannya melalui layanan backend, dan memasukkan token kembali ke halaman.
Skenario Dunia Nyata
Aplikasi Android Anda memuat halaman checkout pihak ketiga di WebView. Halaman ini menyajikan reCAPTCHA v2 sebelum mengizinkan pembayaran. Selama pengujian berinstrumen Espresso, CAPTCHA ini memblokir pengujian verifikasi pembayaran.
Lingkungan: Android Studio, Kotlin, Espresso, AndroidX Test, CaptchaAI API, backend Python.
Langkah 1: Buat Pembantu Tes di Aplikasi
Tambahkan pembantu khusus debug yang dapat mengevaluasi JavaScript di dalam WebView aplikasi:
// 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
}
}
}
Langkah 2: Layanan Pemecah Backend
Jalankan pemecah Python ini di mesin pengembangan Anda selama eksekusi pengujian:
# 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)
Langkah 3: Uji Espresso dengan Penanganan 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 pengaturan token
Thread.sleep(1000)
// Verify checkout completed
onView(withText("Order Confirmed")).check(
androidx.test.espresso.assertion.ViewAssertions.matches(isDisplayed())
)
}
}
Pemecahan Masalah
| Masalah | Penyebab | Solusi |
|---|---|---|
10.0.2.2 tidak dapat dijangkau |
Tidak menggunakan Emulator Android | Gunakan IP host aktual untuk perangkat fisik; 10.0.2.2 khusus untuk emulator |
Panggilan balik evaluateJavascript adalah nol |
WebView tidak dimuat sepenuhnya | Tambahkan pendengar WebViewClient.onPageFinished() sebelum mengevaluasi |
addJavascriptInterface tidak berfungsi |
JavaScript dinonaktifkan | Hubungi webView.settings.javaScriptEnabled = true |
| Permintaan jaringan diblokir oleh kebijakan Cleartext | HTTP ke localhost di Android 9+ | Tambahkan android:usesCleartextTraffic="true" di AndroidManifest.xml (khusus debug) |
Pertanyaan Umum
Bisakah Espresso berinteraksi dengan konten WebView secara langsung?
Espresso memiliki onWebView() untuk interaksi WebView dasar, tetapi Espresso tidak dapat mengevaluasi JavaScript sembarangan. Anda memerlukan evaluateJavascript() dari WebView API untuk penanganan CAPTCHA.
Apakah ini berfungsi pada perangkat nyata untuk CI?
Ya. Ganti 10.0.2.2 dengan IP sebenarnya dari mesin yang menjalankan backend solver. Pastikan perangkat dapat menjangkau backend melalui jaringan.
Bagaimana cara mencegah alat bantu pengujian dikirim ke produksi?
Tempatkan pembantu tes di set sumber src/debug/java/. Varian build Android secara otomatis mengecualikan sumber debug dari build rilis.
Bagaimana dengan reCAPTCHA Enterprise di aplikasi Android?
Pendekatannya serupa tetapi Anda memerlukan kunci situs Perusahaan dan mungkin perlu meneruskan parameter tambahan seperti enterprise: 1 ke CaptchaAI.
Artikel Terkait
- Cara Mengatasi Callback Recaptcha V2 Menggunakan Api
- Membangun Pipeline Pengujian Otomatis dengan CaptchaAI
- Penanganan reCAPTCHA v2 dan Turnstile di Situs yang Sama
Langkah Selanjutnya
Otomatiskan pengujian CAPTCHA Android Anda — dapatkan kunci API CaptchaAI Anda dan siapkan backend pemecah.
Panduan terkait:
- Penanganan CAPTCHA Otomatisasi iOS dengan XCUITest
- Penanganan CAPTCHA dalam Otomatisasi Aplikasi Mobile dengan Appium
- Mengekstrak Parameter reCAPTCHA dari Sumber Halaman