Andrey Epifancev b237e0f1e9 Регрессия: покрытие таблицы корректировок (#198)
У фичи AURORA-1540/1541 (корректировки) и AURORA-1751 (vgo_assignment),
слитых 02.11.2026, не было ни одной проверки в регрессии: ui_smoke.py не
заходил на вкладку корректировок, stand_scenario.py не делал ни одного
запроса к /corrections или /vgo-assignments.

- stand_scenario.py: happy-path GET /vgo-assignments/distribute и
  GET /corrections на каждый сценарий; сохранение корректировки первого
  агрегата и повтор с тем же expected_version_no — 409
  CORRECTION_VERSION_CONFLICT. rb-2024-q3 не имеет подтверждённых форм ББ
  (только ОСВ) — сохранение там штатно получает 422 INVALID_LINE_CODE,
  это не регрессия и репортится как пропуск, не красным.
- ui_smoke.py: страница /groups/{g}/versions/{v}/correction на каждую
  версию сценария.

Проверено: python3 -I scripts/regression/regress.sh --only stand,ui —
зелёный, оба сценария (rb-2024-q3, rb-2025-q1), включая реальный 409 на
rb-2025-q1 и скриншот вкладки «Таблица корректировки» с данными.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-11-02 23:20:06 +04:00

100 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""UI-смоук фронта arm-core/web против стенда регрессии (regress.sh, этап ui; после сценария stand).
Обходит основные экраны под ka_go (токен — в sessionStorage, как после входа), на каждом ждёт затишья сети и
собирает: исключения страницы, ошибки консоли, ответы /api со статусом >= 400. Красный экран — исключение страницы,
ответ /api >= 500 или 4xx (кроме перечисленных в ALLOWED_4XX), пустая страница. Скриншоты — <out>/<экран>.png,
итог — <out>/report.json. Идентификаторы версий и документов берёт из report.json сценария стенда.
uv run --with playwright==1.55.0 python ui_smoke.py --web http://127.0.0.1:13000 --api http://127.0.0.1:18000 \\
--stand-report DIR/stand/report.json --out DIR/ui
"""
import argparse
import json
import sys
import urllib.request
from pathlib import Path
from playwright.sync_api import sync_playwright
# 4xx, которые фронт получает штатно (пусто — любой 4xx красный; дополнять с причиной)
ALLOWED_4XX: set[tuple[str, int]] = set()
def token(api: str, email: str) -> str:
req = urllib.request.Request(
api.rstrip("/") + "/api/v1/dev/token",
data=json.dumps({"email": email}).encode(),
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=30) as r:
return json.loads(r.read())["access_token"]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--web", required=True)
ap.add_argument("--api", required=True)
ap.add_argument("--stand-report", required=True, type=Path)
ap.add_argument("--out", required=True, type=Path)
ap.add_argument("--user", default="ka_go@armka.local")
a = ap.parse_args()
a.out.mkdir(parents=True, exist_ok=True)
ids = json.loads(a.stand_report.read_text()).get("ids", {})
g = ids.get("group_id")
pages = [("groups", "/groups"), ("catalogue-groups", "/catalogue/groups"), ("catalogue-le", "/catalogue/jur_l")]
if g:
pages.append(("group", f"/groups/{g}"))
for sid, vid in (ids.get("versions") or {}).items():
pages += [
(f"{sid}-osv", f"/groups/{g}/versions/{vid}/osv"),
(f"{sid}-chess", f"/groups/{g}/versions/{vid}/chess"),
(f"{sid}-correction", f"/groups/{g}/versions/{vid}/correction"),
]
for name, doc in (ids.get("documents") or {}).items():
pages.append((f"doc-{name}", f"/documents/{doc}"))
tok = token(a.api, a.user)
results = []
with sync_playwright() as p:
browser = p.chromium.launch()
ctx = browser.new_context(viewport={"width": 1600, "height": 1000}, locale="ru-RU")
ctx.add_init_script(f"try {{ sessionStorage.setItem('armka.token', {json.dumps(tok)}); }} catch (e) {{}}")
for name, path in pages:
page = ctx.new_page()
errors, console, bad_api = [], [], []
page.on("pageerror", lambda e, errors=errors: errors.append(str(e)[:300]))
page.on("console", lambda m, console=console: console.append(m.text[:300]) if m.type == "error" else None)
def on_response(r, bad_api=bad_api):
if "/api/" in r.url and r.status >= 400:
bad_api.append(f"{r.status} {r.request.method} {r.url.split('/api/', 1)[1][:150]}")
page.on("response", on_response)
try:
page.goto(a.web.rstrip("/") + path, wait_until="networkidle", timeout=60000)
page.wait_for_timeout(1500)
text = page.inner_text("body")
except Exception as e: # noqa: BLE001 — экран красный, обход продолжается
errors.append(f"{type(e).__name__}: {e}"[:300])
text = ""
page.screenshot(path=str(a.out / f"{name}.png"), full_page=False)
red_api = [
x for x in bad_api
if int(x.split()[0]) >= 500 or (x.split()[1], int(x.split()[0])) not in ALLOWED_4XX
]
ok = not errors and not red_api and len(text.strip()) > 20
results.append({"name": name, "path": path, "ok": ok, "page_errors": errors, "api_errors": bad_api, "console_errors": console[:10]})
print(("ok " if ok else "FAIL ") + f"{name} {path}" + ("" if ok else f": {errors or red_api or 'пустая страница'}"), flush=True)
page.close()
browser.close()
(a.out / "report.json").write_text(json.dumps(results, ensure_ascii=False, indent=1))
red = [r for r in results if not r["ok"]]
print(f"экранов {len(results)}, красных {len(red)}")
return 1 if red else 0
if __name__ == "__main__":
sys.exit(main())