Appium Testlerinde CAPTCHA Nasıl Çözülür (Python İstemcisi)

Bir Appium CAPTCHA adımı, mobil bir koşunun genellikle durup bir insanı beklediği noktadır. Öyle olmak zorunda değil. Appium, challenge’ı tutan öğenin ekran görüntüsünü zaten alabiliyor ve CapSkip kendi makinenizde çalışıp onu cevaplıyor; böylece test sonucu yazıp yoluna devam ediyor. Zor kısım çözüm değil, oturum: Appium sessizleşen bir oturumu sonlandırır ve bir çözüm tam da onun sevmediği türden bir sessizliktir. Bu rehber, karşılaşacağınız iki biçimi de Python ile ele alıyor: yerel bir görsel alanı ve bir WebView içindeki CAPTCHA.
Neye ihtiyacınız var
- Bir Windows makinesinde çalışan CapSkip; API sunucusu açık olmalı.
- Appium 2 ve çalışan bir sürücü: Android için UiAutomator2 ya da iOS için XCUITest; ayrıca hâlihazırda sürebildiğiniz bir cihaz veya emülatör.
- Python 3.10 veya üzeri; Appium istemcisi ve CapSkip paketi kurulu olmalı.
- Çözücü için bir adres. Yerel mod 127.0.0.1 üzerinde yanıt verir, yani ona yalnızca CapSkip makinesinin kendisinde çalışan kod erişebilir; Sunucu modu ise ağ adresinizi veya genel IP’nizi dinler, böylece bir derleme ajanı ya da bir CI runner da erişebilir. Hangisinin geçerli olduğunu Adım 4 anlatıyor; ikisi de şurada yer alır: bağlantı ayarları.
# pip install Appium-Python-Client capskip pip install Appium-Python-Client capskip
Adım 1: oturuma bekleyecek alan tanıyın
Bunu her şeyden önce yapın, çünkü en çok zaman kaybettiren hata bu. Appium, oturum başına çalışan ve şu adı taşıyan bir boşta kalma sayacı tutar: newCommandTimeout. Varsayılanı 60 saniyedir ve o pencere içinde yeni bir komut gelmediğinde sunucu, istemcinin gittiğine karar verip oturumu sonlandırır. Sonraki her çağrı da artık var olmayan bir oturuma karşı başarısız olur.
Bir çözüm, komut akışındaki bir boşluktur. Python kodunuz Appium ile değil CapSkip ile konuşur; yani o çözüm boyunca sürücü boşta oturur. İki saati karşılaştırın, sorun ortada.
| Saat | Varsayılan | Neyi kapsar |
|---|---|---|
| Appium newCommandTimeout | 60 saniye | İki sürücü komutu arasındaki boşta geçen süre, oturum başına |
| CapSkip defaultTimeout | 120 saniye | Görsel CAPTCHA ve ALTCHA yoklaması |
| CapSkip recaptchaTimeout | 300 saniye | reCAPTCHA, Turnstile ve GeeTest yoklaması |
Bir görsel CAPTCHA genellikle kimsenin fark etmeyeceği kadar hızlı döner. Yoğun bir çözücüdeki reCAPTCHA dönmez ve istemci, Appium’un beklediğinden beş kat daha uzun beklemeye razıdır. Boşta kalma sayacını, beklemeye razı olduğunuz en uzun çözümün ötesine çıkarın.
# pip install Appium-Python-Client capskip
from appium import webdriver
from appium.options.android import UiAutomator2Options
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.app = "/path/to/app.apk"
# Default is 60 seconds. A reCAPTCHA solve can outlast that.
options.new_command_timeout = 300
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)O özellik appium:newCommandTimeout yeteneğini yazar; dolayısıyla onu dostça bir adla sunmayan bir sürücü ya da istemci aynı değeri set_capability üzerinden alır. iOS’ta sınıf XCUITestOptions’tır ve yetenek birebir aynıdır, çünkü her sürücü bunu kendisi uygulamak yerine Appium’un ortak temel sürücüsünden devralır. Sunucu adresine de dikkat edin: Appium 2 çıplak port üzerinde hizmet verir, arkasında hiçbir yol yoktur.
Adım 2: yerel bir görsel CAPTCHA çözün
Mobil uygulamalarda yaygın biçim budur: bozuk metni tutan bir ImageView ve altında bir metin alanı. Appium tek bir öğenin ekran görüntüsünü alıp base64 olarak geri verir; bu da tam olarak görsel metodunun aldığı üç girdi biçiminden biridir, yani diske hiçbir şeyin dokunması gerekmez.
from appium.webdriver.common.appiumby import AppiumBy
from capskip import CapSkip
solver = CapSkip(host="127.0.0.1", port=8080)
# Appium crops the element out of a device screenshot for you.
image = driver.find_element(AppiumBy.ID, "com.example.app:id/captcha_image")
result = solver.normal("data:image/png;base64," + image.screenshot_as_base64)
field = driver.find_element(AppiumBy.ID, "com.example.app:id/captcha_input")
field.send_keys(result["code"])code anahtarı okunan metni tutar. Görsel metodu bir dosya yolu ya da uzak bir URL de kabul eder; yani bir adım zaten ekran görüntüsünü kaydettiyse bunun yerine yolu geçebilirsiniz. Ama base64 yolu, bir test koşusunda geçici dosyalardan kurtarır ve sonrasında temizlemesi daha kolaydır.
Bu metotla ilgili iki şeyi, etrafına bir şey kurmadan önce bilmekte fayda var. Proxy desteği yok; burada bu sorun değil, çünkü görsel makinenizden hiç çıkmıyor. Ve daha uzun reCAPTCHA zaman aşımı yerine varsayılan 120 saniyelik zaman aşımına göre yokluyor, çünkü işin içinde bir tarayıcı oturumu yok.
Ekranı değil, görsel öğesini hedefleyin. İçinde CAPTCHA da olan tam ekran bir görüntü, çözücüye okuması için bir telefon arayüzü verir ve cevap, kötü bir kırpmadan çok kötü bir çözüm gibi görünen bir biçimde yanlış çıkar. Bulabildiğiniz öğe, içinde dolgu ve bir etiket bulunan bir kapsayıcıysa bunun yerine içteki görünümü bulun; yoksa fazladan pikseller size doğruluk kaybettirir.
Adım 3: WebView içindeki bir reCAPTCHA’yı çözün
Diğer biçim, aslında bir WebView içindeki web sayfası olan bir giriş ya da kayıt ekranıdır. Burada okunacak bir görsel yok; o yüzden web bağlamına geçin ve DOM ile tıpkı bir tarayıcıdaki gibi çalışın.
# contexts looks like ['NATIVE_APP', 'WEBVIEW_com.example.app']
web = [c for c in driver.contexts if c.startswith("WEBVIEW")][0]
driver.switch_to.context(web)
# Narrow to g-recaptcha: hCaptcha also carries data-sitekey.
sitekey = driver.find_element(
AppiumBy.CSS_SELECTOR,
".g-recaptcha[data-sitekey]").get_attribute("data-sitekey")
result = solver.recaptcha(sitekey=sitekey, url=driver.current_url)
driver.execute_script(
"document.getElementById('g-recaptcha-response').value = arguments[0];",
result["code"],
)
driver.switch_to.context("NATIVE_APP")reCAPTCHA metodunu çağırmadan önce widget’ın gerçekte ne olduğunu kontrol edin. hCaptcha da kendi widget’ına bir data-sitekey koyar ve desteklenen bir tip değildir; yani çıplak bir öznitelik seçicisi size seve seve yanlış anahtarı verir. g-recaptcha sınıfını arayın ya da bir h-captcha sınıfı veya js.hcaptcha.com script’i olup olmadığına bakarak hCaptcha’yı eleyin. WebView girişi, biriyle karşılaşmak için yaygın bir yerdir. FunCaptcha ve Arkose de desteklenmiyor.
Sayfa URL’sini sabit yazmak yerine sürücüden okuyun. Bir WebView çoğu zaman sorgu dizesinde bir oturum ya da dönüş yolu taşıyan bir URL yükler ve çözüm, istendiği sayfaya bağlıdır; dolayısıyla tahmin edilen bir URL, sitenin reddettiği bir token üretir.
Normal gönderim yapan bir formda response alanını doldurmak yeterlidir. reCAPTCHA’nın kendisini geri aramasını bekleyen bir sayfada yeterli değildir; bu, gönder düğmesinin forma değil widget’ın callback’ine bağlandığı düzendir. O durumda callback’in de çağrılması gerekir ve bu, mobil bir sorundan çok kendine özgü bir sorundur: callback çözücü sayfası nelere bakmanız gerektiğini anlatıyor. Yerel düğmelere yeniden dokunmadan önce yerel bağlama geri dönün, yoksa sonraki find_element DOM’da arama yapar ve başarısız olur.
Bağlam listesi hep yalnızca NATIVE_APP gösteriyorsa WebView hata ayıklanabilir değildir. Android’de bu, geliştiricilerin denetlediği uygulama tarafı bir ayardır; yani Appium’u suçlamadan önce onlara sormakta fayda var.
4. adım: çözücünün nerede çalıştığı ve bunun hangi bağlantı modunu gerektirdiği
Mobilde insanların ters anladığı kısım bu, o yüzden açık konuşmakta fayda var. Çözücüyü sizin Python test kodunuz çağırır. Telefon çağırmaz, emülatör çağırmaz, Appium sunucusu da çağırmaz. Yani tek soru test sürecinizin nerede çalıştığıdır ve cihazın kendi ağıyla bunun hiçbir ilgisi yoktur.
Bu da demek oluyor ki, ana makineye erişmekle ilgili alışıldık Android emülatörü tavsiyesi burada geçersiz; uzak bir Appium sunucusunun adresi de öyle. Önemli olan daha basit: testinizi çalıştıran süreç CapSkip makinesindeyse loopback doğrudur. Başka bir yerdeyse değildir ve ilk çözüm bir NetworkException fırlatır.
| Test sürecinin çalıştığı yer | Hangi bağlantı modu |
|---|---|
| Dizüstünüz, üzerinde CapSkip açıkken | Local modu. 127.0.0.1 gerçekten doğru |
| Dizüstünüz, uzak bir Appium sunucusunu ya da bir cihaz bulutunu sürerken | Yine Yerel mod. Yalnızca sürücü çağrısı dışarı çıkar |
| Aynı ağdaki bir derleme ajanı | Sunucu modu, CapSkip makinesinin özel adresinde |
| Barındırılan bir CI runner ya da bir konteyner | Statik bir genel IP ve bir güvenlik duvarı kuralı ile Server modu |
CapSkip’i Sunucu moduna alın; loopback yerine ağ adresinizi veya genel IP’nizi dinler, böylece bunların hepsi aynı HTTP API üzerinden ona erişir. Rota internetten geçiyorsa statik bir genel IP önerilir; yalnızca beklediğiniz adreslere izin veren bir güvenlik duvarı kuralıyla birlikte. Sunucu modu yalnızca çözücünün nerede dinlediğini değiştirir, başka hiçbir şeyi değil: donanım hâlâ sizin ve kullanım hâlâ ölçülmüyor. Tek bir test paketinin her iki yerde de çalışması için host ve port değerlerini ortamdan okuyun. İstemci CAPSKIP_HOST ve CAPSKIP_PORT değerlerini kendi başına okumaz; bu yüzden, aşağıdaki tam örnekte olduğu gibi, bunları yapıcıya geçirin.
Tam çalışan örnek
import os
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
from capskip import CapSkip, ApiException, NetworkException, TimeoutException
solver = CapSkip(
host=os.environ.get("CAPSKIP_HOST", "127.0.0.1"),
port=int(os.environ.get("CAPSKIP_PORT", 8080)),
)
options = UiAutomator2Options()
options.platform_name = "Android"
options.device_name = "emulator-5554"
options.app = "/path/to/app.apk"
options.new_command_timeout = 300 # must outlast the longest solve
driver = webdriver.Remote("http://127.0.0.1:4723", options=options)
try:
image = driver.find_element(AppiumBy.ID, "com.example.app:id/captcha_image")
result = solver.normal("data:image/png;base64," + image.screenshot_as_base64)
driver.find_element(
AppiumBy.ID, "com.example.app:id/captcha_input").send_keys(result["code"])
driver.find_element(AppiumBy.ACCESSIBILITY_ID, "Submit").click()
except ApiException:
print("the solver refused the image")
except NetworkException:
print("solver unreachable: check the host and the connection mode")
except TimeoutException:
print("no answer inside defaultTimeout")
finally:
driver.quit()Dört istisnanın hepsi CapSkipError’dan türüyor; dolayısıyla onu yakalamak, SDK’nın fırlatabileceği her hatayı tek bir blokta karşılar. Yanıt farklılaştığında yukarıdaki gibi özel olanları, farklılaşmadığında CapSkipError’ı yakalayın. driver.quit çağrısını bir finally bloğunda tutun: çözüm ortasında ölen bir test, aksi hâlde az önce yükselttiğiniz boşta kalma sayacı dolana kadar cihazı tutan bir oturum bırakır.
Tiplerin geri kalanı aynı istemciden aynı şekilde çalışıyor. Turnstile bir sitekey ile bir sayfa URL’si alır, GeeTest bir gt değeri, bir challenge ve sayfa URL’sini alır, ALTCHA ise sayfa URL’si ile bir challenge uç noktası alır. Paketin sunduğu bütün metotlar şurada listeleniyor: Python CAPTCHA çözücü sayfası, görsel tipini ise şu anlatıyor: kendine ait bir sayfa.
Sık görülen hatalar ve anlamları
| Gördüğünüz | Neden | Düzeltme |
|---|---|---|
| Yavaş bir çözümden sonra oturum kayboluyor ve sonraki her komut başarısız oluyor | Kodunuz çözücüyü beklerken newCommandTimeout doldu | Ortalamanın değil, en uzun çözümün ötesine çıkarın |
| İlk çözümde bir NetworkException | CapSkip çalışmıyor ya da test süreci bir derleme ajanında ve loopback’e yönlendirilmiş | CapSkip’i başlatın, sonra Yerel mod ile Sunucu modu arasında seçim yapın |
| Bir görselde 120 saniyeyi işaret eden bir TimeoutException | Görsel tipi, daha uzun reCAPTCHA zaman aşımını değil varsayılan yoklama zaman aşımını kullanır | Herhangi bir şeyi yükseltmeden önce çözücünün çalıştığından ve doymadığından emin olun |
| Net bir görselde cevap her seferinde yanlış çıkıyor | Tam ekran bir görüntü ya da dolgu ve etiket içeren bir öğe | Yalnızca CAPTCHA’yı tutan en içteki görünümün ekran görüntüsünü alın |
| base64’ten ya da eksik bir dosyadan söz eden bir ValidationException | Öğe ekran görüntüsü boş döndü, yani dize bir görsel olarak okunamayacak kadar kısaydı | Ekran görüntüsünden önce öğenin ekranda ve görünür olduğunu, ayrıca aramanın gerçekten onu eşleştirdiğini kontrol edin |
| reCAPTCHA metodu, sitenin her seferinde reddettiği bir token dönüyor | Widget hCaptcha; o da data-sitekey taşır ve desteklenen bir tip değildir | Seçiciyi g-recaptcha sınıfına daraltın ve sayfanın hangi widget’ı yüklediğini doğrulayın |
| Bağlam listesi yalnızca NATIVE_APP içeriyor | WebView hata ayıklanabilir değil, yani Appium ona bağlanamıyor | Uygulama ekibinden, test ettiğiniz yapıda WebView hata ayıklamasını açmasını isteyin |
| Bir WebView adımından hemen sonra find_element başarısız oluyor | Sürücü hâlâ web bağlamında ve DOM’da arıyor | Yerel öğelere dokunmadan önce NATIVE_APP’e geri dönün |
| reCAPTCHA alanı doluyor ama düğme hiçbir şey yapmıyor | Sayfa, alanı okumak yerine widget’ın callback’ini bekliyor | Callback’i de çağırın ya da formu doğrudan gönderin |
| Çözücünün döndürdüğü bir token site tarafından reddediliyor | Çözücüye geçilen sayfa URL’si, WebView’dan okunmak yerine tahmin edildi | driver.current_url değerini web bağlamının içinden geçirin |
FAQ
Telefonun ya da emülatörün çözücüye erişmesi gerekiyor mu?
Hayır ve bu kurulum hakkında anlaşılacak en faydalı tek şey bu. CapSkip’e yapılan HTTP çağrısını sizin Python süreciniz yapar; yani cihaz yalnızca bir ekran görüntüsü isteği ile bir send_keys görür. Telefona hiçbir şey kurulması gerekmez, uygulamanın trafiği yeniden yönlendirilmez ve emülatörün kendi ana makine adresi hiç işin içine girmez. Kabloya takılı gerçek bir cihaz, bir emülatör ve bir bulut cihazı çözücünün gözünde birebir aynı davranır.
CI’da çalışan Appium testleri CapSkip kullanabilir mi?
Evet, Sunucu modu üzerinden. Barındırılan bir runner sizin loopback adresinizi göremez; o yüzden CapSkip’i bağlantı ayarları altında ağ adresinizi veya genel IP’nizi dinleyecek şekilde ayarlayın ve host ortam değişkenini ona yöneltin. Rota internetten geçiyorsa statik bir genel IP kullanın ve bir güvenlik duvarı kuralıyla kısıtlayın. Çözücü her durumda sizin sahip olduğunuz donanımda kalır; yani testler masanızdan ayrıldığında lisansta ya da çözüm sayısında hiçbir şey değişmez.
Bunların hepsi yalnızca Android için mi?
Hayır. UiAutomator2Options yerine, appium.options.ios üzerinden içe aktarılan XCUITestOptions kullanın; koşunun biçimi birebir aynı olur, çünkü öğe ekran görüntüleri, bağlam değiştirme ve boşta kalma sayacının hepsi sürücünün üstünde yaşar. Yalnızca konum belirleyiciler değişir, çünkü iOS’ta resource id yoktur: uygulamanın atadığı yerlerde bir accessibility id, atamadığı yerlerde bir predicate ya da class chain kullanın. Çözücü, kendisine hangi platformdan bir görsel verildiğini asla öğrenmez.
CAPTCHA’yı çözmeli miyim yoksa testler için kapatmalı mıyım?
Uygulama sizinse ve yapabiliyorsanız kapatın. Kontrolü atlayan bir test yapısı ya da her zaman geçen bir sağlayıcı test anahtarı, herhangi bir çözümden daha hızlı ve daha belirlenimlidir; ayrıca test paketinizden bir bağımlılığı kaldırır. Çözme, ekranı kontrol etmediğiniz durumlarda yerini hak eder: akışınızın içindeki üçüncü taraf bir giriş, bir iş ortağının kayıt formu, kimsenin sizin için değiştirmeyeceği bir hazırlık ortamı ya da üretime karşı yapılan bir cihaz bulutu koşusu. Seçimin bir çözücü ile bir insan arasında olduğu durumlar bunlar.
Kısa özet
Başka bir şey yazmadan önce newCommandTimeout değerini yükseltin, çünkü varsayılan 60 saniye çözücünün alabileceği süreden kısadır ve oturumun test ortasında ölmesi bambaşka bir hata gibi görünür. Ekranın değil öğenin ekran görüntüsünü alın, onu base64 bir veri URI’si olarak geçin ve sonucu send_keys ile geri yazın. Bir WebView için bağlamı değiştirin, sitekey’i ve geçerli URL’yi DOM’dan okuyun, response alanını doldurun, sonra geri dönün. Test süreci çözücüyle aynı makineyi paylaşmayı bıraktığı anda Sunucu moduna geçin.
- Yerel uygulamadaki bir CAPTCHA’nın arkasındaki tip ve nasıl okunduğu: görsel CAPTCHA çözücü sayfası.
- WebView durumunun tarayıcı durumuyla paylaştığı her şey: reCAPTCHA çözücü sayfası.
Yeniden denemeyi nasıl yazacağınızı belirleyen son bir nokta. Bu captcha çözücü zaten sahip olduğunuz donanımda çalıştığı için, kötü kırpılmış bir görselde ikinci bir deneme hiçbir şeye mal olmaz; böylece bir test, koşuyu başarısız saymak yerine daha temiz bir ekran görüntüsü alıp yeniden deneyebilir.
