r""" Stacked dialogs (live run 2026-10-12, Logs\run_20261012_160331.log): one dialog left on top of another blocked every click after it, and five of seven claims went to Review. * Claim 1: Prog. Notes was clicked a second time while the first viewer was still loading its document — two viewers. Close was clicked on the LOWER one (`.first`), which the upper, blank one covered: 15 s timeout, the viewer stayed, and the CPT tab, the form's Cancel and the next two claims' searches were all intercepted by its iframe. * Claim 6: eCW opened the same HCFA claim twice (two claim windows 648 ms apart). The bot worked on the lower one; its Prog. Notes click was intercepted by the upper one, and the next claim's search too. python tests\test_stacked_dialogs.py """ import io import sys import pathlib import inspect import contextlib ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "src")) FIXTURES = pathlib.Path(__file__).resolve().parent / "fixtures" HCFA = FIXTURES / "hcfa_claim.html" from ecw_bot import settings as cfg # noqa: E402 from ecw_bot.core import page_utils # noqa: E402 from ecw_bot.pages import claim_form, progress_note # noqa: E402 from ecw_bot.selectors import claim_form as sel # noqa: E402 CASES = [] def case(fn): CASES.append(fn) return fn def _quiet(fn, *a, **k): with contextlib.redirect_stdout(io.StringIO()) as out: result = fn(*a, **k) return result, out.getvalue() # ── the helper ─────────────────────────────────────────────────────────────── @case def click_topmost_clicks_the_button_on_top_not_the_first_in_the_dom(page): page.set_content("""
""") assert page_utils.click_topmost(page, ".b", timeout=3_000) is True assert page.evaluate("window.hits") == ["b2"] @case def click_topmost_says_false_when_nothing_matches(page): page.set_content("
nothing here
") assert page_utils.click_topmost(page, ".b", timeout=1_000) is False # ── claim 1: two progress note viewers ─────────────────────────────────────── @case def close_shuts_every_stacked_viewer_top_first(page): page.goto(HCFA.as_uri()) btn = "#claimProgressNoteBtn8888888888888" page.click(btn) page.click(btn) page.evaluate("""() => { window.shut = []; document.querySelectorAll('[ng-click="closeProgressNoteViwer()"]') .forEach((b, i) => b.addEventListener('click', () => window.shut.push(i))); }""") assert page.locator(sel.SEL_PN_CLOSE).filter(visible=True).count() == 2 ok, out = _quiet(progress_note.close, page, wait_ms=2_000) assert ok is True, out assert page.evaluate("window.shut") == [1, 0], "top (second) first" assert page.locator(sel.SEL_PN_CLOSE).filter(visible=True).count() == 0 assert "2 progress note viewers" in out, out @case def one_viewer_with_two_close_buttons_is_one_viewer(page): """Live 2026-10-12: the viewer draws an X and a Close — one viewer.""" page.set_content(""" """) ok, out = _quiet(progress_note.close, page, wait_ms=1_000) assert ok is True, out assert "viewers were open" not in out, out assert page.locator(sel.SEL_PN_CLOSE).count() == 0 @case def no_second_click_while_a_viewer_is_still_loading_its_note(page): """The re-click must not fire while eCW's viewer frame is open.""" page.set_content(""" """) old = cfg.PROGRESS_NOTE_WAIT_MS cfg.PROGRESS_NOTE_WAIT_MS = 2_000 try: (reader, where, _), out = _quiet( claim_form._wait_for_note, page, len(page.context.pages)) finally: cfg.PROGRESS_NOTE_WAIT_MS = old assert reader is None and where == "nothing" assert page.evaluate("window.clicks") == 0, out assert "once more" not in out, out @case def the_second_click_still_fires_when_nothing_at_all_opened(page): """The control: the click that did not take is still retried.""" page.set_content(""" """) old = cfg.PROGRESS_NOTE_WAIT_MS cfg.PROGRESS_NOTE_WAIT_MS = 2_000 try: _, out = _quiet(claim_form._wait_for_note, page, len(page.context.pages)) finally: cfg.PROGRESS_NOTE_WAIT_MS = old assert page.evaluate("window.clicks") == 1, out # ── claim 6: the same HCFA claim opened twice ──────────────────────────────── def _duplicate_hcfa_window(page): """A second copy of the claim window on top, as eCW opened it.""" page.evaluate("""() => { const w = document.querySelector('.claimModalBackdrop'); const copy = w.cloneNode(true); // Over the original, as angular-ui draws it: same place, higher. const r = w.getBoundingClientRect(); Object.assign(copy.style, {position: 'absolute', zIndex: '1060', left: (r.left + scrollX) + 'px', top: (r.top + scrollY) + 'px', width: r.width + 'px', background: '#fff'}); copy.dataset.copy = '1'; document.body.appendChild(copy); window.cancelled = []; for (const win of document.querySelectorAll('.claimModalBackdrop')) { const c = win.querySelector('[ng-click="closeClaimScreen()"]'); const clone = c.cloneNode(true); // drop the fixture's handler c.replaceWith(clone); clone.addEventListener('click', () => { window.cancelled.push(win.dataset.copy ? 'copy' : 'original'); win.remove(); }); } }""") @case def a_claim_opened_twice_loses_its_top_copy_by_cancel(page): page.goto(HCFA.as_uri()) _duplicate_hcfa_window(page) n, out = _quiet(claim_form.close_duplicate_forms, page, "9103000") assert n == 1, out assert page.evaluate("window.cancelled") == ["copy"] assert page.locator(".claimModalBackdrop").count() == 1 assert "opened the claim twice" in out, out @case def one_claim_window_is_left_alone(page): page.goto(HCFA.as_uri()) n, out = _quiet(claim_form.close_duplicate_forms, page, "9103000") assert n == 0, out assert page.locator(".claimModalBackdrop").count() == 1 @case def close_claim_cancels_the_window_on_top(page): """With two windows up, Cancel goes to the one that can be clicked.""" page.goto(HCFA.as_uri()) _duplicate_hcfa_window(page) page.evaluate("""() => { // the original's Cancel is covered window.cancelled = window.cancelled || []; }""") ok, out = _quiet(claim_form.close_claim, page) assert page.evaluate("window.cancelled")[0] == "copy", out @case def a_viewer_left_from_the_last_claim_is_closed_before_the_next_search(page): """ Defence in depth: claims 2 and 3 were lost to claim 1's viewer. Before a claim is searched, any viewer still up is closed. """ page.goto(HCFA.as_uri()) page.click("#claimProgressNoteBtn8888888888888") assert page.locator(sel.SEL_PN_CLOSE).filter(visible=True).count() == 1 done, out = _quiet(claim_form.clear_screen, page, "9103000", page.context.pages) assert page.locator(sel.SEL_PN_CLOSE).filter(visible=True).count() == 0 assert "a progress note viewer" in done, out assert "Left over from the last claim" in out, out 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(viewport={"width": 1600, "height": 1000}) 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())