#!/usr/bin/env python3 """ E2E Test Suite Runner for ANL Homepage Development Verifies: 0. Clean step (npm run clean -> removes .next and tsconfig.tsbuildinfo) 1. ESLint (npm run lint) 2. Next.js Static SSG Build (npm run build) with 0 dynamic routes 3. TypeScript type check (npx tsc --noEmit) 4. Existence and content of static SSG routes HTML/body files 5. Data count losslessness & integrity gate (verify_counts.py) 6. Light/Dark mode theme tokens and CSS attributes in source and build assets 7. DOM & HTML Content Assertions (Unittest) """ import os import sys import subprocess import re ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) REFER_DIR = os.path.join(ROOT_DIR, "refer_landing_page") # Expected Static SSG Routes HTML/Body output paths relative to REFER_DIR EXPECTED_SSG_ROUTES = [ os.path.join(".next", "server", "app", "index.html"), os.path.join(".next", "server", "app", "members.html"), os.path.join(".next", "server", "app", "publications.html"), os.path.join(".next", "server", "app", "publications", "intl-journal-conf.html"), os.path.join(".next", "server", "app", "publications", "domestic-journal-conf.html"), os.path.join(".next", "server", "app", "publications", "patent.html"), os.path.join(".next", "server", "app", "lectures.html"), os.path.join(".next", "server", "app", "standardization.html"), os.path.join(".next", "server", "app", "_not-found.html"), os.path.join(".next", "server", "app", "robots.txt.body"), os.path.join(".next", "server", "app", "sitemap.xml.body"), ] def run_cmd(cmd, cwd=REFER_DIR): print(f"Running: {' '.join(cmd)} (cwd: {cwd})") res = subprocess.run(cmd, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return res def test_clean(): print("\n--- Gate 0: Clean Step (workspace build cache purge) ---") res = run_cmd(["bash", "-c", "npm run clean"]) if res.returncode != 0: print(f"❌ Clean step failed:\n{res.stdout}\n{res.stderr}") return False print("✅ Gate 0 PASSED: Cleaned .next and tsconfig.tsbuildinfo") return True def test_lint(): print("\n--- Gate 1: ESLint Quality Gate ---") res = run_cmd(["npm", "run", "lint"]) if res.returncode != 0: print(f"❌ Lint failed:\n{res.stdout}\n{res.stderr}") return False print("✅ Gate 1 PASSED: ESLint clean (0 errors, 0 warnings)") return True def test_build(): print("\n--- Gate 2: Next.js Static SSG Build Gate ---") res = run_cmd(["npm", "run", "build"]) if res.returncode != 0: print(f"❌ SSG Build failed:\n{res.stdout}\n{res.stderr}") return False # Check stdout/stderr for dynamic routes (f Dynamic) if "ƒ" in res.stdout or "Dynamic" in res.stdout: # Search specifically for dynamic routes output line lines = res.stdout.splitlines() dynamic_lines = [l for l in lines if "ƒ" in l or "Dynamic" in l] if dynamic_lines: print(f"❌ Found dynamic route(s) in build report: {dynamic_lines}") return False print("✅ Gate 2 PASSED: Static SSG Build successful (0 dynamic routes)") return True def test_tsc(): print("\n--- Gate 3: TypeScript Type Safety Gate ---") res = run_cmd(["npx", "tsc", "--noEmit"]) if res.returncode != 0: print(f"❌ Type check failed:\n{res.stdout}\n{res.stderr}") return False print("✅ Gate 3 PASSED: TypeScript type check clean (exit code 0)") return True def test_ssg_routes(): print("\n--- Gate 4: Static SSG HTML Route Verification ---") missing = [] for rel_path in EXPECTED_SSG_ROUTES: full_path = os.path.join(REFER_DIR, rel_path) if not os.path.exists(full_path): missing.append(rel_path) else: size = os.path.getsize(full_path) print(f" [OK] {rel_path} ({size} bytes)") if missing: print(f"❌ Missing {len(missing)} SSG routes:") for m in missing: print(f" - {m}") return False print(f"✅ Gate 4 PASSED: All {len(EXPECTED_SSG_ROUTES)} static SSG routes exist and are non-empty") return True def test_data_counts(): print("\n--- Gate 5: Data Losslessness Integrity Gate ---") script_path = os.path.join(REFER_DIR, "scripts", "extract", "verify_counts.py") res = run_cmd([sys.executable, script_path], cwd=ROOT_DIR) if res.returncode != 0: print(f"❌ Data count verification failed:\n{res.stdout}\n{res.stderr}") return False print(res.stdout.strip()) print("✅ Gate 5 PASSED: Data count integrity & volume losslessness verified") return True def test_theme_tokens(): print("\n--- Gate 6: CSS & Theme Token Verification ---") globals_css = os.path.join(REFER_DIR, "app", "globals.css") tailwind_config = os.path.join(REFER_DIR, "tailwind.config.ts") if not os.path.exists(globals_css): print(f"❌ Missing {globals_css}") return False with open(globals_css, "r", encoding="utf-8") as f: css_text = f.read() tokens = ["--ink", "--ivory", "--paper", "--line", "--vermillion", "--cobalt"] missing_tokens = [t for t in tokens if t not in css_text] if missing_tokens: print(f"❌ Missing CSS theme tokens in globals.css: {missing_tokens}") return False with open(tailwind_config, "r", encoding="utf-8") as f: tw_text = f.read() tw_tokens = ["vermillion", "cobalt", "dark"] missing_tw = [t for t in tw_tokens if t not in tw_text] if missing_tw: print(f"❌ Missing Tailwind theme tokens in tailwind.config.ts: {missing_tw}") return False print("✅ Gate 6 PASSED: Light/Dark theme CSS variables and Tailwind tokens present") return True def test_unittest_suite(): print("\n--- Gate 7: DOM & HTML Content Assertions (Unittest) ---") test_path = os.path.join(REFER_DIR, "tests", "e2e_test_suite.py") res = run_cmd([sys.executable, "-m", "unittest", test_path], cwd=ROOT_DIR) if res.returncode != 0: print(f"❌ Unittest suite failed:\n{res.stdout}\n{res.stderr}") return False print("✅ Gate 7 PASSED: All 6 DOM & static HTML content assertion tests passed") return True def test_layout_architecture(): print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / R-8.17) ---") # 1) Check HTML SSG pages for container-content inside main routes_to_check = [ os.path.join(".next", "server", "app", "index.html"), os.path.join(".next", "server", "app", "members.html"), os.path.join(".next", "server", "app", "publications.html"), os.path.join(".next", "server", "app", "lectures.html"), os.path.join(".next", "server", "app", "standardization.html"), ] for rel_p in routes_to_check: full_p = os.path.join(REFER_DIR, rel_p) if not os.path.exists(full_p): print(f"❌ Missing SSG route HTML: {rel_p}") return False with open(full_p, "r", encoding="utf-8") as f: html = f.read() # Verify
tag not found in {rel_p}") return False main_cls = main_match.group(1) if "max-w-" in main_cls or "px-" in main_cls or "container-content" in main_cls: print(f"❌
tag in {rel_p} restricts width or padding ({main_cls}). Should be w-full flex-1 only.") return False # Verify child wrapper has container-content if "container-content" not in html: print(f"❌ container-content missing in {rel_p}") return False # 2) Check source for forbidden escaped arbitrary classes (AC-26) forbidden_terms = ["w-screen", "-mx-[50vw]", "min-[1240px]:"] for root, _, files in os.walk(os.path.join(REFER_DIR, "app")): for fname in files: if fname.endswith((".ts", ".tsx")): fpath = os.path.join(root, fname) with open(fpath, "r", encoding="utf-8") as f: content = f.read() for term in forbidden_terms: if term in content: print(f"❌ Forbidden term '{term}' found in {fpath}") return False for root, _, files in os.walk(os.path.join(REFER_DIR, "components")): for fname in files: if fname.endswith((".ts", ".tsx")): fpath = os.path.join(root, fname) with open(fpath, "r", encoding="utf-8") as f: content = f.read() for term in forbidden_terms: if term in content: print(f"❌ Forbidden term '{term}' found in {fpath}") return False print("✅ Gate 8 PASSED: Layout 3-tier architecture and AC-25/AC-26 no-escape rule verified") return True def main(): print("=" * 70) print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4") print("=" * 70) success = True success &= test_clean() success &= test_lint() success &= test_build() success &= test_tsc() success &= test_ssg_routes() success &= test_data_counts() success &= test_theme_tokens() success &= test_unittest_suite() success &= test_layout_architecture() print("\n" + "-" * 70) if success: print("ALL E2E TEST SUITES PASSED CLEAN (Exit Code 0).") print("=" * 70) sys.exit(0) else: print("❌ ONE OR MORE E2E GATES FAILED.") print("=" * 70) sys.exit(1) if __name__ == "__main__": main()