Снимок элемента картинки на текущей странице (без повторной загрузки src) уходит в 2captcha как обычная капча (method=base64), ответ набирается в поле target как у typing и возвращается в result действия. Необязательные подсказки: min_len, max_len, numeric, lang, case_sensitive. Клиент: Action.captcha_image. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
3.6 KiB
Python
94 lines
3.6 KiB
Python
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from api.schemas import ActionRequest, SolveRequest, WAIT_FOR_DEFAULT_TIMEOUT
|
|
|
|
|
|
def solve(**kwargs) -> SolveRequest:
|
|
return SolveRequest(**{"url": "https://example.com", "actions": [], **kwargs})
|
|
|
|
|
|
@pytest.mark.parametrize("action, error", [
|
|
({"type": "nope"}, "unknown action type"),
|
|
({"type": "wait_for"}, "requires: selector"),
|
|
({"type": "fill", "selector": "#q"}, "requires: value"),
|
|
({"type": "fill", "selector": "#q", "value": ""}, "requires: value"),
|
|
({"type": "typing", "selector": "#q"}, "requires: value"),
|
|
({"type": "click"}, "requires: selector"),
|
|
({"type": "delay"}, "requires: value"),
|
|
({"type": "delay", "value": "abc"}, "integer"),
|
|
({"type": "delay", "value": "601"}, "between 0 and 600"),
|
|
({"type": "wait_for", "selector": "a", "timeout": 0}, "between 1 and 7200"),
|
|
({"type": "wait_for", "selector": "a", "timeout": 7201}, "between 1 and 7200"),
|
|
({"type": "captcha", "selector": "#c", "value": "datadome"}, "captcha value must be one of"),
|
|
({"type": "captcha", "selector": "img", "value": "image"}, "requires: target"),
|
|
({"type": "captcha", "selector": "#c", "value": "hcaptcha", "target": "#in"}, "only for action 'captcha'"),
|
|
({"type": "typing", "selector": "#q", "value": "x", "numeric": 1}, "only for action 'captcha'"),
|
|
({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "numeric": 5}, "numeric"),
|
|
({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "max_len": 21}, "max_len"),
|
|
({"type": "captcha", "selector": "img", "value": "image", "target": "#in", "min_len": 6, "max_len": 4},
|
|
"min_len must not exceed max_len"),
|
|
])
|
|
def test_invalid_action(action, error):
|
|
with pytest.raises(ValidationError, match=error):
|
|
ActionRequest(**action)
|
|
|
|
|
|
@pytest.mark.parametrize("action", [
|
|
{"type": "wait_for", "selector": "a", "timeout": 10},
|
|
{"type": "fill", "selector": "#q", "value": "x"},
|
|
{"type": "typing", "selector": "#q", "value": "Привет"},
|
|
{"type": "inner_text", "selector": "h1"},
|
|
{"type": "delay", "value": "0"},
|
|
{"type": "pass_challenge"},
|
|
{"type": "captcha", "selector": "#c", "value": "hcaptcha"},
|
|
{"type": "captcha", "selector": "img", "value": "image", "target": "#in"},
|
|
{"type": "captcha", "selector": "img", "value": "image", "target": "#in",
|
|
"min_len": 4, "max_len": 6, "numeric": 1, "lang": "en", "case_sensitive": True},
|
|
{"type": "inner_html", "selector": "h1"},
|
|
])
|
|
def test_valid_action(action):
|
|
assert ActionRequest(**action).type == action["type"]
|
|
|
|
|
|
def test_wait_for_default_timeout():
|
|
assert ActionRequest(type="wait_for", selector="a").timeout == WAIT_FOR_DEFAULT_TIMEOUT
|
|
|
|
|
|
@pytest.mark.parametrize("proxy", [
|
|
"http://host:8080",
|
|
"https://user:pass@host:443",
|
|
"socks5://10.0.0.1:1080",
|
|
])
|
|
def test_valid_proxy(proxy):
|
|
assert solve(proxy=proxy).proxy == proxy
|
|
|
|
|
|
@pytest.mark.parametrize("proxy, error", [
|
|
("host:8080", "scheme"),
|
|
("ftp://host:21", "scheme"),
|
|
("http://host", "host:port"),
|
|
("http://:8080", "host:port"),
|
|
("http://host:99999", "invalid proxy url"),
|
|
])
|
|
def test_invalid_proxy(proxy, error):
|
|
with pytest.raises(ValidationError, match=error):
|
|
solve(proxy=proxy)
|
|
|
|
|
|
def test_proxy_optional():
|
|
assert solve().proxy is None
|
|
assert solve(proxy="").proxy is None
|
|
|
|
|
|
def test_user_agent_rejected():
|
|
with pytest.raises(ValidationError, match="user_agent is not supported"):
|
|
solve(user_agent="Mozilla/5.0")
|
|
|
|
|
|
def test_captcha_action_requires_key():
|
|
action = {"type": "captcha", "selector": "#c", "value": "recaptcha"}
|
|
with pytest.raises(ValidationError, match="requires request field 'captcha'"):
|
|
solve(actions=[action])
|
|
assert solve(actions=[action], captcha="key").captcha == "key"
|