feat(demo): add deep HTTP and browser validation layers
The existing smoke test only checked TCP port connectivity, which cannot
detect a service that accepts connections but returns errors or serves
the wrong content (exactly how the broken Kiwix slipped through).
Add two new validation layers:
1. validate-http.sh — curls every service's health/UI endpoint and
asserts both the HTTP status code AND meaningful body content (JSON
health fields like "database":"ok", XML markers, page strings). Also
verifies Kiwix has a ZIM actually loaded via its OPDS catalog feed.
2. run-browser-tests.sh — drives a real headless Chromium via the
official Playwright Docker image (no host Node install needed) to
confirm pages render correctly after JavaScript execution and SPA
hydration. Updated playwright-services.spec.ts with correct content
tokens and a waitForFunction step that reliably handles SPA timing.
Both are wired into demo-test.sh (new `http` and `browser` subcommands)
and the full test suite. AGENTS.md and demo/AGENTS.md updated with the
new commands and protocols.
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -5,7 +5,7 @@ const services = [
|
||||
name: 'Homepage',
|
||||
url: 'http://localhost:4000',
|
||||
contentCheck: 'tsys developer support stack',
|
||||
titleCheck: 'TSYS Developer Support Stack',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'Pi-hole',
|
||||
@@ -40,17 +40,20 @@ const services = [
|
||||
{
|
||||
name: 'Atomic Tracker',
|
||||
url: 'http://localhost:4012',
|
||||
contentCheck: 'journal',
|
||||
contentCheck: 'atomic',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'ArchiveBox',
|
||||
url: 'http://localhost:4013',
|
||||
contentCheck: 'archive',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'Tube Archivist',
|
||||
url: 'http://localhost:4014',
|
||||
contentCheck: 'tubearchivist',
|
||||
contentCheck: 'tube archivist',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'Wakapi',
|
||||
@@ -71,6 +74,7 @@ const services = [
|
||||
name: 'Reactive Resume',
|
||||
url: 'http://localhost:4016',
|
||||
contentCheck: 'reactive',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'Metrics',
|
||||
@@ -86,6 +90,7 @@ const services = [
|
||||
name: 'Resume Matcher',
|
||||
url: 'http://localhost:4023',
|
||||
contentCheck: 'resume',
|
||||
waitUntil: 'networkidle',
|
||||
},
|
||||
{
|
||||
name: 'Apple Health',
|
||||
@@ -97,12 +102,21 @@ const services = [
|
||||
for (const svc of services) {
|
||||
test(`${svc.name} (${svc.url}) loads successfully`, async ({ page }) => {
|
||||
const response = await page.goto(svc.url, {
|
||||
waitUntil: 'domcontentloaded',
|
||||
waitUntil: svc.waitUntil || 'domcontentloaded',
|
||||
timeout: 30000,
|
||||
});
|
||||
expect(response).not.toBeNull();
|
||||
expect(response!.status()).toBeLessThan(400);
|
||||
|
||||
// Wait for SPA-rendered content to appear (handles Next.js, React, etc.)
|
||||
await page
|
||||
.waitForFunction(
|
||||
(expected: string) => (document.body.textContent || '').toLowerCase().includes(expected),
|
||||
svc.contentCheck.toLowerCase(),
|
||||
{ timeout: 10000 }
|
||||
)
|
||||
.catch(() => {});
|
||||
|
||||
const body = await page.textContent('body').catch(() => '');
|
||||
const title = await page.title().catch(() => '');
|
||||
const combined = (body + ' ' + title).toLowerCase();
|
||||
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# TSYS Developer Support Stack - Browser (Playwright) Validation
|
||||
# Purpose: drive a real headless browser against every service to validate that
|
||||
# pages render and contain expected content. Deeper than HTTP probes:
|
||||
# it executes JavaScript, waits for DOM ready, and checks rendered text.
|
||||
#
|
||||
# Runs entirely inside the official Playwright Docker image (no host Node/npm
|
||||
# install required). All npm artefacts stay inside the container's own
|
||||
# filesystem, so no root-owned files are written to the host. Host services are
|
||||
# reached via the host network.
|
||||
#
|
||||
# Usage: ./tests/e2e/run-browser-tests.sh
|
||||
# Exit: 0 if all browser checks pass, 1 otherwise.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
PLAYWRIGHT_IMAGE="mcr.microsoft.com/playwright:v1.52.0-noble"
|
||||
NETWORK_MODE="${PLAYWRIGHT_NETWORK:-host}"
|
||||
|
||||
log() { echo "[browser-tests] $*"; }
|
||||
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
echo "[ERROR] Docker is not running" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Ensuring Playwright image is present..."
|
||||
docker image inspect "$PLAYWRIGHT_IMAGE" >/dev/null 2>&1 || docker pull "$PLAYWRIGHT_IMAGE"
|
||||
|
||||
log "Installing @playwright/test and running spec inside $PLAYWRIGHT_IMAGE..."
|
||||
# Mount the e2e sources read-only; copy them into a container-local work dir so
|
||||
# npm install never writes root-owned files to the host.
|
||||
docker run --rm \
|
||||
${NETWORK_MODE:+--network "$NETWORK_MODE"} \
|
||||
-v "$SCRIPT_DIR":/src:ro \
|
||||
--entrypoint sh \
|
||||
"$PLAYWRIGHT_IMAGE" \
|
||||
-c 'set -e; cp -r /src/. /work; cd /work; npm install --no-audit --no-fund; npx playwright test'
|
||||
|
||||
log "Browser tests complete."
|
||||
Reference in New Issue
Block a user