File size: 7,820 Bytes
149cadc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | #!/usr/bin/env python3
"""Build the rendered website check report from browser observations."""
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_INPUT = Path("/tmp/xperience_rendered_site_observations.json")
OUTPUT_JSON = ROOT / "docs/data/rendered_site_check.json"
OUTPUT_MD = ROOT / "RENDERED_SITE_CHECK.md"
def load_json(path: Path) -> dict[str, Any]:
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError as exc:
raise SystemExit(
f"Observation file missing: {path}. Run the browser flow and pass --input."
) from exc
except json.JSONDecodeError as exc:
raise SystemExit(f"Observation file is not valid JSON: {path}: {exc}") from exc
def check(name: str, passed: bool, reason: str, **detail: Any) -> dict[str, Any]:
return {"name": name, "status": "pass" if passed else "fail", "reason": reason, **detail}
def build_report(observations: dict[str, Any]) -> dict[str, Any]:
viewport = observations.get("viewport") or {}
checks = [
check(
"page_identity",
observations.get("title") == "Ropedia Xperience-10M Task Suite"
and str(observations.get("url", "")).startswith("http://127.0.0.1:"),
"The rendered page should load the expected local site title and URL.",
title=observations.get("title"),
url=observations.get("url"),
),
check(
"first_meaningful_content",
observations.get("h1") == "Ropedia Xperience-10M Research Task Lab.",
"The first meaningful heading should identify the research task lab.",
h1=observations.get("h1"),
),
check(
"responsive_viewport_recorded",
int(viewport.get("width", 0)) >= 360 and int(viewport.get("height", 0)) >= 600,
"The browser run should record a narrow responsive viewport large enough to expose the mobile/tablet layout.",
viewport=viewport,
),
check(
"tabbed_research_navigation",
observations.get("projectTabCount") == 5
and observations.get("panelCount", 0) >= 19
and observations.get("selectedProjectTab") == "tab-data",
"The rendered top-level tab system should switch to the Data tab for the walkthrough deep link.",
project_tab_count=observations.get("projectTabCount"),
panel_count=observations.get("panelCount"),
selected_project_tab=observations.get("selectedProjectTab"),
),
check(
"task_and_modality_cards_render",
observations.get("taskCardCount") == 12 and observations.get("atlasCardCount") == 7,
"The rendered task and modality sections should expose all 12 task cards and seven modality cards.",
task_card_count=observations.get("taskCardCount"),
atlas_card_count=observations.get("atlasCardCount"),
),
check(
"walkthrough_deep_link",
observations.get("visibleWalkthrough") is True
and observations.get("selectorButtonCount") == 12
and observations.get("storyButtonCount") == 4,
"The walkthrough deep link should reveal the walkthrough player, all task selectors, and four chapter controls.",
visible_walkthrough=observations.get("visibleWalkthrough"),
selector_button_count=observations.get("selectorButtonCount"),
story_button_count=observations.get("storyButtonCount"),
),
check(
"walkthrough_interaction",
observations.get("activeSelectorCount") == 1
and observations.get("activeStoryCount") == 1
and observations.get("activeTask") == "Procedure Step Recognition"
and observations.get("activeStory") == "Process"
and observations.get("frameChip") == "Step 2 / 4 · Process",
"Clicking Next and the Process chapter should update the active task, chapter, counter, and frame label.",
active_task=observations.get("activeTask"),
active_story=observations.get("activeStory"),
player_counter=observations.get("playerCounter"),
frame_chip=observations.get("frameChip"),
active_selector_count=observations.get("activeSelectorCount"),
active_story_count=observations.get("activeStoryCount"),
),
check(
"rendered_check_resource_link",
observations.get("renderedSiteCheckLinkPresent") is True,
"The rendered page should expose the rendered website check JSON from the resource section.",
rendered_site_check_link_present=observations.get("renderedSiteCheckLinkPresent"),
),
check(
"console_health",
observations.get("consoleWarningsOrErrors") == 0,
"The rendered flow should complete without browser console warnings or errors.",
console_warnings_or_errors=observations.get("consoleWarningsOrErrors"),
),
]
return {
"title": "Ropedia Xperience-10M Rendered Website Check",
"status": "pass" if all(item["status"] == "pass" for item in checks) else "fail",
"generated_at_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"flow": observations.get("exercised_flow"),
"checked_at_local": observations.get("checked_at_local"),
"screenshot_path": observations.get("screenshot_path"),
"observations": observations,
"checks": checks,
}
def markdown(report: dict[str, Any]) -> str:
observations = report["observations"]
viewport = observations.get("viewport") or {}
lines = [
"# Rendered Website Check",
"",
"This report records the latest browser-level check of the local static website.",
"",
f"Current status: **{report['status']}**",
"",
"## Browser Flow",
"",
f"- URL: `{observations.get('url')}`",
f"- Title: `{observations.get('title')}`",
f"- Viewport: `{viewport.get('width')} x {viewport.get('height')}`",
f"- Flow: {report.get('flow')}",
f"- Screenshot: `{report.get('screenshot_path')}`",
"",
"## Checks",
"",
"| Check | Status | What it covers |",
"| --- | --- | --- |",
]
for item in report["checks"]:
label = item["name"].replace("_", " ").title()
lines.append(f"| {label} | `{item['status']}` | {item['reason']} |")
lines.extend(
[
"",
"## Regenerate",
"",
"Run the local static website, exercise the walkthrough in a browser, save the observation JSON, then rebuild this report:",
"",
"```bash",
"python scripts/build_rendered_site_check.py --input /tmp/xperience_rendered_site_observations.json",
"```",
"",
]
)
return "\n".join(lines)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--input", type=Path, default=DEFAULT_INPUT)
args = parser.parse_args()
report = build_report(load_json(args.input))
OUTPUT_JSON.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_JSON.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
OUTPUT_MD.write_text(markdown(report), encoding="utf-8")
print(f"{report['status'].upper()}: wrote {OUTPUT_JSON}")
print(f"{report['status'].upper()}: wrote {OUTPUT_MD}")
return 0 if report["status"] == "pass" else 1
if __name__ == "__main__":
raise SystemExit(main())
|