r""" The Portal's Runs pages — `/runs`, `/runs/`, `/runs//shot/` (spec 2026-10-22 Stage 1, EEWA-31), against events files in a temp folder and the fake repository / runner of test_web_app.py. python tests\test_runs_page.py Claim numbers here are made up. """ import sys import json import pathlib import tempfile ROOT = pathlib.Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) sys.path.insert(0, str(ROOT / "src")) sys.path.insert(0, str(ROOT / "tests")) from test_web_app import FakeRepo, FakeRunner, signed_in # noqa: E402 CASES = [] RUN = "20261022_140000" def case(fn): CASES.append(fn) return fn def ev(t, kind, **f): return {"t": f"2026-10-22T{t}.000", "kind": kind, **f} def _setup(events, runner=None, run_id=RUN): d = pathlib.Path(tempfile.mkdtemp()) (d / f"events_{run_id}.jsonl").write_text( "".join(json.dumps(e) + "\n" for e in events), encoding="utf-8") shots = d / "Screenshots" shots.mkdir() client, _ = signed_in(FakeRepo(), runner or FakeRunner(), logs_dir=d, screenshots_root=shots) return client, d, shots START = ev("14:00:00", "run_start", source="portal", started_by="asha", mode="VERIFY", claim_limit=2, test_sheet="", pid=1, log="x") ERROR_RUN = [ START, ev("14:00:01", "login_attempt_start", attempt=1), ev("14:00:10", "login_step", attempt=1, step="twofa_requested"), ev("14:00:52", "login_step", attempt=1, step="mail_received", mail_kind="link"), ev("14:01:00", "login_attempt_end", attempt=1, result="ok", message=""), ev("14:01:10", "queue", claims=["9001", "9002"]), ev("14:01:11", "claim_start", n=1, total=2, claim="9001"), ev("14:01:20", "claim_error", claim="9001", step="search", message="timeout"), ev("14:01:21", "claim_result", n=1, claim="9001", category="Review"), ev("14:01:22", "run_error", step="claims", exit_code=5, message="Target closed", traceback="Traceback (most recent)\nX"), ev("14:01:23", "run_end", exit_code=5), ] @case def the_runs_list_shows_a_run_with_its_result_and_counts(): client, _, _ = _setup(ERROR_RUN) page = client.get("/runs").text assert f'href="/runs/{RUN}"' in page, page assert "asha" in page and "Run Error" in page and "exit 5" in page assert "1 attempt · 59s" in page, "login count + time" @case def the_runs_list_filters_and_rejects_a_bad_filter(): client, _, _ = _setup(ERROR_RUN) assert RUN not in client.get("/runs?status=Done").text assert RUN in client.get("/runs?status=Run+Error").text assert RUN not in client.get("/runs?day_from=2026-10-23").text assert client.get("/runs?day_from=yesterday").status_code == 400 assert client.get("/runs?status=Bogus").status_code == 400 @case def a_runs_page_has_login_errors_not_reached_and_timeline(): client, _, _ = _setup(ERROR_RUN) page = client.get(f"/runs/{RUN}").text assert "eCW Login Attempts" in page and "42s" in page, "2FA wait" assert "Run Error" in page and "Traceback (most recent)" in page assert "Claim Errors (1)" in page assert "Not Reached (1)" in page and "9002" in page assert "Timeline" in page and "Run ended, exit 5" in page assert 'http-equiv="refresh"' not in page, "an ended run does not poll" @case def event_text_reaches_the_page_escaped(): client, _, _ = _setup(ERROR_RUN) page = client.get(f"/runs/{RUN}").text assert "timeout" not in page assert "<b>timeout</b>" in page @case def a_running_run_refreshes_every_five_seconds(): runner = FakeRunner(running=True) st = runner._st runner._st = lambda: {**st(), "log_path": rf"D:\x\Logs\run_{RUN}.log"} client, _, _ = _setup(ERROR_RUN[:7], runner=runner) page = client.get(f"/runs/{RUN}").text assert "Running" in page, page assert '' in page @case def an_unknown_or_bad_run_is_404(): client, _, _ = _setup(ERROR_RUN) assert client.get("/runs/20261022_140001").status_code == 404 assert client.get("/runs/..%5C..%5Cx").status_code in (404, 400) @case def a_screenshot_is_served_and_nothing_else(): d = pathlib.Path(tempfile.mkdtemp()) client, d, shots = _setup([START]) png = shots / "140100_failed.png" png.write_bytes(b"\x89PNG") (d / f"events_{RUN}.jsonl").write_text( json.dumps(START) + "\n" + json.dumps( ev("14:01:00", "screenshot", path=str(png), tag="failed")) + "\n", encoding="utf-8") r = client.get(f"/runs/{RUN}/shot/0") assert r.status_code == 200 and r.content == b"\x89PNG", r.status_code assert client.get(f"/runs/{RUN}/shot/1").status_code == 404 page = client.get(f"/runs/{RUN}").text assert f'/runs/{RUN}/shot/0' in page @case def every_page_links_the_runs_page_and_carries_no_inline_style(): client, _, _ = _setup(ERROR_RUN) for path in ("/", "/runs", f"/runs/{RUN}"): page = client.get(path).text assert 'href="/runs"' in page, path assert " style=" not in page, path 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 print(f"\n{len(CASES) - failures}/{len(CASES)} passed") return 1 if failures else 0 if __name__ == "__main__": sys.exit(main())