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

602 lines
26 KiB
Python

r"""
conditions/medicare_telehealth.py — document condition #22: POS=2/M1=93-or-95
(per audio/video mode) on the claim's primary CPT row, PLUS adding G2025 with
the same modifier and zeroing/unchecking every other CPT row once G2025 is
confirmed on the claim.
python tests\test_conditions_medicare_telehealth.py
Two fixtures, deliberately: ub_claim.html for the base detect/POS/M1 shape
(same as test_conditions_telehealth.py), and hcfa_claim.html for the full
G2025 flow — the Add-button-plus-Fee-Schedule-picker interaction is only
verified evidence on the "Claim"/HCFA and Dental screens (see
hcfa_claim.html's own comment), not the UB form. On ub_claim.html, the
condition correctly reports "could not confirm G2025" and skips the
cleanup step rather than guessing at a flow with no evidence behind it
there — that honest degradation is itself part of what these tests prove.
Two other things this file exists to prove, beyond the basic
detect/apply/describe shape:
* **this condition and the general telehealth condition never both fire
on the same claim** — Medicare is excluded from telehealth.py's own
modifier map specifically so this doesn't happen (see
test_conditions_telehealth.py's `video_modifier_is_none_for_medicare_too`);
* **G2025 gets the SAME audio/video modifier as the primary row**, not a
hardcoded 95 — the document's own wording ("add G2025 with 95 modifier")
is the video case; an audio-only Medicare visit needs 93 on both rows,
the same reasoning `telehealth_mode()` already applies elsewhere.
"""
import sys
import pathlib
import inspect
ROOT = pathlib.Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "src"))
FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "ub_claim.html"
HCFA_FIXTURE = pathlib.Path(__file__).resolve().parent / "fixtures" / "hcfa_claim.html"
from ecw_bot.conditions import medicare_telehealth # noqa: E402
from ecw_bot.conditions.base import ConditionResult, run_all # noqa: E402
from ecw_bot.selectors import claim_form as sel # noqa: E402
from ecw_bot.pages import claim_form # noqa: E402
CASES = []
def case(fn):
CASES.append(fn)
return fn
_MEDICARE_DATA = {
"progress_notes": {"text": "Session Setting (PoS): TeleHealth"},
"insurance": [{"Name": "Humana Medicare"}],
}
@case
def detect_true_for_telehealth_plus_medicare():
assert medicare_telehealth.detect("C1", _MEDICARE_DATA) is True
@case
def detect_false_when_the_note_does_not_mention_telehealth():
data = {
"progress_notes": {"text": "Regular in-office visit."},
"insurance": [{"Name": "Humana Medicare"}],
}
assert medicare_telehealth.detect("C1", data) is False
@case
def detect_false_for_a_non_medicare_payer():
data = {
"progress_notes": {"text": "Session Setting (PoS): TeleHealth"},
"insurance": [{"Name": "BH COLORADO ACCESS"}], # Medicaid
}
assert medicare_telehealth.detect("C1", data) is False
@case
def detect_false_with_no_insurance_row_at_all():
data = {"progress_notes": {"text": "Session Setting (PoS): TeleHealth"}}
assert medicare_telehealth.detect("C1", data) is False
@case
def apply_sets_pos_2_and_mod1_95_on_the_first_row_only(page):
"""
ub_claim.html has no Add-flow evidence (see this file's module
docstring), so G2025 must come back "could not confirm" here — the
POS/M1 change on the primary row must still land regardless, and the
honest failure to confirm G2025 must mean the cleanup step is SKIPPED
entirely, not applied against a claim with nothing else billable.
"""
page.goto(FIXTURE.as_uri())
result = medicare_telehealth.apply(page, "9120000", _MEDICARE_DATA)
assert isinstance(result, ConditionResult)
assert result.condition_name == "medicare_telehealth"
assert result.changed is True
primary = result.changes[0]
assert primary["code"] == "H0023", result.changes
assert primary["after"]["pos"] == "2", result.changes
assert primary["after"]["mod1"] == "95", result.changes
assert [c for c in result.changes if c.get("added")] == [], result.changes
assert [c for c in result.changes if "fee" in c] == [], result.changes
@case
def apply_uses_93_for_an_audio_only_medicare_visit(page):
"""
Real live example (a progress note reading "THV Audio Only: Telehealth
audio phone call only. No video."): Medicare telehealth is NOT always
modifier 95 — an audio-only visit still needs 93 on the primary row,
the same as any other payer's audio-only visit.
"""
page.goto(FIXTURE.as_uri())
data = {
"progress_notes": {"text": "THV Audio Only: Telehealth audio "
"phone call only. No video."},
"insurance": [{"Name": "Humana Medicare"}],
}
result = medicare_telehealth.apply(page, "9120000", data)
assert result.changed is True
assert result.changes[0]["after"]["mod1"] == "93", result.changes
@case
def apply_adds_g2025_via_the_real_fee_schedule_picker_on_the_hcfa_screen(page):
"""
The full real flow, on the screen where it is actually verified (see
hcfa_claim.html's own comment: claim 9109000's live evidence): click
Add, search the Fee Schedule picker for G2025, select the one exact
match, click its OK — never typing straight into the grid's trailing
"*" row. "Other Primary CPT codes" then means every code but G2025,
INCLUDING 99213 (which just got POS=2/M1=95) and G8510 (already
excluded, but its fee is not yet zero) — per the document's own
wording, not a guess.
"""
page.goto(HCFA_FIXTURE.as_uri())
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert result.changed is True
primary = result.changes[0]
assert primary["code"] == "99213", result.changes
added = [c for c in result.changes if c.get("added")]
assert len(added) == 1, result.changes
assert added[0]["code"] == "G2025" and added[0]["modifier"] == "95", added
zeroed = {c["code"] for c in result.changes if "fee" in c}
assert zeroed == {"99213", "J7298", "G8510"}, result.changes
g2025_row = page.locator(sel.SEL_CPT_ROWS).last
assert g2025_row.locator(sel.SEL_CPT_CODE).first.input_value() == "G2025"
assert g2025_row.locator(sel.SEL_CPT_MOD1).first.input_value() == "95"
@case
def apply_leaves_g2025_alone_and_still_cleans_up_when_it_is_already_present(page):
"""
G2025 already on the claim (a prior run, or an operator added it by
hand): add_cpt_row() must never be attempted a second time, but the
unchecked/zero-fee cleanup on every OTHER row still has to run. G2025
is seeded directly here (not via add_cpt_row) - this case is about
what apply() does once G2025 already exists, not about the add
mechanism itself.
"""
page.goto(HCFA_FIXTURE.as_uri())
page.evaluate(
"""(code) => {
const tbody = document.querySelector(
'#claimCPTCodesTable8888888888888 tbody');
const addRowTr = tbody.querySelector('tr:last-child');
const tr = document.createElement('tr');
tr.id = 'CPTROW_3';
tr.innerHTML =
'<td><input data-fieldname="claimCPTCode" value="' + code + '" readonly></td>'
+ '<td><input data-fieldname="ClaimCPTMOD1" value="95"></td>'
+ '<td><input data-fieldname="ClaimCPTBilledFee" value="25.00"></td>';
tbody.insertBefore(tr, addRowTr);
}""", "G2025")
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
added = [c for c in result.changes if c.get("added")]
assert added == [], result.changes # never re-added
zeroed = {c["code"] for c in result.changes if "fee" in c}
assert zeroed == {"99213", "J7298", "G8510"}, result.changes
g2025_row = page.locator(sel.SEL_CPT_ROWS).last
assert g2025_row.locator(sel.SEL_CPT_MOD1).first.input_value() == "95", \
"the pre-existing G2025 row's own modifier must be left untouched"
@case
def describe_names_the_row_and_the_modifier(page):
page.goto(HCFA_FIXTURE.as_uri())
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
text = medicare_telehealth.describe(result)
assert "99213" in text, text
assert "95" in text, text
assert "G2025" in text, text
# _MEDICARE_DATA's note text is "Session Setting (PoS): TeleHealth" —
# the "telehealth" signal — so the operator can see WHY this fired.
assert "telehealth" in text, text
@case
def run_all_fires_only_medicare_telehealth_not_the_general_condition(page):
"""
Against the REAL registry (conditions.CONDITIONS), not fakes — proves
telehealth.py and medicare_telehealth.py do not both write to the same
Medicare claim's primary row. If they did, the second condition's fill()
would overwrite the first's, and the confirmation dialog would show the
same row twice.
"""
page.goto(FIXTURE.as_uri())
results = run_all(page, "9120000", _MEDICARE_DATA)
assert len(results) == 1, results
assert results[0].condition_name == "medicare_telehealth", results
@case
def run_all_still_fires_only_medicare_on_an_audio_only_medicare_claim(page):
"""
The exact scenario that would have double-fired before telehealth.py's
_choose_modifier checked payer group before the audio/video split: an
audio-only Medicare visit. Both conditions' detect() would return True
for this claim, so this proves only one of them actually writes.
"""
page.goto(FIXTURE.as_uri())
data = {
"progress_notes": {"text": "THV Audio Only: Telehealth audio "
"phone call only. No video."},
"insurance": [{"Name": "Humana Medicare"}],
}
results = run_all(page, "9120000", data)
assert len(results) == 1, results
assert results[0].condition_name == "medicare_telehealth", results
assert results[0].changes[0]["after"]["mod1"] == "93", results
@case
def a_g2025_that_cannot_be_added_is_reported_failed_and_sent_to_review(page):
"""
Review item D6: on ub_claim.html the Add flow has no evidence, so
G2025 cannot be confirmed. The primary row DID change (POS 11 -> 2),
so the result is still changed=True — the operator must see it — but
it carries a G2025 failed record, a review reason (which keeps Save
away), a plain describe() line, and a summary comment that says so
instead of the fixed "G2025 added … other CPTs unchecked".
"""
page.goto(FIXTURE.as_uri())
result = medicare_telehealth.apply(page, "9120000", _MEDICARE_DATA)
assert result.changed is True, result
assert result.review == medicare_telehealth.G2025_NOT_ADDED == (
"G2025 NOT added — add it by hand; the other CPT rows were NOT "
"unchecked"), result.review
failed = [c for c in result.changes if c.get("failed")]
assert len(failed) == 1 and failed[0]["code"] == "G2025" \
and failed[0]["row"] is None and failed[0]["modifier"] == "95", \
result.changes
text = medicare_telehealth.describe(result)
assert " G2025 NOT added — add it by hand; other rows NOT unchecked" \
in text, text
assert medicare_telehealth.summary_comment(result) == (
"Medicare Telehealth (POS 02 / modifier 95 on H0023; G2025 NOT "
"added — add by hand)")
# Through the runner: the review reaches the report (what the save
# gate and the Charge Summary read), the comment replaces the fixed one.
page.goto(FIXTURE.as_uri())
report = {}
run_all(page, "9120000", _MEDICARE_DATA,
conditions=[medicare_telehealth], report=report)
assert report["review"] == [{"name": "medicare_telehealth",
"reason": medicare_telehealth.G2025_NOT_ADDED}
], report
assert "G2025 NOT added" in report["comments"]["medicare_telehealth"]
@case
def a_failed_g2025_with_nothing_else_changed_is_declined(page):
"""Primary row already right (POS 02 / 95) and G2025 cannot be added:
nothing changed, but the claim still lacks G2025 — declined, not
none(), so it is a Review row rather than Pass."""
page.goto(FIXTURE.as_uri())
row0 = page.locator(sel.SEL_CPT_ROWS).nth(0)
row0.locator(sel.SEL_CPT_POS).first.fill("02")
row0.locator(sel.SEL_CPT_MOD1).first.fill("95")
result = medicare_telehealth.apply(page, "9120000", _MEDICARE_DATA)
assert result == ConditionResult.declined(
"medicare_telehealth", medicare_telehealth.G2025_NOT_ADDED), result
@case
def a_g2025_modifier_that_does_not_take_is_sent_to_review(page):
"""
The add worked but M1 on the new G2025 row did not take (read back
after `_set_row_modifier`, review item D6). Simulated by making the
modifier write a no-op: the G2025 row reads back with a blank M1.
"""
page.goto(HCFA_FIXTURE.as_uri())
original = claim_form._set_row_modifier
claim_form._set_row_modifier = lambda page, code, modifier: True
try:
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
finally:
claim_form._set_row_modifier = original
assert result.changed is True, result
assert result.review == ("G2025 added but its modifier did not take — "
"check M1"), result.review
assert [c for c in result.changes if c.get("added")], result.changes
@case
def g2025_modifier_ok_is_true_when_the_modifier_took(page):
page.goto(HCFA_FIXTURE.as_uri())
g = claim_form.apply_medicare_telehealth_g2025(page, "95")
assert g["g2025_added"] is True and g["g2025_modifier_ok"] is True, g
@case
def apply_twice_on_the_hcfa_screen_is_none_the_second_time(page):
"""
Review item D3: once G2025 is present, the primary row is POS 2 / 95
and every other row is already unchecked at 0.00, the claim follows
rule #22 — the second apply is none(), not a change and not a review.
"""
page.goto(HCFA_FIXTURE.as_uri())
first = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert first.changed is True and first.review == "", first
# Unchecked rows and fee-only rows are counted apart (final review,
# 2026-10-05; this used to expect "3 other CPT(s) unchecked/fee
# zeroed", which suggested an uncheck on the row that only had its fee
# zeroed).
assert medicare_telehealth.summary_comment(first) == (
"Medicare Telehealth (POS 02 / modifier 95 on 99213; G2025 added; "
"2 other CPT(s) unchecked; 1 other CPT fee(s) zeroed)"), \
medicare_telehealth.summary_comment(first)
second = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert second == ConditionResult.none("medicare_telehealth"), second
@case
def an_already_correct_primary_row_still_runs_the_g2025_step(page):
"""The primary row is already POS 02 / 95: it is not a change, but
G2025 is still added and the other rows still zeroed."""
page.goto(HCFA_FIXTURE.as_uri())
row0 = page.locator(sel.SEL_CPT_ROWS).nth(0)
row0.locator(sel.SEL_CPT_POS).first.fill("02")
row0.locator(sel.SEL_CPT_MOD1).first.fill("95")
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert result.changed is True and result.review == "", result
assert not [c for c in result.changes if "before" in c], result.changes
assert [c for c in result.changes if c.get("added")], result.changes
assert medicare_telehealth.summary_comment(result).startswith(
"Medicare Telehealth (G2025 added; "), \
medicare_telehealth.summary_comment(result)
def _seed_g2025(page, mod1):
"""Put a G2025 row with the given M1 on hcfa_claim.html directly — the
same seeding as the already-present case above."""
page.evaluate(
"""(mod1) => {
const tbody = document.querySelector(
'#claimCPTCodesTable8888888888888 tbody');
const addRowTr = tbody.querySelector('tr:last-child');
const tr = document.createElement('tr');
tr.id = 'CPTROW_3';
tr.innerHTML =
'<td><input data-fieldname="claimCPTCode" value="G2025" readonly></td>'
+ '<td><input data-fieldname="ClaimCPTMOD1" value="' + mod1 + '"></td>'
+ '<td><input data-fieldname="ClaimCPTBilledFee" value="25.00"></td>';
tbody.insertBefore(tr, addRowTr);
}""", mod1)
@case
def an_already_present_g2025_with_the_right_modifier_reads_ok(page):
"""Review fix round 1: a pre-existing G2025's M1 is read back too."""
page.goto(HCFA_FIXTURE.as_uri())
_seed_g2025(page, "95")
g = claim_form.apply_medicare_telehealth_g2025(page, "95")
assert g["g2025_added"] is False and g["g2025_confirmed"] is True, g
assert g["g2025_present_modifier"] == "95", g
assert g["g2025_present_modifier_ok"] is True, g
assert g["g2025_modifier_ok"] is None, g
@case
def an_already_present_g2025_with_a_blank_modifier_is_sent_to_review(page):
"""
Review fix round 1 (2026-10-05): G2025 already on the claim with a
blank M1. Before, its M1 was never read, and once nothing else was left
to change the log said "already correct … G2025 present" and the claim
passed. Now: the other rows are still cleaned up (G2025 IS on the
claim), but the result carries a review, a describe() line and a
summary comment naming the wrong M1 — and the M1 itself is left as is.
"""
page.goto(HCFA_FIXTURE.as_uri())
_seed_g2025(page, "")
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert result.changed is True, result
assert result.review == "G2025 present but M1 is blank, not 95 — check it", \
result.review
zeroed = {c["code"] for c in result.changes if "fee" in c}
assert zeroed == {"99213", "J7298", "G8510"}, result.changes
assert [c for c in result.changes if c.get("mismatch")], result.changes
text = medicare_telehealth.describe(result)
assert " G2025 already present but M1 is blank, not 95 — check it" \
in text, text
assert "G2025 present but M1 blank, not 95 — check it" in \
medicare_telehealth.summary_comment(result), \
medicare_telehealth.summary_comment(result)
g2025_row = page.locator(sel.SEL_CPT_ROWS).last
assert g2025_row.locator(sel.SEL_CPT_MOD1).first.input_value() == "", \
"a pre-existing G2025's modifier is read, never rewritten"
@case
def a_wrong_g2025_modifier_with_nothing_else_to_change_is_declined_not_none(page):
"""
The exact case the reviewer named: the first apply adds G2025 (95) and
cleans up; then the G2025 M1 is blanked. The second apply has nothing
to change — but the claim does NOT follow rule #22, so it is declined
(a Review row), not none() (a Pass).
"""
page.goto(HCFA_FIXTURE.as_uri())
first = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert first.changed is True and first.review == "", first
page.locator(sel.SEL_CPT_ROWS).last.locator(
sel.SEL_CPT_MOD1).first.fill("")
second = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
assert second == ConditionResult.declined(
"medicare_telehealth",
"G2025 present but M1 is blank, not 95 — check it"), second
@case
def a_95_g2025_on_an_audio_only_visit_is_sent_to_review(page):
"""G2025 already present with 95, but the note is audio-only (93):
the primary row is moved to 93 and the G2025 mismatch goes to review."""
page.goto(HCFA_FIXTURE.as_uri())
_seed_g2025(page, "95")
data = {"progress_notes": {"text": "THV Audio Only: Telehealth audio "
"phone call only. No video."},
"insurance": [{"Name": "Humana Medicare"}]}
result = medicare_telehealth.apply(page, "9103000", data)
assert result.changed is True, result
assert result.review == "G2025 present but M1 is 95, not 93 — check it", \
result.review
@case
def a_primary_write_that_does_not_take_stops_before_g2025(page):
"""
Review fix round 1 (2026-10-05): when the primary row's POS/M1 write
does not take, the claim is already known to be wrong — no G2025 add
and no other row zeroed/unchecked, just the changed primary row and a
review. Simulated by making set_cpt_pos_modifier report ok False.
"""
page.goto(HCFA_FIXTURE.as_uri())
before_rows = page.locator(sel.SEL_CPT_ROWS).count()
before_fees = [page.locator(sel.SEL_CPT_ROWS).nth(i)
.locator(sel.SEL_CPT_BILLED_FEE).first.input_value()
for i in range(before_rows)]
original = claim_form.set_cpt_pos_modifier
claim_form.set_cpt_pos_modifier = lambda page, pos, modifier, only_first=False: [
{"row": 0, "code": "99213", "before": {"pos": "11", "mod1": ""},
"after": {"pos": "11", "mod1": ""}, "changed": True, "ok": False}]
try:
result = medicare_telehealth.apply(page, "9103000", _MEDICARE_DATA)
finally:
claim_form.set_cpt_pos_modifier = original
assert result.changed is True, result
assert result.review == (
"POS/modifier did not take on row 1 — set it by hand; G2025 was NOT "
"added and the other CPT rows were NOT unchecked"), result.review
assert [c["code"] for c in result.changes] == ["99213"], result.changes
assert not claim_form.cpt_code_present(page, "G2025"), \
"G2025 must not be added after a primary write that did not take"
assert page.locator(sel.SEL_CPT_ROWS).count() == before_rows
after_fees = [page.locator(sel.SEL_CPT_ROWS).nth(i)
.locator(sel.SEL_CPT_BILLED_FEE).first.input_value()
for i in range(before_rows)]
assert after_fees == before_fees, (before_fees, after_fees)
assert "did NOT take" in medicare_telehealth.describe(result)
@case
def describe_shows_the_mode_evidence(page):
page.goto(FIXTURE.as_uri())
data = {"progress_notes": {"text": "THV Audio Only: Telehealth audio "
"phone call only. No video."},
"insurance": [{"Name": "Humana Medicare"}]}
result = medicare_telehealth.apply(page, "9120000", data)
text = medicare_telehealth.describe(result)
assert 'mode audio (matched "Audio Only")' in text, text
@case
def a_live_grid_that_reads_empty_after_a_full_first_read_is_declined(page):
"""Final review, 2026-10-05: as for telehealth — declined, not none()."""
from ecw_bot.conditions.base import CPT_GRID_NOT_READ
page.goto(FIXTURE.as_uri())
page.evaluate("""() => document.querySelectorAll('tr[id^="CPTROW_"]')
.forEach(r => r.remove())""")
result = medicare_telehealth.apply(
page, "9120000", dict(_MEDICARE_DATA, cpt=[{"code": "99213"}]))
assert result == ConditionResult.declined("medicare_telehealth",
CPT_GRID_NOT_READ), result
@case
def summary_comment_counts_unchecked_and_fee_only_rows_apart():
"""
Final review, 2026-10-05: on the UB form only fees are zeroed, and
"N other CPT(s) unchecked/fee zeroed" suggested an uncheck that never
happened. Each kind is counted on its own. A blank M1 read-back names
the visit's modifier, never "?", and only the field that was fixed.
"""
sc = medicare_telehealth.summary_comment
fee = lambda n: {"row": n, "code": f"9000{n}",
"fee": {"before": "10.00", "after": "0.00"}}
both = lambda n: dict(fee(n), unchecked=True)
primary = {"row": 0, "code": "99213", "changed": True, "ok": False,
"before": {"pos": "2", "mod1": ""},
"after": {"pos": "2", "mod1": ""}, "modifier": "95"}
added = {"row": None, "code": "G2025", "added": True, "modifier": "95"}
r = ConditionResult("medicare_telehealth", True,
[primary, added, fee(1), fee(2)])
text = sc(r)
assert text == ("Medicare Telehealth (modifier 95 on 99213 did NOT take "
"— set by hand; G2025 added; 2 other CPT fee(s) "
"zeroed)"), text
assert "unchecked" not in text and "?" not in text, text
r = ConditionResult("medicare_telehealth", True,
[added, both(1), both(2)])
assert sc(r) == ("Medicare Telehealth (G2025 added; 2 other CPT(s) "
"unchecked)"), sc(r)
def _run_cases() -> 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)
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
def main() -> int:
from ecw_bot import settings as _cfg
# These cases exercise eCW's own click and Add flow, as a LIVE-WRITE run
# does. VERIFY clicks no box and adds no row (eCW saves both at once,
# 2026-10-16): that is tests/test_verify_no_live_writes.py.
orig = _cfg.SAVE_CORRECTIONS
_cfg.SAVE_CORRECTIONS = True
try:
return _run_cases()
finally:
_cfg.SAVE_CORRECTIONS = orig
if __name__ == "__main__":
sys.exit(main())