1212 CONTAINER_REGISTRY_NAMESPACE : ${{ vars.CONTAINER_REGISTRY_NAMESPACE }}
1313 CONTAINER_NAME : ${{ vars.CONTAINER_NAME }}
1414 UDATA_WORKING_DIR : udata
15+ MONGODB_TOOLS : mongodb-database-tools-ubuntu2404-x86_64-100.13.0
16+ # Serves the metrics API, matomo.js and the Tabular API to the E2E tests, see tests/fake-services.ts
17+ FAKE_SERVICES_URL : http://localhost:3100
18+ # Set to record the Tabular API fixtures from the real API instead of replaying them: each
19+ # E2E shard then uploads what it recorded
20+ FAKE_SERVICES_RECORD : ' '
1521
1622permissions :
1723 id-token : write # Required for OIDC for npm publication
@@ -137,19 +143,95 @@ jobs:
137143 echo "Using udata $ref"
138144 echo "ref=$ref" >> "$GITHUB_OUTPUT"
139145
146+ # ============================================
147+ # JOB 2c: udata database the E2E tests start from (runs in parallel with build)
148+ # ============================================
149+ # Loading the zones and the fixtures takes minutes: done once here, each E2E shard restores
150+ # the dump in seconds instead.
151+ udata_fixtures :
152+ needs : udata_ref
153+ # The init downloads the zones and the fixtures without a timeout: a stalled download
154+ # must fail the run rather than hold the E2E shards waiting for hours
155+ timeout-minutes : 10
156+ runs-on : ubuntu-latest
157+ env :
158+ E2E_MONGODB_HOST : mongodb://localhost:27017/udata
159+ E2E_REDIS_URL : redis://localhost:6379
160+ E2E_CACHE_TYPE : flask_caching.backends.SimpleCache
161+
162+ services :
163+ mongodb :
164+ image : mongo:6.0.4
165+ ports :
166+ - 27017:27017
167+ redis :
168+ image : redis
169+ ports :
170+ - 6379:6379
171+
172+ steps :
173+ - name : Checkout cdata
174+ uses : actions/checkout@v6
175+
176+ - name : Checkout udata
177+ uses : actions/checkout@v6
178+ with :
179+ repository : opendatateam/udata
180+ path : ${{ env.UDATA_WORKING_DIR }}
181+ ref : ${{ needs.udata_ref.outputs.ref }}
182+
183+ - name : Set up uv
184+ uses : astral-sh/setup-uv@v6
185+ with :
186+ python-version : " 3.11"
187+ working-directory : ${{ env.UDATA_WORKING_DIR }}
188+
189+ - name : Install Python dependencies for udata
190+ working-directory : ${{ env.UDATA_WORKING_DIR }}
191+ run : uv sync --frozen
192+
193+ - name : Configure udata
194+ run : cp .github/e2e/udata.cfg ${{ env.UDATA_WORKING_DIR }}/udata.cfg
195+
196+ - name : Initialize udata
197+ working-directory : ${{ env.UDATA_WORKING_DIR }}
198+ run : |
199+ uv run udata init
200+
201+ uv run udata licenses
202+ uv run udata spatial load
203+ uv run udata import-fixtures
204+
205+ uv run udata user create --first-name "Admin" --last-name "User" --email "admin@example.com" --password "@1337Password42" --admin
206+ uv run udata user create --first-name "Normal" --last-name "User" --email "normal@example.com" --password "@1337Password42"
207+ uv run udata user create --first-name "2FA" --last-name "User" --email "2fa@example.com" --password "@1337Password42"
208+
209+ # mongodump comes with the service image: nothing to install. TESTING makes udata
210+ # suffix its database name with `-test`.
211+ - name : Dump udata database
212+ run : docker exec ${{ job.services.mongodb.id }} mongodump --db udata-test --archive --gzip > udata.archive
213+
214+ - name : Upload udata database
215+ uses : actions/upload-artifact@v7
216+ with :
217+ name : udata-database
218+ path : udata.archive
219+ retention-days : 1
220+ if-no-files-found : error
221+
140222 # ============================================
141223 # JOB 3: E2E Tests (runs in parallel with quality)
142224 # ============================================
143225 e2e :
144- needs : [build, udata_ref]
226+ needs : [build, udata_ref, udata_fixtures ]
145227 timeout-minutes : 30
146228 runs-on : ubuntu-latest
147229 strategy :
148230 fail-fast : false
149231 matrix :
150232 browser : [chromium, firefox]
151233 # Each (browser, shard) pair runs in its own container with its own MongoDB/Redis/udata,
152- # so Playwright can keep workers=1 (shared backend state) while shards run in parallel .
234+ # and Playwright runs one worker per core of that runner against it .
153235 # Keep the shard count in sync with the `--shard=.../N` value in the "Run E2E tests" step.
154236 shard : [1, 2, 3, 4]
155237 include :
@@ -158,7 +240,7 @@ jobs:
158240 - browser : firefox
159241 projects : " --project=setup --project=setup-normal-user --project=firefox"
160242 container :
161- image : mcr.microsoft.com/playwright:v1.63.0 -noble
243+ image : mcr.microsoft.com/playwright:v1.64.1 -noble
162244 options : --user 1001
163245
164246 services :
@@ -209,76 +291,47 @@ jobs:
209291
210292 - name : Install Python dependencies for udata
211293 working-directory : ${{ env.UDATA_WORKING_DIR }}
212- run : |
213- uv sync --frozen
294+ run : uv sync --frozen
214295
215296 - name : Configure udata
216- working-directory : ${{ env.UDATA_WORKING_DIR }}
217- run : |
218- cat << EOF > udata.cfg
219- from udata.settings import Defaults
220-
221- DEBUG = True
222- TESTING = True
223- SECRET_KEY = 'test-secret-key-for-ci'
224- API_TOKEN_SECRET = 'test-api-token-secret-for-ci'
225- SEND_MAIL = False
226- SERVER_NAME = 'localhost:7000'
227- CDATA_BASE_URL="http://localhost:3000"
228-
229- CACHE_TYPE = 'flask_caching.backends.NullCache'
230- DEFAULT_LANGUAGE = 'fr'
231-
232- SCHEMA_CATALOG_URL='https://schema.data.gouv.fr/schemas/schemas.json'
233-
234- URLS_ALLOW_PRIVATE = True
235- URLS_ALLOW_LOCAL = True
236- URLS_ALLOWED_TLDS = Defaults.URLS_ALLOWED_TLDS | set(['local'])
237- URLS_RESOLVE_HOSTNAME = False
297+ run : cp .github/e2e/udata.cfg ${{ env.UDATA_WORKING_DIR }}/udata.cfg
238298
239- PLUGINS = []
240- # Once https://github.com/etalab/flask-storage/pull/30 is merged, revert to
241- # FS_ROOT = 'fs' and quote the heredoc delimiter again (cat << 'EOF').
242- FS_ROOT = '$PWD/fs'
243-
244- HARVESTER_BACKENDS = ['dcat', 'csw*']
299+ - name : Download udata database
300+ uses : actions/download-artifact@v8
301+ with :
302+ name : udata-database
245303
246- SESSION_COOKIE_SECURE = False
304+ - name : Cache MongoDB database tools
305+ id : mongodb-tools
306+ uses : actions/cache@v6
307+ with :
308+ path : mongodb-tools
309+ key : ${{ env.MONGODB_TOOLS }}
247310
248- SECURITY_EMAIL_VALIDATOR_ARGS = {
249- "check_deliverability": False
250- }
251- SECURITY_TWO_FACTOR = True
311+ - name : Install MongoDB database tools
312+ if : steps.mongodb-tools.outputs.cache-hit != 'true'
313+ run : |
314+ mkdir mongodb-tools
315+ curl -fsSL "https://fastdl.mongodb.org/tools/db/$MONGODB_TOOLS.tgz" | tar -xz --strip-components=1 -C mongodb-tools
252316
253- MONGODB_HOST = 'mongodb://mongodb:27017/udata'
254- CELERY_BROKER_URL = 'redis://redis:6379'
255- CELERY_RESULT_BACKEND = 'redis://redis:6379'
256- # No worker runs here: tasks run within the request that triggers them, so that
257- # what they do (the notifications of a discussion, of a badge) can be tested.
258- CELERY_TASK_ALWAYS_EAGER = True
259- EOF
317+ - name : Restore udata database
318+ run : mongodb-tools/bin/mongorestore --uri mongodb://mongodb:27017 --archive=udata.archive --gzip
260319
261- - name : Initialize udata
320+ - name : Start udata server
262321 working-directory : ${{ env.UDATA_WORKING_DIR }}
263322 run : |
264- # Create fs directory
265323 mkdir -p fs
266324
267- # Initialize udata
268- uv run udata init
269-
270- uv run udata licenses
271- uv run udata spatial load
272- uv run udata import-fixtures
273-
274- uv run udata user create --first-name "Admin" --last-name "User" --email "admin@example.com" --password "@1337Password42" --admin
275- uv run udata user create --first-name "Normal" --last-name "User" --email "normal@example.com" --password "@1337Password42"
276- uv run udata user create --first-name "2FA" --last-name "User" --email "2fa@example.com" --password "@1337Password42"
277-
278325 uv run inv i18nc
279326
280- # No reloader: storage writes in fs/ would restart the server mid-test.
281- uv run inv serve --port=7000 > udata.log 2>&1 &
327+ # uWSGI as in production, with one process per core: the Playwright workers hit udata
328+ # concurrently, and the single-process development server would serialize them.
329+ # lazy-apps loads the app after the fork, so that no PyMongo client is shared across
330+ # processes. die-on-term makes SIGTERM stop uWSGI, which reloads on it otherwise.
331+ # http-keepalive stands for the nginx of production: the HTTP router closes every
332+ # connection otherwise, and the Playwright API client hangs up on the sockets it reuses.
333+ uv run --with pyuwsgi uwsgi --master --http localhost:7000 --http-keepalive \
334+ --module udata.wsgi:app --processes "$(nproc)" --lazy-apps --die-on-term > udata.log 2>&1 &
282335
283336 # Wait for udata to be ready
284337 timeout 90 bash -c 'until curl -f http://localhost:7000/api/1/site/; do sleep 2; done'
@@ -288,15 +341,19 @@ jobs:
288341 env :
289342 NUXT_PUBLIC_API_BASE : http://localhost:7000
290343 NUXT_PUBLIC_HOMEPAGE_HERO_IMAGES : " hero_15.png"
291- # Lets the matomo plugin load (tests stub matomo.js at the context
292- # level, the tracking spec asserts what the app sends). CI pre-starts
293- # this server and Playwright reuses it (reuseExistingServer), so the
294- # webServer.env in playwright.config.ts does not apply here.
295- NUXT_PUBLIC_MATOMO_HOST : http://localhost:3000
344+ # Lets the matomo plugin load (tests/fake-services.ts serves an empty matomo.js,
345+ # the tracking spec asserts what the app sends). CI pre-starts this server and
346+ # Playwright reuses it (reuseExistingServer), so the webServer.env in
347+ # playwright.config.ts does not apply here.
348+ NUXT_PUBLIC_MATOMO_HOST : ${{ env.FAKE_SERVICES_URL }}
349+ # Under a path of its own: the metrics outage tests intercept everything below it,
350+ # and must not take matomo.js down with it
351+ NUXT_PUBLIC_METRICS_API : ${{ env.FAKE_SERVICES_URL }}/metrics
296352 NUXT_SITE_URL : http://localhost:3000
297353 NUXT_PUBLIC_CHARTS_API_BASE : http://localhost:7000
298354 NUXT_PUBLIC_CADA_RESOURCE_ID : 3a6d6fe3-8548-43ed-ad09-72052771447c
299355 NUXT_PUBLIC_GEOPF_ENABLED : " true"
356+ NUXT_PUBLIC_TABULAR_API_URL : ${{ env.FAKE_SERVICES_URL }}/tabular
300357 NUXT_PUBLIC_TABULAR_API_PREVIEW_RESOURCES_ID : 982d9dd0-365a-4c4b-8a83-75dec40c36bb
301358 NUXT_PUBLIC_TABULAR_API_PREVIEW_STATS_ID : 33cf9a65-3f77-4d88-acd1-bca420d83e60
302359 NUXT_PUBLIC_EXPLORER_FEEDBACK_URL : https://example.com/feedback
@@ -312,6 +369,7 @@ jobs:
312369 env :
313370 BASE_URL : http://localhost:3000
314371 NUXT_PUBLIC_API_BASE : http://localhost:7000
372+ NUXT_PUBLIC_METRICS_API : ${{ env.FAKE_SERVICES_URL }}/metrics
315373 UDATA_WORKING_DIR : ${{ env.UDATA_WORKING_DIR }}
316374 CI : true
317375 # Unique blob name per (browser, shard): the default is report-<shard>.zip, which would
@@ -325,7 +383,7 @@ jobs:
325383 working-directory : ${{ env.UDATA_WORKING_DIR }}
326384 run : |
327385 # Kill udata server to release the lock so that post setup-uv can prune the cache
328- pkill -f "manage.py serve" || true
386+ pkill -f uwsgi || true
329387
330388 - name : Show server logs
331389 if : always()
@@ -336,6 +394,14 @@ jobs:
336394 echo "=== CDATA SERVER LOGS ==="
337395 cat cdata.log || echo "No cdata logs found"
338396
397+ - name : Upload recorded Tabular API fixtures
398+ uses : actions/upload-artifact@v7
399+ if : always() && env.FAKE_SERVICES_RECORD
400+ with :
401+ name : tabular-fixtures-${{ matrix.browser }}-${{ matrix.shard }}
402+ path : tests/fixtures/tabular
403+ retention-days : 1
404+
339405 - name : Upload screenshots
340406 uses : actions/upload-artifact@v7
341407 if : always()
0 commit comments