Files
ecw-charge-validation/tests/test_preflight.py
T
ManikandanandClaude Opus 5.5 ba15a574ab Initial import: eCW Charge Validation bot and results Portal
Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one
commit with no earlier history: the tracked files were de-identified
first (synthetic claim ids and patient data only; see Context.md
section 7). Credentials, run logs, screenshots, claim reports and client
workbooks are gitignored and not included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:38:17 +05:30

305 lines
11 KiB
Python

r"""
pages/preflight.py and the DOM capture in core/page_utils.shot (2026-10-21):
a changed eCW screen is found on the first claim, named, its HTML saved,
and the run stopped (exit 7) — not every claim sent to Review one by one.
python tests\test_preflight.py
Screenshots and DOM files go to a temporary folder, never the operator's.
"""
import io
import sys
import inspect
import pathlib
import tempfile
import contextlib
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
FIXTURES = pathlib.Path(__file__).resolve().parent / "fixtures"
from ecw_bot import settings as cfg # noqa: E402
from ecw_bot.core import page_utils # noqa: E402
from ecw_bot.pages import preflight # noqa: E402
from ecw_bot.pages import claim_form # noqa: E402
from ecw_bot.flows import export_medcor_ready as flow # noqa: E402
CASES = []
SEARCH_PAGE = """
<input id="claimLookupIpt10"><button id="btnclaimlookup">Lookup</button>
<select id="claimStatusCodeId"><option>Medcor Ready</option></select>
<input id="fromdate"><input id="todate">
"""
def case(fn):
CASES.append(fn)
return fn
@contextlib.contextmanager
def _sandbox(**settings):
"""Shots in a temp folder; settings set for the case, then put back."""
keys = ["SCREENSHOTS", "PREFLIGHT", "PREFLIGHT_STOP",
"SAVE_DOM_ON_FAILURE", "SAVE_DOM_LIMIT"]
old = {k: getattr(cfg, k) for k in keys}
old_saved = page_utils._dom_saved
with tempfile.TemporaryDirectory() as tmp:
cfg.SCREENSHOTS = pathlib.Path(tmp)
cfg.PREFLIGHT, cfg.PREFLIGHT_STOP = True, True
cfg.SAVE_DOM_ON_FAILURE, cfg.SAVE_DOM_LIMIT = True, 30
for k, v in settings.items():
setattr(cfg, k, v)
page_utils._dom_saved = 0
preflight.reset()
try:
with contextlib.redirect_stdout(io.StringIO()):
yield pathlib.Path(tmp)
finally:
for k, v in old.items():
setattr(cfg, k, v)
page_utils._dom_saved = old_saved
preflight.reset()
def _files(folder, suffix):
return sorted(p.name for p in folder.glob(f"*{suffix}"))
# ── DOM capture in shot() ─────────────────────────────────────────────────────
@case
def a_failure_shot_saves_the_dom_beside_the_png(page):
page.set_content("<div id='x'>fake page</div>"
"<iframe srcdoc='<p id=inner>frame text</p>'></iframe>")
page.wait_for_timeout(200)
with _sandbox() as tmp:
page_utils.shot(page, "claim_form_no_modal_C1")
pngs, htmls = _files(tmp, ".png"), _files(tmp, ".html")
assert len(pngs) == 1 and len(htmls) == 1, (pngs, htmls)
assert htmls[0].endswith("_claim_form_no_modal_C1.html")
assert htmls[0][:6] == pngs[0][:6], "same HHMMSS"
text = (tmp / htmls[0]).read_text(encoding="utf-8")
assert "fake page" in text and "frame 1" in text \
and "frame text" in text, text[:300]
@case
def an_ordinary_shot_saves_no_dom(page):
page.set_content("<div>ok</div>")
with _sandbox() as tmp:
for tag in ("logged_in", "landing", "claims_status",
"progress_note", "claims_balance_cleared"):
page_utils.shot(page, tag)
assert _files(tmp, ".html") == []
assert len(_files(tmp, ".png")) >= 1
@case
def dom_capture_can_be_off_and_is_capped(page):
page.set_content("<div>x</div>")
with _sandbox(SAVE_DOM_ON_FAILURE=False) as tmp:
page_utils.shot(page, "claims_status_failed")
assert _files(tmp, ".html") == []
with _sandbox(SAVE_DOM_LIMIT=2) as tmp:
for n in range(4):
page_utils.shot(page, f"x{n}_failed")
assert page_utils._dom_saved == 2
@case
def failure_tags_are_told_from_ordinary_ones():
for tag in ("claims_status_failed", "login_unconfirmed",
"search_not_ready_1", "claim_notfound_1",
"claim_form_wrong_claim_1", "claim_form_cpt_grid_missing",
"claims_report_nodownload", "2fa_timed_out",
"preflight_failed_search_page"):
assert page_utils.is_failure_tag(tag), tag
for tag in ("logged_in", "landing", "captcha", "approval_page",
"claims_results", "nav_claims", "progress_note",
"claims_balance_cleared", "claims_datetype_open"):
assert not page_utils.is_failure_tag(tag), tag
# ── the search page ──────────────────────────────────────────────────────────
@case
def the_search_page_passes_with_every_selector(page):
page.set_content(SEARCH_PAGE)
with _sandbox() as tmp:
preflight.check_search(page)
assert _files(tmp, ".png") == []
@case
def a_missing_search_selector_stops_the_run_named_and_saved(page):
page.set_content(SEARCH_PAGE.replace('id="btnclaimlookup"', 'id="x"'))
with _sandbox() as tmp:
try:
preflight.check_search(page)
except preflight.PreflightFailed as exc:
assert "Lookup button" in str(exc), exc
else:
raise AssertionError("no PreflightFailed")
assert any("preflight_failed_search_page" in n
for n in _files(tmp, ".html")), _files(tmp, ".html")
@case
def preflight_stop_off_only_warns_and_preflight_off_does_nothing(page):
page.set_content("<p>not the search page</p>")
with _sandbox(PREFLIGHT_STOP=False) as tmp:
preflight.check_search(page)
assert _files(tmp, ".png"), "still shot"
with _sandbox(PREFLIGHT=False) as tmp:
preflight.check_search(page)
assert _files(tmp, ".png") == []
@case
def no_page_at_all_is_skipped_never_raised():
with _sandbox():
preflight.check_search(None)
preflight.check_form(None)
# ── the claim form ───────────────────────────────────────────────────────────
def _open(page, name):
page.goto((FIXTURES / name).as_uri())
with contextlib.redirect_stdout(io.StringIO()):
claim_form.open_cpt_hcpcs_tab(page)
@case
def each_fixture_form_passes(page):
for name in ("ub_claim.html", "hcfa_claim.html", "dental_claim.html"):
_open(page, name)
with _sandbox() as tmp:
preflight.check_form(page)
assert _files(tmp, ".png") == [], name
@case
def a_missing_row_box_names_it(page):
_open(page, "ub_claim.html")
page.evaluate("document.querySelectorAll('[id^=\"uBClaimMod1\"]')"
".forEach(e => e.remove())")
with _sandbox():
try:
preflight.check_form(page)
except preflight.PreflightFailed as exc:
assert "ub claim form" in str(exc) and "M1 box" in str(exc), exc
else:
raise AssertionError("no PreflightFailed")
@case
def each_screen_is_checked_once_a_run(page):
_open(page, "hcfa_claim.html")
with _sandbox():
preflight.check_form(page)
page.evaluate("document.querySelectorAll('[data-fieldname="
"\"ClaimCPTPOS\"]').forEach(e => e.remove())")
preflight.check_form(page) # not checked again: no raise
preflight.reset()
try:
preflight.check_form(page)
except preflight.PreflightFailed as exc:
assert "POS box" in str(exc), exc
else:
raise AssertionError("a new run checks again")
@case
def a_form_with_no_rows_waits_then_fails_on_the_third(page):
_open(page, "hcfa_claim.html")
page.evaluate("document.querySelectorAll('tr[id^=\"CPTROW_\"]')"
".forEach(e => e.remove())")
with _sandbox():
preflight.check_form(page)
preflight.check_form(page)
try:
preflight.check_form(page)
except preflight.PreflightFailed as exc:
assert "CPT rows" in str(exc), exc
else:
raise AssertionError("three claims with no rows should fail")
# ── the flow ─────────────────────────────────────────────────────────────────
@case
def the_flow_lets_preflight_failed_through_and_still_closes_the_form():
closed = []
def boom(page):
raise preflight.PreflightFailed("eCW ub claim form: not found — M1")
stubs = {"open_claim": lambda page, cid: True,
"wait_claim_loaded": lambda page, cid: None,
"close_duplicate_forms": lambda page, cid: None,
"is_dental_claim_form": lambda page: False,
"open_cpt_hcpcs_tab": lambda page: None,
"close_claim": lambda page: closed.append(1) or True}
orig = {k: getattr(claim_form, k) for k in stubs}
orig_check, orig_open = preflight.check_form, cfg.OPEN_CLAIM_FORM
for k, v in stubs.items():
setattr(claim_form, k, v)
preflight.check_form = boom
cfg.OPEN_CLAIM_FORM = True
try:
with contextlib.redirect_stdout(io.StringIO()):
try:
flow._read_claim_form(None, "C1")
except preflight.PreflightFailed:
pass
else:
raise AssertionError("PreflightFailed was swallowed")
assert closed == [1], "the form is still closed"
finally:
for k, v in orig.items():
setattr(claim_form, k, v)
preflight.check_form, cfg.OPEN_CLAIM_FORM = orig_check, orig_open
@case
def the_web_page_explains_exit_7():
sys.path.insert(0, str(ROOT))
from web import run_status
assert "selector" in run_status.exit_text(7)
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())