r""" core/fallback.py and the selector chains (2026-10-21): a renamed id falls back to an alternative taken from the live DOM, and the log says so. python tests\test_fallback_selectors.py """ import io import sys import inspect import pathlib import contextlib ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) from ecw_bot.core import fallback # noqa: E402 from ecw_bot.selectors.fallbacks import CHAINS # noqa: E402 from ecw_bot.selectors import claim_form as form_sel # noqa: E402 from ecw_bot.selectors import claim_lookup as lookup_sel # noqa: E402 from ecw_bot.selectors import login as login_sel # noqa: E402 CASES = [] def case(fn): CASES.append(fn) return fn def _quiet(fn, *a): out = io.StringIO() with contextlib.redirect_stdout(out): result = fn(*a) return result, out.getvalue() # ── the chains themselves ──────────────────────────────────────────────────── @case def each_selector_is_its_chain_with_the_primary_first(): pairs = [(login_sel.SEL_PASSWORD, login_sel.PASSWORD_CHAIN, "#passwordField"), (login_sel.SEL_LOGIN_BTN, login_sel.LOGIN_BTN_CHAIN, "#Login"), (lookup_sel.SEL_CLAIM_NO, lookup_sel.CLAIM_NO_CHAIN, "#claimLookupIpt10"), (lookup_sel.SEL_LOOKUP_BTN, lookup_sel.LOOKUP_BTN_CHAIN, "#btnclaimlookup"), (form_sel.SEL_CANCEL, form_sel.CANCEL_CHAIN, "#UBClaimBtn17"), (form_sel.SEL_CANCEL_DENTAL, form_sel.CANCEL_DENTAL_CHAIN, "#cancelDentalClaimButton"), (form_sel.SEL_PROGRESS_NOTES, form_sel.PROGRESS_NOTES_CHAIN, 'button[ng-click="viewProgNote()"]')] for joined, chain, primary in pairs: assert chain[0] == primary and len(chain) >= 2, chain assert joined == ", ".join(chain), joined @case def every_alternative_is_plain_css(page): # Used in querySelectorAll / :has() too: Playwright-only syntax breaks # there. querySelectorAll throws on anything the browser cannot parse. page.set_content("
") for name, (_, chain) in CHAINS.items(): for selector in chain: assert ":has-text" not in selector and "text=" not in selector, \ (name, selector) page.evaluate("s => document.querySelectorAll(s)", selector) @case def cancel_alternatives_never_match_ok_or_the_x(page): page.set_content("""other page
") names, log = _quiet(fallback.report, page, list(CHAINS), CHAINS) assert names == [] and log == "" def main() -> int: from playwright.sync_api import sync_playwright failures = 0 with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) page = browser.new_page() for fn in CASES: name = fn.__name__.replace("_", " ") try: if len(inspect.signature(fn).parameters) == 0: fn() else: fn(page) print(f" ok {name}") except AssertionError as exc: print(f" FAIL {name}: {exc}") failures += 1 except Exception as exc: print(f" ERROR {name}: {type(exc).__name__}: {exc}") failures += 1 browser.close() print(f"\n{len(CASES) - failures}/{len(CASES)} passed") return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())