Appium टेस्ट में कैप्चा कैसे हल करें (Python क्लाइंट)

Appium का कैप्चा स्टेप वही जगह है जहाँ मोबाइल रन आम तौर पर रुककर किसी इंसान का इंतज़ार करने लगता है। ऐसा होना ज़रूरी नहीं। Appium पहले से ही उस एलिमेंट का स्क्रीनशॉट ले सकता है जिसमें challenge है, और CapSkip आपकी अपनी मशीन पर चलकर उसका जवाब दे देता है, तो टेस्ट नतीजा टाइप करता है और आगे बढ़ जाता है। मुश्किल हिस्सा solve नहीं, सेशन है: Appium ऐसे सेशन को खत्म कर देता है जो चुप हो जाए, और solve ठीक उसी तरह की चुप्पी है जो उसे पसंद नहीं। यह गाइड दोनों शेप कवर करती है जो आपको मिलेंगे, एक नेटिव इमेज फ़ील्ड और WebView के अंदर बैठा कैप्चा, Python के साथ।
आपको क्या चाहिए
- CapSkip किसी Windows मशीन पर चल रहा हो, और API सर्वर चालू हो।
- Appium 2 और एक काम करता ड्राइवर, Android के लिए UiAutomator2 या iOS के लिए XCUITest, साथ में कोई डिवाइस या एमुलेटर जिसे आप पहले से चला सकते हों।
- Python 3.10 या उससे नया, जिसमें Appium क्लाइंट और CapSkip पैकेज इंस्टॉल हों।
- सॉल्वर का एक पता। Local मोड 127.0.0.1 पर जवाब देता है, इसलिए उस तक सिर्फ़ वही कोड पहुँच सकता है जो खुद CapSkip मशीन पर चल रहा हो, और Server मोड आपके नेटवर्क पते या पब्लिक IP पर सुनता है ताकि कोई बिल्ड एजेंट या CI रनर भी उस तक पहुँच सके। Step 4 में बताया गया है कि कौन-सा लागू होता है, और दोनों एक ही जगह मिलते हैं: कनेक्शन सेटिंग्स.
# pip install Appium-Python-Client capskip pip install Appium-Python-Client capskip
Step 1: सेशन को इंतज़ार करने की गुंजाइश दें
यह सबसे पहले कर लें, क्योंकि यही वह विफलता है जो सबसे ज़्यादा समय बर्बाद करती है। Appium हर सेशन के लिए एक idle टाइमर रखता है, जिसका नाम है newCommandTimeout। यह डिफ़ॉल्ट रूप से 60 सेकंड का होता है, और जब उस विंडो के अंदर कोई नई कमांड नहीं आती तो सर्वर मान लेता है कि क्लाइंट जा चुका है और सेशन खत्म कर देता है। इसके बाद हर कॉल ऐसे सेशन पर फ़ेल होती है जो अब मौजूद ही नहीं।
solve कमांड स्ट्रीम में एक खाली जगह है। आपका Python कोड CapSkip से बात कर रहा होता है, Appium से नहीं, इसलिए पूरे solve के दौरान ड्राइवर खाली बैठा रहता है। दोनों घड़ियों की तुलना कीजिए और समस्या साफ़ दिख जाएगी।
| घड़ी | डिफ़ॉल्ट | यह क्या कवर करता है |
|---|---|---|
| Appium newCommandTimeout | 60 सेकंड | दो ड्राइवर कमांड के बीच का खाली समय, प्रति सेशन |
| CapSkip defaultTimeout | 120 सेकंड | इमेज कैप्चा और ALTCHA पोलिंग |
| CapSkip recaptchaTimeout | 300 सेकंड | reCAPTCHA, Turnstile और GeeTest की पोलिंग |
इमेज कैप्चा आम तौर पर इतनी जल्दी लौट आता है कि किसी को पता ही नहीं चलता। व्यस्त सॉल्वर पर reCAPTCHA ऐसा नहीं करता, और क्लाइंट Appium से पाँच गुना ज़्यादा देर तक इंतज़ार करने को तैयार रहता है। idle टाइमर को उस सबसे लंबे solve से ऊपर कर दें जितने का इंतज़ार आप करने को तैयार हैं।
# 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)वह प्रॉपर्टी appium:newCommandTimeout कैपेबिलिटी लिखती है, इसलिए जो ड्राइवर या क्लाइंट इसे किसी आसान नाम से नहीं दिखाता, वह वही वैल्यू set_capability के ज़रिए लेता है। iOS पर क्लास XCUITestOptions है और कैपेबिलिटी बिल्कुल वही रहती है, क्योंकि हर ड्राइवर इसे अपनी अलग से बनाने के बजाय Appium के साझा बेस ड्राइवर से विरासत में लेता है। सर्वर का पता भी ध्यान से देखें: Appium 2 सीधे पोर्ट पर सर्व करता है, उसके बाद कोई पाथ नहीं होता।
Step 2: नेटिव इमेज कैप्चा हल करें
मोबाइल ऐप में यही आम शेप है: एक ImageView जिसमें बिगड़ा हुआ टेक्स्ट है, और उसके नीचे एक टेक्स्ट फ़ील्ड। Appium किसी एक एलिमेंट का स्क्रीनशॉट लेकर उसे base64 के रूप में लौटा देता है, और यह संयोग से ठीक उन तीन इनपुट रूपों में से एक है जो इमेज मेथड लेता है, इसलिए डिस्क को कुछ छूने की ज़रूरत ही नहीं पड़ती।
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 की में वह टेक्स्ट होता है जो पढ़ा गया। इमेज मेथड फ़ाइल पाथ या रिमोट URL भी स्वीकार करता है, इसलिए अगर किसी स्टेप ने पहले ही स्क्रीनशॉट सेव कर लिया है तो आप उसकी जगह पाथ पास कर सकते हैं, लेकिन base64 वाला रास्ता टेस्ट रन में अस्थायी फ़ाइलों से बचाता है और बाद में साफ़ करना आसान रखता है।
इस मेथड के इर्द-गिर्द कुछ बनाने से पहले दो बातें जान लेना ठीक रहेगा। इसमें प्रॉक्सी सपोर्ट नहीं है, जो यहाँ ठीक ही है क्योंकि इमेज आपकी मशीन से बाहर जाती ही नहीं। और यह लंबे reCAPTCHA वाले के बजाय 120 सेकंड के डिफ़ॉल्ट टाइमआउट के सामने पोल करता है, क्योंकि इसमें कोई ब्राउज़र सेशन शामिल नहीं होता।
इमेज एलिमेंट को निशाना बनाएँ, पूरी स्क्रीन को नहीं। पूरी स्क्रीन के ऐसे स्क्रीनशॉट से, जिसमें कहीं कैप्चा भी है, सॉल्वर को पढ़ने के लिए एक फ़ोन इंटरफ़ेस मिल जाता है, और जवाब ऐसे तरीके से ग़लत आएगा जो ख़राब क्रॉप नहीं, ख़राब solve जैसा दिखता है। अगर आपको जो एलिमेंट मिलता है वह पैडिंग और लेबल वाला कोई कंटेनर है, तो उसके भीतर वाला व्यू ढूँढ़ें, वरना फ़ालतू पिक्सल आपकी सटीकता की कीमत पर आएँगे।
Step 3: WebView के अंदर reCAPTCHA हल करें
दूसरा शेप ऐसी लॉगिन या साइनअप स्क्रीन है जो असल में WebView में खुला एक वेब पेज है। यहाँ पढ़ने के लिए कोई इमेज नहीं होती, इसलिए वेब कॉन्टेक्स्ट में स्विच करें और DOM के साथ ठीक वैसे ही काम करें जैसे आप ब्राउज़र में करते।
# 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 मेथड कॉल करने से पहले देख लें कि विजेट असल में है क्या। hCaptcha भी अपने विजेट पर data-sitekey रखता है, और वह सपोर्टेड type नहीं है, इसलिए सिर्फ़ एट्रिब्यूट वाला सेलेक्टर खुशी-खुशी आपको ग़लत की थमा देगा। g-recaptcha क्लास खोजें, या h-captcha क्लास या js.hcaptcha.com स्क्रिप्ट देखकर hCaptcha को बाहर कर दें। WebView लॉगिन वह आम जगह है जहाँ आपका सामना उससे होता है। FunCaptcha और Arkose भी सपोर्टेड नहीं हैं।
पेज URL को हार्ड-कोड करने के बजाय ड्राइवर से पढ़ें। WebView अक्सर ऐसा URL लोड करता है जिसकी क्वेरी स्ट्रिंग में कोई सेशन या रिटर्न पाथ होता है, और solve उसी पेज से बँधा होता है जिसके लिए वह माँगा गया था, इसलिए अंदाज़े से लिखा URL ऐसा टोकन देता है जिसे साइट ठुकरा देती है।
जो फ़ॉर्म सामान्य तरीके से पोस्ट होता है, उस पर रिस्पॉन्स फ़ील्ड भर देना काफ़ी है। लेकिन ऐसे पेज पर काफ़ी नहीं जो reCAPTCHA से वापस कॉल आने का इंतज़ार करता है, यानी वह व्यवस्था जहाँ सबमिट बटन फ़ॉर्म से नहीं, बल्कि विजेट के कॉलबैक से जुड़ा होता है। उस हालत में कॉलबैक को भी चलाना पड़ता है, और यह मोबाइल की नहीं, अपने आप में एक अलग समस्या है: कॉलबैक सॉल्वर पेज बताता है कि क्या देखना है। नेटिव बटन दोबारा छूने से पहले नेटिव कॉन्टेक्स्ट में वापस स्विच करें, वरना अगला find_element DOM में खोजेगा और फ़ेल हो जाएगा।
अगर कॉन्टेक्स्ट की सूची में हमेशा सिर्फ़ NATIVE_APP ही दिखता है, तो WebView डिबग करने लायक नहीं है। Android पर यह ऐप की तरफ़ की सेटिंग है जिसे डेवलपर कंट्रोल करते हैं, इसलिए यह मान लेने से पहले कि गलती Appium की है, उनसे पूछ लेना ठीक रहता है।
स्टेप 4: solver कहाँ चलता है, और उसके लिए कौन सा कनेक्शन मोड चाहिए
मोबाइल पर लोग यही हिस्सा उल्टा समझते हैं, इसलिए इसे साफ़-साफ़ कहना ज़रूरी है। सॉल्वर को आपका Python टेस्ट कोड कॉल करता है। न फ़ोन उसे कॉल करता है, न एमुलेटर, और न ही Appium सर्वर। तो सवाल बस इतना है कि आपकी टेस्ट प्रोसेस कहाँ चलती है, और डिवाइस की अपनी नेटवर्किंग का इससे कोई लेना-देना नहीं।
इसका मतलब है कि होस्ट मशीन तक पहुँचने को लेकर Android एमुलेटर वाली आम सलाह यहाँ बेमानी है, और रिमोट Appium सर्वर का पता भी। मायने इतना ही रखता है: अगर आपका टेस्ट चलाने वाली प्रोसेस CapSkip मशीन पर है, तो लूपबैक सही है। अगर वह कहीं और है, तो नहीं, और पहला ही solve NetworkException उठाएगा।
| टेस्ट प्रोसेस कहाँ चलती है | कौन-सा कनेक्शन मोड |
|---|---|
| आपका लैपटॉप, जिस पर CapSkip खुला है | Local mode। 127.0.0.1 सचमुच सही है |
| आपका लैपटॉप, जो किसी रिमोट Appium सर्वर या डिवाइस क्लाउड को चला रहा है | फिर भी Local मोड। सिर्फ़ ड्राइवर कॉल बाहर जाती है |
| उसी नेटवर्क पर मौजूद कोई बिल्ड एजेंट | Server मोड, CapSkip मशीन के प्राइवेट पते पर |
| कोई होस्टेड CI रनर या कंटेनर | स्टैटिक पब्लिक IP और एक फ़ायरवॉल नियम के साथ Server mode |
CapSkip को Server मोड पर बदल दें और वह लूपबैक के बजाय आपके नेटवर्क पते या पब्लिक IP पर सुनेगा, जिससे इनमें से कोई भी उसी HTTP API के ज़रिए उस तक पहुँच जाएगा। जब रास्ता इंटरनेट से होकर जाता है तो स्टैटिक पब्लिक IP की सलाह दी जाती है, साथ में ऐसा फ़ायरवॉल नियम जो सिर्फ़ उन्हीं पतों को इजाज़त दे जिनकी आप उम्मीद करते हैं। Server मोड सिर्फ़ यह बदलता है कि सॉल्वर कहाँ सुनता है, और कुछ नहीं: हार्डवेयर अब भी आपका ही है, और वह अब भी बिना मीटर के है। होस्ट और पोर्ट एनवायरनमेंट से पढ़ें ताकि एक ही सुइट दोनों जगह चले। क्लाइंट CAPSKIP_HOST या CAPSKIP_PORT को अपने आप नहीं पढ़ता, इसलिए इन्हें constructor को पास करें, जैसा नीचे दिया गया पूरा उदाहरण करता है।
पूरा चलने वाला उदाहरण
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()चारों exception CapSkipError से निकले हैं, इसलिए उसी एक को पकड़ने पर SDK जितनी भी विफलताएँ उठा सकता है, वे सब एक ही ब्लॉक में सँभल जाती हैं। जब जवाब अलग-अलग हो तो ऊपर की तरह खास exception पकड़ें, और जब अलग न हो तो CapSkipError। driver.quit को finally ब्लॉक में रखें: वरना जो टेस्ट solve के बीच में मर जाता है वह एक सेशन को डिवाइस पकड़े हुए छोड़ देता है, और यह तब तक चलता है जब तक आपका अभी-अभी बढ़ाया हुआ idle टाइमर आख़िरकार खत्म न हो जाए।
बाकी types भी उसी क्लाइंट से इसी तरह काम करते हैं। Turnstile एक sitekey और पेज URL लेता है, GeeTest एक gt वैल्यू, एक challenge और पेज URL लेता है, और ALTCHA पेज URL तथा एक challenge endpoint लेता है। पैकेज जितने भी मेथड देता है, उन सबकी सूची Python कैप्चा सॉल्वर पेजपर है, और इमेज type के लिए मौजूद है अपना एक अलग पेज.
आम errors और उनका मतलब
| आप जो देखते हैं | कारण | फिक्स |
|---|---|---|
| धीमे solve के बाद सेशन गायब हो जाता है, और उसके बाद हर कमांड फ़ेल होती है | आपका कोड सॉल्वर का इंतज़ार कर रहा था और तभी newCommandTimeout खत्म हो गया | इसे औसत solve से नहीं, सबसे लंबे solve से ऊपर करें |
| पहले solve पर एक NetworkException | CapSkip चल नहीं रहा, या टेस्ट प्रोसेस किसी बिल्ड एजेंट पर है और लूपबैक की ओर इशारा कर रही है | CapSkip शुरू करें, फिर Local मोड और Server मोड में से चुनें |
| इमेज पर 120 सेकंड बताने वाला TimeoutException | इमेज type लंबे reCAPTCHA वाले के बजाय डिफ़ॉल्ट पोलिंग टाइमआउट इस्तेमाल करता है | कुछ भी बढ़ाने से पहले देखें कि सॉल्वर चल रहा है और ओवरलोड नहीं है |
| साफ़ इमेज पर भी जवाब हर बार ग़लत आता है | पूरी स्क्रीन का स्क्रीनशॉट, या ऐसा एलिमेंट जिसमें पैडिंग और लेबल भी शामिल हैं | सबसे भीतर वाले उस व्यू का स्क्रीनशॉट लें जिसमें सिर्फ़ कैप्चा है |
| base64 या गायब फ़ाइल का ज़िक्र करने वाला ValidationException | एलिमेंट का स्क्रीनशॉट खाली लौटा, इसलिए स्ट्रिंग इतनी छोटी थी कि उसे इमेज की तरह पढ़ा ही नहीं जा सका | देखें कि स्क्रीनशॉट से पहले एलिमेंट स्क्रीन पर और दिखाई देने लायक था, और यह भी कि find ने सचमुच उसी को मैच किया |
| reCAPTCHA मेथड ऐसा टोकन लौटाता है जिसे साइट हर बार ठुकरा देती है | विजेट hCaptcha है, जिस पर data-sitekey भी होता है और जो सपोर्टेड type नहीं है | सेलेक्टर को g-recaptcha क्लास तक सीमित करें और पक्का करें कि पेज कौन-सा विजेट लोड करता है |
| कॉन्टेक्स्ट की सूची में सिर्फ़ NATIVE_APP है | WebView डिबग करने लायक नहीं है, इसलिए Appium उससे जुड़ नहीं सकता | ऐप टीम से कहें कि जिस बिल्ड की आप जाँच कर रहे हैं उसमें WebView डिबगिंग चालू कर दे |
| WebView स्टेप के तुरंत बाद find_element फ़ेल हो जाता है | ड्राइवर अब भी वेब कॉन्टेक्स्ट में है और DOM में खोज रहा है | नेटिव एलिमेंट छूने से पहले NATIVE_APP पर वापस स्विच करें |
| reCAPTCHA फ़ील्ड भर गया है लेकिन बटन कुछ नहीं करता | पेज फ़ील्ड पढ़ने के बजाय विजेट के कॉलबैक का इंतज़ार कर रहा है | कॉलबैक भी चलाएँ, या फ़ॉर्म सीधे सबमिट करें |
| सॉल्वर ने जो टोकन लौटाया, साइट उसे ठुकरा देती है | सॉल्वर को दिया गया पेज URL WebView से पढ़ा नहीं गया था, अंदाज़े से लिखा गया था | वेब कॉन्टेक्स्ट के भीतर से driver.current_url पास करें |
FAQ
क्या फ़ोन या एमुलेटर को सॉल्वर तक पहुँचना ज़रूरी है?
नहीं, और इस सेटअप के बारे में समझने लायक सबसे काम की बात यही है। CapSkip को HTTP कॉल आपकी Python प्रोसेस करती है, इसलिए डिवाइस को कभी सिर्फ़ एक स्क्रीनशॉट रिक्वेस्ट और एक send_keys ही दिखता है। फ़ोन पर कुछ भी इंस्टॉल करने की ज़रूरत नहीं, ऐप का कोई ट्रैफ़िक रीडायरेक्ट नहीं होता, और एमुलेटर का अपना होस्ट पता इसमें कहीं आता ही नहीं। केबल से जुड़ा असली डिवाइस, एमुलेटर और क्लाउड डिवाइस, तीनों सॉल्वर की नज़र में एक जैसा बर्ताव करते हैं।
क्या CI में चलने वाले Appium टेस्ट CapSkip इस्तेमाल कर सकते हैं?
हाँ, Server मोड के ज़रिए। होस्टेड रनर आपके लूपबैक पते को नहीं देख सकता, इसलिए CapSkip को कनेक्शन सेटिंग्स में अपने नेटवर्क पते या पब्लिक IP पर सुनने के लिए बदल दें और होस्ट एनवायरनमेंट वेरिएबल को उसी की ओर कर दें। जब रास्ता इंटरनेट से होकर जाए तो स्टैटिक पब्लिक IP इस्तेमाल करें और उसे फ़ायरवॉल नियम से सीमित रखें। सॉल्वर हर हाल में आपके अपने हार्डवेयर पर रहता है, इसलिए टेस्ट आपकी मेज़ से हटकर कहीं और चले जाएँ तब भी लाइसेंस या solve की संख्या में कुछ नहीं बदलता।
क्या यह सब सिर्फ़ Android के लिए है?
नहीं। UiAutomator2Options की जगह XCUITestOptions रख दें, जिसे appium.options.ios से इम्पोर्ट किया जाता है, और रन का ढाँचा बिल्कुल वही रहता है, क्योंकि एलिमेंट स्क्रीनशॉट, कॉन्टेक्स्ट स्विचिंग और idle टाइमर, तीनों ड्राइवर से ऊपर रहते हैं। सिर्फ़ लोकेटर बदलते हैं, क्योंकि iOS में resource id नहीं होते: जहाँ ऐप सेट करता हो वहाँ accessibility id इस्तेमाल करें, और जहाँ न करता हो वहाँ predicate या class chain। सॉल्वर को कभी पता ही नहीं चलता कि उसे इमेज किस प्लैटफ़ॉर्म से दी गई थी।
मुझे कैप्चा हल करना चाहिए या टेस्ट के लिए उसे बंद कर देना चाहिए?
अगर ऐप आपका है और आप ऐसा कर सकते हैं, तो बंद कर दीजिए। ऐसा टेस्ट बिल्ड जो जाँच छोड़ देता है, या प्रोवाइडर की कोई ऐसी टेस्ट की जो हमेशा पास होती है, किसी भी solve से तेज़ और ज़्यादा भरोसेमंद है और आपके सुइट से एक निर्भरता हटा देती है। हल करना तब अपनी जगह बनाता है जब स्क्रीन आपके कंट्रोल में न हो: आपके flow के अंदर किसी तीसरे पक्ष का लॉगिन, किसी पार्टनर का साइनअप फ़ॉर्म, ऐसा स्टेजिंग एनवायरनमेंट जिसे कोई आपके लिए नहीं बदलेगा, या प्रोडक्शन के सामने चलने वाला डिवाइस-क्लाउड रन। यही वे हालात हैं जहाँ चुनाव सॉल्वर और इंसान के बीच होता है।
संक्षेप में
बाकी कुछ भी लिखने से पहले newCommandTimeout बढ़ाएँ, क्योंकि डिफ़ॉल्ट 60 सेकंड सॉल्वर को मिली छूट से कम हैं और टेस्ट के बीच सेशन का मर जाना बिल्कुल किसी और ही बग जैसा दिखता है। स्क्रीन नहीं, एलिमेंट का स्क्रीनशॉट लें, उसे base64 डेटा URI के रूप में पास करें, और नतीजा send_keys से वापस टाइप करें। WebView के लिए कॉन्टेक्स्ट स्विच करें, DOM से sitekey और मौजूदा URL पढ़ें, रिस्पॉन्स फ़ील्ड भरें, फिर वापस स्विच कर जाएँ। जिस पल टेस्ट प्रोसेस सॉल्वर के साथ एक ही मशीन साझा करना बंद करे, उसी पल Server मोड पर चले जाएँ।
- नेटिव ऐप कैप्चा के पीछे कौन-सा type है और उसे पढ़ा कैसे जाता है: इमेज कैप्चा सॉल्वर पेज.
- WebView वाले मामले में और ब्राउज़र वाले मामले में जो कुछ भी एक जैसा है: reCAPTCHA सॉल्वर पेज.
एक आख़िरी बात, जो तय करती है कि आप retry कैसे लिखेंगे। चूँकि यह कैप्चा सॉल्वर उसी हार्डवेयर पर चलता है जो पहले से आपका है, इसलिए ख़राब क्रॉप हुई इमेज पर दूसरी कोशिश की कोई कीमत नहीं होती, तो रन को फ़ेल करने के बजाय टेस्ट आसानी से एक साफ़ स्क्रीनशॉट लेकर दोबारा कोशिश कर सकता है।
