import os import re import pytest from playwright.sync_api import sync_playwright # ── State label → numeric ID (uppercase keys match site dropdown labels) ────── STATE_LABEL_TO_ID = { "ANDAMAN & NICOBAR ISLANDS": 135, "ANDHRA PRADESH": 128, "ARUNACHAL PRADESH": 112, "ASSAM": 118, "BIHAR": 110, "CHANDIGARH": 104, "CHHATTISGARH": 122, "DADRA & NAGAR HAVELI AND DAMAN & DIU": 138, "DELHI": 107, "GOA": 130, "GUJARAT": 124, "HARYANA": 106, "HIMACHAL PRADESH": 102, "IAF EC SOCIETY": 163, "JAMMU & KASHMIR": 101, "JHARKHAND": 120, "KARNATAKA": 129, "KENDRIYA VIDYALAYA SANGHATHAN": 192, "KERALA": 132, "LADAKH": 137, "LAKSHADWEEP": 131, "MADHYA PRADESH": 123, "MAHARASHTRA": 127, "MANIPUR": 114, "MEGHALAYA": 117, "MIZORAM": 115, "MSRVVP": 161, "NAGALAND": 113, "NAVODAYA VIDYALAYA SAMITI": 193, "NAVY EDUCATION SOCIETY": 162, "ODISHA": 121, "PUDUCHERRY": 134, "PUNJAB": 103, "RAJASTHAN": 108, "SIKKIM": 111, "TAMILNADU": 133, "TELANGANA": 136, "TEST STATE": 199, "TRIPURA": 116, "UTTARAKHAND": 105, "UTTAR PRADESH": 109, "WEST BENGAL": 119, } # ── Early check for unquoted state names ── import sys def _check_unquoted_state_args(): if "--state" in sys.argv: try: idx = sys.argv.index("--state") if idx + 1 < len(sys.argv): state_val = sys.argv[idx + 1] if idx + 2 < len(sys.argv): next_arg = sys.argv[idx + 2] # If the next argument isn't a flag, it might be an unquoted state part. if not next_arg.startswith("-"): combined = (state_val + " " + next_arg).upper() if any(combined in s for s in STATE_LABEL_TO_ID): print(f"\nERROR: It looks like you passed a multi-word state name without quotes.") print(f"Please use double quotes around the state name: --state \"{combined}\"") print(f"Valid state names are:\n " + "\n ".join(sorted(STATE_LABEL_TO_ID.keys())) + "\n") sys.exit(1) except ValueError: pass _check_unquoted_state_args() _OUTPUT_DIR = os.path.normpath( os.path.join(os.path.dirname(os.path.abspath(__file__)), "output") ) def _state_prefix(state_name: str) -> str: """'ANDAMAN & NICOBAR ISLANDS' → 'andaman_nicobar_islands'""" return re.sub(r"[^a-z0-9]+", "_", state_name.lower()).strip("_") # ── CLI option ──────────────────────────────────────────────────────────────── def pytest_addoption(parser): parser.addoption( "--state", action="store", default="GOA", help=( "State name to scrape (case-insensitive). " "Examples: --state GOA or --state 'UTTAR PRADESH'. " "Default: GOA" ), ) # ── State fixtures ──────────────────────────────────────────────────────────── @pytest.fixture(scope="session") def state_name(request): """Validated, uppercased state name from --state CLI option.""" raw = request.config.getoption("--state").strip().upper() if raw not in STATE_LABEL_TO_ID: valid = "\n ".join(sorted(STATE_LABEL_TO_ID)) pytest.fail( f"Unknown state: '{raw}'.\n" f"Valid state names:\n {valid}" ) return raw @pytest.fixture(scope="session") def state_id(state_name): """Numeric state ID for the API (resolved from --state).""" return STATE_LABEL_TO_ID[state_name] @pytest.fixture(scope="session") def results_file(state_name): """Path to the state-specific school data JSON, e.g. output/goa_schools.json.""" os.makedirs(_OUTPUT_DIR, exist_ok=True) return os.path.join(_OUTPUT_DIR, f"{_state_prefix(state_name)}_schools.json") @pytest.fixture(scope="session") def id_map_file(state_name): """Path to the state-specific district/block ID map JSON.""" os.makedirs(_OUTPUT_DIR, exist_ok=True) return os.path.join(_OUTPUT_DIR, f"{_state_prefix(state_name)}_id_map.json") @pytest.fixture(scope="session") def district_cat_results_file(state_name): """Path to the district×category school data JSON. e.g. output/goa_schools_by_category.json """ os.makedirs(_OUTPUT_DIR, exist_ok=True) return os.path.join(_OUTPUT_DIR, f"{_state_prefix(state_name)}_schools_by_category.json") @pytest.fixture(scope="session") def district_id_map_file(state_name): """Path to the state-specific district-only ID map JSON (no blocks). e.g. output/goa_district_id_map.json """ os.makedirs(_OUTPUT_DIR, exist_ok=True) return os.path.join(_OUTPUT_DIR, f"{_state_prefix(state_name)}_district_id_map.json") # ── Browser fixtures ────────────────────────────────────────────────────────── @pytest.fixture(scope="session") def browser(): """Session-scoped browser — launched once for the entire test run.""" with sync_playwright() as p: browser = p.chromium.launch(headless=True, slow_mo=200, args=["--start-minimized"]) yield browser browser.close() @pytest.fixture(scope="session") def page(browser): """Session-scoped page — one browser tab reused across all phases.""" context = browser.new_context(viewport={"width": 1280, "height": 900}) page = context.new_page() yield page context.close()