""" Responsive layout tests across 3 viewports. Tests each page on desktop / tablet / mobile using Playwright sync API. """ import pytest from pathlib import Path from playwright.sync_api import sync_playwright, expect pytestmark = pytest.mark.qa # --------------------------------------------------------------------------- # Viewport definitions # --------------------------------------------------------------------------- VIEWPORTS = { "desktop": {"width": 1280, "height": 900}, "tablet": {"width": 768, "height": 1024}, "mobile": {"width": 375, "height": 812}, } # --------------------------------------------------------------------------- # Pages to test: (path, expected_text_fragment) # expected_text_fragment is matched loosely against page title or any

/

# --------------------------------------------------------------------------- PAGES = [ ("/", "Panou"), ("/logs", "Jurnale"), ("/mappings", "Mapari"), ("/missing-skus", "SKU"), ("/settings", "Setari"), ] # --------------------------------------------------------------------------- # Session-scoped browser (reused across all parametrized tests) # --------------------------------------------------------------------------- @pytest.fixture(scope="session") def pw_browser(): """Launch a Chromium browser for the full QA session.""" with sync_playwright() as pw: browser = pw.chromium.launch(headless=True) yield browser browser.close() # --------------------------------------------------------------------------- # Parametrized test: viewport x page # --------------------------------------------------------------------------- @pytest.mark.parametrize("viewport_name", list(VIEWPORTS.keys())) @pytest.mark.parametrize("page_path,expected_text", PAGES) def test_responsive_page( pw_browser, base_url: str, screenshots_dir: Path, viewport_name: str, page_path: str, expected_text: str, ): """Each page renders without error on every viewport and contains expected text.""" viewport = VIEWPORTS[viewport_name] context = pw_browser.new_context(viewport=viewport) page = context.new_page() try: page.goto(f"{base_url}{page_path}", wait_until="networkidle", timeout=15_000) # Screenshot page_name = page_path.strip("/") or "dashboard" screenshot_path = screenshots_dir / f"{page_name}-{viewport_name}.png" page.screenshot(path=str(screenshot_path), full_page=True) # Basic content check: title or any h1/h4 contains expected text title = page.title() headings = page.locator("h1, h4").all_text_contents() all_text = " ".join([title] + headings) assert expected_text.lower() in all_text.lower(), ( f"Expected '{expected_text}' in page text on {viewport_name} {page_path}. " f"Got title='{title}', headings={headings}" ) finally: context.close() # --------------------------------------------------------------------------- # Mobile-specific: navbar toggler # --------------------------------------------------------------------------- def test_mobile_navbar_visible(pw_browser, base_url: str): """Mobile viewport: navbar should still be visible and functional.""" context = pw_browser.new_context(viewport=VIEWPORTS["mobile"]) page = context.new_page() try: page.goto(base_url, wait_until="networkidle", timeout=15_000) # Custom navbar: .top-navbar with .navbar-brand navbar = page.locator(".top-navbar") expect(navbar).to_be_visible() finally: context.close() # --------------------------------------------------------------------------- # Mobile-specific: tables wrapped in .table-responsive or scrollable # --------------------------------------------------------------------------- @pytest.mark.parametrize("page_path", ["/logs", "/mappings", "/missing-skus"]) def test_mobile_table_responsive(pw_browser, base_url: str, page_path: str): """ On mobile, any should live inside a .table-responsive wrapper OR the page should have a horizontal scroll container around it. If no table is present (empty state), the test is skipped. """ context = pw_browser.new_context(viewport=VIEWPORTS["mobile"]) page = context.new_page() try: page.goto(f"{base_url}{page_path}", wait_until="networkidle", timeout=15_000) tables = page.locator("table").all() if not tables: # No tables means nothing to check — pass (no non-responsive tables exist) return # Check each table has an ancestor with overflow-x scroll or .table-responsive class for table in tables: # Check direct parent chain for .table-responsive wrapped = page.evaluate( """(el) => { let node = el.parentElement; for (let i = 0; i < 6 && node; i++) { if (node.classList.contains('table-responsive')) return true; const style = window.getComputedStyle(node); if (style.overflowX === 'auto' || style.overflowX === 'scroll') return true; node = node.parentElement; } return false; }""", table.element_handle(), ) assert wrapped, ( f"Table on {page_path} is not inside a .table-responsive wrapper " f"or overflow-x:auto/scroll container on mobile viewport" ) finally: context.close()