Show the picture in the real web console, in a real browser
Every earlier camera proof stood in for the browser with a werift peer. The question that decides whether the feature works is whether a person looking at the fleet page sees a picture, and the web console had never been opened. BROWSER=1 on scripts/e2e-camera.sh now starts the real Nuxt app and drives it in real Chrome (puppeteer-core): sign in, open the fleet page, wait for the camera player, and check the result. Measured against the H2C: the app's own Supabase client signs in, the header reports Live (the telemetry socket authenticated with the real token), the player reports connected, Chrome decodes 1680x1080 frames, playback time advances, the frame has picture content (mean luma 88, stdev 62.8), and the browser's own ICE stats show the selected candidate is a TURN relay. No uncaught page errors. The login page only offers a magic link by email, which a script cannot follow, so the driver signs in with the app's $supabase.client and a password just set on a throwaway user. That means AUTH=supabase: the backend verifies against the real project's JWKS rather than the local dev issuer, with e2e-supabase-user.ts creating or reusing continuum-e2e@example.invalid (a reserved .invalid address) and resetting its password each run. The JSON it prints carries a password and a live token, so the shell captures it and never echoes it. The frame is judged by pixel statistics and no screenshot is kept: it is the inside of someone's workshop, and "is there a picture" does not need one stored. The relay is read from getStats() through a wrapped RTCPeerConnection rather than inferred from configuration. A browser run with fewer than its nine results fails, so a driver that dies quietly cannot read as a pass. puppeteer-core is a devDependency for these scripts alone. Its 22 resolved packages are all Apache-2.0, BSD-3-Clause, ISC or MIT.
This commit is contained in:
+108
-8
@@ -42,6 +42,18 @@
|
||||
# GO2RTC_BIN=~/.local/bin/go2rtc
|
||||
# PROXY_DIR=../continuum-proxy
|
||||
# KEEP_DATA=1 leave the seeded farm behind for inspection
|
||||
#
|
||||
# BROWSER=1 also drive the real web console in a real Chrome
|
||||
# (../continuum-app) and check the picture on
|
||||
# screen. Implies AUTH=supabase, because the app's
|
||||
# client needs a real Supabase session; needs
|
||||
# SUPABASE_URL, SUPABASE_SECRET_KEY and
|
||||
# SUPABASE_PUBLISHABLE_KEY (source ../dev-secrets.sh).
|
||||
# It creates/reuses one Supabase user,
|
||||
# continuum-e2e@example.invalid, and resets its
|
||||
# password each run. Port 3042 must be free.
|
||||
# AUTH=supabase use that real user's token for the API phases too
|
||||
# (default is the local dev-auth issuer)
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
@@ -64,10 +76,18 @@ R2_ENDPOINT="${R2_ENDPOINT:-http://localhost:9000}"
|
||||
export DATABASE_URL VALKEY_URL EDGE_JWT_SECRET PORT
|
||||
export R2_ACCESS_KEY_ID R2_SECRET_ACCESS_KEY R2_ENDPOINT
|
||||
|
||||
BROWSER="${BROWSER:-0}"
|
||||
AUTH="${AUTH:-dev}"
|
||||
[ "$BROWSER" = "1" ] && AUTH=supabase
|
||||
APP_DIR="${APP_DIR:-$REPO_ROOT/../continuum-app}"
|
||||
APP_PORT="${APP_PORT:-3042}"
|
||||
CHROME_BIN="${CHROME_BIN:-/usr/bin/google-chrome}"
|
||||
|
||||
WORK="$(mktemp -d)"
|
||||
BACKEND_PID=""
|
||||
PROXY_PID=""
|
||||
AUTH_PID=""
|
||||
APP_PID=""
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
ok() { echo " ok: $*"; }
|
||||
@@ -84,6 +104,9 @@ cleanup() {
|
||||
for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do
|
||||
[ -n "$pid" ] && kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
# The dev server forks workers, so it is signalled as a group: it was started
|
||||
# with setsid, which makes its PID the group id. `kill 0` would hit us.
|
||||
[ -n "$APP_PID" ] && kill -- "-$APP_PID" 2>/dev/null || true
|
||||
for _ in $(seq 1 20); do
|
||||
[ -n "$PROXY_PID" ] && kill -0 "$PROXY_PID" 2>/dev/null || break
|
||||
sleep 0.3
|
||||
@@ -91,6 +114,7 @@ cleanup() {
|
||||
for pid in "$PROXY_PID" "$BACKEND_PID" "$AUTH_PID"; do
|
||||
[ -n "$pid" ] && kill -9 "$pid" 2>/dev/null || true
|
||||
done
|
||||
[ -n "$APP_PID" ] && kill -9 -- "-$APP_PID" 2>/dev/null || true
|
||||
if [ -z "${KEEP_DATA:-}" ] && [ -n "${ORG_ID:-}" ]; then
|
||||
psql_q "DELETE FROM organisations WHERE id = '$ORG_ID'" >/dev/null 2>&1 || true
|
||||
fi
|
||||
@@ -137,6 +161,30 @@ else
|
||||
ok "could not read $CAMERA_ID's address from printers.toml; skipping the 322 pre-check"
|
||||
fi
|
||||
|
||||
if [ "$AUTH" = "supabase" ]; then
|
||||
for name in SUPABASE_URL SUPABASE_SECRET_KEY SUPABASE_PUBLISHABLE_KEY; do
|
||||
[ -n "${!name:-}" ] || fail "$name is not set - source ../dev-secrets.sh (AUTH=supabase / BROWSER=1)"
|
||||
done
|
||||
fi
|
||||
if [ "$BROWSER" = "1" ]; then
|
||||
[ -x "$CHROME_BIN" ] || fail "no Chrome at $CHROME_BIN (set CHROME_BIN)"
|
||||
[ -d "$APP_DIR/apps/web/node_modules" ] || fail "$APP_DIR has no installed web app - run pnpm install there"
|
||||
ss -ltn 2>/dev/null | grep -q ":$APP_PORT " && fail "port $APP_PORT is already in use"
|
||||
fi
|
||||
|
||||
# The real Supabase user, when one is needed. Captured, never echoed: the JSON
|
||||
# holds a password and a live access token.
|
||||
if [ "$AUTH" = "supabase" ]; then
|
||||
SB_USER="$(bun scripts/e2e-supabase-user.ts)" || fail "could not provision the Supabase test user"
|
||||
sb_field() { printf '%s' "$SB_USER" | python3 -c 'import json,sys; print(json.load(sys.stdin)[sys.argv[1]])' "$1"; }
|
||||
USER_ID="$(sb_field id)"
|
||||
USER_TOKEN="$(sb_field accessToken)"
|
||||
SB_EMAIL="$(sb_field email)"
|
||||
SB_PASSWORD="$(sb_field password)"
|
||||
unset SB_USER
|
||||
ok "signed in as the real Supabase test user $USER_ID"
|
||||
fi
|
||||
|
||||
# --- 1. seed ---------------------------------------------------------------
|
||||
|
||||
echo "==> seeding an organisation and a farm claiming '$CAMERA_ID'"
|
||||
@@ -154,14 +202,18 @@ ok "org $ORG_ID, farm $FARM_ID, viewer $USER_ID"
|
||||
|
||||
# --- 2. backend ------------------------------------------------------------
|
||||
|
||||
echo "==> starting the dev-auth issuer on :$AUTH_PORT and the backend on :$PORT"
|
||||
bun scripts/dev-auth.ts --port "$AUTH_PORT" > "$WORK/auth.log" 2>&1 &
|
||||
AUTH_PID=$!
|
||||
AUTH_URL="http://localhost:$AUTH_PORT"
|
||||
for _ in $(seq 1 40); do curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 && break; sleep 0.25; done
|
||||
curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 || fail "dev-auth did not come up"
|
||||
export SUPABASE_URL="$AUTH_URL"
|
||||
USER_TOKEN="$(curl -sf "$AUTH_URL/token?sub=$USER_ID" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')"
|
||||
if [ "$AUTH" = "dev" ]; then
|
||||
echo "==> starting the dev-auth issuer on :$AUTH_PORT and the backend on :$PORT"
|
||||
bun scripts/dev-auth.ts --port "$AUTH_PORT" > "$WORK/auth.log" 2>&1 &
|
||||
AUTH_PID=$!
|
||||
AUTH_URL="http://localhost:$AUTH_PORT"
|
||||
for _ in $(seq 1 40); do curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 && break; sleep 0.25; done
|
||||
curl -sf "$AUTH_URL/auth/v1/.well-known/jwks.json" >/dev/null 2>&1 || fail "dev-auth did not come up"
|
||||
export SUPABASE_URL="$AUTH_URL"
|
||||
USER_TOKEN="$(curl -sf "$AUTH_URL/token?sub=$USER_ID" | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])')"
|
||||
else
|
||||
echo "==> starting the backend on :$PORT, verifying tokens against the real Supabase JWKS"
|
||||
fi
|
||||
bun run src/index.ts > "$WORK/backend.log" 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
for _ in $(seq 1 60); do curl -sf "$BASE/health" >/dev/null 2>&1 && break; sleep 0.5; done
|
||||
@@ -251,9 +303,57 @@ else
|
||||
echo " The TURN-relayed path, which the browser is pinned to, is NOT verified by this run."
|
||||
fi
|
||||
|
||||
# --- 5. the real web console in a real browser -------------------------------
|
||||
|
||||
if [ "$BROWSER" = "1" ]; then
|
||||
echo "==> starting the web console on :$APP_PORT"
|
||||
# setsid so the whole dev server (it forks workers) shares one process group
|
||||
# that cleanup can signal; the app is told where the backend is and given the
|
||||
# real Supabase project it signs in against.
|
||||
setsid env -C "$APP_DIR/apps/web" \
|
||||
NUXT_PUBLIC_API_BASE="$BASE" \
|
||||
NUXT_PUBLIC_WS_BASE="ws://localhost:$PORT" \
|
||||
SUPABASE_URL="$SUPABASE_URL" \
|
||||
SUPABASE_KEY="$SUPABASE_PUBLISHABLE_KEY" \
|
||||
pnpm exec nuxt dev --host 127.0.0.1 --port "$APP_PORT" > "$WORK/app.log" 2>&1 &
|
||||
APP_PID=$!
|
||||
for _ in $(seq 1 120); do
|
||||
curl -sf -o /dev/null "http://127.0.0.1:$APP_PORT/login" 2>/dev/null && break
|
||||
sleep 1
|
||||
done
|
||||
curl -sf -o /dev/null "http://127.0.0.1:$APP_PORT/login" \
|
||||
|| { tail -20 "$WORK/app.log" >&2; fail "the web console did not start on :$APP_PORT"; }
|
||||
ok "the web console is up"
|
||||
|
||||
echo "==> the real console in real Chrome: sign in, open the fleet, watch the camera"
|
||||
BROWSER_OUT="$(env APP_URL="http://127.0.0.1:$APP_PORT" SUPABASE_E2E_EMAIL="$SB_EMAIL" \
|
||||
SUPABASE_E2E_PASSWORD="$SB_PASSWORD" CHROME_BIN="$CHROME_BIN" \
|
||||
timeout 240 bun scripts/e2e-camera-browser.ts 2>&1 || true)"
|
||||
seen=0; bad=0
|
||||
while IFS=$'\t' read -r verdict label; do
|
||||
case "$verdict" in
|
||||
ok) ok "$label"; seen=$((seen + 1)) ;;
|
||||
FAILED) echo " FAILED: $label" >&2; seen=$((seen + 1)); bad=$((bad + 1)) ;;
|
||||
esac
|
||||
done < <(printf '%s\n' "$BROWSER_OUT" | grep -E "^(ok|FAILED)$(printf '\t')")
|
||||
# A complete run is nine results. Fewer means the driver died part way, and a
|
||||
# silent driver must not read as a pass.
|
||||
if [ "$seen" -lt 9 ] && [ "$bad" -eq 0 ]; then
|
||||
bad=1; echo " FAILED: the browser run produced only $seen of 9 results" >&2
|
||||
fi
|
||||
if [ "$bad" -ne 0 ]; then
|
||||
printf '%s\n' "$BROWSER_OUT" | sed 's/^/ | /' >&2
|
||||
echo "--- web console log (tail) ---" >&2; tail -15 "$WORK/app.log" >&2
|
||||
fail "the real browser did not show the camera"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "PASS - a real WebRTC peer received video from the printer's camera"
|
||||
echo " through the backend relay, the uplink, the proxy and go2rtc."
|
||||
if [ -n "${CLOUDFLARE_TURN_KEY_ID:-}" ] && [ -n "${CLOUDFLARE_TURN_API_TOKEN:-}" ]; then
|
||||
echo " Both directly and pinned to relay-only through Cloudflare TURN."
|
||||
fi
|
||||
if [ "$BROWSER" = "1" ]; then
|
||||
echo " And the real web console, in real Chrome, showed the picture."
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user