From bbdf2a80c701ad428b4f7a55580a9b9ef15a0228 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Mon, 24 Aug 2026 18:39:11 +0300 Subject: [PATCH 1/9] test: exercise summary report in headless browser --- tests/test_summary_report_browser.py | 49 ++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/test_summary_report_browser.py b/tests/test_summary_report_browser.py index 7c32b8ed..406ef989 100644 --- a/tests/test_summary_report_browser.py +++ b/tests/test_summary_report_browser.py @@ -1,8 +1,11 @@ import json +import shutil +import subprocess from pathlib import Path from typing import Any, cast import numpy as np +import pytest from rtichoke.summary_report import summary_report as summary_report_module from rtichoke.summary_report.summary_report import create_summary_report @@ -38,6 +41,33 @@ def _embedded_report(html: str) -> dict[str, Any]: return cast(dict[str, Any], json.loads(html[start:end])) +def _chrome_executable() -> str: + for candidate in ("google-chrome", "google-chrome-stable", "chromium", "chromium-browser"): + executable = shutil.which(candidate) + if executable is not None: + return executable + pytest.skip("headless Chrome/Chromium is not available") + + +def _dump_dom(path: Path) -> subprocess.CompletedProcess[str]: + return subprocess.run( + [ + _chrome_executable(), + "--headless=new", + "--no-sandbox", + "--disable-gpu", + "--enable-logging=stderr", + "--log-level=0", + "--dump-dom", + path.resolve().as_uri(), + ], + check=False, + capture_output=True, + text=True, + timeout=30, + ) + + def test_default_summary_report_keeps_historical_r_backend(monkeypatch, capsys): probs, reals = _inputs() calls = [] @@ -114,6 +144,25 @@ def test_browser_summary_report_is_opt_in_and_uses_real_canonical_components( assert "renderCalibrationV2" not in html +def test_browser_summary_report_executes_when_opened_directly(tmp_path): + probs, reals = _inputs() + output = create_summary_report( + probs, + reals, + renderer="browser", + output_file=tmp_path / "browser_report.html", + ) + assert isinstance(output, Path) + + browser = _dump_dom(output) + + assert browser.returncode == 0, browser.stderr + assert 'id="rtichoke-report">' not in browser.stdout + assert "Performance" in browser.stdout + assert "ROC" in browser.stdout + assert "Calibration" in browser.stdout + + def test_browser_summary_report_preserves_component_local_identity(tmp_path): probs = { "Population A": np.array([0.05, 0.2, 0.7, 0.95]), From 39c0baa475387d85a2cd6d086b2f390dabe52eda Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Mon, 24 Aug 2026 18:40:35 +0300 Subject: [PATCH 2/9] style: format browser smoke test --- tests/test_summary_report_browser.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_summary_report_browser.py b/tests/test_summary_report_browser.py index 406ef989..1306a202 100644 --- a/tests/test_summary_report_browser.py +++ b/tests/test_summary_report_browser.py @@ -42,7 +42,12 @@ def _embedded_report(html: str) -> dict[str, Any]: def _chrome_executable() -> str: - for candidate in ("google-chrome", "google-chrome-stable", "chromium", "chromium-browser"): + for candidate in ( + "google-chrome", + "google-chrome-stable", + "chromium", + "chromium-browser", + ): executable = shutil.which(candidate) if executable is not None: return executable From 103d978f3504ea51ed2cbddf374094228b57c727 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Mon, 24 Aug 2026 18:43:22 +0300 Subject: [PATCH 3/9] test: compare file and localhost browser execution --- tests/test_summary_report_browser.py | 66 ++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/tests/test_summary_report_browser.py b/tests/test_summary_report_browser.py index 1306a202..ae0c525f 100644 --- a/tests/test_summary_report_browser.py +++ b/tests/test_summary_report_browser.py @@ -1,8 +1,12 @@ import json import shutil import subprocess +from contextlib import contextmanager +from functools import partial +from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path -from typing import Any, cast +from threading import Thread +from typing import Any, Iterator, cast import numpy as np import pytest @@ -54,7 +58,7 @@ def _chrome_executable() -> str: pytest.skip("headless Chrome/Chromium is not available") -def _dump_dom(path: Path) -> subprocess.CompletedProcess[str]: +def _dump_dom(url: str) -> subprocess.CompletedProcess[str]: return subprocess.run( [ _chrome_executable(), @@ -64,7 +68,7 @@ def _dump_dom(path: Path) -> subprocess.CompletedProcess[str]: "--enable-logging=stderr", "--log-level=0", "--dump-dom", - path.resolve().as_uri(), + url, ], check=False, capture_output=True, @@ -73,6 +77,38 @@ def _dump_dom(path: Path) -> subprocess.CompletedProcess[str]: ) +def _rendered_report_html(dom: str) -> str: + marker = '
' + start = dom.index(marker) + len(marker) + end = dom.index('