"""Pointer-reachability check for the OMNI panel. A control whose centre is covered by another element cannot be clicked by a human, and Playwright stalls on it instead of failing fast. Regression guard for the mobile sidebar bug where the collapsed menu kept a fixed height: nav links slid under `.sidebar-footer`, so the last items ("Обзор", "Запуски", "Ключи") were visible in DevTools but untappable. Used by tools/ui_smoke.py and tools/panel_scenarios.py. """ from __future__ import annotations from typing import Any, Optional from playwright.async_api import Page JS = """(args) => { const describe = (el) => { if (!el) return 'none'; const chain = []; let cur = el; for (let i = 0; i < 3 && cur; i++) { const cls = typeof cur.className === 'string' && cur.className ? '.' + cur.className.trim().split(/\\s+/).slice(0, 2).join('.') : ''; chain.push(cur.tagName.toLowerCase() + (cur.id ? '#' + cur.id : '') + cls); cur = cur.parentElement; } return chain.join(' < '); }; const covered = []; const els = [...document.querySelectorAll(args.selector)]; els.forEach((el, i) => { const key = (args.keyAttr && el.dataset[args.keyAttr]) || el.id || (el.textContent || '').trim().slice(0, 28) || (args.selector + '[' + i + ']'); const details = el.closest('details'); if (details && !details.open) return; const style = getComputedStyle(el); if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return; if (el.closest('#sug:not(.show), #drawer:not(.show), #modal:not(.show)')) return; el.scrollIntoView({block: 'center'}); const r = el.getBoundingClientRect(); if (r.width < 2 || r.height < 2) return; const x = r.left + r.width / 2, y = r.top + r.height / 2; if (x < 0 || y < 0 || x > innerWidth || y > innerHeight) return; const top = document.elementFromPoint(x, y); if (top && (top === el || el.contains(top))) return; covered.push({key, covered_by: describe(top), at: [Math.round(x), Math.round(y)], box: {x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height)}}); }); return covered.slice(0, 12); }""" async def find_covered(page: Page, selector: str, key_attr: Optional[str] = None) -> list[dict[str, Any]]: """Return controls matched by `selector` whose centre is painted over by another element.""" return await page.evaluate(JS, {"selector": selector, "keyAttr": key_attr})