"""End-to-end checks of the Streamlit app, run headlessly via AppTest. These drive the real `app.py` script: type into the box, submit, inspect what was rendered. They are deliberately few -- enough to prove the search path, the multi-occurrence path and the error paths work. """ import pytest from spareparts import config pytest.importorskip("streamlit") from streamlit.testing.v1 import AppTest # noqa: E402 pytestmark = pytest.mark.skipif( not config.DB_PATH.exists(), reason="run `python -m spareparts.build_index` first" ) APP = str(config.PROJECT_ROOT / "app.py") def _search(term: str, timeout: int = 60) -> AppTest: app = AppTest.from_file(APP, default_timeout=timeout).run() app.text_input[0].set_value(term) app.button[0].click().run() return app def _text(app: AppTest) -> str: parts = [el.value for el in app.markdown] + [el.value for el in app.caption] parts += [el.value for el in app.subheader] + [el.value for el in app.warning] parts += [el.value for el in app.error] + [el.value for el in app.info] return "\n".join(str(p) for p in parts) def test_app_starts_without_a_search(): app = AppTest.from_file(APP, default_timeout=60).run() assert not app.exception assert app.title[0].value == "Machine Part Finder" assert app.text_input[0].label == "Part number" def test_valid_part_shows_metadata_and_an_image(): app = _search("30642") assert not app.exception body = _text(app) assert "30642" in body assert "worm,cpl." in body # canonical English name assert "Motor-drive" in body # catalogue section title assert "Located" in body # localization method, stated plainly # PDF page and section number are shown as distinct facts. metrics = {m.label: m.value for m in app.metric} assert metrics["PDF page"] == "34" assert metrics["Catalogue section"] == "07" assert len(app.image) == 1, "the annotated diagram should be displayed" def test_unknown_part_is_reported_not_crashed(): app = _search("99999") assert not app.exception assert app.error, "an unknown part must produce an error message" assert "not found in the catalogue" in app.error[0].value assert not app.image @pytest.mark.parametrize("term", ["", " ", "abc", "!!!"]) def test_empty_and_non_numeric_input_is_handled(term): app = _search(term) assert not app.exception assert not app.image if term.strip(): assert app.warning or app.error def test_messy_input_is_normalized_then_found(): app = _search(" part no. 30642 ") assert not app.exception assert not app.error assert "worm,cpl." in _text(app) def test_multiple_occurrences_offer_a_selector(): app = _search("73290") # drawn on ten catalogue pages assert not app.exception assert app.radio, "a part on several pages must offer a choice" options = app.radio[0].options assert len(options) > 1 assert all(o.startswith("Occurrence ") for o in options) assert "PDF page" in options[0] # One page is shown at a time, and it is one the user chose. assert len(app.image) == 1 def test_selecting_another_occurrence_switches_page(): app = _search("73290") first = {m.label: m.value for m in app.metric}["PDF page"] app.radio[0].set_value(app.radio[0].options[2]).run() assert not app.exception second = {m.label: m.value for m in app.metric}["PDF page"] assert first != second def test_low_confidence_result_is_shown_as_approximate(): """A conservative fallback must be visible as such, not dressed up.""" from spareparts import index_db conn = index_db.connect() try: row = conn.execute( "SELECT l.part_no FROM labels l JOIN components c ON c.label_id = l.id" " WHERE c.method = 'capped' AND l.part_no IN" " (SELECT part_no FROM labels GROUP BY part_no HAVING COUNT(*) = 1)" " LIMIT 1" ).fetchone() finally: conn.close() app = _search(row[0]) assert not app.exception body = _text(app) assert "Approximate" in body assert "Confidence" in body def test_listed_but_not_drawn_part_shows_the_page_and_says_so(): app = _search("70248") # listed on page 39, not drawn there assert not app.exception assert not app.error assert app.warning, "the user must be told why there is no highlight" assert "no callout" in app.warning[0].value assert len(app.image) == 1, "the page itself should still be shown" def test_no_ocr_engine_is_loaded_during_a_search(): """Definition of done: searching must not run OCR or localization. Checked in a subprocess so that nothing another test imported can mask it. """ import subprocess import sys import textwrap script = textwrap.dedent( """ import sys from streamlit.testing.v1 import AppTest app = AppTest.from_file(sys.argv[1], default_timeout=60).run() app.text_input[0].set_value("30642") app.button[0].click().run() assert not app.exception, app.exception banned = [m for m in sys.modules if "rapidocr" in m or m.startswith("spareparts.ocr") or m.startswith("spareparts.localizer")] print("LOADED:" + ",".join(sorted(banned))) """ ) result = subprocess.run( [sys.executable, "-c", script, APP], capture_output=True, text=True, cwd=str(config.PROJECT_ROOT), timeout=300, ) assert result.returncode == 0, result.stderr[-2000:] line = next(l for l in result.stdout.splitlines() if l.startswith("LOADED:")) assert line == "LOADED:", f"search pulled in offline-only modules: {line}"