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>
This commit is contained in:
ManikandanandClaude Opus 5.5 committed 2026-10-23 18:38:17 +05:30
commit ba15a574ab
205 files changed
+56772

No files matched your search

+175
View File
@@ -0,0 +1,175 @@
r"""
`core/page_utils.auto_dialog` — how the bot answers a page's native dialog
(fixed 2026-10-22; Context.md §10 had it as an open finding).
python tests\test_auto_dialog.py
The old handler called `dialog.message()`, but in Playwright Python
`message` is a property: the TypeError was swallowed and every alert /
confirm was left open — and an unanswered dialog crashed the tab ("Target
crashed", seen in tests). What has to hold now:
* **Every dialog is answered**, so the tab never wedges or crashes.
* **alert** → OK: it only informs.
* **confirm / prompt** → OK only in LIVE-WRITE (SAVE_CORRECTIONS=1). In
VERIFY → Cancel: live eCW is read-only, and a confirm's OK may be a save.
* **beforeunload** → dismissed: accepting means "yes, close this page".
* Each one is in the log and is a Warning on the Runs page, with what the
bot answered.
* A second handler for the same dialog (it is registered on the context
and on the page) does not raise.
Fake dialogs for the rules, then a real Chromium for the crash.
"""
import io
import sys
import pathlib
import contextlib
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
from ecw_bot import settings as cfg # noqa: E402
from ecw_bot.core import page_utils # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
class FakeDialog:
"""`type` and `message` as properties, like Playwright's."""
def __init__(self, kind, message="Are you sure?", raises=False):
self._kind, self._message, self.raises = kind, message, raises
self.answer = None
@property
def type(self):
return self._kind
@property
def message(self):
return self._message
def accept(self, prompt_text=None):
if self.raises:
raise RuntimeError("Cannot accept dialog which is already handled")
self.answer = "accept"
def dismiss(self):
if self.raises:
raise RuntimeError("Cannot dismiss dialog which is already handled")
self.answer = "dismiss"
def _answer(kind, save=False, **kw):
saved = cfg.SAVE_CORRECTIONS
cfg.SAVE_CORRECTIONS = save
d = FakeDialog(kind, **kw)
out = io.StringIO()
try:
with contextlib.redirect_stdout(out):
page_utils.auto_dialog(d)
finally:
cfg.SAVE_CORRECTIONS = saved
return d.answer, out.getvalue()
@case
def an_alert_is_accepted_in_both_modes():
assert _answer("alert")[0] == "accept"
assert _answer("alert", save=True)[0] == "accept"
@case
def a_confirm_or_prompt_is_cancelled_in_verify():
assert _answer("confirm")[0] == "dismiss"
assert _answer("prompt")[0] == "dismiss"
@case
def a_confirm_or_prompt_is_accepted_in_live_write():
assert _answer("confirm", save=True)[0] == "accept"
assert _answer("prompt", save=True)[0] == "accept"
@case
def beforeunload_is_always_dismissed():
assert _answer("beforeunload")[0] == "dismiss"
assert _answer("beforeunload", save=True)[0] == "dismiss"
@case
def the_log_says_what_it_was_and_what_the_bot_answered():
_, out = _answer("confirm", message="Save changes?")
assert "[DIALOG] confirm: 'Save changes?' -> Cancel" in out, out
_, out = _answer("alert", message="Done")
assert "-> OK" in out, out
@case
def an_already_answered_dialog_does_not_raise():
_answer("confirm", raises=True) # would raise out of the handler
def _real_browser_cases() -> int:
from playwright.sync_api import sync_playwright
failures = 0
saved = cfg.SAVE_CORRECTIONS
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
try:
for save, want in ((False, False), (True, True)):
name = (f"a real confirm answers {want} "
f"in {'LIVE-WRITE' if save else 'VERIFY'}, no crash")
cfg.SAVE_CORRECTIONS = save
ctx = browser.new_context()
# The way core/browser.py wires it: context AND page.
ctx.on("page", lambda p: p.on("dialog", page_utils.auto_dialog))
page = ctx.new_page()
page.on("dialog", page_utils.auto_dialog)
page.set_default_timeout(10_000)
page.set_content("<p>x</p>")
try:
with contextlib.redirect_stdout(io.StringIO()):
page.evaluate("alert('note')")
got = page.evaluate("confirm('Save changes?')")
assert got is want, got
assert page.evaluate("1 + 1") == 2, "tab still alive"
print(f" ok {name}")
except Exception as exc:
print(f" FAIL {name}: {type(exc).__name__}: {exc}")
failures += 1
ctx.close()
finally:
cfg.SAVE_CORRECTIONS = saved
browser.close()
return failures
def main() -> int:
failures = 0
for fn in CASES:
name = fn.__name__.replace("_", " ")
try:
fn()
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
failures += _real_browser_cases()
total = len(CASES) + 2
print(f"\n{total - failures}/{total} passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())