#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT_PATH="${BASH_SOURCE[0]}" TIMESTAMP="$(date '+%Y%m%d-%H%M%S')" DEFAULT_REBUILD_PACKAGES="core,tools,dicomImageLoader" DEFAULT_BUILD_NODE_OPTIONS="--max_old_space_size=32896" SUITE="legacy" FORCE_COMPAT="false" FORCE_CPU_RENDERING="false" FORCE_REBUILD="false" USE_ALL_PROJECTS="false" RUN_ALL_MODES="false" declare -a PLAYWRIGHT_ARGS=() for arg in "$@"; do case "$arg" in --next) SUITE="next" ;; --compat) FORCE_COMPAT="true" ;; --cpu) FORCE_CPU_RENDERING="true" ;; --force) FORCE_REBUILD="true" ;; --all-projects) USE_ALL_PROJECTS="true" ;; --all) RUN_ALL_MODES="true" ;; *) PLAYWRIGHT_ARGS+=("$arg") ;; esac done should_skip_rebuild_value() { local value="${1:-}" value="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')" [[ "$value" == "1" || "$value" == "true" || "$value" == "yes" ]] } build_static_examples() { local packages="${PLAYWRIGHT_REBUILD_PACKAGES:-$DEFAULT_REBUILD_PACKAGES}" local build_node_options="${PLAYWRIGHT_BUILD_NODE_OPTIONS:-$DEFAULT_BUILD_NODE_OPTIONS}" echo "Building static examples for Playwright" echo "Packages: $packages" echo NODE_OPTIONS="$build_node_options" \ node "$ROOT_DIR/utils/ExampleRunner/build-all-examples-cli.js" \ --build \ --fromRoot \ --packages "$packages" } # Fail fast if the Playwright port is already occupied by a *foreign* server. # Playwright's webServer.reuseExistingServer (true locally) will silently reuse # whatever is already on the port instead of serving .static-examples. If that is # something else (e.g. a Docker/Grafana container on 3333), every test times out # waiting for example links. Catch that here instead of after a full build + run. preflight_examples_server() { if should_skip_rebuild_value "${PLAYWRIGHT_SKIP_PREFLIGHT:-}"; then return 0 fi local port base_url body port="${PLAYWRIGHT_PORT:-3333}" base_url="${PLAYWRIGHT_BASE_URL:-${PLAYWRIGHT_EXAMPLE_BASE_URL:-http://localhost:${port}}}" # Nothing has started our examples server yet (Playwright starts it later), so a # response here can only be a pre-existing server occupying the port. body="$(curl -sS -L --max-time 5 "$base_url" 2>/dev/null || true)" if [[ -z "$body" ]]; then return 0 # port free / nothing responding -> Playwright will serve the examples fi # Recognize our own examples server. `serve .static-examples` returns an # auto-generated directory listing ("Files within .static-examples/"); a built # examples index uses id="demo-title" / "CornerstoneJS Examples". Match any of # these so reuse is allowed in both cases. A foreign server (e.g. Grafana) has none. if printf '%s' "$body" | grep -qE 'static-examples|demo-title|CornerstoneJS Examples'; then echo "Preflight: reusing existing Cornerstone examples server at $base_url" return 0 fi { echo echo "ERROR: $base_url is already serving something that is NOT the Cornerstone examples." echo "Playwright would reuse that server (reuseExistingServer) and every test would time" echo "out waiting for example links. Aborting before wasting a full build + run." echo echo "Listening on port $port:" lsof -nP -iTCP:"$port" -sTCP:LISTEN 2>/dev/null | sed 's/^/ /' || true echo echo "Fix one of:" echo " - Free the port (stop the offending process/container), then re-run; or" echo " - Use a free port: PLAYWRIGHT_PORT= PLAYWRIGHT_REUSE_EXISTING_SERVER=false $SCRIPT_PATH $*" echo " - Bypass this check: PLAYWRIGHT_SKIP_PREFLIGHT=true $SCRIPT_PATH $*" } >&2 exit 1 } preflight_examples_server if [[ "$RUN_ALL_MODES" == "true" ]]; then declare -a PASSTHROUGH_ARGS=() if [[ "$USE_ALL_PROJECTS" == "true" ]]; then PASSTHROUGH_ARGS+=("--all-projects") fi if [[ "$FORCE_CPU_RENDERING" == "true" ]]; then PASSTHROUGH_ARGS+=("--cpu") fi if [[ "$FORCE_REBUILD" == "true" ]]; then PASSTHROUGH_ARGS+=("--force") fi PASSTHROUGH_ARGS+=("${PLAYWRIGHT_ARGS[@]+"${PLAYWRIGHT_ARGS[@]}"}") declare -a MODES=("legacy" "compat" "next") declare -a MODE_EXIT_CODES=() declare -a MODE_DURATIONS=() declare -a MODE_LOG_FILES=() OVERALL_EXIT=0 compute_mode_slug() { local mode="$1" local cpu_suffix="" if [[ "$FORCE_CPU_RENDERING" == "true" && "$mode" != "next" ]]; then cpu_suffix="-cpu" fi case "$mode" in next) echo "generic-viewport-playwright" ;; *) echo "${mode}${cpu_suffix}-playwright" ;; esac } find_latest_log() { local slug="$1" local log_dir="$ROOT_DIR/reports/$slug" [[ -d "$log_dir" ]] || return 0 local latest latest=$(ls -1t "$log_dir" 2>/dev/null | head -n 1 || true) [[ -n "$latest" ]] || return 0 local log_file="$log_dir/$latest/$slug.log" [[ -f "$log_file" ]] || return 0 echo "$log_file" } format_duration() { local total="${1:-0}" local mins=$((total / 60)) local secs=$((total % 60)) if [[ $mins -gt 0 ]]; then printf '%dm%02ds' "$mins" "$secs" else printf '%ds' "$secs" fi } summarize_log() { local log_file="$1" if [[ -z "$log_file" || ! -f "$log_file" ]]; then echo " (log not available)" return 0 fi local tail_lines tail_lines=$(tail -n 400 "$log_file" | sed $'s/\033\\[[0-9;]*m//g') local counts counts=$(echo "$tail_lines" | grep -E '^[[:space:]]*[0-9]+[[:space:]]+(passed|failed|flaky|skipped|did not run|interrupted)( |$)' || true) if [[ -n "$counts" ]]; then echo "$counts" | sed -E 's/^[[:space:]]+/ /' fi local failed_tests failed_tests=$(echo "$tail_lines" | grep -E '^[[:space:]]*\[.+\][[:space:]]+›' | sort -u || true) if [[ -n "$failed_tests" ]]; then echo " Failing:" echo "$failed_tests" | head -n 15 | sed -E 's/^[[:space:]]+/ /' local total total=$(echo "$failed_tests" | wc -l | tr -d ' ') if [[ "$total" -gt 15 ]]; then echo " ... $((total - 15)) more" fi fi if [[ -z "$counts" && -z "$failed_tests" ]]; then echo " (no Playwright summary found; see $log_file)" fi return 0 } mode_index=0 for mode in "${MODES[@]}"; do echo echo "============================================================" echo " Running playwright suite: $mode" echo "============================================================" echo declare -a MODE_ARGS=() case "$mode" in compat) MODE_ARGS+=("--compat") ;; next) MODE_ARGS+=("--next") ;; esac START_TIME=$(date +%s) set +e if [[ $mode_index -eq 0 || "$FORCE_REBUILD" == "true" ]]; then bash "$SCRIPT_PATH" "${MODE_ARGS[@]+"${MODE_ARGS[@]}"}" "${PASSTHROUGH_ARGS[@]+"${PASSTHROUGH_ARGS[@]}"}" else PLAYWRIGHT_SKIP_REBUILD=true \ bash "$SCRIPT_PATH" "${MODE_ARGS[@]+"${MODE_ARGS[@]}"}" "${PASSTHROUGH_ARGS[@]+"${PASSTHROUGH_ARGS[@]}"}" fi MODE_EXIT_CODES[$mode_index]=$? set -e END_TIME=$(date +%s) MODE_DURATIONS[$mode_index]=$((END_TIME - START_TIME)) MODE_LOG_FILES[$mode_index]="$(find_latest_log "$(compute_mode_slug "$mode")")" if [[ "${MODE_EXIT_CODES[$mode_index]}" -ne 0 ]]; then OVERALL_EXIT="${MODE_EXIT_CODES[$mode_index]}" fi mode_index=$((mode_index + 1)) done echo echo "============================================================" echo " Summary" echo "============================================================" first_mode="true" summary_index=0 for mode in "${MODES[@]}"; do if [[ "$first_mode" == "true" ]]; then first_mode="false" else echo echo " ------------------------------------------------------------" fi code="${MODE_EXIT_CODES[$summary_index]}" duration_str="$(format_duration "${MODE_DURATIONS[$summary_index]:-0}")" if [[ "$code" -eq 0 ]]; then echo " $mode: PASS (${duration_str})" else echo " $mode: FAIL (exit $code, ${duration_str})" fi summarize_log "${MODE_LOG_FILES[$summary_index]:-}" log_file="${MODE_LOG_FILES[$summary_index]:-}" if [[ -n "$log_file" ]]; then run_dir="$(dirname "$log_file")" html_index="$run_dir/html-report/index.html" echo " Log: $log_file" if [[ -f "$html_index" ]]; then echo " Report: $html_index" else echo " Report: $run_dir/html-report (index.html not found)" fi fi summary_index=$((summary_index + 1)) done exit "$OVERALL_EXIT" fi # Default to chromium-only locally unless user explicitly opts into the full # matrix (--all-projects) or passes their own --project=... flag. if [[ "$USE_ALL_PROJECTS" == "false" ]]; then HAS_PROJECT_FLAG="false" for a in "${PLAYWRIGHT_ARGS[@]+"${PLAYWRIGHT_ARGS[@]}"}"; do case "$a" in --project|--project=*) HAS_PROJECT_FLAG="true" ;; esac done if [[ "$HAS_PROJECT_FLAG" == "false" ]]; then PLAYWRIGHT_ARGS+=("--project=chromium") fi fi # Next mode manages its own viewport/cpu settings via per-test query params if [[ "$SUITE" == "next" ]]; then FORCE_COMPAT="false" FORCE_CPU_RENDERING="false" fi VIEWPORT_MODE="legacy" if [[ "$SUITE" == "next" ]]; then VIEWPORT_MODE="next" elif [[ "$FORCE_COMPAT" == "true" ]]; then VIEWPORT_MODE="compat" fi CPU_MODE_SUFFIX="" if [[ "$FORCE_CPU_RENDERING" == "true" ]]; then CPU_MODE_SUFFIX="-cpu" fi if [[ "$SUITE" == "next" ]]; then RUN_SLUG="generic-viewport-playwright" else RUN_SLUG="${VIEWPORT_MODE}${CPU_MODE_SUFFIX}-playwright" fi # Auto-discover test files by directory convention declare -a SELECTED_TESTS=() if [[ "$SUITE" == "next" ]]; then while IFS= read -r test_file; do SELECTED_TESTS+=("$test_file") done < <(find "$ROOT_DIR/tests/genericViewport" -name '*.spec.ts' | sort) else while IFS= read -r test_file; do SELECTED_TESTS+=("$test_file") done < <(find "$ROOT_DIR/tests" -name '*.spec.ts' -not -path '*/genericViewport/*' | sort) fi PROJECTS_DESC="chromium" if [[ "$USE_ALL_PROJECTS" == "true" ]]; then PROJECTS_DESC="all" fi echo "Suite: $SUITE | Mode: $VIEWPORT_MODE | CPU: $FORCE_CPU_RENDERING | Projects: $PROJECTS_DESC | Tests: ${#SELECTED_TESTS[@]}" echo "Force rebuild: $FORCE_REBUILD" echo RUN_DIR="$ROOT_DIR/reports/$RUN_SLUG/$TIMESTAMP" LOG_FILE="$RUN_DIR/$RUN_SLUG.log" HTML_REPORT_DIR="$RUN_DIR/html-report" TEST_RESULTS_DIR="$RUN_DIR/test-results" cd "$ROOT_DIR" mkdir -p "$RUN_DIR" if [[ "$FORCE_REBUILD" == "true" ]]; then PLAYWRIGHT_SKIP_REBUILD_VALUE="false" else PLAYWRIGHT_SKIP_REBUILD_VALUE="${PLAYWRIGHT_SKIP_REBUILD:-}" fi if ! should_skip_rebuild_value "$PLAYWRIGHT_SKIP_REBUILD_VALUE"; then build_static_examples fi # Playwright starts webServer before globalSetup. Build here so the static # server has files to serve, then skip the duplicate globalSetup rebuild. PLAYWRIGHT_SKIP_REBUILD_VALUE="true" set +e PLAYWRIGHT_USE_BUNDLED_CHROMIUM=true \ PLAYWRIGHT_FORCE_COMPAT="$FORCE_COMPAT" \ PLAYWRIGHT_FORCE_CPU_RENDERING="$FORCE_CPU_RENDERING" \ PLAYWRIGHT_SKIP_REBUILD="$PLAYWRIGHT_SKIP_REBUILD_VALUE" \ PLAYWRIGHT_HTML_OUTPUT_DIR="$HTML_REPORT_DIR" \ PLAYWRIGHT_HTML_OPEN="never" \ npx playwright test \ "${SELECTED_TESTS[@]}" \ --output "$TEST_RESULTS_DIR" \ "${PLAYWRIGHT_ARGS[@]}" 2>&1 | tee "$LOG_FILE" PLAYWRIGHT_EXIT_CODE=${PIPESTATUS[0]} set -e echo echo "Compat mode forced: $FORCE_COMPAT" echo "CPU rendering forced: $FORCE_CPU_RENDERING" echo "Force rebuild: $FORCE_REBUILD" echo "Run directory: $RUN_DIR" echo "HTML report: $HTML_REPORT_DIR/index.html" exit "$PLAYWRIGHT_EXIT_CODE"