nido-core 0.0.0-alpha.0

Pre-release metadata crate. Real Nido core types and traits ship at >=0.1.0.
Documentation
#!/usr/bin/env python3
"""Browser e2e coverage for the generated GitHub Pages artifact."""

from __future__ import annotations

import os
import threading
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path

from playwright.sync_api import expect, sync_playwright


def assert_catalog_contract(site_dir: Path) -> None:
    import json

    payload = json.loads((site_dir / "groundwork.json").read_text(encoding="utf-8"))
    capabilities = payload["capabilities"]
    assert len(capabilities) == 53
    node_exporter = next(item for item in capabilities if item["role"] == "node_exporter")
    assert node_exporter["adoption_status"] == "bundled"
    assert node_exporter["release_gate"] == "standard"


def start_server(directory: Path) -> tuple[ThreadingHTTPServer, str]:
    handler = partial(SimpleHTTPRequestHandler, directory=str(directory))
    server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    host, port = server.server_address
    return server, f"http://{host}:{port}/"


def wait_for_service_worker_control(page) -> None:
    controlled = page.evaluate(
        """
        async () => {
          if (!("serviceWorker" in navigator)) return false;
          await navigator.serviceWorker.ready;
          if (navigator.serviceWorker.controller) return true;
          await new Promise((resolve) => {
            navigator.serviceWorker.addEventListener("controllerchange", resolve, { once: true });
          });
          return Boolean(navigator.serviceWorker.controller);
        }
        """
    )
    assert controlled, "page must be controlled by the service worker before offline checks"


def main() -> None:
    site_dir = Path(os.environ.get("NIDO_PAGES_SITE_DIR", "target/pages-site"))
    base_url = os.environ.get("NIDO_PAGES_BASE_URL")
    server = None
    if not base_url:
        if not site_dir.exists():
            raise SystemExit(f"missing site artifact: {site_dir}")
        assert_catalog_contract(site_dir)
        server, base_url = start_server(site_dir)

    try:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch(headless=True)
            context = browser.new_context(viewport={"width": 1440, "height": 960})
            page = context.new_page()
            console_errors: list[str] = []
            page.on("console", lambda msg: console_errors.append(msg.text) if msg.type == "error" else None)

            page.goto(base_url, wait_until="networkidle")
            wait_for_service_worker_control(page)
            expect(page).to_have_title("Nido Groundwork")
            expect(page.locator("[data-total-count]")).to_have_text("53")
            expect(page.locator("[data-gated-count]")).to_have_text("7")
            expect(page.locator("[data-layer-count]")).not_to_have_text("0")
            expect(page.locator("[data-role='minio']")).to_be_visible()
            page.evaluate("localStorage.setItem('nido-groundwork-compare', 'not-json')")
            page.reload(wait_until="networkidle")
            expect(page.locator("[data-total-count]")).to_have_text("53")

            page.locator("#search").fill("minio")
            expect(page.locator("[data-visible-count]")).to_have_text("1")
            expect(page.locator(".detail-title")).to_have_text("minio")
            expect(page.locator(".command-box code")).to_contain_text("nido ansible show-groundwork minio --json")

            page.locator("button[data-gate='license-review']").click()
            page.locator("#search").fill("")
            expect(page.locator("[data-visible-count]")).to_have_text("7")
            expect(page.locator("[data-role='grafana']")).to_be_visible()
            expect(page.locator("[data-role='keycloak']")).to_have_count(0)

            page.locator("#clearFilters").click()
            page.locator("button[data-adoption='bundled']").click()
            expect(page.locator("[data-visible-count]")).to_have_text("1")
            expect(page.locator("[data-role='node_exporter']")).to_be_visible()

            page.locator("#clearFilters").click()
            page.locator("[data-role='grafana']").click()
            page.get_by_role("button", name="Pin").click()
            expect(page.locator(".compare-table")).to_contain_text("grafana")
            expect(page.locator(".compare-table")).to_contain_text("Observability")
            page.reload(wait_until="networkidle")
            expect(page.locator(".compare-table")).to_contain_text("grafana")

            manifest = page.goto(f"{base_url}manifest.webmanifest")
            assert manifest and manifest.ok, "manifest must load"
            sw = page.goto(f"{base_url}sw.js")
            assert sw and sw.ok, "service worker must load"

            page.goto(base_url, wait_until="networkidle")
            wait_for_service_worker_control(page)
            console_errors.clear()
            context.set_offline(True)
            page.reload(wait_until="domcontentloaded")
            expect(page.locator("[data-total-count]")).to_have_text("53")
            expect(page.locator("[data-role='minio']")).to_be_visible()
            context.set_offline(False)
            assert not console_errors, "offline reload console errors: " + " | ".join(console_errors)

            mobile = browser.new_page(viewport={"width": 390, "height": 844}, is_mobile=True)
            mobile.goto(base_url, wait_until="networkidle")
            expect(mobile.locator(".brand-name")).to_have_text("Nido")
            expect(mobile.locator("[data-role='minio']")).to_be_visible()
            overflow = mobile.evaluate("document.documentElement.scrollWidth - window.innerWidth")
            assert overflow <= 1, f"mobile layout has horizontal overflow: {overflow}px"
            mobile.close()

            context.close()
            browser.close()
            assert not console_errors, "console errors: " + " | ".join(console_errors)
    finally:
        if server:
            server.shutdown()


if __name__ == "__main__":
    main()