Перейти к содержимому

Лучшие практики и незаметность

Это правила, которые делают автоматизацию Santiago одновременно быстрой и необнаружимой. Следуйте им — и типичный поток заполнения формы выполняется за несколько сотен миллисекунд на страницу с очеловеченным, доверенным вводом; игнорируйте их — и вы либо отдаёте события isTrusted: false антибот-системам, либо тратите десятки секунд на слепые ожидания.

Все примеры обращаются к локальному демону. Задайте ID профиля один раз, затем вызывайте только действия /api/automation/$PROFILE/*:

Set the profile
PROFILE=<profile-id>

Конверт ответа всегда имеет вид { "ok": true, "data": {...} } или { "ok": false, "error": { "code", "message" } }.

evaluate выполняет JavaScript страницы через протокол отладки. Сам протокол невидим для страницы, но то, что делает ваш код внутри evaluate, полностью видно скриптам сайта. Используйте его для чтения, никогда — для взаимодействия.

Безопасно (невидимо для страницы)Запрещено (обнаружимо, обходит humanize)
document.titleelement.click()
querySelectorAll, значения атрибутовelement.focus()
getBoundingClientRect() (координаты элемента)element.dispatchEvent(...)
вычисленные стилиприсваивание element.value = ...
чтение document.activeElement, aria-expandedпрограммный form.submit(), прокрутка

Синтетический element.click() порождает MouseEvent с isTrusted: false, clientX/Y: 0 и без предшествующих mousemove / pointerdown / mouseover. Современные антибот-системы напрямую помечают isTrusted: false. Когда у профиля включён humanize, Camoufox добавляет движение курсора по кривой Безье только к нативным действиям Playwright — всё, что вы делаете через evaluate, обходит это полностью.

Read coords with evaluate, then click them
# OK -- evaluate stays read-only
curl -s localhost:7891/api/automation/$PROFILE/evaluate -X POST \
-H 'Content-Type: application/json' \
-d '{"code":"JSON.stringify(document.querySelector(\"#btn\").getBoundingClientRect())"}'
# Trusted, humanized click on the coords you just read
curl -s localhost:7891/api/automation/$PROFILE/mouse/click -X POST \
-H 'Content-Type: application/json' -d '{"x":512,"y":340}'
FORBIDDEN -- detectable, bypasses humanize
# curl ... /evaluate -d '{"code":"document.querySelector(\"#btn\").click()"}'
# curl ... /evaluate -d '{"code":"document.querySelector(\"input\").value = \"x\""}'
# curl ... /evaluate -d '{"code":"form.submit()"}'

fill-page — основной инструмент заполнения форм

Заголовок раздела «fill-page — основной инструмент заполнения форм»

fill-page — выбор по умолчанию для любой формы. Он полностью обходит локаторы Playwright (evaluatemouse.clickkeyboard.type), поэтому работает на сложных, анимированных и переходных страницах, где действия на основе ref застревают на проверках стабильности. Он обрабатывает текстовые поля и выпадающие списки combobox и включает отправку в том же вызове.

Fill all fields and submit in one call
curl -s localhost:7891/api/automation/$PROFILE/fill-page -X POST \
-H 'Content-Type: application/json' -d '{
"fields": [
{"selector": "#firstName", "value": "Daniel"},
{"selector": "#lastName", "value": "Moreno"},
{"selector": "[role=combobox]", "value": "March", "type": "combobox", "nth": 0},
{"selector": "[role=combobox]", "value": "Male", "type": "combobox", "nth": 1}
],
"submit": {"text": "Next"},
"waitAfterSubmit": 2500
}'
# Response: { results: [{selector, type, ok, selectedValue?}], submit: {ok, url} }

Типы полей:

  • "text" (по умолчанию): клик в центр → Ctrl+A → Backspace → keyboard.type.
  • "combobox": клик, чтобы открыть → поиск опции по тексту → клик по опции → проверка и авто-повтор.
  • nth (с нуля): когда selector совпадает с несколькими видимыми элементами, выбирает N-й — [role=combobox] с nth: 0 это первый, nth: 1 — второй.

Используйте batch для действий, не связанных с формами (клики по ссылкам, наведение, навигация, ожидание), или когда нужно нацеливание по ref из снимка. fill-form устарел — он сначала пробует локатор (2с) и откатывается на координаты; предпочитайте fill-page.

См. Заполнение форм для полного разобранного потока.

То, как выпадающий список выглядит в снимке, определяет эндпоинт.

Снимок показываетИспользуйтеПоведение
<select> (нативный элемент)select-option с values[]задаёт нативное значение напрямую
роль combobox (ARIA)select-combobox с valueклик → ожидание listbox → клик по опции, один вызов
Native select
curl -s localhost:7891/api/automation/$PROFILE/select-option -X POST \
-H 'Content-Type: application/json' -d '{"ref":"e2","values":["California"]}'
ARIA combobox
curl -s localhost:7891/api/automation/$PROFILE/select-combobox -X POST \
-H 'Content-Type: application/json' -d '{"ref":"e5","value":"March"}'

Некоторые сайты рендерят listbox в отдельном оверлее вне цели aria-controls у combobox. Тогда select-combobox завершается по таймауту с “locator #cN is hidden”. Не повторяйте select-combobox — вместо этого откройте выпадающий список настоящим click, прочитайте координаты центра опции через evaluate (только чтение), затем сделайте mouse/click по этим координатам (доверенный, очеловеченный):

Detached-overlay dropdown fallback
# 1. Open the dropdown with a real click
curl -s localhost:7891/api/automation/$PROFILE/click -X POST \
-H 'Content-Type: application/json' -d '{"ref":"e102"}'
# 2. Read the target option's center -- evaluate stays read-only
curl -s localhost:7891/api/automation/$PROFILE/evaluate -X POST \
-H 'Content-Type: application/json' \
-d '{"code":"const o = Array.from(document.querySelectorAll(\"li[role=option]\")).filter(e => e.offsetParent !== null).find(e => e.textContent.trim() === \"June\"); const r = o.getBoundingClientRect(); JSON.stringify({x: r.x + r.width/2, y: r.y + r.height/2})"}'
# 3. Click the coords with the mouse (trusted, humanized)
curl -s localhost:7891/api/automation/$PROFILE/mouse/click -X POST \
-H 'Content-Type: application/json' -d '{"x":512,"y":340}'

Один снимок, один batch — пересоздавайте снимок только при смене страницы

Заголовок раздела «Один снимок, один batch — пересоздавайте снимок только при смене страницы»

Сделайте один снимок, определите все нужные элементы, затем отправьте один batch-вызов со всеми действиями. Никогда не пересоздавайте снимок между действиями на одной странице.

The shape of a good flow
snapshot (~110ms) -> batch of N actions (N x ~200ms) -> done

Делайте новый снимок только когда страница фундаментально меняется:

  • переход на новый URL,
  • отправка формы, загружающая новую страницу,
  • модальное окно или оверлей, заменяющие содержимое страницы.

Не пересоздавайте снимок после заполнения полей, открытия выпадающих списков, ввода или наведения. Ссылки (e1, e2, …) остаются валидными, пока страница не изменится; они устаревают только после навигации, отправки на новый URL, перезагрузки или динамического содержимого, заменяющего DOM.

Batch останавливается на первой ошибке — если действие провалилось (неверный ref, элемент не найден), оставшиеся действия пропускаются. Изучите массив results ({ index, action, ok, data?, error? }), чтобы найти провалившееся действие, исправьте его и перезапустите с этой точки.

Playwright автоматически ждёт элементы перед действием, а эндпоинт batch уже добавляет случайные задержки 80–250мс между действиями для человекоподобного темпа. Оборачивание вызовов в sleep 1 / sleep 2 && curl … — чистая трата, которая складывается в десятки секунд на протяжении потока.

Если поток кажется более чем в ~2 раза медленнее ожидаемого, причина почти всегда в добавленном sleep или пересоздании снимка посреди страницы — уберите и то, и другое.

Когда вам действительно нужно дождаться конкретного сигнала, используйте wait с условием, никогда — слепой таймаут:

Wait for a real signal, not a clock
curl -s localhost:7891/api/automation/$PROFILE/wait -X POST \
-H 'Content-Type: application/json' -d '{"text":"Success"}'
curl -s localhost:7891/api/automation/$PROFILE/wait -X POST \
-H 'Content-Type: application/json' -d '{"selector":".loaded","state":"visible"}'
curl -s localhost:7891/api/automation/$PROFILE/wait -X POST \
-H 'Content-Type: application/json' -d '{"selector":".spinner","state":"hidden"}'

Если действие провалилось, повтор с теми же параметрами провалится так же. Каждый повтор должен что-то менять — другой эндпоинт, другой селектор, клавиатуру вместо мыши. Перечитывание текущей ошибки перед повтором обязательно.

Самый наглядный пример: select-combobox завершается по таймауту на скрытом listbox #cN. Не запускайте его снова — переключитесь на стратегию открытие-кликом → чтение-координат → mouse/click выше. Таймаут действия — 2 секунды (а не дефолтные 30с у Playwright), поэтому провальные попытки дёшевы; потратьте эту дешевизну на новый подход, а не на тот же самый пять раз.

Один evaluate только для чтения, возвращающий эти четыре значения, скажет вам, какая стратегия сработает, прежде чем вы спалите повторы:

  1. document.activeElement — приземлился ли фокус туда, куда вы ожидали?
  2. aria-expanded на combobox — действительно ли он открылся?
  3. aria-activedescendant — какая опция подсвечена?
  4. getBoundingClientRect().y vs window.innerHeight — находится ли цель вообще во вьюпорте?

Ввод с упреждением (type-ahead) для упрямых выпадающих списков

Заголовок раздела «Ввод с упреждением (type-ahead) для упрямых выпадающих списков»

Когда fill-page / select-combobox не подходят (нестандартная разметка combobox, прокручиваемые listbox с опциями за пределами вьюпорта, клики мыши не дают фокус), сначала переключайтесь на клавиатуру, а не на хаки с координатами. Клики мышью по опциям listbox должны быть последним средством — они требуют, чтобы опция была внутри вьюпорта и внутри собственной обрезающей области listbox, а прокрутка listbox ненадёжна.

  • Type-ahead на combobox — откройте через Enter или клик, затем press-key целевое значение посимвольно (1, 9, 9, 3 для года 1993). Большинство ARIA-combobox сразу прыгают к совпадению, минуя любую гимнастику со scrollTop. Подтвердите через Enter.
  • Tab для перемещения фокуса — когда клик отказывается дать фокус элементу или кнопка отправки ниже вьюпорта, а прокрутка колесом перехватывается, делайте Tab от последнего известного сфокусированного элемента, пока document.activeElement.textContent не совпадёт, затем активируйте через Enter.
  • ArrowDown × N + Enter — работает для коротких списков опций (≤5 пунктов, например Gender: Female / Male / Custom).
Type-ahead a birth year into a combobox
# Open the combobox with a real click (trusted)
curl -s localhost:7891/api/automation/$PROFILE/click -X POST \
-H 'Content-Type: application/json' -d '{"ref":"e12"}'
# Type-ahead the value, one key at a time
for k in 1 9 9 3; do
curl -s localhost:7891/api/automation/$PROFILE/press-key -X POST \
-H 'Content-Type: application/json' -d "{\"key\":\"$k\"}"
done
# Commit the selection
curl -s localhost:7891/api/automation/$PROFILE/press-key -X POST \
-H 'Content-Type: application/json' -d '{"key":"Enter"}'
СимптомЧто делать
”Ref eN not found”Страница изменилась. Пересоздайте снимок.
select-option падает: “not a select element”Это ARIA combobox — используйте select-combobox.
select-combobox таймаутится на скрытом #cNОтдельный оверлей. Не повторяйте — откройте через click, прочитайте координаты через evaluate, mouse/click.
locator.scrollIntoViewIfNeeded: TimeoutЭлемент анимируется. Используйте fill-page (по координатам); переключите всю страницу.
Снимок слишком большойОграничьте область: {"selector":"#main","depth":5}.
Элемент не виденmouse/wheel {"deltaY":500}, затем пересоздайте снимок.
Поток кажется в >2 раза медленнееВы добавили sleep или пересоздали снимок посреди страницы. Уберите и то, и другое.
Нужно кликнуть что-то, найденное через evaluateПрочитайте getBoundingClientRect(), затем mouse/click {x, y}. Никогда .click() в evaluate.