From 22e5ae60ab884fa5460304e984253a663c15275a Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 10 Oct 2026 16:57:37 +0200 Subject: [PATCH] CI: run the frontend, graph embed and Switch Bay VS tests Adds the frontend Node tests, the CE embed and VS Code graph bridge Node tests, the Switch Bay VS compile and tests, a check that the old graph viewer copy stays removed, and the graph-ce-embed browser test. Signed-off-by: benjsmith --- .github/workflows/ci.yml | 123 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9daa19d..6a33b0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,52 @@ jobs: ) print("htmlExtraction: 6/6 (no skips)") PY + - name: CE embed + VS Code graph bridge Node tests (29) + working-directory: frontend + run: | + set -euo pipefail + node --experimental-strip-types --test --test-reporter tap \ + src/widgets/embed/ceSidebarHeader.test.ts \ + src/widgets/embed/ceHostBridge.test.ts \ + src/widgets/embed/embedMount.test.ts \ + src/widgets/embed/vscodeGraphBridge.test.ts > "${RUNNER_TEMP}/ce-embed.tap" + cat "${RUNNER_TEMP}/ce-embed.tap" + python3 - "${RUNNER_TEMP}/ce-embed.tap" <<'PY' + from pathlib import Path + import sys + lines = [ln.strip() for ln in Path(sys.argv[1]).read_text(errors="replace").splitlines()] + meta = {} + for ln in lines: + for key in ("pass", "fail", "skipped", "todo", "cancelled"): + if ln.startswith(f"# {key} "): + meta[key] = int(ln.split()[-1]) + bad = any(meta.get(k, 0) for k in ("fail", "skipped", "todo", "cancelled")) + if bad or meta.get("pass") != 29 or "1..29" not in lines: + raise SystemExit(f"expected 29 passing CE embed tests, no skips: {meta}") + print("ce embed: 29/29 (no skips)") + PY + - name: Frontend Node tests (10) + working-directory: frontend + run: | + set -euo pipefail + node --experimental-strip-types --test --test-reporter tap \ + src/lib/uploadVault.test.ts \ + src/widgets/agents/agentSpace.test.ts > "${RUNNER_TEMP}/frontend-node.tap" + cat "${RUNNER_TEMP}/frontend-node.tap" + python3 - "${RUNNER_TEMP}/frontend-node.tap" <<'PY' + from pathlib import Path + import sys + lines = [ln.strip() for ln in Path(sys.argv[1]).read_text(errors="replace").splitlines()] + meta = {} + for ln in lines: + for key in ("pass", "fail", "skipped", "todo", "cancelled"): + if ln.startswith(f"# {key} "): + meta[key] = int(ln.split()[-1]) + bad = any(meta.get(k, 0) for k in ("fail", "skipped", "todo", "cancelled")) + if bad or meta.get("pass") != 10 or "1..10" not in lines: + raise SystemExit(f"expected 10 passing frontend Node tests, no skips: {meta}") + print("frontend node: 10/10 (no skips)") + PY - name: Web-policy race Node test working-directory: frontend run: | @@ -106,6 +152,41 @@ jobs: run: pnpm --dir frontend run build - name: Webview graph build run: pnpm --dir frontend run build:webview + - name: Switch Bay VS install + run: pnpm --dir extensions/switchbay-vs install --frozen-lockfile + - name: Switch Bay VS compile + Node tests (20) + working-directory: extensions/switchbay-vs + run: | + set -euo pipefail + pnpm exec tsc -p ./ + node --test --test-reporter tap out/*.test.js > "${RUNNER_TEMP}/switchbay-vs.tap" + cat "${RUNNER_TEMP}/switchbay-vs.tap" + python3 - "${RUNNER_TEMP}/switchbay-vs.tap" <<'PY' + from pathlib import Path + import sys + lines = [ln.strip() for ln in Path(sys.argv[1]).read_text(errors="replace").splitlines()] + meta = {} + for ln in lines: + for key in ("pass", "fail", "skipped", "todo", "cancelled"): + if ln.startswith(f"# {key} "): + meta[key] = int(ln.split()[-1]) + bad = any(meta.get(k, 0) for k in ("fail", "skipped", "todo", "cancelled")) + if bad or meta.get("pass") != 20 or "1..20" not in lines: + raise SystemExit(f"expected 20 passing Switch Bay VS tests, no skips: {meta}") + print("switchbay-vs: 20/20 (no skips)") + PY + - name: Graph webview has no fork left + run: | + set -euo pipefail + test -f extensions/switchbay-vs/media/graph/webview-graph.html + if [ -e frontend/src/widgets/graph ]; then + echo "frontend/src/widgets/graph was removed; the graph views mount Curiosity Engine's viewer" + exit 1 + fi + if grep -rl 'widgets/graph' frontend/src frontend/*.html frontend/vite*.config.ts extensions/switchbay-vs/src; then + echo "references to the removed graph fork" + exit 1 + fi browser-e2e: name: Browser e2e (mocked backend) @@ -140,6 +221,7 @@ jobs: test -f frontend/tests/e2e/mock_backend.py test -f frontend/tests/e2e/zen-web-policy.spec.ts test -f frontend/tests/e2e/comms-desks.spec.ts + test -f frontend/tests/e2e/graph-ce-embed.spec.ts grep -F 'e2e_mock' frontend/tests/e2e/zen-web-policy.spec.ts grep -F 'e2e_mock' frontend/tests/e2e/comms-desks.spec.ts grep -F 'e2e_mock' frontend/tests/e2e/mock_backend.py @@ -148,7 +230,8 @@ jobs: grep -F '/tmp/switchbay-e2e-mock-ws' frontend/tests/e2e/mock_backend.py if grep -nE 'test\.(skip|fixme)\(' \ frontend/tests/e2e/zen-web-policy.spec.ts \ - frontend/tests/e2e/comms-desks.spec.ts; then + frontend/tests/e2e/comms-desks.spec.ts \ + frontend/tests/e2e/graph-ce-embed.spec.ts; then echo "e2e specs must not skip" exit 1 fi @@ -230,6 +313,44 @@ jobs: raise SystemExit(f"{label}: expected {want} passed, got {got}") print(f"{label}: {got}/{want} passed, no skips") PY + - name: Playwright graph-ce-embed (mock port 41767) + working-directory: frontend + env: + E2E_MOCK: "1" + E2E_CE_PORT: "41767" + CI: "true" + run: | + set -euo pipefail + unset E2E_BASE_URL || true + if [ -n "${E2E_BASE_URL:-}" ]; then + echo "E2E_BASE_URL must stay unset so specs use their own ports" + exit 1 + fi + pnpm exec playwright test tests/e2e/graph-ce-embed.spec.ts \ + --reporter=list --forbid-only --workers=1 \ + | tee "${RUNNER_TEMP}/pw-graph-ce-embed.txt" + python3 - "${RUNNER_TEMP}/pw-graph-ce-embed.txt" 3 graph-ce-embed <<'PY' + from pathlib import Path + import re, sys + plain = re.sub(r"\x1b\[[0-9;]*[A-Za-z]", "", Path(sys.argv[1]).read_text(errors="replace")) + want, label = int(sys.argv[2]), sys.argv[3] + if "graph-ce-embed.spec.ts" not in plain: + raise SystemExit(f"{label}: spec file did not run") + if re.search(r"\b[1-9]\d*\s+skipped\b", plain, re.I): + raise SystemExit(f"{label}: skips are not allowed") + if re.search(r"\b[1-9]\d*\s+flaky\b", plain, re.I): + raise SystemExit(f"{label}: flaky results are not allowed") + failed = re.search(r"\b(\d+)\s+failed\b", plain, re.I) + if failed and int(failed.group(1)) != 0: + raise SystemExit(f"{label}: failed tests") + passed = re.findall(r"(?m)^\s*(\d+)\s+passed\b", plain) + if not passed: + raise SystemExit(f"{label}: no 'N passed' summary") + got = int(passed[-1]) + if got != want: + raise SystemExit(f"{label}: expected {want} passed, got {got}") + print(f"{label}: {got}/{want} passed, no skips") + PY - name: Upload Playwright failure artifacts if: failure() uses: actions/upload-artifact@v4