"""Smoke tests: every page renders without exception in EN and ES. The cheapest, highest-value GUI tests in the project. They catch: - Page-level Python errors (import failures, syntax errors that ``ast.parse`` misses because they're runtime, e.g., a missing attribute on a module). - i18n pack key drift (a string that used to render in EN now renders literally as ``"chrome.language_label"`` because someone renamed the key in en.json but forgot es.json or the call site). - Streamlit API churn that breaks ``set_page_config`` / ``hide_streamlit_chrome`` on a single page. What they don't cover: user interactions. Those live in the workflow tests. """ from __future__ import annotations import pytest from .conftest import collected_text, with_language # Every page that ships in the sidebar nav. Slugs match the filenames # under ``src/gui/pages/`` so failures point at a real file. PAGE_SLUGS = [ "1_Deduplicator", "2_Text_Cleaner", "3_Format_Standardizer", "4_Missing_Values", "5_Column_Mapper", "6_Outlier_Detector", "7_Multi_File_Merger", "8_Validator_Reporter", "9_Pipeline_Runner", "99_Close", ] # Substrings that must appear on each page for each language. # # v1.6 coverage reality (also documented in docs/USER-GUIDE.md §3.4): # only the home page, the Close page, and the shared chrome / # components ship Spanish strings. Per-tool page bodies are still # hard-coded English in both modes — translating them is tracked as a # follow-up. The substrings below reflect that reality: a page that # isn't translated yet asserts the same English substring under both # languages. The fact that the page *renders at all* in 'es' is still # the value of the smoke test. # # When a page gains real Spanish translation, flip its 'es' entry to # the localized substring — the test surface stays the same. EXPECTED_SUBSTRINGS: dict[str, dict[str, str]] = { "1_Deduplicator": {"en": "Find Duplicates", "es": "Buscar duplicados"}, "2_Text_Cleaner": {"en": "Clean Text", "es": "Limpiar texto"}, "3_Format_Standardizer": {"en": "Standardize", "es": "Estandarizar"}, "4_Missing_Values": {"en": "Fix Missing", "es": "Corregir valores"}, "5_Column_Mapper": {"en": "Map Columns", "es": "Mapear columnas"}, "6_Outlier_Detector": {"en": "Unusual", "es": "atípicos"}, "7_Multi_File_Merger": {"en": "Combine Files", "es": "Combinar archivos"}, "8_Validator_Reporter": {"en": "Quality Check", "es": "Verificación de calidad"}, "9_Pipeline_Runner": {"en": "Automated", "es": "Flujos automatizados"}, "99_Close": {"en": "Shutting down", "es": "Cerrando"}, } class TestHomePageRenders: """The home page is the only one with full EN/ES coverage in v1.6. Pin it independently so its translation is non-regressable.""" @pytest.mark.parametrize("lang,expected", [ ("en", "DataTools — Data Cleaning Mastery"), ("es", "DataTools — Maestría en limpieza de datos"), ]) def test_home_renders_in_language(self, home_app, lang, expected): with_language(home_app, lang) home_app.run() assert home_app.exception is None or home_app.exception == [], ( f"home page raised: {home_app.exception}" ) assert expected in collected_text(home_app) def test_home_renders_footer_in_es(self, home_app): with_language(home_app, "es") home_app.run() text = collected_text(home_app) assert "Tus datos nunca salen" in text or "Se ejecuta localmente" in text class TestEveryPageRenders: """Parametrize over (page, language). Failure tells you exactly which page + which language broke.""" @pytest.mark.parametrize("slug", PAGE_SLUGS) @pytest.mark.parametrize("lang", ["en", "es"]) def test_renders_without_exception(self, app_factory, slug, lang): app = app_factory(slug) with_language(app, lang) app.run() # AppTest exposes ``exception`` as a list of element-wrapped # exceptions (empty when no error fired). assert not app.exception, ( f"page {slug!r} raised in language {lang!r}: {app.exception}" ) @pytest.mark.parametrize("slug", PAGE_SLUGS) @pytest.mark.parametrize("lang", ["en", "es"]) def test_expected_substring_present(self, app_factory, slug, lang): app = app_factory(slug) with_language(app, lang) app.run() needle = EXPECTED_SUBSTRINGS[slug][lang] text = collected_text(app) assert needle in text, ( f"page {slug!r} ({lang!r}) missing expected substring " f"{needle!r}\nGot:\n{text[:500]}…" ) class TestPageHasLanguageSelector: """Every page that calls ``hide_streamlit_chrome`` should mount the sidebar language selector. This is the only place the picker is rendered — if the chrome helper stops calling it, the test fails.""" @pytest.mark.parametrize("slug", PAGE_SLUGS) def test_sidebar_selectbox_present(self, app_factory, slug): app = app_factory(slug) app.run() # The selector is the only sidebar selectbox we ship today; if # a page adds another the test should be loosened to "at least # one selectbox with the language label." assert len(app.sidebar.selectbox) >= 1, ( f"page {slug!r} has no sidebar selectbox — " f"hide_streamlit_chrome() should have mounted the language " f"selector." )