-
Notifications
You must be signed in to change notification settings - Fork 1.3k
333 lines (289 loc) · 14.1 KB
/
run-e2e-tests.yml
File metadata and controls
333 lines (289 loc) · 14.1 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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
on:
workflow_call:
inputs:
db-type:
required: true
type: string
redis_enabled:
required: true
type: boolean
python-version:
required: true
type: string
is-fork:
required: true
type: boolean
backend-image-name:
required: true
type: string
frontend-image-name:
required: true
type: string
jobs:
# Run tests with all services in one job
run-tests:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
REDIS: ${{ inputs.redis_enabled }}
REDIS_HOST: keep-redis
REDIS_PORT: 6379
BACKEND_IMAGE: ${{ inputs.backend-image-name }}
FRONTEND_IMAGE: ${{ inputs.frontend-image-name }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
if: ${{ inputs.is-fork != true }}
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python ${{ inputs.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ inputs.python-version }}
- name: Install Poetry
uses: snok/install-poetry@v1
with:
virtualenvs-create: true
virtualenvs-in-project: true
- name: Restore dependencies cache
id: cache-deps
uses: actions/cache@v4.2.0
with:
path: .venv
key: pydeps-${{ hashFiles('**/poetry.lock') }}
# Always install dependencies to ensure venv is valid
# When cached, this completes quickly; when broken, this fixes it
- name: Install dependencies using poetry
run: poetry install --no-interaction --no-root --with dev
- name: Get Playwright version from poetry.lock
id: playwright-version
run: |
PLAYWRIGHT_VERSION=$(grep "playwright" poetry.lock -A 5 | grep "version" | head -n 1 | cut -d'"' -f2)
echo "version=$PLAYWRIGHT_VERSION" >> $GITHUB_OUTPUT
- name: Cache Playwright browsers
id: playwright-cache
uses: actions/cache@v4.2.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ steps.playwright-version.outputs.version }}
- name: Install Playwright and dependencies
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: |
poetry run playwright install --with-deps
# For forks: Build images locally again since they don't persist between jobs
- name: Set up Docker Buildx
if: ${{ inputs.is-fork == true }}
id: buildx
uses: docker/setup-buildx-action@v2
- name: Rebuild frontend image locally for fork PRs
if: ${{ inputs.is-fork == true }}
uses: docker/build-push-action@v4
with:
context: keep-ui
file: ./docker/Dockerfile.ui
push: false
load: true
tags: |
keep-frontend:local
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1
- name: Rebuild backend image locally for fork PRs
if: ${{ inputs.is-fork == true }}
uses: docker/build-push-action@v4
with:
context: .
file: ./docker/Dockerfile.api
push: false
load: true
tags: |
keep-backend:local
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILDKIT_INLINE_CACHE=1
# Create a modified compose file with our built images
- name: Create modified docker-compose file with built images
run: |
cp tests/e2e_tests/docker-compose-e2e-${{ inputs.db-type }}.yml tests/e2e_tests/docker-compose-modified.yml
# Replace image placeholders with actual image references
sed -i "s|%KEEPFRONTEND_IMAGE%|${{ env.FRONTEND_IMAGE }}|g" tests/e2e_tests/docker-compose-modified.yml
sed -i "s|%KEEPBACKEND_IMAGE%|${{ env.BACKEND_IMAGE }}|g" tests/e2e_tests/docker-compose-modified.yml
# cat the modified file for debugging
cat tests/e2e_tests/docker-compose-modified.yml
# Start ALL services in one go
- name: Start ALL services
run: |
echo "Starting ALL services for ${{ inputs.db-type }}..."
# Pull the required images first (only needed for non-fork builds)
if [[ "${{ inputs.is-fork }}" != "true" ]]; then
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml pull
fi
# Start all services together
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml up -d
# Show running containers
docker ps
# Show the images sha of the running containers
docker images
# Wait for all services to be ready
- name: Wait for services to be ready
run: |
# Function for exponential backoff
function wait_for_service() {
local service_name=$1
local check_command=$2
local max_attempts=$3
local compose_service=$4 # Docker Compose service name
local attempt=0
local wait_time=1
echo "Waiting for $service_name to be ready..."
until eval "$check_command"; do
if [ "$attempt" -ge "$max_attempts" ]; then
echo "Max attempts reached, exiting..."
# Show final logs before exiting
if [ ! -z "$compose_service" ]; then
echo "===== FINAL LOGS FOR ON ERROR EXIT $compose_service ====="
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs $compose_service
echo "=========================================="
fi
exit 1
fi
echo "Waiting for $service_name... (Attempt: $((attempt+1)), waiting ${wait_time}s)"
# Print logs using docker compose
if [ ! -z "$compose_service" ]; then
echo "===== RECENT LOGS FOR $compose_service ====="
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs $compose_service --tail 100
echo "=========================================="
fi
attempt=$((attempt+1))
sleep $wait_time
# Exponential backoff with max of 8 seconds
wait_time=$((wait_time * 2 > 8 ? 8 : wait_time * 2))
done
echo "$service_name is ready!"
# last time, print logs using docker compose
if [ ! -z "$compose_service" ]; then
echo "===== FINAL LOGS FOR $compose_service ====="
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs $compose_service --tail 100
echo "=========================================="
fi
}
# Database checks
if [ "${{ inputs.db-type }}" == "mysql" ]; then
wait_for_service "MySQL Database" "docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml exec -T keep-database mysqladmin ping -h \"localhost\" --silent" 10 "keep-database"
wait_for_service "MySQL Database (DB AUTH)" "docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml exec -T keep-database-db-auth mysqladmin ping -h \"localhost\" --silent" 10 "keep-database-db-auth"
elif [ "${{ inputs.db-type }}" == "postgres" ]; then
wait_for_service "Postgres Database" "docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml exec -T keep-database pg_isready -h localhost -U keepuser" 10 "keep-database"
wait_for_service "Postgres Database (DB AUTH)" "docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml exec -T keep-database-db-auth pg_isready -h localhost -U keepuser" 10 "keep-database-db-auth"
fi
# Wait for services with health checks
wait_for_service "Keep backend" "curl --output /dev/null --silent --fail http://localhost:8080/healthcheck" 15 "keep-backend"
wait_for_service "Keep backend (DB AUTH)" "curl --output /dev/null --silent --fail http://localhost:8081/healthcheck" 15 "keep-backend-db-auth"
wait_for_service "Keep frontend" "curl --output /dev/null --silent --fail http://localhost:3000/" 15 "keep-frontend"
wait_for_service "Keep frontend (DB AUTH)" "curl --output /dev/null --silent --fail http://localhost:3001/" 15 "keep-frontend-db-auth"
# Give Prometheus and Grafana extra time to initialize
# (using direct curl commands instead of container exec)
echo "Waiting for Prometheus to be ready..."
MAX_ATTEMPTS=15
for i in $(seq 1 $MAX_ATTEMPTS); do
if curl --output /dev/null --silent --fail http://localhost:9090/-/healthy; then
echo "Prometheus is ready!"
break
elif [ $i -eq $MAX_ATTEMPTS ]; then
echo "Prometheus did not become ready in time, but continuing..."
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs prometheus-server-for-test-target --tail 50
else
echo "Waiting for Prometheus... Attempt $i/$MAX_ATTEMPTS"
sleep 5
fi
done
echo "Waiting for Grafana to be ready..."
MAX_ATTEMPTS=15
for i in $(seq 1 $MAX_ATTEMPTS); do
if curl --output /dev/null --silent --fail http://localhost:3002/api/health; then
echo "Grafana is ready!"
break
elif [ $i -eq $MAX_ATTEMPTS ]; then
echo "Grafana did not become ready in time, but continuing..."
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs grafana --tail 50
else
echo "Waiting for Grafana... Attempt $i/$MAX_ATTEMPTS"
sleep 5
fi
done
# Give everything a bit more time to stabilize
echo "Giving services additional time to stabilize..."
sleep 10
# Debug the environment before running tests
- name: Debug environment
run: |
echo "Checking all container status..."
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml ps
echo "Network information:"
docker network ls
docker network inspect keep_default || true
echo "Testing Prometheus API..."
curl -v http://localhost:9090/api/v1/status/config || echo "Prometheus API not responding, but continuing..."
echo "Testing Grafana API..."
curl -v http://localhost:3002/api/health || echo "Grafana API not responding, but continuing..."
echo "Test Keep Frontend..."
curl -v http://localhost:3000/ || echo "Keep Frontend not responding, but continuing..."
echo "Test Keep Frontend with DB Auth..."
curl -v http://localhost:3001/ || echo "Keep Frontend with DB Auth not responding, but continuing..."
echo "Listing available ports:"
netstat -tuln | grep -E '3000|3001|3002|8080|8081|9090'
# Run e2e tests
- name: Run e2e tests and report coverage
run: |
echo "Running tests..."
poetry run coverage run --branch -m pytest -v tests/e2e_tests/ -n 4 --dist=loadfile
echo "Tests completed!"
- name: Convert coverage results to JSON (for CodeCov support)
run: poetry run coverage json --omit="keep/providers/*"
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: false
files: coverage.json
verbose: true
# Collect logs
- name: Dump logs
if: always()
run: |
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs keep-backend > backend_logs-${{ inputs.db-type }}.txt
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs keep-frontend > frontend_logs-${{ inputs.db-type }}.txt
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs keep-backend-db-auth > backend_logs-${{ inputs.db-type }}-db-auth.txt
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs keep-frontend-db-auth > frontend_logs-${{ inputs.db-type }}-db-auth.txt
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs prometheus-server-for-test-target > prometheus_logs-${{ inputs.db-type }}.txt
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml logs grafana > grafana_logs-${{ inputs.db-type }}.txt
continue-on-error: true
# Upload artifacts
- name: Upload test artifacts on failure
if: always()
uses: actions/upload-artifact@v4.4.3
with:
name: test-artifacts-db-${{ inputs.db-type }}-redis-${{ inputs.redis_enabled }}
path: |
playwright_dump_*.html
playwright_dump_*.png
playwright_dump_*.txt
playwright_dump_*.json
backend_logs-${{ inputs.db-type }}.txt
frontend_logs-${{ inputs.db-type }}.txt
backend_logs-${{ inputs.db-type }}-db-auth.txt
frontend_logs-${{ inputs.db-type }}-db-auth.txt
prometheus_logs-${{ inputs.db-type }}.txt
grafana_logs-${{ inputs.db-type }}.txt
continue-on-error: true
# Tear down environment
- name: Tear down environment
if: always()
run: |
docker compose -p keep --project-directory . -f tests/e2e_tests/docker-compose-modified.yml down