Мобильная эмуляция чаще всего ломает автоматизацию не из-за самой CAPTCHA, а из-за того, что скрипт ищет виджет по десктопным селекторам и заголовкам User-Agent, которых мобильная вёрстка не использует. Саму reCAPTCHA v2 CaptchaAI решает одинаково — по sitekey и pageurl — независимо от того, эмулируете вы iPhone, гоняете Chrome на реальном Android-устройстве через ADB или тестируете обычный десктоп. Разница в другом: адаптивная вёрстка, touch-события вместо кликов мыши и другой набор сигналов User-Agent, из-за которых сайт может отдать CAPTCHA-виджет в другой разметке.
В этом руководстве — два рабочих подхода: мобильная эмуляция Playwright на десктопе и Selenium с Chrome для Android через ADB.
Сценарий: форма reCAPTCHA v2 на мобильной версии сайта
Вы автоматизируете отправку формы на сайте, который определяет мобильный User-Agent и рендерит адаптированный под телефон макет с reCAPTCHA v2. Задача — извлечь sitekey из мобильной вёрстки, решить CAPTCHA через API и отправить форму из того же браузерного контекста, в котором проходило решение.
Это типичный кейс для QA-автоматизации мобильной версии интернет-магазина, формы записи или staging-окружения перед релизом: десктопные тесты уже зелёные, а мобильный сценарий падает именно на шаге с CAPTCHA, потому что верстка и селекторы там другие. Ниже — оба рабочих подхода, от эмуляции до реального устройства.
Чем мобильный контекст отличается от десктопного
Прежде чем писать код, полезно понимать, что именно меняется при переходе на мобильный контекст — это объясняет большинство ошибок из раздела «Типичные проблемы» ниже.
| Фактор | Десктоп | Мобильный | Влияние на CAPTCHA |
|---|---|---|---|
| User-Agent | Desktop Chrome/Firefox | Mobile Safari/Chrome | Некоторые сайты отдают разную конфигурацию CAPTCHA в зависимости от устройства |
| Область просмотра | 1920×1080+ | 390×844 | Виджет CAPTCHA может отрисовываться иначе |
| Тип взаимодействия | События мыши | Touch-события | Некоторые CAPTCHA проверяют тип взаимодействия перед выдачей задачи |
| Сеть | Широкополосный доступ | 4G/5G | Могут потребоваться более длинные таймауты и повторные попытки |
Для команд, чья тестовая аудитория подключается из регионов с менее стабильным мобильным интернетом (например, из Казахстана или других частей Центральной Азии), последняя строка — не теория: таймаут в 10–15 секунд, который отлично отрабатывает на широкополосном десктопе в CI, на реальном 4G может оборваться раньше, чем reCAPTCHA успеет отрисоваться. Закладывайте запас по таймауту и повтор запроса с самого начала, а не после первого падения пайплайна.
Способ 1: эмуляция устройства в Playwright (Python)
Эмуляция устройства в Playwright — самый простой способ автоматизировать мобильный браузер вместе с решением CAPTCHA: физическое устройство не нужно.
# playwright_mobile_captcha.py
import asyncio
import httpx
from playwright.async_api import async_playwright
API_KEY = "YOUR_API_KEY"
async def solve_recaptcha(sitekey: str, pageurl: str) -> str:
"""Submit reCAPTCHA v2 to CaptchaAI and poll for result."""
async with httpx.AsyncClient(timeout=180) as client:
# Submit task
resp = await client.get(
"https://ocr.captchaai.com/in.php",
params={
"key": API_KEY,
"method": "userrecaptcha",
"googlekey": sitekey,
"pageurl": pageurl,
"json": "1",
},
)
result = resp.json()
if result["status"] != 1:
raise Exception(f"Submit failed: {result['request']}")
task_id = result["request"]
# Poll for result
for _ in range(30):
await asyncio.sleep(5)
poll = await client.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["status"] == 1:
return poll_result["request"]
if poll_result["request"] != "CAPCHA_NOT_READY":
raise Exception(f"Solve failed: {poll_result['request']}")
raise Exception("Polling timeout")
async def main():
async with async_playwright() as p:
# Launch with iPhone 13 emulation
iphone = p.devices["iPhone 13"]
browser = await p.chromium.launch(headless=False)
context = await browser.new_context(**iphone)
page = await context.new_page()
await page.goto("https://example.com/mobile-form")
await page.wait_for_selector(".g-recaptcha", timeout=10000)
# Extract sitekey
sitekey = await page.get_attribute(".g-recaptcha", "data-sitekey")
pageurl = page.url
print(f"Found sitekey: {sitekey}")
# Solve via CaptchaAI
token = await solve_recaptcha(sitekey, pageurl)
print(f"Token received: {token[:50]}...")
# Inject token
await page.evaluate(f"""
document.getElementById('g-recaptcha-response').value = '{token}';
document.getElementById('g-recaptcha-response').style.display = '';
""")
# Trigger callback if it exists
await page.evaluate(f"""
try {{
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach(k => {{
Object.keys(clients[k]).forEach(j => {{
if (clients[k][j] && clients[k][j].callback) {{
clients[k][j].callback('{token}');
}}
}});
}});
}} catch(e) {{}}
""")
# Submit form
await page.click('button[type="submit"]')
await page.wait_for_load_state("networkidle")
print("Form submitted successfully")
await browser.close()
asyncio.run(main())
Способ 2: мобильная эмуляция в Selenium (JavaScript)
Для Node.js-пайплайнов используйте Selenium с мобильной эмуляцией через Chrome DevTools Protocol.
// selenium_mobile_captcha.js
const { Builder, By, until } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const axios = require('axios');
const API_KEY = 'YOUR_API_KEY';
async function solveCaptcha(sitekey, pageurl) {
// Submit task
const submitResp = await axios.get('https://ocr.captchaai.com/in.php', {
params: {
key: API_KEY,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: pageurl,
json: '1',
},
});
if (submitResp.data.status !== 1) {
throw new Error(`Submit failed: ${submitResp.data.request}`);
}
const taskId = submitResp.data.request;
// Poll for result
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
const pollResp = await axios.get('https://ocr.captchaai.com/res.php', {
params: { key: API_KEY, action: 'get', id: taskId, json: '1' },
});
if (pollResp.data.status === 1) return pollResp.data.request;
if (pollResp.data.request !== 'CAPCHA_NOT_READY') {
throw new Error(`Solve failed: ${pollResp.data.request}`);
}
}
throw new Error('Polling timeout');
}
async function main() {
// Configure mobile emulation
const mobileEmulation = {
deviceMetrics: { width: 390, height: 844, pixelRatio: 3.0 },
userAgent:
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) ' +
'AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1',
};
const options = new chrome.Options();
options.setMobileEmulation(mobileEmulation);
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
try {
await driver.get('https://example.com/mobile-form');
await driver.wait(until.elementLocated(By.css('.g-recaptcha')), 10000);
// Extract sitekey
const captchaEl = await driver.findElement(By.css('.g-recaptcha'));
const sitekey = await captchaEl.getAttribute('data-sitekey');
const pageurl = await driver.getCurrentUrl();
console.log(`Sitekey: ${sitekey}`);
// Solve CAPTCHA
const token = await solveCaptcha(sitekey, pageurl);
console.log(`Token: ${token.substring(0, 50)}...`);
// Inject token
await driver.executeScript(`
document.getElementById('g-recaptcha-response').value = arguments[0];
`, token);
// Submit form
await driver.findElement(By.css('button[type="submit"]')).click();
console.log('Form submitted');
} finally {
await driver.quit();
}
}
main().catch(console.error);
Типичные проблемы мобильной эмуляции и их решения
Если форма не проходит с первого раза, сверьтесь со списком — обычно причина одна из четырёх:
- CAPTCHA не отображается при мобильной эмуляции. Причина: сайт определяет эмуляцию через
navigator.platform. Решение: задайте переопределение platform в DevTools Protocol. - Поле
g-recaptcha-responseне находится. Причина: в мобильной вёрстке CAPTCHA рендерится по-другому. Решение: ищите textarea по атрибутуname, а не поid. - Токен подставлен, но форма всё равно не проходит. Причина: сервер сверяет User-Agent запроса решения и запроса отправки формы. Решение: используйте один и тот же User-Agent на обоих этапах.
- Страница долго грузится в эмуляции. Причина: тяжёлые ресурсы — изображения, шрифты, лишние скрипты. Решение: ускорьте загрузку флагом Chrome
--disable-images.
Вопросы про решение CAPTCHA в мобильной автоматизации
Почему поле для токена не находится по id в мобильной вёрстке?
Некоторые мобильные шаблоны рендерят g-recaptcha-response без стандартного id или прячут его глубже в DOM. Ищите элемент по name="g-recaptcha-response" — это надёжнее, чем полагаться на id, который вёрстка меняет чаще.
Нужно ли сообщать CaptchaAI, что CAPTCHA мобильная?
Нет. CaptchaAI решает задачу по sitekey и pageurl, а мобильный это контекст или десктопный — на процесс решения не влияет. Вся разница остаётся на стороне вашего скрипта: как он находит виджет и куда подставляет токен.
Как быть с таймаутами на нестабильном мобильном интернете?
Увеличивайте таймаут ожидания виджета и добавляйте повтор опроса res.php с экспоненциальной задержкой. На реальном 4G/5G, особенно в тестах из регионов с менее стабильным покрытием, разрыв соединения — обычный сценарий, а не исключение.
Чем ADB-автоматизация Android отличается от эмуляции в Playwright?
Playwright подделывает viewport, User-Agent и touch-события на десктопном Chromium — этого достаточно для большинства форм. ADB запускает Chrome на настоящем Android-устройстве или системном эмуляторе, поэтому видит реальные аппаратные сигналы, которые некоторые сайты проверяют отдельно от вёрстки.
Можно ли использовать прокси вместе с мобильной эмуляцией?
Да. И Playwright, и Selenium поддерживают прокси одновременно с мобильной эмуляцией. Передавайте тот же прокси в запрос к CaptchaAI, чтобы IP решения CAPTCHA совпадал с IP, с которого отправляется форма.
Что дальше
Оба подхода из этого руководства используют один и тот же API-вызов к CaptchaAI — разница только в том, как скрипт получает sitekey и куда подставляет токен. Дальше пригодятся следующие материалы: