feature: действие captcha value=image — капча-картинка через 2captcha (#109)
Снимок элемента картинки на текущей странице (без повторной загрузки 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>
This commit is contained in:
1 parent
502b6e1003
commit
5ed2cfbb3d
15 files changed
+301
-9
No files matched your search
@@ -221,3 +221,66 @@ def test_typing_like_a_human(client):
|
||||
assert data["status"] == "ok", data
|
||||
assert json.loads(data["actions"][-1]["result"])["form"]["custname"] == "Иван Smith-2"
|
||||
assert data["actions"][1]["duration_ms"] > 12 * 50 # посимвольно, с паузами
|
||||
|
||||
|
||||
|
||||
# капча-картинка (#109): картинка рисуется на canvas при загрузке, поэтому каждый новый запрос src дал бы другой код
|
||||
CAPTCHA_PAGE = b"""<!doctype html><body>
|
||||
<form id="signup"><img class="captcha"><input name="captcha" autocomplete="off"><div id="sent"></div></form>
|
||||
<script>
|
||||
const c = document.createElement('canvas'); c.width = 120; c.height = 40;
|
||||
const x = c.getContext('2d'); x.fillStyle = '#fff'; x.fillRect(0, 0, 120, 40);
|
||||
x.fillStyle = '#000'; x.font = '24px monospace'; x.fillText('k' + Math.random().toString(36).slice(2, 6), 8, 28);
|
||||
document.querySelector('img.captcha').src = c.toDataURL();
|
||||
document.querySelector('input').addEventListener('input', e => document.getElementById('sent').textContent = e.target.value);
|
||||
</script></body>"""
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def captcha_site():
|
||||
"""Страница с капчей-картинкой на локальном HTTP-сервере."""
|
||||
import threading
|
||||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||
self.end_headers()
|
||||
self.wfile.write(CAPTCHA_PAGE)
|
||||
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
|
||||
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
||||
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||
yield f"http://127.0.0.1:{server.server_port}/signup"
|
||||
server.shutdown()
|
||||
|
||||
|
||||
def test_captcha_image(client, captcha_site, monkeypatch):
|
||||
"""Снимок элемента (PNG размером с картинку) уходит в 2captcha, ответ набирается в поле."""
|
||||
import base64
|
||||
import struct
|
||||
from twocaptcha import TwoCaptcha
|
||||
|
||||
sent = {}
|
||||
|
||||
def fake_solve(self, **kwargs):
|
||||
sent.update(kwargs)
|
||||
return {"captchaId": "1", "code": "k42z"}
|
||||
|
||||
monkeypatch.setattr(TwoCaptcha, "solve", fake_solve)
|
||||
|
||||
data = solve(client, url=captcha_site, captcha="KEY", actions=[
|
||||
{"type": "captcha", "selector": "img.captcha", "value": "image", "target": "input[name=captcha]", "max_len": 6},
|
||||
{"type": "inner_text", "selector": "#sent"},
|
||||
])
|
||||
assert data["status"] == "ok", data
|
||||
assert [a.get("result") for a in data["actions"]] == ["k42z", "k42z"]
|
||||
|
||||
png = base64.b64decode(sent["body"])
|
||||
assert png.startswith(b"\x89PNG")
|
||||
width, height = struct.unpack(">II", png[16:24])
|
||||
assert (width, height) == (120, 40)
|
||||
assert sent["method"] == "base64" and sent["maxLen"] == 6
|
||||
Reference in new issue
Block a user