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
name: e2e-ui fixture (offline + browser)
# Nested Todos template under tests/e2e-ui — not the root workspace.
# Local parity:
# cd tests/e2e-ui && make check-client
# cd tests/e2e-ui && make test
# cd tests/e2e-ui && make up && make run # then make test-browser
on:
workflow_call:
env:
CARGO_TERM_COLOR: always
# Playwright / Auth.js over HTTP on localhost
CI: true
jobs:
offline:
name: e2e-ui offline (domain + suite + UI unit)
runs-on: ubuntu-latest
defaults:
run:
working-directory: tests/e2e-ui
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
toolchain: stable
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
workspaces: tests/e2e-ui -> target
shared-key: e2e-ui-offline
- name: Install wasm-pack
run: cargo install wasm-pack --locked || cargo install wasm-pack
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
js/package-lock.json
tests/e2e-ui/ui/package-lock.json
# Install/build shared JS prerequisites once, then overlap the independent
# Rust and UI pipelines while retaining byte-for-byte generated drift.
- name: Verify generated artifacts and run offline suite
run: make ci-offline
browser:
name: e2e-ui Playwright (live stack)
runs-on: ubuntu-latest
timeout-minutes: 45
defaults:
run:
working-directory: tests/e2e-ui
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
with:
toolchain: stable
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
with:
workspaces: tests/e2e-ui -> target
shared-key: e2e-ui-browser
- name: Install wasm-pack
run: cargo install wasm-pack --locked || cargo install wasm-pack
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: |
js/package-lock.json
tests/e2e-ui/package-lock.json
tests/e2e-ui/ui/package-lock.json
- name: Install host tools
run: sudo apt-get update && sudo apt-get install -y jq openssl curl
- name: Docker stack + OIDC bootstrap (make up)
run: |
chmod +x scripts/up.sh
make up
- name: Build e2e-ui API binary
run: cargo build -p e2e-runner --bin e2e-ui
- name: Install UI deps
run: make ui-install
- name: Start API + UI
run: |
set -euo pipefail
set -a
# shellcheck disable=SC1091
. ./e2e-ui.env
set +a
export AUTH_URL="${E2E_UI_ORIGIN:-http://localhost:5180}"
export AUTH_USE_SECURE_COOKIES=false
export AUTH_TRUST_HOST=true
./target/debug/e2e-ui > .make-runner.log 2>&1 &
echo $! > .make-runner.pid
ok=0
for i in $(seq 1 90); do
code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:8791/graphql" \
-H 'content-type: application/json' \
-d '{"query":"{ __typename }"}' 2>/dev/null || echo 000)
if [ "$code" = "200" ] || [ "$code" = "401" ]; then ok=1; break; fi
sleep 0.5
done
if [ "$ok" != "1" ]; then
echo "API failed to become ready"; tail -100 .make-runner.log; exit 1
fi
echo "API ready (HTTP $code)"
cd ui
npm run dev -- --host localhost --port 5180 > ../.make-ui.log 2>&1 &
echo $! > ../.make-ui.pid
cd ..
ok=0
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:5180/" 2>/dev/null || echo 000)
# 200 home, or 303 redirect when auth wraps routes — not 000/5xx
if [ "$code" = "200" ] || [ "$code" = "303" ] || [ "$code" = "302" ]; then ok=1; break; fi
sleep 0.5
done
if [ "$ok" != "1" ]; then
echo "UI failed to become ready (last HTTP $code)"; tail -80 .make-ui.log; exit 1
fi
echo "UI ready (HTTP $code)"
- name: Install Playwright + Chromium
run: |
npm install
npx playwright install chromium --with-deps
- name: Run browser e2e
run: npm run test:browser
env:
E2E_UI_ORIGIN: http://localhost:5180
E2E_API_ORIGIN: http://127.0.0.1:8791
CI: true
- name: Upload Playwright report
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-ui-playwright-report
path: |
tests/e2e-ui/playwright-report
tests/e2e-ui/test-results
if-no-files-found: ignore
retention-days: 7
- name: Dump logs on failure
if: failure()
run: |
echo '=== API log ==='
tail -150 .make-runner.log || true
echo '=== UI log ==='
tail -80 .make-ui.log || true
docker compose -f docker/docker-compose.yml ps -a || true
docker compose -f docker/docker-compose.yml logs --tail=100 || true
- name: Stop API/UI
if: always()
run: |
[ -f .make-ui.pid ] && kill "$(cat .make-ui.pid)" 2>/dev/null || true
[ -f .make-runner.pid ] && kill "$(cat .make-runner.pid)" 2>/dev/null || true
lsof -ti:8791 2>/dev/null | xargs -r kill -9 2>/dev/null || true
lsof -ti:5180 2>/dev/null | xargs -r kill -9 2>/dev/null || true
- name: Tear down Docker
if: always()
run: docker compose -f docker/docker-compose.yml down -v || true