-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathexecute-e2e-local.sh
More file actions
executable file
·260 lines (228 loc) · 9.76 KB
/
Copy pathexecute-e2e-local.sh
File metadata and controls
executable file
·260 lines (228 loc) · 9.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#!/usr/bin/env bash
set -euo pipefail
# ============================================================================
# Local E2E Test Runner
#
# Starts all required services (Docker, server, client) and runs Playwright
# E2E tests against them. The server and client are always restarted to ensure
# the latest code changes are picked up.
#
# Services are left running after tests complete so you can re-run tests
# quickly with "cd client && pnpm e2e". Use --stop to shut everything down.
#
# Usage:
# ./execute-e2e-local.sh Run E2E tests (starts/restarts services)
# ./execute-e2e-local.sh --stop Stop all services started by this script
# ./execute-e2e-local.sh --ui Run tests in interactive Playwright UI mode
# ./execute-e2e-local.sh --headed Run tests in headed browser mode
#
# Logs:
# Server log: .e2e-server.log
# Client log: .e2e-client.log
# ============================================================================
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
CLIENT_DIR="$ROOT_DIR/client"
SERVER_DIR="$ROOT_DIR/server"
PID_DIR="$ROOT_DIR/.e2e-pids" # stores PIDs of background processes
# Terminal colors for log output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m'
log() { echo -e "${CYAN}[e2e]${NC} $*"; }
ok() { echo -e "${GREEN}[e2e]${NC} $*"; }
warn() { echo -e "${YELLOW}[e2e]${NC} $*"; }
err() { echo -e "${RED}[e2e]${NC} $*"; }
# Ensure pnpm is available (via corepack, shipped with Node >=16.10).
if ! command -v pnpm >/dev/null 2>&1; then
log "pnpm not on PATH — enabling Corepack..."
corepack enable >/dev/null 2>&1 || {
err "Failed to enable Corepack. Install pnpm manually: npm install -g pnpm"
exit 1
}
fi
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
# Check whether a given TCP port has a process listening on it.
# Uses ss on Linux (where it is standard) and falls back to lsof on macOS.
is_port_open() {
if command -v ss >/dev/null 2>&1; then
ss -tlnp "sport = :$1" 2>/dev/null | grep -q LISTEN
else
lsof -iTCP:"$1" -sTCP:LISTEN -P -n -t >/dev/null 2>&1
fi
}
# Poll a URL until it responds successfully, with a configurable timeout.
wait_for_url() {
local url="$1" label="$2" max_wait="${3:-120}"
log "Waiting for $label ..."
for i in $(seq 1 "$max_wait"); do
if curl -sf "$url" >/dev/null 2>&1; then
ok "$label is ready (${i}s)"
return 0
fi
sleep 1
done
err "$label did not start within ${max_wait}s"
return 1
}
# Persist a background process PID so we can stop it later (including across
# script invocations with --stop).
save_pid() {
mkdir -p "$PID_DIR"
echo "$2" > "$PID_DIR/$1.pid"
}
read_pid() {
local f="$PID_DIR/$1.pid"
[[ -f "$f" ]] && cat "$f" || echo ""
}
# Gracefully stop a previously saved background process and its child tree.
# pkill -P kills direct children (e.g. the JVM spawned by gradlew, or the
# node process spawned by npx) before terminating the wrapper process itself.
kill_pid() {
local pid
pid=$(read_pid "$1")
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
pkill -P "$pid" 2>/dev/null || true
kill "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
ok "Stopped $1 (PID $pid)"
fi
rm -f "$PID_DIR/$1.pid"
}
# Wait up to 30 seconds for a port to be released after stopping a process.
wait_for_port_release() {
local port="$1"
for i in $(seq 1 30); do
is_port_open "$port" || return 0
sleep 1
done
err "Port $port still in use after 30s — aborting"
exit 1
}
# ---------------------------------------------------------------------------
# --stop: Shut down all services and exit
# ---------------------------------------------------------------------------
stop_all() {
log "Stopping E2E services..."
kill_pid "client"
kill_pid "server"
(cd "$ROOT_DIR" && docker compose stop 2>/dev/null) || true
rm -rf "$PID_DIR"
ok "All services stopped."
exit 0
}
# ---------------------------------------------------------------------------
# Parse command-line arguments
# ---------------------------------------------------------------------------
# Separate our flags (--stop, --ui, --headed) from any extra args that should
# be forwarded to Playwright (e.g. test file filters, --grep, etc.).
PLAYWRIGHT_ARGS=()
for arg in "$@"; do
case "$arg" in
--stop) stop_all ;;
--ui) PLAYWRIGHT_ARGS+=(--ui) ;;
--headed) PLAYWRIGHT_ARGS+=(--headed) ;;
*) PLAYWRIGHT_ARGS+=("$arg") ;;
esac
done
# ---------------------------------------------------------------------------
# 1. Docker services (PostgreSQL + Keycloak)
# ---------------------------------------------------------------------------
# Reset Docker services each run to ensure a fresh database. This removes
# anonymous volumes (PostgreSQL data), so Liquibase migrations and seed data
# recreate a clean state every time.
log "Resetting Docker services (fresh database)..."
(cd "$ROOT_DIR" && docker compose down -v 2>/dev/null) || true
log "Starting Docker services..."
(cd "$ROOT_DIR" && docker compose up -d) 2>&1 | while IFS= read -r line; do echo " $line"; done
# Keycloak's /health endpoint is unavailable in dev mode, so we check the
# realm endpoint instead to confirm it is ready to accept auth requests.
wait_for_url "http://localhost:8181/realms/thesis-management" "Keycloak" 90
# ---------------------------------------------------------------------------
# 2. Server (Spring Boot with dev profile)
# ---------------------------------------------------------------------------
# Always restart the server to ensure the latest Java/Gradle changes are
# compiled and running. gradlew bootRun recompiles before starting.
if is_port_open 8180; then
warn "Server already running on port 8180 — restarting to pick up latest changes..."
kill_pid "server"
wait_for_port_release 8180
fi
log "Starting server (dev profile)..."
(cd "$SERVER_DIR" && exec ./gradlew bootRun --args='--spring.profiles.active=dev' \
> "$ROOT_DIR/.e2e-server.log" 2>&1) &
save_pid "server" $!
# ---------------------------------------------------------------------------
# 3. Client static bundle (production webpack output)
# ---------------------------------------------------------------------------
# E2E tests run against the production build, not webpack-dev-server. The
# dev server's error overlay iframe occasionally intercepts clicks during
# tests; serving the prod bundle eliminates that class of flakes entirely.
if is_port_open 3100; then
warn "Client already running on port 3100 — restarting to pick up latest changes..."
kill_pid "client"
wait_for_port_release 3100
fi
log "Building client (production)..."
(cd "$CLIENT_DIR" && pnpm build > "$ROOT_DIR/.e2e-client-build.log" 2>&1) || {
err "Client build failed. See $ROOT_DIR/.e2e-client-build.log"
exit 1
}
log "Generating runtime-env.js..."
(cd "$CLIENT_DIR/build" && node ../public/generate-runtime-env.js)
log "Starting static client server (serve)..."
# Serve with serve.e2e.json (cleanUrls:false), matching the CI e2e job. Without
# this config, serve's default cleanUrls strips the ".html" from requests and
# the SPA fallback returns index.html for the Keycloak silent-check-sso iframe
# (/silent-check-sso.html). The iframe then loads the full app instead of the
# tiny postMessage page, keycloak-js init never completes, and the header
# "Login" button never redirects to Keycloak — which made every auth-dependent
# e2e test (all of auth.setup) fail locally while passing in CI.
(cd "$CLIENT_DIR" && exec pnpm dlx serve@14 -s build -l 3100 -c ../serve.e2e.json --no-clipboard --no-port-switching \
> "$ROOT_DIR/.e2e-client.log" 2>&1) &
save_pid "client" $!
# ---------------------------------------------------------------------------
# 4. Wait for server and client to be ready
# ---------------------------------------------------------------------------
# The server exposes an actuator health endpoint; the client just needs to
# serve its index page. We wait for both before running tests.
wait_for_url "http://localhost:8180/api/actuator/health" "Server" 180
wait_for_url "http://localhost:3100" "Client" 60
# ---------------------------------------------------------------------------
# 5. Playwright browsers
# ---------------------------------------------------------------------------
# Playwright stores browser binaries outside node_modules (in the user's
# OS cache dir), so `pnpm install` does not provide them. `playwright
# install` is idempotent and fast when binaries are already present, so
# run it unconditionally — keeps fresh-checkout setups one command shorter.
log "Ensuring Playwright chromium browser is installed..."
(cd "$CLIENT_DIR" && pnpm exec playwright install chromium) || {
err "Playwright browser install failed."
exit 1
}
# ---------------------------------------------------------------------------
# 6. Run Playwright E2E tests
# ---------------------------------------------------------------------------
echo ""
log "Running Playwright E2E tests..."
echo ""
cd "$CLIENT_DIR"
# Disable `set -e` so a non-zero playwright exit doesn't bypass the result
# summary, "view report" hint, and the "services still running" message
# below. We capture the exit code and re-raise it at the end of the script.
EXIT_CODE=0
pnpm exec playwright test "${PLAYWRIGHT_ARGS[@]+"${PLAYWRIGHT_ARGS[@]}"}" || EXIT_CODE=$?
echo ""
if [[ $EXIT_CODE -eq 0 ]]; then
ok "All E2E tests passed!"
else
err "Some E2E tests failed (exit code $EXIT_CODE)"
warn "View report: cd client && pnpm exec playwright show-report"
fi
# Services are intentionally left running so you can quickly re-run tests
# with "cd client && pnpm e2e" without waiting for services to start again.
warn "Services are still running. Use './execute-e2e-local.sh --stop' to stop them."
exit $EXIT_CODE