Includes: API (Express/TypeScript), Astro site, Python workers, document generators, FCC compliance tools, Canada CRTC formation, Ansible infrastructure, and deployment scripts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
580 lines
23 KiB
Python
580 lines
23 KiB
Python
"""
|
|
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()
|