Ожидание, фреймы и SPA
navigate вернул управление — это не значит, что кнопка уже в DOM. На SPA форма рисуется после XHR, во iframe её не видно из корневого документа. Надёжный порядок: открыть → ждать конкретный элемент → кликать.
Почему не sleep
Фиксированные sleep(3) либо мало (элемент ещё нет), либо много (робот тормозит на каждой странице). Сеть, кэш и SPA непредсказуемы: одна и та же форма на офисном канале появляется за 0,4 с, через прокси — за 8 с.
Sleep маскирует гонку: сегодня «повезло», завтра после релиза фронта — клик в пустоту. Wait привязан к факту: поле есть в DOM, текст «Готово» появился, спиннер пропал.
Что именно ждать
Выберите один стабильный признак готовности и не меняйте его внутри сценария:
- Поле формы —
name/idлогина. Дальше заполнение: гайд по формам. - Текст — «Корзина», «Спасибо», заголовок шага wizard.
- URL — если редирект после submit надёжнее DOM (осторожно: SPA часто не меняет path).
- Исчезновение — спиннер/оверлей: сначала wait появления контента под ним, не «подождать 2 секунды».
Нестабильно: порядковый номер элемента, класс из CSS-модулей, динамический id вида react-aria-…. Их хватает на один прогон и ломает следующий релиз сайта.
wait и exists
Общие методы — в a_common. Две семьи:
- wait_* — крутить проверку, пока элемент не появится или не кончится таймаут шаблона.
- is_exist_* — один снимок «есть / нет», без ожидания. Удобно в ветке: капча всплыла или нет.
Wait по разным ключам (все на хабе a_common):
- wait_element_exist_by_name
- wait_element_exist_by_id
- wait_element_exist_by_attribute($attr_name, $attr_value, $exactly=true, $frame=-1) — с 7.0.45
- wait_element_exist_by_inner_text · by_inner_html
- by_outer_text · by_outer_html
- wait_element_exist_by_xpath
- wait_element_exist_by_number — хрупко, только если других ключей нет
- Поле внутри формы: by_attribute_by_form_name / by_form_number
Разовая проверка: is_exist_by_name($name, $frame=-1).
$browser->navigate("https://example.com/app");
if ($browser->get_last_navigation_error()) {
echo $browser->get_last_navigation_error();
}
$input->wait_element_exist_by_attribute("name", "login", true, -1);
if (!$input->is_exist_by_name("login")) {
echo "поля логина нет — другой шаблон страницы";
}
Поиск: wait_element_exist.
Таймаут шаблона
Время ожидания задаётся переменной $iSecondsWaitElementExistBeforeAction (по умолчанию 15 секунд) в файле шаблона xweb_human_emulator для каждого языка. Не плодите разные sleep в каждом скрипте: один таймаут на проект, исключения — явно в комментарии.
15 секунд мало для тяжёлого кабинета за медленным прокси — поднимите в шаблоне. 15 секунд много для внутреннего стенда — уменьшите, чтобы падать быстрее на опечатке селектора.
Один таймаут на проект лучше десятка разных sleep. Если один шаг (оплата, выгрузка отчёта) объективно дольше — поднимите переменную перед ним и верните обратно, либо вынесите этот шаг в отдельный скрипт с своим шаблоном. Не копируйте «магические» 30 секунд в каждый файл «на всякий случай»: ночной робот просто станет медленным.
Элемент во iframe
Почти все DOM-методы принимают $frame = -1 (корень документа). Если поле внутри фрейма — передайте номер. Вложенные фреймы: строка "0:1" (первый фрейм страницы, внутри него второй). Объект frame — посчитать и найти сами фреймы.
// форма оплаты во втором фрейме корня
$input->wait_element_exist_by_name("card", 1);
// фрейм внутри фрейма
$input->wait_element_exist_by_name("cvv", "0:1");
Методы *_in_frame* и параметр $frame у обычных wait — один и тот же смысл: область поиска не корень.
SPA и «пустая» страница
navigate может завершиться по загрузке каркаса, пока данные ещё в полёте. Документ «готов», форма — нет.
- ключевое поле формы — как в гайде по формам;
- текст в контейнере: wait_element_exist_by_inner_text или by_inner_html;
- селектор CSS — get_by_query_selector.
get_last_navigation_error отвечает за HTTP/сеть (DNS, 404, обрыв), не за отрисовку React. Пустая ошибка навигации и пустой DOM одновременно — классика SPA: сеть ок, JS ещё рисует.
Спиннер: не sleep(2) после клика. Ждите появления текста результата или исчезновения оверлея через повторный is_exist в цикле с тем же таймаутом шаблона. Cookie-баннер и модалка логина часто перекрывают поле: сначала закройте их, потом wait формы.
Полный прогон: открыть → ждать → кликнуть
Минимальный каркас, который стоит копировать в новый скрипт вместо «navigate и сразу click»:
$browser->navigate("https://example.com/cabinet");
$err = $browser->get_last_navigation_error();
if ($err) {
echo $err;
}
$input->wait_element_exist_by_attribute("name", "login", true, -1);
if (!$input->is_exist_by_name("login")) {
echo "нет поля login — другой шаблон или iframe";
}
$input->set_value_by_name("login", "user");
$anchor->wait_element_exist_by_inner_text("Войти");
$anchor->click_by_inner_text("Войти");
$anchor->wait_element_exist_by_inner_text("Выход");
После wait заполнение полей — гайд по формам. Лог пишите после wait: logger.
Чеклист, если wait сорвался
- Инспектор: атрибут совпадает с тем, что в скрипте (регистр, пробелы).
- Элемент во frame? Смените
$frame. - Страница ещё на промежуточном URL (редирект логина).
- Оверлей/cookie-баннер перекрывает поле — сначала закрыть баннер.
- Таймаут шаблона меньше реальной загрузки.
- Не тот экземпляр Studio: порты.
Пишите в лог после wait, не до: logger. Иначе в журнале «страница готова», а клик уже упал.
FAQ
Wait всегда таймаутится. Селектор устарел или смотрите не тот frame. Сверьте в инспекторе, не в DevTools чужого Chrome.
Нашёл элемент, клик мимо. Элемент в DOM, но невидимый / нулевой размер / под модалкой. Wait проверяет наличие, не hit-test мыши. Иногда нужен click по видимому родителю.
XPath хрупкий? Да. Держите xpath запасным вариантом после name/attribute.
Wait нашёл элемент, is_exist сразу false. Страница перерисовалась (SPA), элемент с тем же name появился заново. После wait ещё раз проверьте exists перед click.
Фрейм «прыгает». Реклама и чат добавляют iframe; номер 1 сегодня — платёжка, завтра — виджет. Ищите frame по содержимому через объект frame, не зашивайте номер навсегда без комментария.
Связанные сбои: хаб ошибок, навигация.