Тесты: харденинг orca-playtest.js — pageerror-хуки, бэкофф ретраев, диагностика кнопок

This commit is contained in:
2026-09-01 18:39:52 +03:00
parent 5c95309ce0
commit d65815ad95
2 changed files with 41 additions and 10 deletions
+6 -3
View File
@@ -36,6 +36,7 @@ node tests/orca-playtest.js # скриншоты orca-*.png кладутся
|---|---|---|
| 0 | `status` | runtime готов; иначе «запусти Orca app» и exit 1 |
| 1 | `tab create --url file://…/index.html` | получился `browserPageId` |
| 1b | установка error-хуков (`window.__errs`, `error` + `unhandledrejection`) | хуки стоят до старта геймплея (polling переживает eval-ошибки грузящейся страницы) |
| 2 | ожидание DOM | меню (`#menu`) показано |
| 3 | клик «Играть» (`.click()` в DOM) | `state === "IDLE"` |
| 4 | `pointerdown` на canvas → polling `CHARGE` → удержание ~0.7 c → `pointerup` | `CHARGE`, затем `WAIT` (бросок) |
@@ -48,7 +49,8 @@ node tests/orca-playtest.js # скриншоты orca-*.png кладутся
| 11 | «🛒» → вкладка «Снасти» → поиск «Фонарик» в `#shopBody` | в снастях есть «Фонарик» (500 🪙, ночь) |
| 12 | `forceBoss = true` + `pickSpecies` → осётр → полный цикл: `REEL` (старт боя: `stateBoss`, `bossPhase=1`) → 3 фазы (в каждой `reel.d = 0.4` → `SNAP` **со сбросом `holding` в том же eval** → авто-`REEL` следующей фазы; фаза 3 → `CARD`) → скриншот → `forceBoss = false` в `finally` | старт босс-боя, карточка после 3 фаз (`stateBoss=false`), имя «Осётр-БОСС» с бейджем |
| 13 | `console --limit 100` | нет сообщений уровня `error` |
| 14 | `tab close` (в `finally`) | вкладка закрыта при любом исходе |
| 13b | чтение `window.__errs` | нет pageerror за прогон (JS-ошибки и unhandledrejection) |
| 14 | `tab close` (в `finally`, 2 попытки — close идемпотентен) | вкладка закрыта при любом исходе |
Итог — сводка `ИТОГ: N/N` и exit code: `0` все PASS, `1` есть FAIL, `2` краш сценария, `3` глобальный таймаут (~4 мин).
@@ -101,8 +103,9 @@ PASS | босс: карточка после 3 фаз (stateBoss=false)
PASS | скриншот orca-boss.png
PASS | босс: карточка «Осётр-БОСС» с бейджем 🏆 | Осётр-БОСС | 🏆 БОСС!
PASS | нет ошибок в консоли (console --limit 100)
PASS | нет pageerror за прогон (window.onerror / unhandledrejection) | []
===== ИТОГ: 46/46 =====
===== ИТОГ: 47/47 =====
```
## Подводные камни
@@ -112,7 +115,7 @@ PASS | нет ошибок в консоли (console --limit 100)
3. **Цитирование: только `execFile` с массивом аргументов, без shell.** JS-выражения содержат кавычки — при передаче одной строкой через shell (PTY) они ломаются. В скрипте все вызовы — `child_process.execFile(ORCA, argsArray, …)`; никакого `exec("orca-ide …")`.
4. **Pointer Events с уникальным `pointerId`.** Игра слушает `pointerdown` на canvas и `pointerup` на window. Каждое взаимодействие (down/up-пара) получает свой `pointerId` (счётчик в скрипте) — так события не путаются с реальными.
5. **Тосты недолгие (1.6 с).** Текст тоста near-miss читается **в том же eval**, что и ожидание `WAIT` (`state === "WAIT" ? toast.textContent : ""`) — отдельный последующий вызов может опоздать. Скриншот `orca-escape.png` — best-effort (тост мог погаснуть); ok() ставится по тексту.
6. **CLI может transient-повиснуть.** Наблюдались единичные зависания `eval` (~1 мин), проходящие при повторе. Поэтому `orca()` ретраит вызовы (3 раза), а `execFile` жжёт зависший процесс таймаутом 20 с. **Исключение — `tab create`: ретраить нельзя** (получится дубль вкладки), он идёт через `orcaCall` один раз.
6. **CLI может transient-повиснуть.** Наблюдались единичные зависания `eval` (~1 мин), проходящие при повторе. Поэтому `orca()` ретраит вызовы (3 раза, линейный бэкофф 0.5/1 c; повторы пишутся в **stderr**, чтобы не мешать stdout с PASS/FAIL), а `execFile` жжёт зависший процесс таймаутом 20 с. **Исключение — `tab create`: ретраить нельзя** (получится дубль вкладки), он идёт через `orcaCall` один раз. `tab close` в `finally` ретраится 2 раза — вызов идемпотентен.
7. **Эмодзи — квадраты без шрифта.** Без `font-noto-emoji` эмодзи в скриншотах (и в окне Orca) рендерятся тофу. На скринах это ОК — проверка не по пикселям; для красивых скриншотов поставьте шрифт.
8. **`eval` возвращает объекты JSON-строкой.** `result.result` для объекта/массива — двойная сериализация (строка-JSON); `evalPage` в скрипте декодирует. Притом строка-«MENU» парсится как есть.
9. **Босс-фазы: `SNAP` не сбрасывает `reel.holding`.** `onUp` слушает только `CHARGE`/`REEL` — отпускание пальца в состоянии `SNAP` (босс-обрыв фазы) оставляет `holding = true`, и после авто-перехода в следующую фазу игра «втягивает» сама. Поэтому в шаге 12 сброс `reel.holding = false` делается **в том же eval, что детекция `SNAP`** (тот же приём, что `biteT = 5` в п. 2). Сам босс-бой — цикл из 3 фаз с polling'ом (`SNAP` → авто-`REEL` → снова), `forceCatch()` тут не подходит: на фазах 1–2 вместо `CARD` будет босс-обрыв.