r""" UB Claim form extraction, against a real browser. python tests\test_claim_form.py This one launches a **local headless chromium** and loads `tests\fixtures\ub_claim.html`. It never touches eCW: no proxy, no login, no 2FA mail, no attempt against the guard. So unlike a live run it costs nothing and can be run as often as you like — which matters, because the alternative way to test this code is a three-minute login and one of four hourly attempts. What it is really checking is the thing most likely to break silently: * **No selector depends on eCW's per-open timestamp.** The fixture uses `9999999999999` where the captured DOM had `1788790287089`. A selector with the number baked in matches nothing here, and the assertions fail. * **Values are read from inputs, not text.** The ICD code, every CPT column, the dates and the claim status all live in inputs or selects. Reading `textContent` returns blanks for them and the run looks successful. * **eCW's blank padding rows are dropped.** Every grid is padded with empty `ng-repeat` rows to hold a fixed height, plus a `*` entry row. Counting those would report five CPT lines on a claim that has two. """ import sys import pathlib ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "ub_claim.html" from ecw_bot.pages import claim_form # noqa: E402 from ecw_bot.selectors import claim_form as sel # noqa: E402 CHECKS = [] def check(fn): CHECKS.append(fn) return fn @check def claim_and_patient(page, data): assert data["claim_no"] == "9104000", data["claim_no"] p = data["patient"] assert p["name"] == "Sample, Pat, Q", p["name"] assert p["dob"] == "05/11/1979", p["dob"] assert p["account_no"] == "900123", p["account_no"] assert p["phone"] == "555-010-0100", p["phone"] # The visible text is ellipsised by eCW's own filter; title= has it whole. assert p["address"] == "100 SAMPLE ST, , CO 80000-0000", p["address"] @check def header_reads_inputs_and_the_select(page, data): h = data["header"] assert h["claim_date"] == "06/29/2026", h["claim_date"] assert h["bill_type"] == "131", h["bill_type"] assert h["facility"] == "MWC:Brighton Womens Clinic", h["facility"] assert h["attending"] == "Clinician, Casey M", h["attending"] # Both halves of the status: the code is what eCW submits, the label is what # a person recognises, and only checking one of them has burned us before. assert h["claim_status_code"] == "108ME", h["claim_status_code"] assert h["claim_status_label"] == "Medcor Ready", h["claim_status_label"] @check def icd_code_comes_from_the_input(page, data): icd = data["icd"] assert len(icd) == 1, f"expected 1 ICD row, got {len(icd)}: {icd}" row = icd[0] assert row["Code"] == "R69", row assert row["Name"] == "Illness, unspecified", row @check def cpt_grid_is_keyed_by_input_id(page, data): cpt = data["cpt"] # Two real rows. The bottom "*" entry row has no CPTROW_ id and must not # become a third. assert len(cpt) == 2, f"expected 2 CPT rows, got {len(cpt)}: {cpt}" a, b = cpt assert a["code"] == "H0023" and a["mod1"] == "GT", a assert a["pos"] == "11" and a["units"] == "1" and a["billed_fee"] == "74.00", a assert a["sdos"] == "06/29/2026" and a["edos"] == "06/29/2026", a assert a["icd1"] == "1" and a["icd2"] == "", a assert "Behavioral Health Outreach" in a["description"], a assert b["code"] == "90791" and b["units"] == "2", b assert b["billed_fee"] == "150.00", b @check def revenue_and_totals(page, data): rev = data["revenue"] assert len(rev) == 1, f"expected 1 revenue row, got {len(rev)}: {rev}" row = rev[0] assert row["42 RV CC"] == "0900", row assert row["43 Description"] == "Psychological Services", row assert row["44 HCPCS/Rates"] == "H0023 GT", row assert row["47 Total Charges"] == "74.00", row t = data["totals"] assert t["total_charges"] == "74.00", t assert t["charges"] == "224.00" and t["balance"] == "224.00", t assert t["pt_balance"] == "0.00", t @check def insurance_and_payments(page, data): ins = data["insurance"] assert len(ins) == 1, f"expected 1 insurance row, got {len(ins)}: {ins}" assert ins[0]["Name"] == "BH COLORADO ACCESS", ins[0] assert ins[0]["Subscriber No."] == "J000000", ins[0] assert ins[0]["State"] == "CO", ins[0] pay = data["payments"] assert len(pay) == 1, f"expected 1 payment row, got {len(pay)}: {pay}" assert pay[0]["Id"] == "2000001", pay[0] assert pay[0]["Date"] == "06/29/2026", pay[0] assert pay[0]["From"] == "Patient", pay[0] @check def notes_and_empty_error_table(page, data): assert data["notes"] == "Awaiting coding review.", data["notes"] # "Error Logs ()" on the live claim — an empty tbody must be [], not a row # of blanks. assert data["errors"] == [], data["errors"] @check def cpt_hcpcs_tab_opens(page, data): assert claim_form.open_cpt_hcpcs_tab(page) is True assert page.locator(sel.SEL_CPT_GRID).first.is_visible() @check def progress_notes_is_discovered_not_assumed(page, data): """ The whole chain: click, find the note wherever it landed, read it, close. The fixture serves the note in an iframe, as eCW does. `opened == "frame"` is the assertion that matters - an earlier version of this code read the hosting modal's innerText and would have come back with nothing, because iframe content is not part of it. """ pn = claim_form.open_progress_notes(page) assert pn["opened"] == "frame", pn # Structure, not a text blob: eCW's own prisma-section labels. assert pn["assessments"][0]["code"] == "R69", pn["assessments"] assert pn["procedure_codes"][0]["code"] == "H0023", pn["procedure_codes"] assert pn["procedure_codes"][0]["modifiers"] == ["GT"], pn["procedure_codes"] assert pn["signoff_status"] == "Completed", pn["signoff_status"] assert pn["chart_locked"] is True, pn["chart_locked"] # And the viewer is gone, so the Cancel button underneath is reachable. assert page.locator(sel.SEL_PN_CLOSE).count() == 0 or page.locator(".modal#progressNoteViewerDiv9999999999999").first .is_visible() is False @check def summary_is_readable(page, data): data = dict(data) data["progress_notes"] = {"opened": "modal", "text": "x" * 120} text = claim_form.summary(data) for want in ("9104000", "Sample, Pat, Q", "Medcor Ready", "H0023", "90791", "224.00", "Prog. Notes"): assert want in text, f"{want!r} missing from summary:\n{text}" @check def no_selector_carries_the_timestamp(page, data): """ No selector VALUE in selectors/claim_form.py contains a long digit run. Parsed with ast rather than scanned as text: the module docstring quotes the real timestamped ids on purpose, to explain the hazard, and a text scan flagged those quotes as the bug they warn about. Only actual assignments are checked. The fixture stamps 9999999999999 where the captured DOM had 1788790287089, so a selector with the number baked in also fails the assertions above — this check is what names the cause instead of leaving eleven empty fields. """ import ast import re src = (ROOT / "src" / "ecw_bot" / "selectors" / "claim_form.py").read_text( encoding="utf-8") for node in ast.parse(src).body: if not isinstance(node, ast.Assign): continue for const in ast.walk(node.value): if isinstance(const, ast.Constant) and isinstance(const.value, str): assert not re.search(r"\d{6,}", const.value), ( f"timestamped selector: " f"{[t.id for t in node.targets if isinstance(t, ast.Name)]}" f" = {const.value!r}") def _run() -> int: if not FIXTURE.exists(): print(f" FAIL fixture missing: {FIXTURE}") return 1 try: from playwright.sync_api import sync_playwright except ImportError: print(" SKIP playwright is not installed") return 0 failures = 0 with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) # local only, no proxy page = browser.new_page() page.goto(FIXTURE.as_uri()) data = claim_form.extract(page) for fn in CHECKS: name = fn.__name__.replace("_", " ") try: fn(page, data) 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(CHECKS) - failures}/{len(CHECKS)} passed") return 1 if failures else 0 def main() -> int: """ shot() writes into the operator's dated Screenshots\ folder; a test run must not, so the cases run with it pointed at a temp folder. """ import tempfile from ecw_bot import settings as _cfg orig = _cfg.SCREENSHOTS with tempfile.TemporaryDirectory() as tmp: _cfg.SCREENSHOTS = pathlib.Path(tmp) try: return _run() finally: _cfg.SCREENSHOTS = orig if __name__ == "__main__": sys.exit(main())