Entegrasyonlar

XCUITest ve CaptchaAI ile iOS Otomasyonu CAPTCHA Kullanımı

XCUITest ile yapılan iOS uygulama testleri, genellikle yerleşik WKWebView'lerdeki CAPTCHA'lara (giriş formları, ödeme ağ geçitleri ve reCAPTCHA zorlukları sunan üçüncü taraf entegrasyonlarına) ulaşır.CaptchaAIbunları çözerek kullanıcı arayüzü testlerinizin manuel müdahale olmadan uçtan uca akışları tamamlayabilmesini sağlar.

Bu kılavuz, XCUITest çalıştırmaları sırasında WKWebView'deki CAPTCHA'ların nasıl tespit edileceğini, bunların yardımcı bir hizmetten CaptchaAI aracılığıyla nasıl çözüleceğini ve belirtecin web içeriğine geri nasıl enjekte edileceğini gösterir.

Gerçek Dünya Senaryosu

İOS uygulamanız WKWebView'da bir kayıt formu yükler. Form reCAPTCHA v2'yi içerir. Otomatik test sırasında bu CAPTCHA, testin ilerlemesini engeller. Şunları sağlayacak bir çözüme ihtiyacınız var:

  1. Test yürütme sırasında Web Görünümünde CAPTCHA'yı algılar
  2. Site anahtarını programlı olarak çıkarır
  3. CaptchaAI aracılığıyla çözer
  4. Formun gönderilebilmesi için jetonu enjekte eder

Ortam: Xcode 15+, Swift, XCUITest, macOS test çalıştırıcısı, CaptchaAI API.

Mimarlık

XCUITest, WKWebView'da doğrudan JavaScript çalıştıramaz. Yaklaşım, uygulamanın test sırasında çağırdığı bir yardımcı uç noktayı kullanır:

Bileşen Rol
XCUITest Kullanıcı arayüzünü yönetir, test yardımcısı aracılığıyla CAPTCHA çözümünü tetikler
Test Yardımcısı API'si Site anahtarını + URL'yi alır, CaptchaAI'yi çağırır, jetonu döndürür
Uygulama Testi Kancası /inject'yi tespit etmek için WKWebView'deki JavaScript'i değerlendirir
CaptchaAI API'si CAPTCHA sorununu çözer

1. Adım: Uygulamaya bir Test Kancası ekleyin

Uygulamanızın WKWebView denetleyicisine, erişilebilirlik tanımlayıcıları veya URL şeması aracılığıyla tetiklenebilecek bir test modu CAPTCHA işleyicisi ekleyin:

// 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

Adım 2: Arka Uç Çözücü Hizmeti

Test sırasında CaptchaAI ile iletişim kuran yerel bir çözümleyici hizmetini çalıştırın:

# 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)

Adım 3: XCUITest Entegrasyonu

XCUITest'inizde, CAPTCHA içeren Web Görünümü yüklendiğinde CAPTCHA çözüm akışını tetikleyin:

// 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))
    }
}

Köprü sözleşmesi

  • Hedef URL ve sorgulama meta verileri de dahil olmak üzere, test çalıştırıcısının yardımcı hizmete gönderdiği istek yükünü tanımlayın.
  • Test katmanının temiz bir şekilde dallanabilmesi için belirteç, süre sonu ve hata nedeni ile yapılandırılmış bir yanıt döndürün.
  • Daha hızlı önceliklendirme için simülatör günlüklerini ve yardımcı hizmet günlüklerini paylaşılan bir iz tanımlayıcıyla bağlantılı tutun.

Sorun giderme

Sorun Sebep Düzeltme
evaluateJavaScript sıfır değerini döndürür Web Görünümü yüklemeyi tamamlamadı JS'yi enjekte etmeden önce webView.isLoading == false'yi bekleyin
Simülatörden arka uca ulaşılamıyor localhost'a erişilemiyor 127.0.0.1'yi veya Mac'in ağ IP'sini kullanın; Uygulama Taşıma Güvenliğini kontrol edin
Jeton enjeksiyonu geri aramayı tetiklemiyor Karmaşık nesnede iç içe geçmiş reCAPTCHA geri araması ___grecaptcha_cfg.clients'nin tüm özelliklerini yinelemeli olarak yineleyin
XCUITest zaman aşımı çözümü beklerken Uzun CaptchaAI çözüm süresi CAPTCHA ile ilgili testler için test zaman aşımını 120+ saniyeye ayarlayın

SSS

XCUITest, JavaScript'i doğrudan WKWebView'da çalıştırabilir mi?

Hayır. XCUITest, kullanıcı arayüzü öğeleriyle etkileşime girer ancak JavaScript'i değerlendiremez. Bu boşluğu kapatmak için uygulama kodunda bir test kancasına (yalnızca hata ayıklama derlemesi) ihtiyacınız vardır.

Bu yaklaşım CI/CD işlem hatlarında işe yarayacak mı?

Evet, çözücü arka ucunu CI makinesinde ve iOS Simulator'da çalıştırın. Çözücü hizmeti, her ortamda çalışan CaptchaAI ile HTTPS üzerinden iletişim kurar.

Test kancasının üretime gönderilmesini nasıl önleyebilirim?

Tüm test yardımcı kodunu #if DEBUG derleyici yönergelerine sarın. Kod, sürüm yapılarından çıkarılacaktır.

CAPTCHA üçüncü taraf bir SDK Web Görünümündeyse ne olur?

WebView'ı kontrol etmiyorsanız bunun yerine Appium'u kullanın; bu, uygulama tarafı kancalarına ihtiyaç duymadan herhangi bir WebView bağlamında execute_script yetenekleri sağlar.

İlgili Makaleler

Sonraki Adımlar

CaptchaAI'yi iOS test hattınıza entegre edin -API anahtarınızı alınve CAPTCHA korumalı akışlar aracılığıyla otomatikleştirin.

İlgili kılavuzlar:

Bu makale için yorumlar devre dışı bırakılmıştır.