Files
ui/scripts/run-performance-tests.sh
T
Oleksandr Bezdieniezhnykh 38eb87fb08 [AZ-456] Test infrastructure: Vitest + MSW + Playwright + scripts
Scaffolds the Blackbox test project per AZ-456 / environment.md across
the three profiles:

- fast  : Vitest 3.x + jsdom + MSW 2.x + RTL/jest-dom; tests/setup.ts
          boots the MSW Node server with onUnhandledRequest:'error',
          afterEach resets handlers, clears bearer + navigate-to-login
          spy. Default handlers ship for every suite service plus OWM
          and tile stand-ins. Fixtures mirror seed_* in test-data.md.
- e2e   : Playwright ^1.49 with chromium + firefox projects against the
          suite docker-compose stack; owm-stub + tile-stub Bun servers,
          playwright-runner image, seeds.sql for the test-db.
- static: scripts/run-tests.sh extended — tsc --noEmit (test config),
          vite build, ripgrep checks (with grep -r fallback), CSV
          report at test-output/static-report.csv per AC-7 columns.

Smoke tests cover AC-3, AC-4 (fast, 5 tests, PASS) and AC-1, AC-2,
AC-5, AC-8 (e2e, gated by Risk 4 docker availability). Static profile
(13 checks) PASS — STC-SEC1 (no literal OWM key) lifted from
QUARANTINE per AZ-447 with a narrowed pattern.

Files:
  +24 tests/**, +10 e2e/**, +vitest.config.ts, +tsconfig.test.json
  ~package.json (test scripts + devDeps for vitest, @testing-library/*,
   msw, @playwright/test, jsdom, @types/node, @vitest/coverage-v8)
  ~scripts/run-tests.sh, scripts/run-performance-tests.sh — switched
   RESULTS_DIR to test-output/, compose path to project-local
  ~.gitignore — added /test-output/

Verification:
  bun run test:fast        → 11 / 11 PASS
  ./scripts/run-tests.sh   → static 13/13 + fast 11/11 PASS, exit 0

Tracker: AZ-456 → In Testing.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 02:57:04 +03:00

174 lines
7.2 KiB
Bash
Executable File

#!/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"