mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
## Summary - Isolate dev, E2E, and integration test environments so multiple git worktrees can run all three simultaneously without port conflicts - Each worktree gets a deterministic slot (0-99) with unique port ranges: dev (30100-31199), E2E (20320-21399), CI integration (14320-40098) - Dev portal dashboard (http://localhost:9900) auto-discovers all running stacks, streams logs, and provides a History tab for past run logs ## Port Isolation | Environment | Port Range | Project Name | |---|---|---| | Dev stack | 30100-31199 | `hdx-dev-<slot>` | | E2E tests | 20320-21399 | `e2e-<slot>` | | CI integration | 14320-40098 | `int-<slot>` | All three can run simultaneously from the same worktree with zero port conflicts. ## Dev Portal Features **Live tab:** - Auto-discovers dev, E2E, and integration Docker containers + local services (API, App) - Groups all environments for the same worktree into a single card - SSE log streaming with ANSI color rendering, capped at 5000 lines - Auto-starts in background from `make dev`, `make dev-e2e`, `make dev-int` **History tab:** - Logs archived to `~/.config/hyperdx/dev-slots/<slot>/history/` on exit (instead of deleted) - Each archived run includes `meta.json` with worktree/branch metadata - Grouped by worktree with collapsible cards, search by worktree/branch - View any past log file in the same log panel, delete individual runs or clear all - Custom dark-themed confirm modal (no native browser dialogs) ## What Changed - **`scripts/dev-env.sh`** — Slot-based port assignments, portal auto-start, log archival on exit - **`scripts/test-e2e.sh`** — E2E port range (20320-21399), log capture via `tee`, portal auto-start, log archival - **`scripts/ensure-dev-portal.sh`** — Shared singleton portal launcher (works sourced or executed) - **`scripts/dev-portal/server.js`** — Discovery for dev/E2E/CI containers, history API (list/read/delete), local service port probing - **`scripts/dev-portal/index.html`** — Live/History tabs, worktree-grouped cards, search, collapse/expand, custom confirm modal, ANSI color log rendering - **`docker-compose.dev.yml`** — Parameterized ports/volumes/project name with `hdx.dev.*` labels - **`packages/app/tests/e2e/docker-compose.yml`** — Updated to new E2E port defaults - **`Makefile`** — `dev-int`/`dev-e2e` targets with log capture + portal auto-start; `dev-portal-stop`; `dev-clean` stops everything + wipes slot data - **`.env` files** — Ports use `${VAR:-default}` syntax across dev, E2E, and CI environments - **`agent_docs/development.md`** — Full documentation for isolation, port tables, E2E/CI port ranges ## How to Use ```bash # Start dev stack (auto-starts portal) make dev # Run E2E tests (auto-starts portal, separate ports) make dev-e2e FILE=navigation # Run integration tests (auto-starts portal, separate ports) make dev-int FILE=alerts # All three can run simultaneously from the same worktree # Portal at http://localhost:9900 shows everything # Stop portal make dev-portal-stop # Clean up everything (all stacks + portal + history) make dev-clean ``` ## Dev Portal <img width="1692" height="944" alt="image" src="https://github.com/user-attachments/assets/6ed388a3-43bc-4552-aa8d-688077b79fb7" /> <img width="1689" height="935" alt="image" src="https://github.com/user-attachments/assets/8677a138-0a40-4746-93ed-3b355c8bd45e" /> ## Test Plan - [x] Run `make dev` — verify services start with slot-assigned ports - [x] Run `make dev` in a second worktree — verify different ports, no conflicts - [x] Run `make dev-e2e` and `make dev-int` simultaneously — no port conflicts - [x] Open http://localhost:9900 — verify all stacks grouped by worktree - [x] Click a service to view logs — verify ANSI colors render correctly - [x] Stop a stack — verify logs archived to History tab with correct worktree - [x] History tab — search, collapse/expand, view archived logs, delete - [x] `make dev-clean` — stops everything, wipes slot data and history
257 lines
9.2 KiB
Bash
Executable file
257 lines
9.2 KiB
Bash
Executable file
#!/bin/bash
|
|
# Run E2E tests in full-stack or local mode
|
|
# Full-stack mode (default): MongoDB + API + local ClickHouse
|
|
# Local mode: Frontend + local ClickHouse (no MongoDB/API)
|
|
#
|
|
# Usage:
|
|
# ./scripts/test-e2e.sh # Run all tests in fullstack mode
|
|
# ./scripts/test-e2e.sh --local # Run in local mode (frontend + ClickHouse only)
|
|
# ./scripts/test-e2e.sh --keep-running # Keep containers running after tests (fast iteration!)
|
|
# ./scripts/test-e2e.sh --ui # Run with Playwright UI
|
|
# ./scripts/test-e2e.sh --last-failed # Run only failed tests
|
|
# ./scripts/test-e2e.sh --headed # Run with visible browser
|
|
# ./scripts/test-e2e.sh --debug # Run in debug mode
|
|
# ./scripts/test-e2e.sh --grep "dashboard" # Run tests matching pattern
|
|
#
|
|
# Development workflow (recommended):
|
|
# ./scripts/test-e2e.sh --keep-running --ui # Start containers and open UI
|
|
# # Make changes, tests auto-rerun in UI mode
|
|
# # When done:
|
|
# docker compose -p e2e-<slot> -f packages/app/tests/e2e/docker-compose.yml down -v
|
|
#
|
|
# All Playwright flags are passed through automatically
|
|
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
DOCKER_COMPOSE_FILE="$REPO_ROOT/packages/app/tests/e2e/docker-compose.yml"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Multi-agent / worktree isolation
|
|
# ---------------------------------------------------------------------------
|
|
# Compute a deterministic port offset (0-99) from the working directory name
|
|
# so that multiple worktrees can run E2E tests in parallel without port
|
|
# conflicts. Override HDX_E2E_SLOT manually if you need a specific slot.
|
|
#
|
|
# Port allocation — E2E gets its own range (20320-21399) so it can run
|
|
# simultaneously with CI integration tests (14320-40098) and the dev
|
|
# stack (30100-31199). All ports are below the OS ephemeral range
|
|
# (32768 Linux, 49152 macOS).
|
|
#
|
|
# Port mapping (base + slot):
|
|
# OpAMP : 20320 + slot (20320-20419)
|
|
# ClickHouse HTTP : 20500 + slot (20500-20599)
|
|
# ClickHouse Native: 20600 + slot (20600-20699)
|
|
# API server : 21000 + slot (21000-21099)
|
|
# MongoDB : 21100 + slot (21100-21199)
|
|
# App (local) : 21200 + slot (21200-21299)
|
|
# App (fullstack) : 21300 + slot (21300-21399)
|
|
# ---------------------------------------------------------------------------
|
|
export HDX_E2E_SLOT="${HDX_E2E_SLOT:-$(printf '%s' "$(basename "$REPO_ROOT")" | cksum | awk '{print $1 % 100}')}"
|
|
|
|
export HDX_E2E_OPAMP_PORT="${HDX_E2E_OPAMP_PORT:-$((20320 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_CH_PORT="${HDX_E2E_CH_PORT:-$((20500 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_CH_NATIVE_PORT="${HDX_E2E_CH_NATIVE_PORT:-$((20600 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_API_PORT="${HDX_E2E_API_PORT:-$((21000 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_MONGO_PORT="${HDX_E2E_MONGO_PORT:-$((21100 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_APP_LOCAL_PORT="${HDX_E2E_APP_LOCAL_PORT:-$((21200 + HDX_E2E_SLOT))}"
|
|
export HDX_E2E_APP_PORT="${HDX_E2E_APP_PORT:-$((21300 + HDX_E2E_SLOT))}"
|
|
|
|
export E2E_PROJECT="e2e-${HDX_E2E_SLOT}"
|
|
|
|
# --- Log capture for dev-portal visibility ---
|
|
HDX_E2E_SLOTS_DIR="${HOME}/.config/hyperdx/dev-slots"
|
|
HDX_E2E_LOGS_DIR="${HDX_E2E_SLOTS_DIR}/${HDX_E2E_SLOT}/logs-e2e"
|
|
mkdir -p "$HDX_E2E_LOGS_DIR"
|
|
exec > >(tee "$HDX_E2E_LOGS_DIR/e2e.log") 2>&1
|
|
|
|
# --- Start dev portal in background if not already running ---
|
|
# shellcheck source=./ensure-dev-portal.sh
|
|
source "${REPO_ROOT}/scripts/ensure-dev-portal.sh"
|
|
|
|
echo "Using E2E slot ${HDX_E2E_SLOT} (project=${E2E_PROJECT} ch=${HDX_E2E_CH_PORT} ch-native=${HDX_E2E_CH_NATIVE_PORT} mongo=${HDX_E2E_MONGO_PORT} api=${HDX_E2E_API_PORT} app=${HDX_E2E_APP_PORT} app-local=${HDX_E2E_APP_LOCAL_PORT} opamp=${HDX_E2E_OPAMP_PORT})"
|
|
|
|
# Configuration constants
|
|
readonly MAX_MONGODB_WAIT_ATTEMPTS=15
|
|
readonly MONGODB_WAIT_DELAY_SECONDS=1
|
|
readonly MAX_CLICKHOUSE_WAIT_ATTEMPTS=30
|
|
readonly CLICKHOUSE_WAIT_DELAY_SECONDS=1
|
|
|
|
# Parse arguments
|
|
LOCAL_MODE=false
|
|
SKIP_CLEANUP=false
|
|
PLAYWRIGHT_FLAGS=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--local)
|
|
LOCAL_MODE=true
|
|
shift
|
|
;;
|
|
--keep-running|--no-cleanup)
|
|
SKIP_CLEANUP=true
|
|
shift
|
|
;;
|
|
*)
|
|
# Pass any other flags through to Playwright
|
|
PLAYWRIGHT_FLAGS+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
|
|
cleanup_services() {
|
|
echo "Stopping E2E services and removing volumes..."
|
|
docker compose -p "$E2E_PROJECT" -f "$DOCKER_COMPOSE_FILE" down -v
|
|
# Archive logs to history instead of deleting
|
|
if [ -d "$HDX_E2E_LOGS_DIR" ] && [ -n "$(ls -A "$HDX_E2E_LOGS_DIR" 2>/dev/null)" ]; then
|
|
_ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
_hist="${HDX_E2E_SLOTS_DIR}/${HDX_E2E_SLOT}/history/e2e-${_ts}"
|
|
mkdir -p "$_hist"
|
|
mv "$HDX_E2E_LOGS_DIR"/* "$_hist/" 2>/dev/null || true
|
|
_wt=$(basename "$(git -C "$REPO_ROOT" rev-parse --show-toplevel 2>/dev/null || echo "$REPO_ROOT")")
|
|
_br=$(git -C "$REPO_ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
cat > "$_hist/meta.json" <<METAEOF
|
|
{"worktree":"${_wt}","branch":"${_br}","worktreePath":"${REPO_ROOT}"}
|
|
METAEOF
|
|
fi
|
|
rm -rf "$HDX_E2E_LOGS_DIR" 2>/dev/null || true
|
|
}
|
|
|
|
check_mongodb_health() {
|
|
# Health check script that tests ping, insert, and delete operations
|
|
# Note: MongoDB runs on port 27017 inside the container (default)
|
|
docker compose -p "$E2E_PROJECT" -f "$DOCKER_COMPOSE_FILE" exec -T db mongosh --quiet --eval "
|
|
try {
|
|
db.adminCommand('ping');
|
|
db.getSiblingDB('test').test.insertOne({_id: 'healthcheck', ts: new Date()});
|
|
db.getSiblingDB('test').test.deleteOne({_id: 'healthcheck'});
|
|
print('ready');
|
|
} catch(e) {
|
|
print('not ready: ' + e);
|
|
quit(1);
|
|
}
|
|
" 2>&1
|
|
}
|
|
|
|
check_clickhouse_health() {
|
|
# Health check from HOST perspective (not inside container)
|
|
# This ensures the port is actually accessible to Playwright
|
|
curl -sf "http://localhost:${HDX_E2E_CH_PORT}/ping" >/dev/null 2>&1 || wget --spider -q "http://localhost:${HDX_E2E_CH_PORT}/ping" 2>&1
|
|
}
|
|
|
|
wait_for_clickhouse() {
|
|
echo "Waiting for ClickHouse to be ready..."
|
|
local attempt=1
|
|
|
|
while [ $attempt -le $MAX_CLICKHOUSE_WAIT_ATTEMPTS ]; do
|
|
if check_clickhouse_health >/dev/null 2>&1; then
|
|
echo "ClickHouse is ready"
|
|
return 0
|
|
fi
|
|
|
|
if [ $attempt -eq $MAX_CLICKHOUSE_WAIT_ATTEMPTS ]; then
|
|
local total_wait=$((MAX_CLICKHOUSE_WAIT_ATTEMPTS * CLICKHOUSE_WAIT_DELAY_SECONDS))
|
|
echo "ClickHouse failed to become ready after $total_wait seconds"
|
|
echo "Try running: docker compose -p $E2E_PROJECT -f $DOCKER_COMPOSE_FILE logs ch-server"
|
|
return 1
|
|
fi
|
|
|
|
echo "Waiting for ClickHouse... ($attempt/$MAX_CLICKHOUSE_WAIT_ATTEMPTS)"
|
|
attempt=$((attempt + 1))
|
|
sleep $CLICKHOUSE_WAIT_DELAY_SECONDS
|
|
done
|
|
}
|
|
|
|
wait_for_mongodb() {
|
|
echo "Waiting for MongoDB to be ready..."
|
|
local attempt=1
|
|
|
|
# Verify mongosh is available in the container
|
|
if ! docker compose -p "$E2E_PROJECT" -f "$DOCKER_COMPOSE_FILE" exec -T db which mongosh >/dev/null 2>&1; then
|
|
echo "ERROR: mongosh not found in MongoDB container"
|
|
echo "Container may not be running or using incompatible image"
|
|
echo "Try running: docker compose -p $E2E_PROJECT -f $DOCKER_COMPOSE_FILE logs db"
|
|
return 1
|
|
fi
|
|
|
|
while [ $attempt -le $MAX_MONGODB_WAIT_ATTEMPTS ]; do
|
|
local result
|
|
result=$(check_mongodb_health)
|
|
|
|
if echo "$result" | grep -q "ready"; then
|
|
echo "MongoDB is ready and accepting writes"
|
|
return 0
|
|
fi
|
|
|
|
if [ $attempt -eq $MAX_MONGODB_WAIT_ATTEMPTS ]; then
|
|
local total_wait=$((MAX_MONGODB_WAIT_ATTEMPTS * MONGODB_WAIT_DELAY_SECONDS))
|
|
echo "MongoDB failed to become ready after $total_wait seconds"
|
|
echo "Last error: $result"
|
|
return 1
|
|
fi
|
|
|
|
echo "Waiting for MongoDB... ($attempt/$MAX_MONGODB_WAIT_ATTEMPTS)"
|
|
attempt=$((attempt + 1))
|
|
sleep $MONGODB_WAIT_DELAY_SECONDS
|
|
done
|
|
}
|
|
|
|
# Main execution
|
|
setup_cleanup_trap() {
|
|
if [ "$SKIP_CLEANUP" = false ]; then
|
|
trap cleanup_services EXIT ERR
|
|
else
|
|
echo "⚠️ Skipping cleanup - containers will remain running"
|
|
echo " Use 'docker compose -p $E2E_PROJECT -f $DOCKER_COMPOSE_FILE down -v' to stop them manually"
|
|
fi
|
|
}
|
|
|
|
setup_clickhouse() {
|
|
echo "Starting ClickHouse..."
|
|
docker compose -p "$E2E_PROJECT" -f "$DOCKER_COMPOSE_FILE" up -d ch-server
|
|
|
|
if ! wait_for_clickhouse; then
|
|
exit 1
|
|
fi
|
|
|
|
# Note: ClickHouse seeding is handled by Playwright global setup
|
|
# - Fullstack mode: global-setup-fullstack.ts
|
|
# - Local mode: global-setup-local.ts
|
|
}
|
|
|
|
run_tests() {
|
|
cd "$REPO_ROOT/packages/app"
|
|
|
|
if [ "$LOCAL_MODE" = true ]; then
|
|
echo "Running tests in local mode (frontend + ClickHouse)..."
|
|
yarn test:e2e --local "${PLAYWRIGHT_FLAGS[@]}"
|
|
else
|
|
echo "Running tests in full-stack mode (MongoDB + API + ClickHouse)..."
|
|
yarn test:e2e "${PLAYWRIGHT_FLAGS[@]}"
|
|
fi
|
|
}
|
|
|
|
# Set up cleanup trap
|
|
setup_cleanup_trap
|
|
|
|
# Clean up E2E Next.js build directory to avoid stale lock/cache issues
|
|
rm -rf "$REPO_ROOT/packages/app/.next-e2e" 2>/dev/null || true
|
|
|
|
# Always start and seed ClickHouse (shared by both modes)
|
|
setup_clickhouse
|
|
|
|
# Conditionally start MongoDB for full-stack mode
|
|
if [ "$LOCAL_MODE" = false ]; then
|
|
echo "Starting MongoDB for full-stack mode..."
|
|
docker compose -p "$E2E_PROJECT" -f "$DOCKER_COMPOSE_FILE" up -d db
|
|
|
|
if ! wait_for_mongodb; then
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Run tests
|
|
run_tests
|