Snapshot of D:\Ecw Process a11c9ff (2026-10-30). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
r"""
|
|
auth/login.py `wait_after_username()` — what comes after the username's
|
|
Next (live 2026-10-28 17:59: the security image page came late, the old
|
|
one-look check missed it, and the bot clicked a password box that stayed
|
|
disabled until it timed out).
|
|
|
|
It waits, up to its timeout, for whichever comes first: eCW's "VERIFY YOUR
|
|
SECURITY IMAGE" page or a password box that is visible AND enabled. Neither
|
|
→ a screenshot (with the DOM) and an error that says so.
|
|
|
|
python tests\test_login_username_step.py
|
|
|
|
Local headless chromium, `page.set_content()` only. No eCW, no login.
|
|
"""
|
|
|
|
import sys
|
|
import time
|
|
import pathlib
|
|
import tempfile
|
|
|
|
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.auth import login # noqa: E402
|
|
|
|
CASES = []
|
|
|
|
|
|
def case(fn):
|
|
CASES.append(fn)
|
|
return fn
|
|
|
|
|
|
# The password box is on the page from the start, disabled — the shape
|
|
# seen live; eCW enables it once the username step is done.
|
|
_PAGE = """
|
|
<input id="doctorID" value="someone">
|
|
<input disabled type="password" id="passwordField">
|
|
<div id="img" style="display:none">VERIFY YOUR SECURITY IMAGE</div>
|
|
<script>%s</script>
|
|
"""
|
|
|
|
|
|
@case
|
|
def a_late_security_image_page_is_seen(page):
|
|
page.set_content(_PAGE % "setTimeout(() => document.getElementById("
|
|
"'img').style.display = '', 3000);")
|
|
assert login.wait_after_username(page, 10_000) == "image"
|
|
|
|
|
|
@case
|
|
def a_password_box_enabled_late_is_waited_for(page):
|
|
page.set_content(_PAGE % "setTimeout(() => document.getElementById("
|
|
"'passwordField').disabled = false, 2500);")
|
|
t0 = time.monotonic()
|
|
assert login.wait_after_username(page, 10_000) == "password"
|
|
assert time.monotonic() - t0 >= 2, "returned before it was enabled"
|
|
|
|
|
|
@case
|
|
def after_the_image_only_the_password_box_counts(page):
|
|
page.set_content(_PAGE % "document.getElementById('img').style.display"
|
|
" = ''; setTimeout(() => document.getElementById("
|
|
"'passwordField').disabled = false, 1500);")
|
|
assert login.wait_after_username(page, 10_000, image=False) == "password"
|
|
|
|
|
|
@case
|
|
def a_password_box_that_stays_disabled_is_an_error_with_a_screenshot(page):
|
|
page.set_content(_PAGE % "")
|
|
try:
|
|
login.wait_after_username(page, 2_000)
|
|
except RuntimeError as exc:
|
|
assert "password box stayed disabled" in str(exc), exc
|
|
else:
|
|
raise AssertionError("no error for a box that never enabled")
|
|
shots = list(pathlib.Path(cfg.SCREENSHOTS).glob("*_username_step_disabled.*"))
|
|
assert {p.suffix for p in shots} == {".png", ".html"}, shots
|
|
|
|
|
|
# The security image page with its own Continue: clicking it is what lets
|
|
# eCW enable the password box (operator, 2026-10-30: no manual click).
|
|
_IMAGE_PAGE = """
|
|
<input id="doctorID" value="someone">
|
|
<input disabled type="password" id="passwordField">
|
|
<div id="img">VERIFY YOUR SECURITY IMAGE
|
|
<input type="button" value="%s" onclick="document.getElementById('img')
|
|
.style.display = 'none'; document.getElementById('passwordField')
|
|
.disabled = false;">
|
|
</div>
|
|
"""
|
|
|
|
|
|
@case
|
|
def continue_on_the_security_image_page_is_clicked_by_the_bot(page):
|
|
page.set_content(_IMAGE_PAGE % "Continue")
|
|
assert login.wait_after_username(page, 5_000) == "image"
|
|
assert login.continue_security_image(page) is True
|
|
assert login.wait_after_username(page, 5_000, image=False) == "password"
|
|
|
|
|
|
@case
|
|
def no_continue_button_is_left_to_a_person(page):
|
|
page.set_content(_IMAGE_PAGE % "Not me")
|
|
assert login.continue_security_image(page) is False
|
|
assert page.locator("#passwordField").is_disabled()
|
|
|
|
|
|
def main() -> int:
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
failures = 0
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
cfg.SCREENSHOTS = pathlib.Path(tmp) # never the real folder
|
|
with sync_playwright() as pw:
|
|
browser = pw.chromium.launch(headless=True)
|
|
page = browser.new_page()
|
|
for fn in CASES:
|
|
name = fn.__name__.replace("_", " ")
|
|
try:
|
|
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())
|