From 3179bac0264a247ced3ac0da837bde738d6fce66 Mon Sep 17 00:00:00 2001 From: apuc Date: Tue, 22 Sep 2026 12:43:04 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=BE=20?= =?UTF-8?q?=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BF=D0=B0=D0=B4=D0=B0=D0=B5=D1=82=20=D0=B2=20=D0=B2?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=BE=D1=81=20=D1=81=D0=BA=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B8=D0=BD=D0=B3=D0=B0=20=E2=80=94=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=20=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D1=8B=D0=B9=20fallback?= =?UTF-8?q?=20'textarea'=20=D0=BF=D1=80=D0=B8=20=D0=BD=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=87=D0=B8=D0=B8=20task=5F*,=20=D0=BD=D0=B0=D0=B4=D1=91=D0=B6?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B8=D0=BA=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=B5=D0=BB=D1=8E=20=D0=BF=D0=B8=D1=81=D1=8C=D0=BC=D0=B0,=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE?= =?UTF-8?q?=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hh_browser.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/hh_browser.py b/hh_browser.py index 24dd0b9..714edc5 100644 --- a/hh_browser.py +++ b/hh_browser.py @@ -29,12 +29,13 @@ RESPONSE_BUTTONS = [ ] # hh.ru прячет поле письма за переключателем «Сопроводительное письмо / Добавить»: # до клика по нему на форме есть только textarea вопроса скрининга (task_*). +# ВАЖНО: запасной селектор 'textarea' НЕ используется, если есть вопросы скрининга — +# иначе текст письма попадёт в первый вопрос вместо поля письма. LETTER_TOGGLE = '[data-qa="vacancy-response-letter-toggle"]' MESSAGE_FIELDS = [ 'textarea[data-qa="vacancy-response-popup-form-letter-input"]', 'textarea[data-qa="vacancy-response-letter"]', 'textarea[data-qa="vacancy-response-popup-form"] textarea', - 'textarea', ] SUBMIT_BUTTONS = [ '[data-qa="vacancy-response-submit-popup"]', @@ -272,18 +273,18 @@ def open_response_page(account_dir, user, account, vacancy, message, return False, "Не нашёл кнопку «Откликнуться» — проверьте скриншот", shot # Ждём модалку - page.wait_for_timeout(2000) + page.wait_for_timeout(2500) # hh.ru прячет поле письма за переключателем «Сопроводительное письмо» — # раскрываем его, иначе заполнится textarea вопроса скрининга (task_*) try: toggle = page.locator(LETTER_TOGGLE).first - if toggle.is_visible(timeout=2000): - toggle.click() - page.wait_for_timeout(800) - print(" ✓ Переключатель «Сопроводительное письмо» раскрыт") + toggle.wait_for(state="visible", timeout=5000) + toggle.click() + page.wait_for_timeout(800) + print(" ✓ Переключатель «Сопроводительное письмо» раскрыт") except Exception: - pass + print(" ⚠ Переключатель письма не найден — поле может отсутствовать") # Выбор резюме (радио-кнопка name="resumeId" value="{hash}") if resume_id: @@ -307,6 +308,19 @@ def open_response_page(account_dir, user, account, vacancy, message, except Exception: continue + if not filled: + # Запасной вариант: если вопросов скрининга НЕТ, единственная + # textarea на форме — это поле письма (старая версия формы). + # При наличии task_* НЕ заполняем — иначе письмо попадёт в вопрос. + try: + if page.locator('textarea[name^="task_"]').count() == 0: + field = page.locator('textarea').first + if field.is_visible(timeout=2000): + field.fill(message) + filled = True + except Exception: + pass + if not filled: shot = _save_screenshot(page, screenshot_dir, vacancy["id"]) return False, "Не нашёл поле сообщения — проверьте скриншот", shot @@ -336,7 +350,22 @@ def open_response_page(account_dir, user, account, vacancy, message, # ловим любые ошибки, но отклик уже отправлен. try: page.wait_for_timeout(2500) + # Проверяем, не осталась ли форма открытой с ошибкой валидации + # (например, не заполнены обязательные вопросы скрининга) + form_open = page.locator('[data-qa="vacancy-response-popup-form"]').count() + err_visible = False + if form_open: + for sel in ['[data-qa="vacancy-response-error"]', + '[data-qa="vacancy-response-popup-form"] [class*="error"]']: + try: + if page.locator(sel).first.is_visible(timeout=1000): + err_visible = True + break + except Exception: + continue shot = _save_screenshot(page, screenshot_dir, vacancy["id"]) + if form_open and err_visible: + return False, "hh.ru не принял отклик — проверьте скриншот (возможно, не заполнены обязательные вопросы)", shot except Exception: shot = None return True, "Отклик отправлен", shot