- Set cache: 'no-store' in lib/api.ts to trigger real-time REST API requests on every page load - Add export const dynamic = 'force-dynamic' across all 6 app pages (/home, /members, /publications, /lectures, /standardization) - Update REQUIREMENTS.md and E2E test runner to validate live server routes and dynamic runtime fetching - Pass all 12 quality gates cleanly
234 lines
9.6 KiB
Python
234 lines
9.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Python Unittest Suite for ANL Homepage E2E Verification
|
|
Queries live running Next.js server to assert DOM structure, text content, and theme attributes.
|
|
"""
|
|
|
|
import unittest
|
|
import os
|
|
import re
|
|
import time
|
|
import subprocess
|
|
import urllib.request
|
|
|
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
TEST_SERVER_URL = os.environ.get("TEST_SERVER_URL", "http://localhost:3000")
|
|
|
|
class TestANLHomepageE2E(unittest.TestCase):
|
|
_server_proc = None
|
|
_html_cache = {}
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# Check if server is already responding at TEST_SERVER_URL
|
|
is_ready = False
|
|
try:
|
|
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
|
|
if resp.status == 200:
|
|
is_ready = True
|
|
except Exception:
|
|
is_ready = False
|
|
|
|
if not is_ready:
|
|
# Spawn local next start server
|
|
cls._server_proc = subprocess.Popen(
|
|
["npm", "run", "start"],
|
|
cwd=BASE_DIR,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
text=True,
|
|
)
|
|
for _ in range(30):
|
|
try:
|
|
with urllib.request.urlopen(f"{TEST_SERVER_URL}/", timeout=1) as resp:
|
|
if resp.status == 200:
|
|
is_ready = True
|
|
break
|
|
except Exception:
|
|
time.sleep(0.5)
|
|
|
|
if not is_ready:
|
|
cls._server_proc.terminate()
|
|
raise RuntimeError(f"Next.js server failed to become ready at {TEST_SERVER_URL}")
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
if cls._server_proc:
|
|
cls._server_proc.terminate()
|
|
try:
|
|
cls._server_proc.wait(timeout=5)
|
|
except Exception:
|
|
cls._server_proc.kill()
|
|
|
|
def get_route_html(self, route):
|
|
if route in self._html_cache:
|
|
return self._html_cache[route]
|
|
url = f"{TEST_SERVER_URL}{route}"
|
|
req = urllib.request.Request(url, headers={"User-Agent": "E2ETestRunner"})
|
|
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
html = resp.read().decode("utf-8")
|
|
self._html_cache[route] = html
|
|
return html
|
|
|
|
def read_html(self, relative_path):
|
|
route_map = {
|
|
"index.html": "/",
|
|
"members.html": "/members",
|
|
"publications.html": "/publications",
|
|
os.path.join("publications", "intl-journal-conf.html"): "/publications/intl-journal-conf",
|
|
"publications/intl-journal-conf.html": "/publications/intl-journal-conf",
|
|
os.path.join("publications", "domestic-journal-conf.html"): "/publications/domestic-journal-conf",
|
|
"publications/domestic-journal-conf.html": "/publications/domestic-journal-conf",
|
|
os.path.join("publications", "patent.html"): "/publications/patent",
|
|
"publications/patent.html": "/publications/patent",
|
|
"lectures.html": "/lectures",
|
|
"standardization.html": "/standardization",
|
|
}
|
|
route = route_map.get(relative_path, f"/{relative_path}")
|
|
return self.get_route_html(route)
|
|
|
|
def test_01_home_page_structure(self):
|
|
"""TC-T1-F3: Verify Home page HTML content and semantic structures."""
|
|
html = self.read_html("index.html")
|
|
|
|
# 1. Brand & Header titles
|
|
self.assertIn("ANL", html)
|
|
self.assertIn("AI Agent Networking LAB", html)
|
|
|
|
# 2. Research Areas keywords
|
|
self.assertIn("Quick UDP Internet Connection (QUIC)", html)
|
|
self.assertIn("Constrained Application Protocol (CoAP)", html)
|
|
self.assertIn("Stream Control Transmission Protocol (SCTP)", html)
|
|
self.assertIn("Tabu Search and Genetic Algorithm", html)
|
|
|
|
# 3. Location / Address section
|
|
self.assertIn("address", html.lower())
|
|
self.assertIn("Kyungpook National University", html)
|
|
self.assertIn("College of IT Engineering", html)
|
|
self.assertIn("Prof. Seok-Joo Koh", html)
|
|
self.assertIn("sjkoh@knu.ac.kr", html)
|
|
|
|
def test_02_members_page_content(self):
|
|
"""TC-T1-F4: Verify Members page active members & alumni records."""
|
|
html = self.read_html("members.html")
|
|
|
|
# Professor profile
|
|
self.assertIn("Prof. Seok-Joo Koh", html)
|
|
self.assertIn("Seok-Joo Koh", html)
|
|
self.assertIn("sjkoh@knu.ac.kr", html)
|
|
|
|
# Sections & Degree formatting checks
|
|
self.assertIn("LAB MEMBERS", html)
|
|
self.assertIn("Active Researchers", html)
|
|
self.assertIn("Graduates", html)
|
|
self.assertIn("Ph. D. Candidate", html)
|
|
self.assertIn("Project Editor", html)
|
|
|
|
def test_03_publications_category_routes(self):
|
|
"""TC-T1-F5: Verify Publications categories live dynamic pages."""
|
|
cats = [
|
|
("intl-journal-conf.html", "International Journal"),
|
|
("domestic-journal-conf.html", "Domestic Journal"),
|
|
("patent.html", "Patent"),
|
|
]
|
|
for path_suffix, expected_text in cats:
|
|
rel_path = os.path.join("publications", path_suffix)
|
|
html = self.read_html(rel_path)
|
|
self.assertIn(expected_text, html, f"Category route {path_suffix} missing '{expected_text}'")
|
|
|
|
def test_04_lectures_page_content(self):
|
|
"""TC-T1-F6: Verify Lectures page HTML structure."""
|
|
html = self.read_html("lectures.html")
|
|
self.assertIn("Lectures", html)
|
|
|
|
def test_05_standardization_page_content(self):
|
|
"""TC-T1-F6: Verify Standardization page standards bodies."""
|
|
html = self.read_html("standardization.html")
|
|
self.assertIn("Standardization", html)
|
|
self.assertIn("IEC TC100", html)
|
|
|
|
def test_06_theme_css_variables(self):
|
|
"""TC-T1-F2: Verify globals.css and built assets contain theme tokens."""
|
|
globals_path = os.path.join(BASE_DIR, "app", "globals.css")
|
|
with open(globals_path, "r", encoding="utf-8") as f:
|
|
css = f.read()
|
|
|
|
for token in ["--ink", "--ivory", "--paper", "--line", "--vermillion", "--cobalt"]:
|
|
self.assertIn(token, css, f"Missing CSS variable {token} in globals.css")
|
|
|
|
def test_07_research_projects_pageview(self):
|
|
"""TC-T1-F7: Verify Research Project PageView content in Home HTML."""
|
|
html = self.read_html("index.html")
|
|
|
|
# 1. Project PageView Title & Projects
|
|
self.assertIn("Research Projects", html)
|
|
self.assertIn("AI-RAN", html)
|
|
self.assertIn("MCM", html)
|
|
self.assertIn("MVQM", html)
|
|
|
|
# 2. Standards track labels
|
|
self.assertIn("National Research Foundation of Korea", html)
|
|
self.assertIn("IEC/TC 100/WG 12 supported by KEIT", html)
|
|
|
|
# 3. International Standardizations Section
|
|
self.assertIn("International Standardizations", html)
|
|
self.assertIn("IEC TC100: Multimedia Systems and Equipment", html)
|
|
self.assertIn("ISO/IEC JTC1/SC6: Reliable Multicasting", html)
|
|
|
|
# 4. Assert zero placeholder Funder labels
|
|
self.assertNotIn("Funder:", html)
|
|
|
|
def test_08_publications_highlights_and_category_nav(self):
|
|
"""TC-T1-F8: Verify 5 representative highlights and CategoryNav active border in Publications."""
|
|
html = self.read_html("publications.html")
|
|
|
|
# 1. Verify 5 designated representative publications
|
|
expected_highlights = [
|
|
"Globally Integrated Trust Authority (GITA) for Resource-Constrained Edge Devices in IoT and 6G",
|
|
"Application Level Trust Authority (APPLETA) for Resource-Constrained Edge Devices in IoT and 6G",
|
|
"mQUIC: Use of QUIC for Handover Support with Connection Migration in Wireless/Mobile Networks",
|
|
"Use of QUIC for CoAP Transport in IoT Networks",
|
|
"Use of QUIC for Mobile-Oriented Future Internet (Q-MOFI)",
|
|
]
|
|
for title in expected_highlights:
|
|
self.assertIn(title, html, f"Missing representative highlight publication: '{title}'")
|
|
|
|
# 2. Verify Highlights heading & section
|
|
self.assertIn("Highlights", html)
|
|
self.assertIn("Categories", html)
|
|
self.assertIn("(IoT Architecture & Protocols, etc ...)", html)
|
|
|
|
# 3. Verify category detail active card border & label
|
|
patent_html = self.read_html(os.path.join("publications", "patent.html"))
|
|
self.assertIn("border-2 border-cobalt-dark", patent_html)
|
|
self.assertIn("Active View ●", patent_html)
|
|
self.assertIn("Patent", patent_html)
|
|
|
|
def test_09_footer_cite_and_brand_links(self):
|
|
"""TC-T1-F9: Verify Footer CITE abbreviation and brand/contact elements."""
|
|
html = self.read_html("index.html")
|
|
|
|
# 1. Institution link label CITE
|
|
self.assertIn("<span>CITE</span>", html)
|
|
self.assertIn("<span>KNU</span>", html)
|
|
self.assertIn("<span>CSE</span>", html)
|
|
|
|
# 2. Advisor contact info
|
|
self.assertIn("Prof. Seok-Joo Koh", html)
|
|
self.assertIn("sjkoh@knu.ac.kr", html)
|
|
|
|
def test_10_subpage_layout_and_english_headers(self):
|
|
"""TC-T1-F10: Verify English page headers across all subpages."""
|
|
subpages = [
|
|
("members.html", "LAB MEMBERS"),
|
|
("publications.html", "PUBLICATIONS"),
|
|
("lectures.html", "LECTURES"),
|
|
("standardization.html", "International Standardization"),
|
|
]
|
|
for rel_path, expected_h1 in subpages:
|
|
html = self.read_html(rel_path)
|
|
self.assertIn(expected_h1, html, f"Subpage {rel_path} missing H1 '{expected_h1}'")
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|