""" E2E test: FCC Compliance Platform Tests: 1. Compliance wizard page — load, enter FRN, verify checks render 2. 499-A questionnaire — full 7-step flow with de minimis + LIRE classification 3. Entity CRUD API — create, list, get, update, per-entity compliance 4. Guide pages — all 6 return 200 Usage: python3 -m scripts.tests.e2e_fcc_compliance python3 -m scripts.tests.e2e_fcc_compliance --headed """ from __future__ import annotations import argparse import json import logging import os import sys import time import uuid from datetime import datetime from pathlib import Path from dotenv import load_dotenv env_path = Path(__file__).parent / ".env.test" if env_path.exists(): load_dotenv(env_path) import requests from playwright.sync_api import sync_playwright, Page LOG = logging.getLogger("tests.fcc") logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s %(message)s", stream=sys.stdout, ) SITE_URL = os.environ.get("SITE_URL", "https://dev.performancewest.net") API_URL = os.environ.get("DEV_API_URL", "http://207.174.124.71:3002") DEV_DB_URL = os.environ.get( "DEV_DATABASE_URL", "postgresql://pw:pw_dev_2026@207.174.124.71:5433/performancewest", ) SCREENSHOT_DIR = Path(__file__).parent / "screenshots" / "fcc" SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True) PASS = 0 FAIL = 0 RESULTS: list[dict] = [] def screenshot(page: Page, name: str) -> Path: ts = datetime.now().strftime("%H%M%S") path = SCREENSHOT_DIR / f"{name}_{ts}.png" try: page.screenshot(path=str(path), full_page=True, timeout=15000) LOG.info(" Screenshot: %s", path.name) except Exception as e: LOG.warning(" Screenshot failed: %s", e) return path def check(label: str, ok: bool, detail: str = ""): global PASS, FAIL if ok: PASS += 1 LOG.info(" PASS: %s", label) else: FAIL += 1 LOG.info(" FAIL: %s — %s", label, detail) RESULTS.append({"label": label, "ok": ok, "detail": detail}) # ────────────────────────────────────────────────────────────────────── # Test 1: Compliance Wizard Page # ────────────────────────────────────────────────────────────────────── def test_compliance_wizard(page: Page): LOG.info("\n=== Test 1: Compliance Wizard Page ===") page.goto(f"{SITE_URL}/tools/fcc-compliance-check", wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(1000) check("Wizard page loads", "fcc-compliance-check" in page.url) check("Has FRN input", page.locator("#frn-input").count() > 0) check("Has Check button", page.locator("#btn-check").count() > 0) check("Has title", page.locator("text=FCC Compliance Check").count() > 0) screenshot(page, "fcc_wizard_01_loaded") # Enter a test FRN and run check page.fill("#frn-input", "0004309000") page.click("#btn-check") page.wait_for_timeout(5000) screenshot(page, "fcc_wizard_02_results") # Verify results rendered results_visible = not page.locator("#results").is_hidden() check("Results section visible", results_visible) if results_visible: checks_count = page.locator("#checks-container > div").count() check("Has compliance checks rendered", checks_count >= 4, f"got {checks_count}") # Check for entity header entity_frn = page.locator("#entity-frn").text_content() check("FRN displayed in results", "0004309000" in (entity_frn or ""), entity_frn) # Check for CTA section has_cta = page.locator("text=Need help with FCC compliance").count() > 0 check("CTA section present", has_cta) # Test with entity ID parameter page.goto(f"{SITE_URL}/tools/fcc-compliance-check?frn=0004309000", wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(5000) screenshot(page, "fcc_wizard_03_url_param") check("Auto-runs with FRN in URL", not page.locator("#results").is_hidden()) # ────────────────────────────────────────────────────────────────────── # Test 2: 499-A Questionnaire (Full 7-Step Flow) # ────────────────────────────────────────────────────────────────────── def test_499a_questionnaire(page: Page): LOG.info("\n=== Test 2: 499-A Questionnaire ===") page.goto(f"{SITE_URL}/order/fcc-499a", wait_until="domcontentloaded", timeout=30000) page.wait_for_timeout(1000) check("Questionnaire page loads", "fcc-499a" in page.url) check("Has pricing tiers", page.locator("text=$499").count() > 0) screenshot(page, "fcc_499a_01_loaded") # ── Step 1: Business Classification ── LOG.info(" Step 1: Business Classification") # Select "Yes" for provides telecom page.locator('input[name="provides_telecom"][value="yes"]').check() page.wait_for_timeout(300) # PSTN connected = Yes (interconnected VoIP) page.locator('input[name="pstn_connected"][value="yes"]').check() page.wait_for_timeout(300) # Infrastructure = Reseller page.locator('input[name="infra_type"][value="reseller"]').check() page.wait_for_timeout(300) # Verify reseller note appears reseller_note = page.locator("#reseller-note") check("Step 1: Reseller note visible", not reseller_note.is_hidden()) screenshot(page, "fcc_499a_02_step1") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 2: Service Categories ── LOG.info(" Step 2: Service Categories") check("Step 2 visible", not page.locator("#step-2").is_hidden()) # Select interconnected VoIP + IXC page.locator('input[name="svc_cat"][value="interconnected_voip"]').check() page.locator('input[name="svc_cat"][value="ixc"]').check() page.wait_for_timeout(300) screenshot(page, "fcc_499a_03_step2") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 3: Revenue Estimation ── LOG.info(" Step 3: Revenue Estimation") check("Step 3 visible", not page.locator("#step-3").is_hidden()) page.fill("#total_revenue", "500000") page.fill("#pct_interstate", "60") page.fill("#pct_international", "15") page.fill("#pct_intrastate", "25") page.fill("#pct_end_user", "90") page.fill("#pct_wholesale", "10") # Verify percentage total shows 100% pct_total_text = page.locator("#pct-total").text_content() check("Step 3: Pct total = 100%", "100%" in (pct_total_text or ""), pct_total_text) screenshot(page, "fcc_499a_04_step3") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 4: Classification Results ── LOG.info(" Step 4: Classification Results") check("Step 4 visible", not page.locator("#step-4").is_hidden()) # Check filer type filer_type = page.locator("#result-filer-type").text_content() check("Step 4: Filer type = Interconnected VoIP", "Interconnected VoIP" in (filer_type or ""), filer_type) # Check de minimis status # $500K * 75% (interstate+intl) * 90% (end user) / 4 = $84,375/quarter > $37,175 → Full Contributor dm_label = page.locator("#result-dm-label").text_content() check("Step 4: Full contributor (not de minimis)", "Full Contributor" in (dm_label or ""), dm_label) # Check LIRE status # Interstate=$300K, International=$75K, ratio = 300K/375K = 80% > 12% → NOT LIRE lire_label = page.locator("#result-lire-label").text_content() or "" check("Step 4: Not LIRE eligible", "Not LIRE" in lire_label, lire_label) # Check required filings list filings_text = page.locator("#result-filings").text_content() or "" check("Step 4: Shows 499-A required", "499-A" in filings_text) check("Step 4: Shows 499-Q required", "499-Q" in filings_text and "exempt" not in filings_text.lower()) screenshot(page, "fcc_499a_05_step4_results") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 5: Company Information ── LOG.info(" Step 5: Company Information") check("Step 5 visible", not page.locator("#step-5").is_hidden()) # Verify import section exists check("Step 5: Import from USAC section present", page.locator("#btn-import").count() > 0) uid = uuid.uuid4().hex[:6] page.fill("#entity_name", f"E2E Test VoIP {uid}") page.fill("#dba_name", "TestVoIP") page.fill("#ein", "12-3456789") page.fill("#frn", "0099887766") page.locator("#new_filer").check() page.fill("#service_start_date", "2024-06") page.fill("#address_street", "100 Test Blvd") page.fill("#address_city", "Houston") page.fill("#address_state", "TX") page.fill("#address_zip", "77001") page.fill("#contact_name", "Test Contact") page.fill("#contact_email", f"fcc-test+{uid}@performancewest.net") page.fill("#contact_phone", "+17135551234") page.fill("#ceo_name", "Jane CEO") screenshot(page, "fcc_499a_06_step5") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 6: Revenue Detail ── LOG.info(" Step 6: Revenue Detail") check("Step 6 visible", not page.locator("#step-6").is_hidden()) # Verify revenue lines populated based on service categories rev_rows = page.locator("#revenue-lines tr").count() check("Step 6: Revenue lines populated", rev_rows > 0, f"got {rev_rows} rows") # Fill some revenue values rev_inputs = page.locator(".rev-input") if rev_inputs.count() >= 2: rev_inputs.first.fill("300000") rev_inputs.nth(1).fill("50000") screenshot(page, "fcc_499a_07_step6") page.click("#btn-next") page.wait_for_timeout(500) # ── Step 7: Review & Submit ── LOG.info(" Step 7: Review & Submit") check("Step 7 visible", not page.locator("#step-7").is_hidden()) # Verify review summary populated review_summary = page.locator("#review-summary").text_content() or "" check("Step 7: Review shows entity name", f"E2E Test VoIP {uid}" in review_summary, review_summary[:80]) # Select service tier page.locator('input[name="service_tier"][value="499a_499q"]').check() # Accept disclaimer page.locator("#disclaimer").check() page.wait_for_timeout(300) screenshot(page, "fcc_499a_08_step7_review") # Submit page.click("#btn-next") page.wait_for_timeout(3000) # Verify success message submit_status = page.locator("#submit-status") status_visible = submit_status.is_visible() check("Step 7: Submit status visible", status_visible) if status_visible: status_text = submit_status.text_content() or "" check("Step 7: Shows success message", "received" in status_text.lower() or "entity" in status_text.lower(), status_text[:100]) # Check entity was created check("Step 7: Entity ID in response", "Entity ID" in status_text or "received" in status_text.lower(), status_text[:100]) screenshot(page, "fcc_499a_09_submitted") return uid # ────────────────────────────────────────────────────────────────────── # Test 3: Entity CRUD API # ────────────────────────────────────────────────────────────────────── def test_entity_api(): LOG.info("\n=== Test 3: Entity CRUD API ===") uid = uuid.uuid4().hex[:6] # Create FCC entity LOG.info(" Creating FCC entity...") resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={ "jurisdiction": "FCC", "legal_name": f"API Test Corp {uid}", "frn": "0011223344", "filer_id_499": "999888", "filer_type": "interconnected_voip", "infra_type": "reseller", "is_deminimis": False, "is_lire": False, "service_categories": ["interconnected_voip", "ixc"], "contact_name": "API Tester", "contact_email": f"api-test+{uid}@performancewest.net", "address_state": "TX", "ein": "98-7654321", }, timeout=10) check("Create FCC entity: 201", resp.status_code == 201, f"got {resp.status_code}") fcc_entity = resp.json() if resp.ok else {} fcc_id = fcc_entity.get("id") if fcc_id: check("Entity has ID", fcc_id is not None) check("Entity legal_name correct", fcc_entity.get("legal_name") == f"API Test Corp {uid}") check("Entity jurisdiction = FCC", fcc_entity.get("jurisdiction") == "FCC") check("Entity FRN stored", fcc_entity.get("frn") == "0011223344") # Create CRTC entity LOG.info(" Creating CRTC entity...") resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={ "jurisdiction": "CRTC", "legal_name": f"9876543 B.C. Ltd. ({uid})", "incorporation_number": "BC9876543", "incorporation_province": "BC", "crtc_registration_number": f"CRTC-TEST-{uid}", "contact_email": f"api-test+{uid}@performancewest.net", }, timeout=10) check("Create CRTC entity: 201", resp.status_code == 201, f"got {resp.status_code}") crtc_entity = resp.json() if resp.ok else {} crtc_id = crtc_entity.get("id") if crtc_id: check("CRTC entity jurisdiction", crtc_entity.get("jurisdiction") == "CRTC") check("CRTC incorporation_province = BC", crtc_entity.get("incorporation_province") == "BC") # Get single entity if fcc_id: LOG.info(" Getting entity by ID...") resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{fcc_id}", timeout=10) check("Get entity: 200", resp.status_code == 200, f"got {resp.status_code}") # Update entity if fcc_id: LOG.info(" Updating entity...") resp = requests.patch(f"{API_URL}/api/v1/entities/telecom/{fcc_id}", json={ "dba_name": "Updated DBA", "is_deminimis": True, "notes": "Updated by e2e test", }, timeout=10) check("Update entity: 200", resp.status_code == 200, f"got {resp.status_code}") if resp.ok: updated = resp.json() check("Update: dba_name changed", updated.get("dba_name") == "Updated DBA") check("Update: is_deminimis changed", updated.get("is_deminimis") is True) # Per-entity compliance check (FCC) if fcc_id: LOG.info(" Running per-entity compliance check (FCC)...") resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{fcc_id}/compliance", timeout=30) check("FCC compliance check: 200", resp.status_code == 200, f"got {resp.status_code}") if resp.ok: data = resp.json() check("Compliance has entity info", "entity" in data) check("Compliance has checks", "checks" in data and len(data["checks"]) >= 4, f"got {len(data.get('checks', []))} checks") # Per-entity compliance check (CRTC) if crtc_id: LOG.info(" Running per-entity compliance check (CRTC)...") resp = requests.get(f"{API_URL}/api/v1/entities/telecom/{crtc_id}/compliance", timeout=10) check("CRTC compliance check: 200", resp.status_code == 200, f"got {resp.status_code}") if resp.ok: data = resp.json() check("CRTC has entity info", data.get("entity", {}).get("jurisdiction") == "CRTC") check("CRTC has incorporation check", any(c["id"] == "provincial_incorporation" for c in data.get("checks", []))) # Validation: missing legal_name LOG.info(" Testing validation...") resp = requests.post(f"{API_URL}/api/v1/entities/telecom", json={"jurisdiction": "FCC"}, timeout=10) check("Missing legal_name: 400", resp.status_code == 400, f"got {resp.status_code}") # Nonexistent entity resp = requests.get(f"{API_URL}/api/v1/entities/telecom/99999", timeout=10) check("Nonexistent entity: 404", resp.status_code == 404, f"got {resp.status_code}") return fcc_id, crtc_id # ────────────────────────────────────────────────────────────────────── # Test 4: Guide Pages (all 6 return 200) # ────────────────────────────────────────────────────────────────────── def test_guide_pages(): LOG.info("\n=== Test 4: Guide Pages ===") guides = [ "/services/telecom/guides/fcc-499a", "/services/telecom/guides/fcc-499q", "/services/telecom/guides/rmd-filing", "/services/telecom/guides/cpni-certification", "/services/telecom/guides/cores-registration", "/services/telecom/guides/usac-account-setup", ] for path in guides: try: resp = requests.get(f"{SITE_URL}{path}", timeout=10) name = path.split("/")[-1] check(f"Guide {name}: 200", resp.status_code == 200, f"got {resp.status_code}") except Exception as e: check(f"Guide {path}: reachable", False, str(e)) # ────────────────────────────────────────────────────────────────────── # Test 5: Service Pages # ────────────────────────────────────────────────────────────────────── def test_service_pages(): LOG.info("\n=== Test 5: Service Pages ===") pages = [ ("/services/telecom/fcc-499a", "FCC 499-A service page"), ("/services/telecom/cpni", "CPNI service page"), ] for path, label in pages: try: resp = requests.get(f"{SITE_URL}{path}", timeout=10) check(f"{label}: 200", resp.status_code == 200, f"got {resp.status_code}") if resp.ok: check(f"{label}: has pricing", "$" in resp.text) except Exception as e: check(f"{label}: reachable", False, str(e)) # ────────────────────────────────────────────────────────────────────── # Test 6: FCC Lookup API # ────────────────────────────────────────────────────────────────────── def test_fcc_lookup_api(): LOG.info("\n=== Test 6: FCC Lookup API ===") # Valid FRN resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", params={"frn": "0004309000"}, timeout=30) check("Lookup API: 200", resp.status_code == 200, f"got {resp.status_code}") if resp.ok: data = resp.json() check("Lookup: has frn", data.get("frn") == "0004309000") check("Lookup: has checks array", "checks" in data and len(data["checks"]) >= 4) check("Lookup: has checked_at", "checked_at" in data) # Invalid FRN resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", params={"frn": "abc"}, timeout=10) check("Invalid FRN: 400", resp.status_code == 400, f"got {resp.status_code}") # Missing FRN resp = requests.get(f"{API_URL}/api/v1/fcc/lookup", timeout=10) check("Missing FRN: 400", resp.status_code == 400, f"got {resp.status_code}") # ────────────────────────────────────────────────────────────────────── # Cleanup # ────────────────────────────────────────────────────────────────────── def cleanup(): LOG.info("\n=== Cleanup ===") try: import psycopg2 conn = psycopg2.connect(DEV_DB_URL) cur = conn.cursor() cur.execute("DELETE FROM telecom_entities WHERE contact_email LIKE '%%test+%%@performancewest.net' OR legal_name LIKE 'E2E Test%%' OR legal_name LIKE 'API Test%%'") deleted = cur.rowcount conn.commit() conn.close() LOG.info(" Deleted %d test entity(ies)", deleted) except Exception as e: LOG.warning(" Cleanup failed: %s", e) # ────────────────────────────────────────────────────────────────────── # Main # ────────────────────────────────────────────────────────────────────── def main(): parser = argparse.ArgumentParser(description="FCC Compliance Platform E2E Test") parser.add_argument("--headed", action="store_true", help="Run browser in headed mode") parser.add_argument("--keep", action="store_true", help="Don't cleanup test data") args = parser.parse_args() LOG.info("=" * 60) LOG.info(" FCC COMPLIANCE PLATFORM E2E TEST") LOG.info(" Site: %s | API: %s", SITE_URL, API_URL) LOG.info("=" * 60) with sync_playwright() as p: browser = p.chromium.launch( headless=not args.headed, args=["--disable-blink-features=AutomationControlled"], ) context = browser.new_context( viewport={"width": 1440, "height": 900}, user_agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", ) page = context.new_page() page.on("console", lambda msg: LOG.warning("[browser] %s: %s", msg.type, msg.text) if msg.type in ("error",) else None) # Browser-based tests test_compliance_wizard(page) test_499a_questionnaire(page) browser.close() # API-only tests test_entity_api() test_guide_pages() test_service_pages() test_fcc_lookup_api() if not args.keep: cleanup() # Report LOG.info("\n" + "=" * 60) LOG.info(" RESULTS: %d passed, %d failed, %d total", PASS, FAIL, PASS + FAIL) LOG.info("=" * 60) if FAIL > 0: LOG.info("\n Failed checks:") for r in RESULTS: if not r["ok"]: LOG.info(" X %s — %s", r["label"], r["detail"]) LOG.info("") LOG.info(" Screenshots: %s", SCREENSHOT_DIR) if FAIL == 0: LOG.info(" ALL CHECKS PASSED") else: LOG.info(" %d CHECK(S) FAILED", FAIL) LOG.info("=" * 60) sys.exit(1 if FAIL > 0 else 0) if __name__ == "__main__": main()