Thử nghiệm ứng dụng iOS với XCUITest thường đạt được CAPTCHA trong WKWebView được nhúng – biểu mẫu đăng nhập, cổng thanh toán và tích hợp của bên thứ ba đưa ra các thách thức về reCAPTCHA.CaptchaAIgiải quyết những vấn đề này để các bài kiểm tra giao diện người dùng của bạn có thể hoàn thành các luồng từ đầu đến cuối mà không cần can thiệp thủ công.
Hướng dẫn này cho biết cách phát hiện CAPTCHA trong WKWebView trong khi chạy XCUITest, giải quyết chúng thông qua CaptchaAI từ một dịch vụ đồng hành và đưa lại mã thông báo vào nội dung web.
Kịch bản thế giới thực
Ứng dụng iOS của bạn tải biểu mẫu đăng ký trong WKWebView. Biểu mẫu bao gồm reCAPTCHA v2. Trong quá trình kiểm tra tự động, CAPTCHA này chặn tiến trình kiểm tra. Bạn cần một giải pháp:
- Phát hiện CAPTCHA trong WebView trong quá trình thực hiện kiểm tra
- Trích xuất khóa trang web theo chương trình
- Giải quyết nó thông qua CaptchaAI
- Tiêm mã thông báo để biểu mẫu có thể gửi
Môi trường: Xcode 15+, Swift, XCUITest, trình chạy thử nghiệm macOS, API CaptchaAI.
Kiến trúc
XCUITest không thể thực thi trực tiếp JavaScript trong WKWebView. Cách tiếp cận này sử dụng điểm cuối của trình trợ giúp mà ứng dụng gọi trong quá trình thử nghiệm:
| thành phần | Vai trò |
|---|---|
| XCUITest | Điều khiển giao diện người dùng, kích hoạt giải CAPTCHA thông qua trình trợ giúp kiểm tra |
| API trợ giúp kiểm tra | Nhận sitekey + URL, gọi CaptchaAI, trả về mã thông báo |
| Móc thử nghiệm ứng dụng | Đánh giá JavaScript trong WKWebView để phát hiện/inject |
| API CaptchaAI | Giải quyết thử thách CAPTCHA |
Bước 1: Thêm Test Hook vào ứng dụng
Trong bộ điều khiển WKWebView của ứng dụng, hãy thêm trình xử lý CAPTCHA ở chế độ thử nghiệm có thể được kích hoạt thông qua mã nhận dạng trợ năng hoặc lược đồ URL:
// CaptchaTestHelper.swift — Add to app target (test build only)
import WebKit
#if DEBUG
class CaptchaTestHelper {
private let webView: WKWebView
init(webView: WKWebView) {
self.webView = webView
}
func detectCaptcha(completion: @escaping (String?, String?) -> Void) {
let script = """
(function() {
var el = document.querySelector('.g-recaptcha');
if (el) {
return JSON.stringify({
sitekey: el.getAttribute('data-sitekey'),
pageurl: window.location.href
});
}
return null;
})();
"""
webView.evaluateJavaScript(script) { result, error in
guard let jsonString = result as? String,
let data = jsonString.data(using: .utf8),
let json = try? JSONSerialization.jsonObject(with: data) as? [String: String] else {
completion(nil, nil)
return
}
completion(json["sitekey"], json["pageurl"])
}
}
func injectToken(_ token: String, completion: @escaping (Bool) -> Void) {
let script = """
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) {}
true;
"""
webView.evaluateJavaScript(script) { _, error in
completion(error == nil)
}
}
func solveCaptchaViaBackend(
sitekey: String, pageurl: String,
completion: @escaping (Result<String, Error>) -> Void
) {
guard let url = URL(string: "http://localhost:3000/api/solve-captcha") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: String] = [
"captchaType": "recaptcha_v2",
"sitekey": sitekey,
"pageurl": pageurl
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let token = json["token"] as? String else {
completion(.failure(NSError(domain: "", code: -1,
userInfo: [NSLocalizedDescriptionKey: "No token"])))
return
}
completion(.success(token))
}.resume()
}
}
#endif
Bước 2: Dịch vụ bộ giải phụ trợ
Chạy dịch vụ bộ giải cục bộ trong quá trình thử nghiệm giao tiếp với CaptchaAI:
# ios_test_solver.py — Run on test machine during XCUITest execution
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
sitekey = data["sitekey"]
pageurl = data["pageurl"]
# Submit to CaptchaAI
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
})
result = resp.json()
if result.get("status") != 1:
return jsonify({"error": result.get("request")}), 400
task_id = result["request"]
# Poll
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: Tích hợp XCUITest
Trong XCUITest của bạn, hãy kích hoạt luồng giải CAPTCHA khi WebView có CAPTCHA tải:
// CaptchaUITests.swift
import XCTest
class CaptchaUITests: XCTestCase {
func testRegistrationWithCaptcha() throws {
let app = XCUIApplication()
app.launchArguments.append("--captcha-test-mode")
app.launch()
// Navigate to registration
app.buttons["Register"].tap()
// Wait for WebView to load
let webView = app.webViews.firstMatch
XCTAssertTrue(webView.waitForExistence(timeout: 15))
// Trigger CAPTCHA solve via test helper button
// (The app shows this button only in test mode)
let solveButton = app.buttons["SolveCaptchaTestHelper"]
if solveButton.waitForExistence(timeout: 5) {
solveButton.tap()
// Wait for solve completion indicator
let solved = app.staticTexts["CaptchaSolved"]
XCTAssertTrue(solved.waitForExistence(timeout: 120),
"CAPTCHA should be solved within 2 minutes")
}
// Continue with form submission
app.buttons["SubmitForm"].tap()
// Verify success
let success = app.staticTexts["Registration Complete"]
XCTAssertTrue(success.waitForExistence(timeout: 10))
}
}
Hợp đồng cầu
- Xác định trọng tải yêu cầu mà người chạy thử nghiệm gửi đến dịch vụ trợ giúp, bao gồm URL mục tiêu và siêu dữ liệu thử thách.
- Trả về phản hồi có cấu trúc kèm theo mã thông báo, thời hạn sử dụng và lý do lỗi để lớp kiểm tra có thể phân nhánh rõ ràng.
- Giữ nhật ký trình mô phỏng và nhật ký dịch vụ trợ giúp được liên kết bằng mã định danh theo dõi dùng chung để phân loại nhanh hơn.
Khắc phục sự cố
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
evaluateJavaScript trả về con số 0 |
WebView chưa tải xong | Đợi webView.isLoading == false trước khi tiêm JS |
| Phần phụ trợ không thể truy cập được từ Trình mô phỏng | localhost không truy cập được | Sử dụng 127.0.0.1 hoặc IP mạng của máy Mac; kiểm tra bảo mật vận chuyển ứng dụng |
| Việc tiêm mã thông báo không kích hoạt cuộc gọi lại | Lệnh gọi lại reCAPTCHA được lồng trong đối tượng phức tạp | Lặp lại tất cả các thuộc tính của ___grecaptcha_cfg.clients theo cách đệ quy |
| XCUITest hết thời gian chờ giải quyết | Thời gian giải quyết CaptchaAI dài | Đặt thời gian chờ kiểm tra thành hơn 120 giây cho các kiểm tra liên quan đến CAPTCHA |
Câu hỏi thường gặp
XCUITest có thể thực thi JavaScript trực tiếp trong WKWebView không?
Không. XCUITest tương tác với các thành phần giao diện người dùng nhưng không thể đánh giá JavaScript. Bạn cần một hook thử nghiệm trong mã ứng dụng (chỉ bản dựng gỡ lỗi) để thu hẹp khoảng cách này.
Cách tiếp cận này có hiệu quả trong đường ống CI/CD không?
Có, chạy chương trình phụ trợ của bộ giải trên máy CI và Trình mô phỏng iOS. Dịch vụ bộ giải giao tiếp với CaptchaAI qua HTTPS, hoạt động trong mọi môi trường.
Làm cách nào để ngăn móc thử nghiệm được vận chuyển đến nơi sản xuất?
Bao bọc tất cả mã trợ giúp kiểm tra trong các chỉ thị của trình biên dịch #if DEBUG. Mã sẽ bị loại bỏ khỏi bản phát hành.
Điều gì sẽ xảy ra nếu CAPTCHA có trong SDK WebView của bên thứ ba?
Nếu bạn không kiểm soát WebView, hãy sử dụng Appium thay thế - nó cung cấp khả năng execute_script trên mọi ngữ cảnh WebView mà không cần móc nối phía ứng dụng.
bài viết liên quan
- Cách giải quyết cuộc gọi lại Recaptcha V2 bằng Api
- Zapier Captchaai Tự động hóa không cần mã
- Xử lý cửa quay Recaptcha V2 trên cùng một trang web
Các bước tiếp theo
Tích hợp CaptchaAI vào quy trình thử nghiệm iOS của bạn —lấy khóa API của bạnvà tự động hóa thông qua các luồng được bảo vệ bằng CAPTCHA.
Hướng dẫn liên quan: