fix(ui): пункты меню не прячутся под футером; сквозные сценарии панели
web/index.html: - мобильное меню после раскрытия не держит анимационную высоту Bootstrap: нижние пункты (Обзор/История/API-ключи) уезжали под .sidebar-footer и не нажимались - на десктопе рельс #nav скроллится сам, футер не сжимается; активный пункт доскролливается в область видимости - failure() принимает заголовок: ошибка валидации источника больше не подписана «Не удалось загрузить данные» tools/reachability.py: общая проверка «элемент виден, но перекрыт другим» — hit-test по центру с scrollIntoView, пропуск скрытых узлов и закрытых <details>. tools/panel_scenarios.py: 11 сквозных сценариев реального пользователя — deep-link фильтр, экспорт CSV/JSON по байтам, читалка и ссылка на оригинал, дайджест, поиск источников, Telegram-сессия, форма источника (отказ без записи в реестр), клавиатурный flow (/ ? Esc 1-9) со сверкой строк ленты с API, мобильное меню, адаптивность 320-1920 без горизонтального переполнения, зум. tools/ui_smoke.py: reachability-проверки десктопного рельса, контролов и мобильного меню. tests: +3 регрессии на CSS/заголовки ошибок; pytest 93 passed, smoke exit 0, сценарии exit 0, page_errors пуст.
This commit is contained in:
@@ -12,6 +12,9 @@ from urllib.parse import parse_qs, quote, urljoin, urlsplit
|
||||
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
|
||||
from reachability import find_covered # noqa: E402
|
||||
|
||||
|
||||
async def run(url, out):
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
@@ -96,6 +99,12 @@ async def run(url, out):
|
||||
assert not hits, f"Text painted on text at {label}: {hits}"
|
||||
report.setdefault("no_text_collision", []).append(label)
|
||||
|
||||
async def assert_reachable(label, selector, key_attr=None):
|
||||
"""Fail when a visible control is painted over: a human could not click it."""
|
||||
covered = await find_covered(page, selector, key_attr)
|
||||
assert not covered, f"Unreachable controls at {label}: {covered}"
|
||||
report.setdefault("reachable", []).append(label)
|
||||
|
||||
async def assert_dark_theme(label):
|
||||
colors = await page.evaluate("""() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
@@ -130,6 +139,11 @@ async def run(url, out):
|
||||
assert report["navigation_routes"] == 9
|
||||
await assert_no_overflow("desktop")
|
||||
await assert_no_text_collision("feed 1440")
|
||||
await assert_reachable("desktop rail 1440", "#sidebar-menu [data-nav]", "nav")
|
||||
await assert_reachable(
|
||||
"desktop controls 1440",
|
||||
"#page-actions .btn, #view .nav-tabs .nav-link, #export-csv, #export-json, #q",
|
||||
)
|
||||
await assert_dark_theme("feed")
|
||||
step("feed")
|
||||
await page.screenshot(path=str(out / "black_feed.png"))
|
||||
@@ -268,6 +282,11 @@ async def run(url, out):
|
||||
await page.locator("#sidebar-toggle").click()
|
||||
await page.locator("#sidebar-menu").wait_for(state="visible")
|
||||
assert await page.locator('[data-nav="sources"]').is_visible()
|
||||
await assert_reachable(
|
||||
"mobile menu 390",
|
||||
"#sidebar-menu [data-nav], .sidebar-footer button, #sidebar-toggle",
|
||||
"nav",
|
||||
)
|
||||
await assert_no_overflow("mobile navigation 390")
|
||||
await assert_dark_theme("mobile navigation")
|
||||
await page.screenshot(path=str(out / "black_mobile_navigation.png"))
|
||||
|
||||
Reference in New Issue
Block a user