Bir PWA'da CAPTCHA ilk HTML yanıtında yer almaz; JavaScript çerçevesi arayüzü render ettikten sonra DOM'a eklenir. Bu yüzden bir Aşamalı Web Uygulamasında CAPTCHA çözmenin asıl zorluğu çözüm motoru değil, zamanlamadır: widget henüz oluşturulmadan sitekey'i okumaya çalışırsanız elinizde null kalır.
Doğru sıralama üç adımdan ibaret; bu rehber Playwright ve CaptchaAI ile bunların hepsini kurar (sonda Selenium tabanlı bir Python alternatifi de var):
- CAPTCHA widget'ının DOM'a girmesini bir MutationObserver ile bekleyin
- sitekey ile pageurl'i CaptchaAI API'sine gönderin
- dönen token'ı SPA formuna işleyip callback'i tetikleyin
Geleneksel sitelerle PWA'lar arasındaki fark
Geleneksel bir web sitesi CAPTCHA'yı ilk HTML yanıtında sunar; okumak için tek yapmanız gereken sayfayı çekmektir. PWA'lar bu varsayımı birkaç noktada bozar:
- CAPTCHA yüklenmesi: geleneksel sitede ilk HTML yanıtında gelir; PWA'da sayfa yüklendikten sonra JavaScript ile render edilir.
- Sayfa gezinmesi: gelenekselde tam sayfa yeniden yüklenir; PWA'da istemci tarafı yönlendirme kullanılır, yeniden yükleme olmaz.
- Service Worker: geleneksel sitede yoktur; PWA'da kaynakları önbelleğe alır ve istekleri yakalayabilir.
- DOM erişilebilirliği: gelenekselde anında hazırdır; PWA'da yalnızca çerçeve render ettikten sonra.
- Ağ istekleri: gelenekselde doğrudan gider; PWA'da Service Worker tarafından yakalanabilir.
Örnek senaryo: e-ticaret ödeme akışı QA'sı
Türkiye'deki e-ticaret ekipleri ödeme (checkout) akışlarını çoğu zaman React ya da Vue tabanlı PWA'lar olarak geliştirir. Bir QA mühendisi staging.example.com/qa-checkout ortamında sepetten ödeme adımına geçtiğinde, Turnstile widget'ının yalnızca istemci tarafı yönlendirmeden sonra render edildiğini görür. İşte tam olarak burada, aşağıdaki Adım 3'teki rota dinleyicisi devreye girer: önce waitForURL('**/checkout') ile rota geçişinin tamamlanmasını bekleyin, ardından widget'ı yakalayın.
KVKK notu: Kişisel veri içeren test senaryolarını yalnızca yetkili QA ortamlarında çalıştırın; canlı müşteri verisiyle otomasyon testi yapmayın.
Adım 1: Dinamik CAPTCHA render'ını bekleyin
En sık yapılan hata, PWA çerçevesi CAPTCHA widget'ını henüz oluşturmadan sitekey çıkarmaya çalışmaktır. Bunun yerine bir MutationObserver ile DOM'u izleyin ya da çerçeveye özgü sinyalleri bekleyin:
// pwa_captcha_detector.js — Playwright script
const { chromium } = require('playwright');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function detectCaptchaInPWA(page) {
// Wait for the PWA app shell to render
await page.waitForLoadState('networkidle');
// Use MutationObserver to detect dynamically loaded CAPTCHAs
const captchaInfo = await page.evaluate(() => {
return new Promise((resolve) => {
// Check if CAPTCHA is already present
const existing = document.querySelector('.g-recaptcha, .cf-turnstile');
if (existing) {
resolve({
type: existing.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: existing.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
// Watch for CAPTCHA elements added dynamically
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
if (node.nodeType !== 1) continue;
const captcha = node.matches?.('.g-recaptcha, .cf-turnstile')
? node
: node.querySelector?.('.g-recaptcha, .cf-turnstile');
if (captcha) {
observer.disconnect();
resolve({
type: captcha.classList.contains('g-recaptcha')
? 'recaptcha_v2' : 'turnstile',
sitekey: captcha.getAttribute('data-sitekey'),
pageurl: window.location.href,
});
return;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true,
});
// Timeout after 15 seconds
setTimeout(() => {
observer.disconnect();
resolve(null);
}, 15000);
});
});
return captchaInfo;
}
async function main() {
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext();
const page = await context.newPage();
await page.goto('https://example-pwa.com/login');
const captcha = await detectCaptchaInPWA(page);
if (!captcha) {
console.log('No CAPTCHA detected');
await browser.close();
return;
}
console.log(`Detected ${captcha.type}: ${captcha.sitekey}`);
// Solve with CaptchaAI
const token = await solveCaptcha(captcha);
console.log(`Token: ${token.substring(0, 50)}...`);
// Inject token
await injectToken(page, captcha.type, token);
// Submit form
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: 'networkidle' });
console.log('Form submitted');
await browser.close();
}
async function solveCaptcha(captcha) {
const params = {
key: API_KEY,
pageurl: captcha.pageurl,
json: '1',
};
if (captcha.type === 'recaptcha_v2') {
params.method = 'userrecaptcha';
params.googlekey = captcha.sitekey;
} else {
params.method = 'turnstile';
params.sitekey = captcha.sitekey;
}
const submit = await axios.get(
'https://ocr.captchaai.com/in.php', { params }
);
if (submit.data.status !== 1) throw new Error(submit.data.request);
const taskId = submit.data.request;
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
const poll = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: '1' },
});
if (poll.data.status === 1) return poll.data.request;
if (poll.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(poll.data.request);
}
}
throw new Error('Timeout');
}
async function injectToken(page, type, token) {
if (type === 'recaptcha_v2') {
await page.evaluate((t) => {
document.getElementById('g-recaptcha-response').value = t;
try {
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach((k) => {
Object.keys(clients[k]).forEach((j) => {
if (clients[k][j]?.callback) clients[k][j].callback(t);
});
});
} catch (e) {}
}, token);
} else {
await page.evaluate((t) => {
const input = document.querySelector('[name="cf-turnstile-response"]');
if (input) input.value = t;
const cb = document.querySelector('.cf-turnstile')
?.getAttribute('data-callback');
if (cb && typeof window[cb] === 'function') window[cb](t);
}, token);
}
}
main().catch(console.error);
Adım 2: Service Worker önbelleğini yönetin
Service Worker'lar CAPTCHA komut dosyalarını önbelleğe alabilir; bu da eski (stale) widget'ların gösterilmesine yol açar. Gerektiğinde ilgili istekler için önbelleği devre dışı bırakın:
// Intercept and QA tanılama Service Worker cache for CAPTCHA scripts
await page.route('**/recaptcha/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
await page.route('**/turnstile/**', (route) => {
route.continue({ headers: { ...route.request().headers(), 'Cache-Control': 'no-cache' } });
});
İpucu: Service Worker'ı tamamen kapatmak yerine yalnızca CAPTCHA kaynaklarının önbelleğini geçersiz kılmak, uygulamanın geri kalanının hızını korur.
Adım 3: İstemci tarafı gezinmeyi izleyin
PWA'lar istemci tarafı yönlendirme kullanır; CAPTCHA korumalı bir rotaya gitmek tam sayfa yüklemesini tetiklemez. Rota değişikliklerini dinleyin ve yeni rotada algılamayı yeniden çalıştırın:
// Monitor PWA route changes for new CAPTCHAs
await page.evaluate(() => {
const originalPushState = history.pushState;
history.pushState = function() {
originalPushState.apply(this, arguments);
window.dispatchEvent(new Event('pwa-route-change'));
};
});
page.on('console', async (msg) => {
// React to route changes if needed
});
// Or wait for specific route
await page.waitForURL('**/checkout', { waitUntil: 'networkidle' });
// Then detect CAPTCHA on the new route
const captcha = await detectCaptchaInPWA(page);
Adım 4: Selenium ile Python alternatifi
Playwright yerine Selenium kullanıyorsanız aynı akış — bekle, çıkar, çöz, forma işle — Python'da şöyle görünür:
# pwa_captcha_selenium.py
import time
import requests
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
API_KEY = "YOUR_API_KEY"
driver = webdriver.Chrome()
driver.get("https://example-pwa.com/login")
# Wait for PWA to render CAPTCHA
wait = WebDriverWait(driver, 20)
captcha_el = wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".g-recaptcha, .cf-turnstile"))
)
sitekey = captcha_el.get_attribute("data-sitekey")
pageurl = driver.current_url
is_turnstile = "cf-turnstile" in captcha_el.get_attribute("class")
# Submit to CaptchaAI
params = {"key": API_KEY, "pageurl": pageurl, "json": "1"}
if is_turnstile:
params["method"] = "turnstile"
params["sitekey"] = sitekey
else:
params["method"] = "userrecaptcha"
params["googlekey"] = sitekey
resp = requests.get("https://ocr.captchaai.com/in.php", params=params)
task_id = resp.json()["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",
})
if poll.json().get("status") == 1:
token = poll.json()["request"]
break
else:
raise TimeoutError("CAPTCHA not solved")
# Inject token
driver.execute_script(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
""")
driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()
print("Form submitted")
driver.quit()
Sorun giderme
| Sorun | Sebep | Çözüm |
|---|---|---|
| CAPTCHA öğesi hiç görünmüyor | PWA rotayı henüz render etmedi | waitForSelector'ı uzatılmış zaman aşımıyla kullanın; istemci tarafı yönlendirmenin bittiğinden emin olun |
| Gezinmeden sonra eski sitekey | Service Worker önbellekten HTML sundu | CAPTCHA kaynakları için önbellek başlıklarını devre dışı bırakın |
| Token enjeksiyon callback'i bulunamadı | PWA çerçevesi durumu farklı yönetir | React/Vue/Angular durum yönetimini kontrol edin; form durumu güncellemesini tetikleyin |
| Form gönderimi token'ı iletmiyor | SPA form işleyicisi DOM'dan değil bileşen durumundan okuyor | Çerçevenin durumunu da güncelleyin (ör. React ref, Vue reactive özelliği) |
Token geçerliliğini yönetme
Bir SPA'da çözülmüş token uzun süre ekranda kalabilir, ama bu geçerli olduğu anlamına gelmez. Şu kuralları uygulayın:
- Rota değişiklikleri, kimlik doğrulama durumu değişiklikleri veya Service Worker yenilemeleri sayfa içeriğini değiştirdiğinde, önbelleğe alınmış CAPTCHA token'larını geçersiz kılın.
- Çözülmüş token'ı aktif görünüme yeniden bağlamak için yalnızca gereken minimum durumu saklayın.
- Arka planda çözülmüş bir token'ın hâlâ geçerli olduğunu varsaymak yerine, göndermeden hemen önce güncelliğini doğrulayın.
SSS
PWA'da CAPTCHA sitekey'ini ne zaman çıkarmalıyım?
Widget DOM'a eklendikten sonra. PWA çerçevesi arayüzü render etmeden data-sitekey çoğu zaman null döner; bu yüzden MutationObserver veya waitForSelector ile öğenin görünmesini bekleyin, ancak ondan sonra değeri okuyun.
Service Worker önbelleği çözümü nasıl bozar?
Service Worker eski bir CAPTCHA komut dosyasını ya da HTML'i önbellekten sunarsa, geçersiz bir sitekey'le çalışırsınız. Önbellek başlıklarını no-cache ile geçersiz kılın veya otomasyon sırasında Service Worker'ı devre dışı bırakın.
React veya Vue tabanlı bir SPA'da token neden forma işlenmiyor?
Çünkü SPA form işleyicisi değeri DOM'dan değil bileşen durumundan okur. Gizli input alanını doldurmak tek başına yeterli değildir; çerçevenin durumunu da (React ref, Vue reactive özelliği) güncelleyip callback'i tetiklemeniz gerekir.
CaptchaAI PWA'larda hangi CAPTCHA türlerini çözer?
reCAPTCHA v2 (görünmez ve callback varyantları dahil), reCAPTCHA v3 ve Cloudflare Turnstile gibi türleri çözer — doğrulamanın bir PWA'da mı yoksa klasik bir sayfada mı sunulduğu fark etmez, token aynıdır. hCaptcha ve FunCaptcha ise henüz desteklenmiyor.
PWA otomasyonu için hangi CaptchaAI planı yeterli?
Tek bir tarayıcı oturumu ve düşük hacimli QA için BASIC ($15/ay, 5 thread) başlangıç olarak yeterlidir. Paralel çok sayıda oturum çalıştıracaksanız, daha fazla eşzamanlı thread sunan STANDARD ($30/ay, 15 thread) veya ADVANCE ($90/ay, 50 thread) planlarına geçin.
İlgili makaleler
- reCAPTCHA v2 callback sorununu API ile çözme
- GeeTest ve Cloudflare Turnstile karşılaştırması
- Token düzeltmesinden sonra Cloudflare Turnstile 403 hatası
Sonraki adımlar
PWA'larda CAPTCHA çözmeye başlayın — CaptchaAI API anahtarınızı alın ve dinamik algılamayı otomasyon iş akışınıza ekleyin.
İlgili kılavuzlar: