Files
ecw-charge-validation/tests/test_open_claim_recovery.py
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

658 lines
26 KiB
Python

r"""
open_claim() recovering from eCW's own stray "Account Inquiry" panel.
python tests\test_open_claim_recovery.py
Seen live (claim 9128000, Logs\run_20260915_195515.log): eCW opens its own
modal-style panel over the results grid —
`id="claimModal<timestamp>" class="modal fade bluetheme billing-width
right-panel-mode in"`, with a `claimModalBackdrop`-classed backdrop div that
covers the full page — and it intercepts the click meant for the claim
number underneath. Playwright's own retry trace named this element; nothing
in this codebase opens it on purpose. `open_claim()` retried the blocked
click for the full 30s timeout and gave up before ever reaching the claim
form, so Condition 1 (or any future condition) never got a chance to run.
This is a navigation problem, not a telehealth-detection problem — it
blocks every claim, not just telehealth ones — which is why it is tested
here rather than in test_telehealth_fix.py.
Local headless chromium only, exactly like the other page tests: no proxy,
no login, no eCW. Everything is built with `page.set_content()` rather than
a fixture file, the same pattern test_telehealth_fix.py already uses for its
HCFA-fallback cases — this scenario is about a page state (a stray backdrop
sitting over a claim link) that has nothing to do with the UB form fixture.
"""
import sys
import pathlib
import inspect
import tempfile
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
from ecw_bot.pages import claim_form # noqa: E402
from ecw_bot.selectors import claim_form as sel # noqa: E402
from ecw_bot.core import page_utils # noqa: E402
from ecw_bot import settings as cfg # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
# A backdrop shaped like eCW's real one: covers the whole viewport, so a real
# (non-forced) Playwright click on anything underneath is genuinely blocked —
# this is what makes the test prove the fix rather than assume it.
_BACKDROP = (
'<div class="modal fade ng-isolate-scope claimModalBackdrop in" '
'style="position:fixed;top:0;left:0;width:100%;height:100%;'
'background:rgba(0,0,0,0.01);z-index:1050;"></div>'
)
_RESULTS_GRID = (
'<span id="claimLink">9128000</span>'
'<div id="UBClaimDiv" style="display:none">'
' <div class="ubClaimNumberText"><span id="claimNoText"></span></div>'
# Non-empty and sized: SEL_ANY_CLAIM_FORM_OPEN is a comma (OR) selector
# and this element comes first in DOM order — an empty div has zero
# area, so wait_for_selector's state="visible" check would sit waiting
# on *this* forever even once #UBClaimBtn17 is genuinely visible.
' <div id="BillType" style="width:10px;height:10px;">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
'</div>'
'<script>'
'document.addEventListener("keydown", function (e) {'
' if (e.key === "Escape") {'
' var b = document.querySelector(".claimModalBackdrop");'
' if (b) b.remove();'
' }'
'});'
'document.getElementById("claimLink").addEventListener("click", function () {'
' document.getElementById("UBClaimDiv").style.display = "block";'
' document.getElementById("claimNoText").textContent = "9128000";'
'});'
'</script>'
)
@case
def dismiss_stray_modal_returns_false_when_nothing_is_there(page):
page.set_content(_RESULTS_GRID)
assert claim_form.dismiss_stray_modal(page) is False
@case
def dismiss_stray_modal_removes_a_visible_backdrop(page):
page.set_content(_BACKDROP + _RESULTS_GRID)
assert page.locator(sel.SEL_STRAY_MODAL_BACKDROP).first.is_visible()
assert claim_form.dismiss_stray_modal(page) is True
assert page.locator(sel.SEL_STRAY_MODAL_BACKDROP).count() == 0
@case
def open_claim_succeeds_normally_with_no_stray_modal(page):
"""Regression check: the new dismiss step must not change the happy path."""
page.set_content(_RESULTS_GRID)
assert claim_form.open_claim(page, "9128000") is True
assert page.locator("#UBClaimDiv").is_visible()
_DENTAL_GRID = (
'<span id="claimLink">9125000</span>'
'<div id="dentalClaim7777777777777" style="display:none">'
' <button id="cancelDentalClaimButton">Cancel</button>'
'</div>'
'<script>'
'document.getElementById("claimLink").addEventListener("click", function () {'
' document.getElementById("dentalClaim7777777777777").style.display = "block";'
'});'
'</script>'
)
def _open_and_capture(page, claim_id):
import io, contextlib
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
assert claim_form.open_claim(page, claim_id) is True
return buf.getvalue()
@case
def open_claim_logs_a_dental_claim_as_dental_not_hcfa(page):
"""Live run 2026-09-30: every Dental claim logged "(HCFA/professional
screen)" because the only check was "not UB"."""
page.set_content(_DENTAL_GRID)
out = _open_and_capture(page, "9125000")
assert "Claim form open (Dental screen)" in out, out
assert "HCFA" not in out, out
@case
def open_claim_dismisses_a_stray_modal_that_would_otherwise_block_the_click(page):
"""
The exact failure from run_20260915_195515.log, reproduced: a backdrop
already covers the claim link before open_claim() is ever called. Without
the proactive dismiss, this click blocks for the full timeout and raises
— this proves it doesn't, not just that the function returns something.
"""
page.set_content(_BACKDROP + _RESULTS_GRID)
assert claim_form.open_claim(page, "9128000") is True
assert page.locator("#UBClaimDiv").is_visible()
assert page.locator(sel.SEL_STRAY_MODAL_BACKDROP).count() == 0
# ── close_claim() recovering from the same stray backdrop ────────────────────
# The exact shape of the claim 9127000 failure (Logs\run_20260917_172622.log):
# Cancel, X, then Cancel again all timed out trying to close the form. Nothing
# in that log names what blocked the clicks, but it is the same failure mode
# open_claim() already had — a stray overlay sitting on top of the button —
# and dismiss_stray_modal() was never wired into close_claim()'s retry loop.
_UB_FORM_OPEN = (
'<div id="UBClaimDiv">'
' <div id="BillType" style="width:10px;height:10px;">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
' <button id="UBClaimBtn3">X</button>'
'</div>'
'<script>'
'document.addEventListener("keydown", function (e) {'
' if (e.key === "Escape") {'
' var b = document.querySelector(".claimModalBackdrop");'
' if (b) b.remove();'
' }'
'});'
'function closeUb() { document.getElementById("UBClaimDiv").style.display = "none"; }'
'document.getElementById("UBClaimBtn17").addEventListener("click", closeUb);'
'document.getElementById("UBClaimBtn3").addEventListener("click", closeUb);'
'</script>'
)
@case
def close_claim_succeeds_normally_with_no_stray_modal(page):
"""Regression check: the new dismiss step must not change the happy path."""
page.set_content(_UB_FORM_OPEN)
assert claim_form.close_claim(page) is True
assert page.locator("#UBClaimDiv").first.is_visible() is False
@case
def close_claim_dismisses_a_stray_modal_that_would_otherwise_block_cancel(page):
"""
Without the proactive dismiss, this click blocks for the full attempt
chain (Cancel, X, HCFA Cancel — none of them present or clickable under
the backdrop) and close_claim() gives up and returns False. This proves
it doesn't, not just that the function returns something.
"""
page.set_content(_BACKDROP + _UB_FORM_OPEN)
assert claim_form.close_claim(page) is True
assert page.locator("#UBClaimDiv").first.is_visible() is False
assert page.locator(sel.SEL_STRAY_MODAL_BACKDROP).count() == 0
# ── close_claim() when the operator already closed the form by hand ─────────
# Seen live on claim 9123000 (Logs\run_20260923_165056.log): the operator
# clicked eCW's own Cancel while the review box was up, and close_claim()
# then burned ~45s on four click attempts at buttons that no longer existed,
# ending in "Could not close the claim form" for a form that was closed.
_UB_FORM_ALREADY_CLOSED = (
'<div id="UBClaimDiv" style="display:none">'
' <div id="BillType">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
' <button id="UBClaimBtn3">X</button>'
'</div>'
)
@case
def close_claim_returns_at_once_when_the_form_was_already_closed_by_hand(page):
import time
page.set_content(_UB_FORM_ALREADY_CLOSED)
started = time.monotonic()
assert claim_form.close_claim(page) is True
elapsed = time.monotonic() - started
assert elapsed < 3, f"took {elapsed:.1f}s — still trying to click Cancel"
@case
def close_claim_still_closes_an_hcfa_form_behind_a_hidden_leftover_ub_form(page):
"""
The already-closed check must look at every screen's marker, not only
the first match in the page: a hidden UB form left in the DOM ahead of
an open HCFA form must not make it look like nothing is open.
"""
page.set_content(
_UB_FORM_ALREADY_CLOSED
+ '<div id="hcfa"><button ng-click="closeClaimScreen()" '
'onclick="document.getElementById(\'hcfa\').style.display=\'none\'">'
'Cancel</button></div>')
assert claim_form.close_claim(page) is True
assert page.locator("#hcfa").is_visible() is False, \
"the open HCFA form was skipped instead of closed"
# A permanent overlay with NO known class (unlike .claimModalBackdrop) —
# dismiss_stray_modal() cannot recognize or remove this one, so every click
# attempt on claimLink genuinely fails, for the whole test. The form opens
# underneath it regardless, exactly like the "Follow up" panel seen live on
# claim 9129000 (Logs\run_20260916_162654.log) sitting beside an open, fully
# populated UB form.
#
# It opens a second after load, while the first click is still being
# blocked — not before open_claim() is called: a form already on screen
# before the click is a LEFTOVER from an earlier claim, which open_claim()
# now Cancels first (final review, 2026-10-05; see the leftover cases below).
_ALREADY_OPEN_WITH_UNKNOWN_OVERLAY = (
'<span id="claimLink">9128000</span>'
'<div id="UBClaimDiv" style="display:none">'
' <div class="ubClaimNumberText"><span id="claimNoText">9128000</span></div>'
' <div id="BillType" style="width:10px;height:10px;">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
'</div>'
'<div id="unknownOverlay" style="position:fixed;top:0;left:0;'
'width:100%;height:100%;background:rgba(0,0,0,0.01);z-index:2000;">'
'</div>'
'<script>'
'setTimeout(function () {'
' document.getElementById("UBClaimDiv").style.display = "block";'
'}, 1000);'
'</script>'
)
@case
def open_claim_treats_the_form_as_open_even_when_the_click_keeps_failing(page):
"""
The exact shape of the claim 9129000 failure: a stray overlay this
codebase has no selector for at all blocks every click attempt, but the
claim form is already open and populated underneath it regardless.
open_claim() must trust the form's own presence over a reported click
failure, rather than raising when the claim actually did open.
"""
page.set_content(_ALREADY_OPEN_WITH_UNKNOWN_OVERLAY)
assert claim_form.open_claim(page, "9128000") is True
# Same unknown overlay, but the form is NOT open yet when the click gives up:
# it finishes opening about a second later (6s after load; the first click
# now gives up at 5s). Review item D11, 2026-10-01: the fallback used to be
# `visible(timeout=1_000)`, which Playwright 1.62 answers instantly — so a
# form still opening was missed, and attempt 2's Escape closed it (on HCFA
# without closeClaimScreen()). Escapes are counted so the test can tell.
_OPENS_LATE_UNDER_UNKNOWN_OVERLAY = (
'<span id="claimLink">9128000</span>'
'<div id="UBClaimDiv" style="display:none">'
' <div class="ubClaimNumberText"><span id="claimNoText">9128000</span></div>'
' <div id="BillType" style="width:10px;height:10px;">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
'</div>'
'<div id="unknownOverlay" style="position:fixed;top:0;left:0;'
'width:100%;height:100%;background:rgba(0,0,0,0.01);z-index:2000;">'
'</div>'
'<script>'
'window.escapes = 0;'
'document.addEventListener("keydown", function (e) {'
' if (e.key === "Escape") window.escapes++;'
'});'
'setTimeout(function () {'
' document.getElementById("UBClaimDiv").style.display = "block";'
'}, 6000);'
'</script>'
)
@case
def open_claim_fallback_waits_for_a_form_still_opening(page):
"""
The fallback is `appears(..., 5_000)`, a real wait, and the first click
gives up at 5s, not 30s. With the old 30s click this takes 30s+; with the
old instant check it Escapes and spends another 15s on attempt 2 — both
fail the timing and Escape assertions below.
"""
import time
page.set_content(_OPENS_LATE_UNDER_UNKNOWN_OVERLAY)
started = time.monotonic()
out = _open_and_capture(page, "9128000")
took = time.monotonic() - started
assert took < 12, f"took {took:.1f}s"
assert page.evaluate("window.escapes") == 0, \
"attempt 2 ran — the fallback did not wait for the form"
# The click's own first line and its elapsed seconds, for the root-cause
# study of why eCW's click reports failure on a form that opened.
assert "Click reported failure, but the claim form is already open" \
in out, out
assert "(click: " in out and "failed after 5." in out \
and "s later)" in out, out
class _ClickRaisesBlank:
"""
The real page, except the claim link's click raises an exception with
an EMPTY message. `str(exc).splitlines()[0]` is an IndexError on that —
raised inside the except block, out of open_claim(), losing a claim
whose form was open. Playwright's own errors always carry a message;
this proves the log line does not depend on it.
"""
def __init__(self, page):
self._page = page
def __getattr__(self, name):
return getattr(self._page, name)
def get_by_text(self, *a, **k):
class _Link:
@property
def first(self):
return self
def click(self, **kw):
raise Exception()
return _Link()
@case
def open_claim_survives_a_click_error_with_no_message(page):
page.set_content(_ALREADY_OPEN_WITH_UNKNOWN_OVERLAY)
out = _open_and_capture(_ClickRaisesBlank(page), "9128000")
assert "already open" in out and "(click: Exception;" in out, out
# ── A form left open by an earlier claim ─────────────────────────────────────
# Final review, 2026-10-05: when the previous claim's close_claim() gave up,
# its form was still on screen at the next open_claim(), and the "click
# reported failure, but the claim form is already open" fallback accepted it
# as the new claim's. On an HCFA screen with no claim-number field nothing
# checks identity after that, so the conditions ran — and were logged — on
# the wrong claim. open_claim() now Cancels a leftover form before its click
# and refuses to go on over one that will not close.
# An earlier claim's UB form still up (claim 9112000); its Cancel works. The
# link opens this claim's form and fills in its number.
_LEFTOVER_UB_THAT_CLOSES = (
'<span id="claimLink">9128000</span>'
'<div id="UBClaimDiv" style="display:block">'
' <div class="ubClaimNumberText"><span id="claimNoText">9112000</span></div>'
' <div id="BillType" style="width:10px;height:10px;">Claim</div>'
' <button id="UBClaimBtn17">Cancel</button>'
'</div>'
'<script>'
'window.cancels = 0; window.linkClicks = 0;'
'document.getElementById("UBClaimBtn17").addEventListener("click", function () {'
' window.cancels++;'
' document.getElementById("UBClaimDiv").style.display = "none";'
'});'
'document.getElementById("claimLink").addEventListener("click", function () {'
' window.linkClicks++;'
' document.getElementById("claimNoText").textContent = "9128000";'
' document.getElementById("UBClaimDiv").style.display = "block";'
'});'
'</script>'
)
@case
def open_claim_cancels_a_form_left_open_by_an_earlier_claim_first(page):
page.set_content(_LEFTOVER_UB_THAT_CLOSES)
out = _open_and_capture(page, "9128000")
assert "already open — left from an earlier claim" in out, out
assert page.evaluate("window.cancels") == 1, \
"the leftover form was not Cancelled before the click"
assert page.evaluate("window.linkClicks") == 1
assert page.locator("#claimNoText").inner_text() == "9128000"
# An earlier claim's HCFA screen (no claim-number field) still up, and the
# claim link is blocked by an overlay nothing can dismiss — the exact shape
# where the fallback used to take the stale form for this claim's.
_LEFTOVER_HCFA_UNDER_OVERLAY = (
'<span id="claimLink">9128000</span>'
'<div id="hcfa"><button ng-click="closeClaimScreen()" id="hcfaCancel">'
'Cancel</button></div>'
'<div id="unknownOverlay" style="position:fixed;top:0;left:0;'
'width:100%;height:100%;background:rgba(0,0,0,0.01);z-index:2000;">'
'</div>'
'<script>'
'window.cancels = 0;'
'document.getElementById("hcfaCancel").addEventListener("click", function () {'
' window.cancels++;'
'});'
'</script>'
)
@case
def open_claim_refuses_to_go_on_over_a_leftover_form_that_will_not_close(page):
"""
close_claim() gives up (stood in for: its four real attempts take the
best part of two minutes). Without the guard the blocked click's fallback
sees the HCFA screen and returns True — the stale form, read as 9128000.
"""
page.set_content(_LEFTOVER_HCFA_UNDER_OVERLAY)
orig = claim_form.close_claim
claim_form.close_claim = lambda p: False
try:
try:
_open_and_capture(page, "9128000")
except RuntimeError as exc:
assert "still open and would not close" in str(exc), exc
else:
raise AssertionError("open_claim() accepted the leftover form")
finally:
claim_form.close_claim = orig
@case
def open_claim_does_not_trust_a_cancel_that_left_an_hcfa_form_up(page):
"""
close_claim()'s own "hidden" check is on the UB form's div, which the
HCFA screen does not have, so it answers True while this Cancel (one
that does nothing) leaves the screen up. open_claim() checks every
screen itself before it clicks.
"""
page.set_content(_LEFTOVER_HCFA_UNDER_OVERLAY.replace(
'z-index:2000;', 'z-index:-1;'))
try:
_open_and_capture(page, "9128000")
except RuntimeError as exc:
assert "still open and would not close" in str(exc), exc
else:
raise AssertionError("open_claim() accepted the leftover form")
assert page.evaluate("window.cancels") >= 1
# A hidden leftover backdrop FIRST in the DOM, the live panel second; Escape
# starts the live one's close, which takes 1s (an animation). Final review,
# 2026-10-05: the check counts any VISIBLE match, but the wait was on
# `.first` — the hidden copy — so it returned at once, with the panel still
# up over the grid.
_HIDDEN_THEN_LIVE_BACKDROP = (
'<div class="modal fade claimModalBackdrop" style="display:none"></div>'
'<div id="live" class="modal fade ng-isolate-scope claimModalBackdrop in" '
'style="position:fixed;top:0;left:0;width:100%;height:100%;'
'background:rgba(0,0,0,0.01);z-index:1050;"></div>'
'<script>'
'document.addEventListener("keydown", function (e) {'
' if (e.key === "Escape") setTimeout(function () {'
' var b = document.getElementById("live"); if (b) b.remove();'
' }, 1000);'
'});'
'</script>'
)
@case
def dismiss_stray_modal_waits_for_the_visible_backdrop_not_a_hidden_copy(page):
page.set_content(_HIDDEN_THEN_LIVE_BACKDROP)
assert claim_form.dismiss_stray_modal(page) is True
assert page.locator(".claimModalBackdrop").filter(
visible=True).count() == 0, \
"returned while the live panel was still up"
# ── The stranded scroll lock ──────────────────────────────────────────────────
# Captured live on the Claim Lookup screen: `<body class="modal-open">` with no
# `.modal-backdrop` in the document and every `.modal` still `fade` without
# `in`. Bootstrap's own stylesheet hangs `overflow: hidden` off that class, so
# the page cannot scroll and there is no dialog on screen to explain it.
#
# The stylesheet is real here rather than mocked: without `body.modal-open
# {overflow:hidden}` actually loaded, the assertions below would pass against a
# body that was never locked in the first place and prove nothing.
_LOCK_CSS = (
"<style>"
"body.modal-open{overflow:hidden;}"
".modal{display:none;}"
".modal.in{display:block;width:200px;height:200px;}"
"</style>"
)
def _doc(body_class, inner):
return (f"<html><head>{_LOCK_CSS}</head>"
f'<body class="{body_class}">{inner}'
'<div style="height:3000px">tall enough to need scrolling</div>'
"</body></html>")
_CLOSED_DIALOG = '<div class="modal fade" id="someDialog">Account Inquiry</div>'
_OPEN_DIALOG = '<div class="modal fade in" id="someDialog">Account Inquiry</div>'
def _overflow(page):
return page.evaluate("getComputedStyle(document.body).overflow")
@case
def clear_stale_modal_lock_is_false_when_body_was_never_locked(page):
page.set_content(_doc("", _CLOSED_DIALOG))
assert claim_form.clear_stale_modal_lock(page) is False
@case
def clear_stale_modal_lock_unlocks_a_body_with_no_modal_showing(page):
"""The exact captured state: modal-open, no backdrop, nothing rendered."""
page.set_content(_doc("modal-open", _CLOSED_DIALOG))
assert _overflow(page) == "hidden", "fixture did not actually lock the body"
assert claim_form.clear_stale_modal_lock(page) is True
assert _overflow(page) != "hidden", "body is still scroll-locked"
assert page.evaluate(
"document.body.classList.contains('modal-open')") is False
@case
def clear_stale_modal_lock_leaves_a_genuinely_open_modal_alone(page):
"""
The guard that makes this safe to call unconditionally. A modal that is
really on screen owns the lock legitimately — stripping it would let the
page scroll around behind a live dialog, which is worse than the bug.
"""
page.set_content(_doc("modal-open", _OPEN_DIALOG))
assert claim_form.clear_stale_modal_lock(page) is False
assert _overflow(page) == "hidden", "a live dialog's lock was stripped"
assert page.evaluate(
"document.body.classList.contains('modal-open')") is True
@case
def clear_stale_modal_lock_also_removes_orphaned_backdrops(page):
page.set_content(_doc(
"modal-open",
_CLOSED_DIALOG + '<div class="modal-backdrop fade in"></div>'
'<div class="modal-backdrop fade in"></div>'))
assert claim_form.clear_stale_modal_lock(page) is True
assert page.locator(".modal-backdrop").count() == 0
@case
def dismiss_stray_modal_clears_a_lock_left_behind_with_no_backdrop(page):
"""
Why the lock check cannot be gated on a backdrop being present: there is no
`.claimModalBackdrop` here at all, so the original backdrop-only path bails
out at the first line and reports "nothing to do" over a body that cannot
scroll.
"""
page.set_content(_doc("modal-open", _CLOSED_DIALOG))
assert claim_form.dismiss_stray_modal(page) is True
assert _overflow(page) != "hidden", "body is still scroll-locked"
@case
def dismiss_stray_modal_handles_a_backdrop_and_a_lock_together(page):
"""
Escape closing the panel does not, on its own, put the class back — so the
lock check runs after the Escape rather than instead of it.
"""
# _RESULTS_GRID carries the keydown handler that makes Escape actually
# remove the backdrop, the same way the real panel responds to it.
page.set_content(_doc("modal-open",
_BACKDROP + _RESULTS_GRID + _CLOSED_DIALOG))
assert claim_form.dismiss_stray_modal(page) is True
assert page.locator(sel.SEL_STRAY_MODAL_BACKDROP).count() == 0, "Escape should have removed the stray backdrop"
assert _overflow(page) != "hidden", "backdrop went but the scroll lock stayed behind"
@case
def reset_scroll_names_the_inner_container_that_actually_scrolled(page):
"""
`window.scrollTo(0, 0)` is a no-op when the document is not the scroller —
the whole reason reset_scroll sweeps for the real container and reports it.
"""
page.set_content(
'<div id="grid" style="height:100px;overflow:auto;">'
'<div style="height:2000px">rows</div></div>')
page.evaluate("document.getElementById('grid').scrollTop = 400")
moved = page_utils.reset_scroll(page)
assert any("#grid" in m for m in moved), moved
assert page.evaluate("document.getElementById('grid').scrollTop") == 0
def main() -> int:
try:
from playwright.sync_api import sync_playwright
except ImportError:
print(" SKIP playwright is not installed")
return 0
failures = 0
# The leftover-form cases take a screenshot on their raise path: into a
# temp folder, never the operators' real dated Screenshots folder.
orig_shots = cfg.SCREENSHOTS
with tempfile.TemporaryDirectory() as tmp, sync_playwright() as pw:
cfg.SCREENSHOTS = pathlib.Path(tmp)
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()
cfg.SCREENSHOTS = orig_shots
print(f"\n{len(CASES) - failures}/{len(CASES)} passed")
return 1 if failures else 0
if __name__ == "__main__":
sys.exit(main())