Repository navigation
Expand file tree
/
Copy pathtests-e2e-playwright-docker.yml
More file actions
265 lines (234 loc) · 10.8 KB
/
Copy pathtests-e2e-playwright-docker.yml
File metadata and controls
265 lines (234 loc) · 10.8 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
name: E2E Playwright - Docker
on:
workflow_call:
inputs:
environment:
description: 'Environment to run tests'
type: string
default: 'staging'
debug:
description: 'Enable SSH debugging'
type: boolean
default: false
env:
E2E_DIR: './tests/e2e-playwright'
jobs:
e2e-docker:
name: E2E Docker (Chromium)
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
timeout-minutes: 60
env:
E2E_CLOUD_DATABASE_USERNAME: ${{ secrets.E2E_CLOUD_DATABASE_USERNAME }}
E2E_CLOUD_DATABASE_PASSWORD: ${{ secrets.E2E_CLOUD_DATABASE_PASSWORD }}
E2E_CLOUD_API_ACCESS_KEY: ${{ secrets.E2E_CLOUD_API_ACCESS_KEY }}
E2E_CLOUD_DATABASE_HOST: ${{ secrets.E2E_CLOUD_DATABASE_HOST }}
E2E_CLOUD_DATABASE_PORT: ${{ secrets.E2E_CLOUD_DATABASE_PORT }}
E2E_CLOUD_DATABASE_NAME: ${{ secrets.E2E_CLOUD_DATABASE_NAME }}
E2E_CLOUD_API_SECRET_KEY: ${{ secrets.E2E_CLOUD_API_SECRET_KEY }}
# Not a secret: dependabot[bot] runs cannot read Actions secrets, and this
# only encrypts credentials for this job's throwaway Redis containers.
E2E_RI_ENCRYPTION_KEY: 'e2e-tests-encryption-key'
RI_ENCRYPTION_KEY: 'e2e-tests-encryption-key'
TEST_BIG_DB_DUMP: ${{ secrets.TEST_BIG_DB_DUMP }}
outputs:
digest: ${{ steps.summarize.outputs.digest }}
steps:
- uses: actions/checkout@v7.0.1
- name: Enable SSH Debug
if: ${{ inputs.debug }}
uses: mxschmitt/action-tmate@v3
with:
detached: true
- name: Setup E2E environment
uses: ./.github/actions/setup-e2e-playwright
with:
browsers: 'chromium'
working-directory: ${{ env.E2E_DIR }}
- name: Download Docker Artifacts
uses: actions/download-artifact@v8
with:
name: docker-builds
path: ./release
- name: Load built docker image
run: |
docker image load -i ./release/docker/docker-linux-alpine.amd64.tar
# An empty NODE_ENV makes the app fall back to its development config and
# exit before it can write a log, which reads as a startup timeout further
# down. The compose file pins the value, so this only reports what the
# image itself carries.
- name: Report image NODE_ENV
run: |
node_env=$(docker image inspect redisinsight:amd64 \
--format '{{range .Config.Env}}{{println .}}{{end}}' \
| grep '^NODE_ENV=' || true)
echo "image carries ${node_env:-no NODE_ENV}"
if [ -z "$node_env" ] || [ "$node_env" = "NODE_ENV=" ]; then
echo "::warning::Image built without NODE_ENV; compose pins it to staging for this run"
fi
- name: Start Redis test environment
uses: ./.github/actions/redis-test-env-up
with:
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Start RedisInsight Docker container
run: |
E2E_RI_ENCRYPTION_KEY="$E2E_RI_ENCRYPTION_KEY" \
docker compose -p e2e-ri-docker \
-f tests/e2e/docker.web.docker-compose.yml \
up --detach --force-recreate
# /api/health and GET /api/databases both answer while POST to the same
# path still 404s, so gate on the write the suite depends on.
npx wait-on http://localhost:5540/api/health --timeout 60000
echo "Waiting for POST /api/databases to be served..."
for i in $(seq 1 60); do
code=$(curl -s -o /tmp/ri-probe.json -w '%{http_code}' \
-X POST http://localhost:5540/api/databases \
-H 'Content-Type: application/json' \
-d '{"name":"test-readiness-probe","host":"host.docker.internal","port":8100}' || true)
if [ "$code" = "200" ] || [ "$code" = "201" ]; then
# Prefixed "test-" so browser.setup cleans it up if this delete fails.
id=$(jq -r '.id // empty' /tmp/ri-probe.json)
[ -n "$id" ] && curl -s -o /dev/null -X DELETE "http://localhost:5540/api/databases/$id" || true
echo "✅ POST /api/databases served after ~$((i*2))s"
break
fi
if [ "$i" = "60" ]; then
echo "::error::POST /api/databases still returning $code after 120s"
cat /tmp/ri-probe.json || true
exit 1
fi
sleep 2
done
- name: Start connectivity probes
run: |
docker rm -f conn-probe >/dev/null 2>&1 || true
docker run -d --name conn-probe \
--add-host host.docker.internal:host-gateway \
--add-host master-hostname-7-1:host-gateway \
redis:7.0.0 \
sh -c 'probe() {
s=$(date +%s%3N)
if timeout 5 redis-cli -h "$1" -p "$2" ping >/dev/null 2>&1; then r=ok; else r=FAIL; fi
e=$(date +%s%3N)
echo "$(date -u +%T) container $3 $r $((e-s))ms"
}
while true; do
probe host.docker.internal 8100 standalone
probe master-hostname-7-1 8210 cluster-hostname
sleep 2
done'
# perl rather than `date +%s%3N`, which is GNU-only. It adds ~40ms per
# sample, so compare failures and stalls between the probes, not baselines.
nohup bash -c 'nowms() { perl -MTime::HiRes=time -e "printf \"%d\n\", time*1000"; }
while true; do
s=$(nowms)
if timeout 5 bash -c "exec 3<>/dev/tcp/127.0.0.1/8100" 2>/dev/null; then r=ok; else r=FAIL; fi
e=$(nowms)
echo "$(date -u +%T) host $r $((e-s))ms"
sleep 2
done' > /tmp/host-probe.log 2>&1 &
echo "probes started"
- name: Run Playwright tests (Chromium - Docker)
working-directory: ${{ env.E2E_DIR }}
run: npx playwright test --project=chromium-parallel --project=chromium-serial
env:
CI: true
# Override URLs to point to Docker container (serves both client and API on 5540)
RI_CLIENT_URL: 'http://localhost:5540'
RI_API_URL: 'http://localhost:5540'
# Use host.docker.internal for Redis hosts so the Docker container can reach them
# The Redis test environment exposes ports to the host, and the Docker container
# uses extra_hosts to map host.docker.internal to the host gateway
OSS_STANDALONE_HOST: 'host.docker.internal'
OSS_STANDALONE_V5_HOST: 'host.docker.internal'
OSS_STANDALONE_V7_HOST: 'host.docker.internal'
OSS_STANDALONE_V8_HOST: 'host.docker.internal'
OSS_STANDALONE_V8_8_0_HOST: 'host.docker.internal'
OSS_STANDALONE_EMPTY_HOST: 'host.docker.internal'
OSS_STANDALONE_BIG_HOST: 'host.docker.internal'
OSS_STANDALONE_TLS_HOST: 'host.docker.internal'
OSS_CLUSTER_HOST: 'host.docker.internal'
OSS_CLUSTER_HOSTNAME_HOST: 'host.docker.internal'
- name: Collect connectivity probes
if: always()
run: |
dest="${{ env.E2E_DIR }}/test-results"
mkdir -p "$dest"
docker logs conn-probe > "$dest/probe-container.log" 2>&1 || true
cp /tmp/host-probe.log "$dest/probe-host.log" 2>/dev/null || true
docker rm -f conn-probe >/dev/null 2>&1 || true
# Stop the host loop so it does not keep sampling through the remaining steps.
pkill -f 'Time::HiRes' >/dev/null 2>&1 || true
summarize() {
local label=$1 file=$2 filter=${3:-}
local samples
samples=$( [ -n "$filter" ] && grep " $filter " "$file" 2>/dev/null || cat "$file" 2>/dev/null )
[ -n "$samples" ] || { echo "$label: no samples"; return; }
local total fails slowest
total=$(printf '%s\n' "$samples" | wc -l | tr -d ' ')
fails=$(printf '%s\n' "$samples" | grep -c FAIL || true)
slowest=$(printf '%s\n' "$samples" | grep -oE '[0-9]+ms$' | tr -d 'ms' | sort -n | tail -1)
echo "$label: $total samples, $fails failed, slowest ${slowest}ms"
}
summarize "container -> host.docker.internal:8100 " "$dest/probe-container.log" standalone
summarize "container -> master-hostname-7-1:8210 " "$dest/probe-container.log" cluster-hostname
summarize "host -> 127.0.0.1:8100 " "$dest/probe-host.log"
echo "--- slowest 15 container samples ---"
grep -E 'FAIL|[0-9]{4,}ms' "$dest/probe-container.log" | head -15 || true
# `docker logs` cannot be used here: the compose service sets
# `logging: driver: none` and RI_STDOUT_LOGGER=false.
- name: Capture RedisInsight container logs
if: failure()
run: |
dest="${{ env.E2E_DIR }}/test-results/container-logs"
mkdir -p "$dest"
docker inspect e2e-ri-docker-app-1 \
--format 'status={{.State.Status}} restarts={{.RestartCount}} exit={{.State.ExitCode}} oom={{.State.OOMKilled}} startedAt={{.State.StartedAt}}' \
| tee "$dest/container-state.txt" || true
if docker cp e2e-ri-docker-app-1:/data/logs "$dest" 2>"$dest/copy-error.txt"; then
find "$dest" -type f -exec ls -l {} \;
echo "--- last 100 lines ---"
tail -n 100 "$dest"/logs/* 2>/dev/null || true
else
echo "::warning::could not copy /data/logs out of the app container"
cat "$dest/copy-error.txt" || true
fi
- name: Summarize Playwright results
id: summarize
if: always()
working-directory: ${{ env.E2E_DIR }}
run: |
node scripts/summarize-results.mjs \
--json test-results/agent-digest.json \
--markdown >> "$GITHUB_STEP_SUMMARY"
{
echo 'digest<<E2E_DIGEST_EOF'
cat test-results/agent-digest.json
echo 'E2E_DIGEST_EOF'
} >> "$GITHUB_OUTPUT"
- name: Upload agent digest
if: always()
uses: actions/upload-artifact@v7
with:
name: e2e-digest-docker
path: ${{ env.E2E_DIR }}/test-results/agent-digest.json
retention-days: 14
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-report-docker
path: |
${{ env.E2E_DIR }}/test-results
${{ env.E2E_DIR }}/playwright-report
retention-days: 14
- name: Stop RedisInsight Docker container
if: always()
run: |
docker compose -p e2e-ri-docker \
-f tests/e2e/docker.web.docker-compose.yml \
down --volumes --remove-orphans
- name: Stop Redis test environment
if: always()
uses: ./.github/actions/redis-test-env-down