#!/usr/bin/env bash # Azaion UI — performance test runner. # # Generated by .cursor/skills/test-spec phase 4. Implements the NFT-PERF-* scenarios # from _docs/02_document/tests/performance-tests.md. Thresholds are sourced from # _docs/00_problem/input_data/expected_results/results_report.md (rows 11, 40, 98 + AC-11 + AC-23). # # Most NFT-PERF-* tests are observable browser timings, not server load tests. The # script therefore runs Playwright-based measurements rather than k6/locust. # # Profile mapping (per environment.md → Test Execution): # - NFT-PERF-01 (bundle ≤ 2 MB gzip) : static — checks dist/ on host # - NFT-PERF-02..09 : fast or e2e — Playwright # - NFT-PERF-10 (FCP ≤ 3 000 ms on /flights) : e2e — Playwright against the suite stack # # Usage: # scripts/run-performance-tests.sh # run all NFT-PERF-* (skips quarantined) # scripts/run-performance-tests.sh --static-only # only NFT-PERF-01 (bundle size) # scripts/run-performance-tests.sh --e2e-only # only NFT-PERF-* that require the stack # scripts/run-performance-tests.sh --bundle-max-bytes 2097152 # override bundle threshold set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" SUITE_ROOT="$(cd "$PROJECT_ROOT/.." && pwd)" RESULTS_DIR="$PROJECT_ROOT/test-output" RUN_STATIC=true RUN_E2E=true # Thresholds (defaults from results_report.md; overridable via flags). BUNDLE_MAX_BYTES=$((2 * 1024 * 1024)) # AC-11 / NFT-PERF-01 (row 40): 2 MB gzipped initial JS FCP_MAX_MS=3000 # NFT-PERF-10 (row 98): warm-cache FCP on /flights, edge profile AUTH_REFRESH_MAX_MS=200 # NFT-PERF-02 (row 11): refresh round-trip target for arg in "$@"; do case "$arg" in --static-only) RUN_STATIC=true; RUN_E2E=false ;; --e2e-only) RUN_STATIC=false; RUN_E2E=true ;; --bundle-max-bytes=*) BUNDLE_MAX_BYTES="${arg#*=}" ;; --bundle-max-bytes) shift; BUNDLE_MAX_BYTES="${1:-$BUNDLE_MAX_BYTES}" ;; --fcp-max-ms=*) FCP_MAX_MS="${arg#*=}" ;; --fcp-max-ms) shift; FCP_MAX_MS="${1:-$FCP_MAX_MS}" ;; --auth-refresh-max-ms=*) AUTH_REFRESH_MAX_MS="${arg#*=}" ;; --auth-refresh-max-ms) shift; AUTH_REFRESH_MAX_MS="${1:-$AUTH_REFRESH_MAX_MS}" ;; -h|--help) sed -n '2,22p' "$0" | sed 's/^# \{0,1\}//' exit 0 ;; *) echo "Unknown argument: $arg" >&2 exit 2 ;; esac done E2E_COMPOSE_STARTED_HERE=false cleanup() { if [ "$E2E_COMPOSE_STARTED_HERE" = "true" ]; then docker compose -f "$PROJECT_ROOT/e2e/docker-compose.suite-e2e.yml" down -v --remove-orphans || true fi } trap cleanup EXIT mkdir -p "$RESULTS_DIR" cd "$PROJECT_ROOT" echo "[run-performance-tests] thresholds:" echo " bundle (NFT-PERF-01) : ≤ $BUNDLE_MAX_BYTES bytes gzipped" echo " FCP (NFT-PERF-10) : ≤ $FCP_MAX_MS ms" echo " auth refresh (NFT-PERF-02): ≤ $AUTH_REFRESH_MAX_MS ms" echo " static : $RUN_STATIC" echo " e2e : $RUN_E2E" # ---------------------------------------------------------------------------- # Install deps (matches run-tests.sh). # ---------------------------------------------------------------------------- if ! command -v bun >/dev/null 2>&1; then echo "[run-performance-tests] FATAL: bun is required (project pins bun@1.3.11)." >&2 exit 1 fi echo "[run-performance-tests] installing dependencies..." if [ -f "$PROJECT_ROOT/bun.lock" ] || [ -f "$PROJECT_ROOT/bun.lockb" ]; then bun install --frozen-lockfile else bun install fi OVERALL_EXIT=0 SUMMARY_FILE="$RESULTS_DIR/performance-summary.txt" : > "$SUMMARY_FILE" record() { # $1 = scenario id, $2 = result (PASS|FAIL|SKIP|QUARANTINE), $3 = measured, $4 = threshold printf '%-14s %-12s measured=%-14s threshold=%s\n' "$1" "$2" "$3" "$4" | tee -a "$SUMMARY_FILE" } # ---------------------------------------------------------------------------- # Static perf scenarios. # ---------------------------------------------------------------------------- if [ "$RUN_STATIC" = "true" ]; then echo "[run-performance-tests] === NFT-PERF-01 (bundle size) ===" if [ ! -d "$PROJECT_ROOT/dist" ]; then echo "[NFT-PERF-01] dist/ not present — running 'bun run build'..." bun run build fi # Sum gzipped sizes of dist/assets/*.js (the initial JS bundle is index-*.js per Vite). BUNDLE_BYTES=$( find "$PROJECT_ROOT/dist/assets" -maxdepth 1 -name '*.js' -print0 2>/dev/null \ | xargs -0 -I{} sh -c 'gzip -c "{}" | wc -c' \ | awk '{ s += $1 } END { print (s ? s : 0) }' ) echo "[NFT-PERF-01] gzipped dist/assets/*.js = $BUNDLE_BYTES bytes" if [ "$BUNDLE_BYTES" -le "$BUNDLE_MAX_BYTES" ]; then record "NFT-PERF-01" "PASS" "${BUNDLE_BYTES}B" "≤ ${BUNDLE_MAX_BYTES}B" else record "NFT-PERF-01" "FAIL" "${BUNDLE_BYTES}B" "≤ ${BUNDLE_MAX_BYTES}B" OVERALL_EXIT=1 fi fi # ---------------------------------------------------------------------------- # E2E perf scenarios (Playwright-based). # The Playwright project lands at autodev Step 5 (Decompose Tests). Until it # ships, NFT-PERF-02..10 are SKIPPED (not FAILED) so this script can run on # the spec-only baseline without producing false negatives. # ---------------------------------------------------------------------------- if [ "$RUN_E2E" = "true" ]; then COMPOSE_FILE="$PROJECT_ROOT/e2e/docker-compose.suite-e2e.yml" PERF_PROJECT="$PROJECT_ROOT/e2e/playwright.perf.config.ts" if [ ! -f "$PERF_PROJECT" ]; then echo "[run-performance-tests] Playwright perf project ($PERF_PROJECT) not yet wired." echo "[run-performance-tests] Awaiting NFT-PERF-* task implementations (AZ-457..AZ-482); until then the e2e perf scenarios are SKIPPED." for id in NFT-PERF-02 NFT-PERF-03 NFT-PERF-04 NFT-PERF-05 NFT-PERF-06 NFT-PERF-07 NFT-PERF-08 NFT-PERF-09 NFT-PERF-10; do record "$id" "SKIP" "n/a" "deferred to per-AC test tasks" done elif [ ! -f "$COMPOSE_FILE" ]; then echo "[run-performance-tests] FATAL: $COMPOSE_FILE not found." >&2 OVERALL_EXIT=1 elif ! command -v docker >/dev/null 2>&1; then echo "[run-performance-tests] FATAL: docker is required for the e2e perf profile." >&2 OVERALL_EXIT=1 else echo "[run-performance-tests] starting compose stack..." docker compose -f "$COMPOSE_FILE" up -d --build E2E_COMPOSE_STARTED_HERE=true echo "[run-performance-tests] running Playwright perf project..." if FCP_MAX_MS="$FCP_MAX_MS" AUTH_REFRESH_MAX_MS="$AUTH_REFRESH_MAX_MS" \ bunx playwright test --config "$PERF_PROJECT" 2>&1 | tee "$RESULTS_DIR/perf-playwright.txt"; then echo "[run-performance-tests] Playwright perf PASSED" else echo "[run-performance-tests] Playwright perf FAILED — see $RESULTS_DIR/perf-playwright.txt" OVERALL_EXIT=1 fi fi fi # Quarantined scenarios (documentary only — never gate today). record "NFT-PERF-03" "QUARANTINE" "—" "Step 8 hardening (SSE refresh rotation)" record "NFT-PERF-08" "QUARANTINE" "—" "Step 4 fix (panel-width persistence)" record "NFT-PERF-09" "QUARANTINE" "—" "Step 4 fix (settings save error surfacing)" echo "" echo "[run-performance-tests] summary written to $SUMMARY_FILE" echo "[run-performance-tests] exit code: $OVERALL_EXIT" exit "$OVERALL_EXIT"