Local test targets read the running stack, not the deployment's domain
Docs / docs (push) Successful in 21s
Playwright Tests / test-playwright (1, 2) (push) Successful in 1m57s
Playwright Tests / test-playwright (2, 2) (push) Successful in 1m43s
pre-commit / pre-commit (push) Failing after 3m32s
Test Backend / test-backend (push) Successful in 2m18s
Compose Smoke Test / test-compose (push) Successful in 31s
Playwright Tests / merge-reports (push) Successful in 1m27s

`make test-frontend` built PLAYWRIGHT_BASE_URL from DOMAIN in .env, which in a
checkout configured for a deployment is that deployment's domain — so the suite
that creates and deletes flows, dashboards and users was pointed at
app.fluksio.com, held local only by --add-host and tests/guard.ts.

The hostname now comes off the running stack (the frontend container's own
Traefik rule), so a name no local container answers to cannot be reached at
all, and the local targets default to *.localhost instead of reading .env.
Target-specific on purpose: an exported DOMAIN outranks --env-file in compose
interpolation and would put the production targets on localhost.

`rebuild-frontend` replaces the raw compose line CLAUDE.md spelled out, taking
the same domain so the baked VITE_API_URL cannot disagree with what Traefik
serves. Also: a coverage HTML report that cannot be written no longer fails
test-backend after a green suite, and both artifact actions in playwright.yml
drop to @v3, which is the only version without the github.com-only guard that
failed every run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 13:48:05 +02:00
co-authored by Claude Opus 5
parent bda67d1c0d
commit b80b92aad7
3 changed files with 47 additions and 18 deletions
+14 -7
View File
@@ -87,13 +87,16 @@ jobs:
- name: Tear down - name: Tear down
if: always() if: always()
run: docker compose down -v --remove-orphans run: docker compose down -v --remove-orphans
# v3 deliberately: @v4 of both artifact actions refuses to run against
# anything that is not github.com (GHESNotSupportedError), which failed
# every run here after the tests had already passed. `include-hidden-files`
# is v4-only and comes off with it — a blob report holds no dotfiles.
- name: Upload blob report - name: Upload blob report
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: blob-report-${{ matrix.shardIndex }} name: blob-report-${{ matrix.shardIndex }}
path: frontend/blob-report path: frontend/blob-report
include-hidden-files: true
retention-days: 1 retention-days: 1
merge-reports: merge-reports:
@@ -108,19 +111,23 @@ jobs:
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
- name: Install dependencies - name: Install dependencies
run: bun install --frozen-lockfile run: bun install --frozen-lockfile
# v3 has no `pattern:`/`merge-multiple:`, so it lands one directory per
# artifact and merge-reports needs them flattened first.
- name: Download blob reports - name: Download blob reports
uses: actions/download-artifact@v4 uses: actions/download-artifact@v3
with: with:
path: frontend/all-blob-reports path: frontend/all-blob-reports
pattern: blob-report-* - name: Flatten the per-shard directories
merge-multiple: true run: |
find frontend/all-blob-reports -mindepth 2 -type f \
-exec mv -t frontend/all-blob-reports {} +
find frontend/all-blob-reports -mindepth 1 -type d -empty -delete
- name: Merge into an HTML report - name: Merge into an HTML report
run: bunx playwright merge-reports --reporter html ./all-blob-reports run: bunx playwright merge-reports --reporter html ./all-blob-reports
working-directory: frontend working-directory: frontend
- name: Upload HTML report - name: Upload HTML report
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: html-report--attempt-${{ github.run_attempt }} name: html-report--attempt-${{ github.run_attempt }}
path: frontend/playwright-report path: frontend/playwright-report
include-hidden-files: true
retention-days: 7 retention-days: 7
+29 -10
View File
@@ -2,7 +2,7 @@
# Convenience targets for development, testing, linting, and deployment. # Convenience targets for development, testing, linting, and deployment.
# The workspace root delegates to these (see ../Makefile). # The workspace root delegates to these (see ../Makefile).
.PHONY: dev-utils dev dev-local dev-lan up down update install dev-backend dev-frontend \ .PHONY: dev-utils dev dev-local dev-lan rebuild-frontend up down update install dev-backend dev-frontend \
generate-client sync-example test test-backend test-frontend soak bench-startup lint lint-backend \ generate-client sync-example test test-backend test-frontend soak bench-startup lint lint-backend \
lint-frontend format-frontend umami build docs docs-serve clean help lint-frontend format-frontend umami build docs docs-serve clean help
@@ -31,6 +31,14 @@ help: ## Show available targets
# ── Development (Docker) ────────────────────────────────────────── # ── Development (Docker) ──────────────────────────────────────────
# Hostname the local stack is served under. Never .env's DOMAIN: a checkout
# configured for a deployment carries that deployment's domain, and a local
# stack started under it answers to the same names the live installation does.
# Target-specific on purpose — an exported DOMAIN outranks --env-file in
# compose interpolation, which would put the *production* targets on localhost.
# `make dev DOMAIN=fluksio.com` still wins.
dev dev-utils dev-local dev-lan rebuild-frontend: export DOMAIN ?= localhost
dev-utils: ## Start only the utility containers (proxy, mailcatcher) dev-utils: ## Start only the utility containers (proxy, mailcatcher)
$(COMPOSE_DEV) up --build proxy mailcatcher $(COMPOSE_DEV) up --build proxy mailcatcher
@@ -45,6 +53,15 @@ dev-lan: ## Same, plus the app on http://<host-ip>:$(APP_PORT) (no DNS needed)
DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} APP_PORT=$(APP_PORT) \ DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} APP_PORT=$(APP_PORT) \
$(COMPOSE_LAN) up --build -d proxy backend frontend mailcatcher $(COMPOSE_LAN) up --build -d proxy backend frontend mailcatcher
# The frontend is an nginx image, so a UI change needs a rebuild. DOMAIN comes
# off the running stack: VITE_API_URL is baked in at build time, and a rebuild
# under a different domain leaves the SPA calling an API that answers elsewhere.
rebuild-frontend: ## Rebuild and restart the local frontend (after a UI change)
@domain=$$(docker inspect fluksio-app 2>/dev/null \
| grep -o 'Host(`app\.[^`]*`)' | head -1 | sed 's/Host(`app\.//;s/`)//'); \
DOMAIN=$${domain:-$$DOMAIN} ENVIRONMENT=$${ENVIRONMENT:-local} \
$(COMPOSE_LOCAL) up --build -d frontend
up: ## Start the production stack up: ## Start the production stack
$(COMPOSE_PROD_RUN) up --build -d $(COMPOSE_PROD_RUN) up --build -d
@@ -115,25 +132,27 @@ test-backend: ## Run backend tests (pytest + coverage)
# nothing running and touches no development data. # nothing running and touches no development data.
cd backend && uv run bash scripts/test.sh cd backend && uv run bash scripts/test.sh
# The hostname the stack is served under. A checkout configured for a
# deployment carries the deployment's domain, and *.fluksio.com resolves to the
# live instance from here — which is why the run below maps both names onto the
# local Traefik by address and never lets DNS decide.
export DOMAIN ?= $(shell sed -n 's/^DOMAIN=//p' $(COMPOSE_ROOT)/.env | head -1)
PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1) PW_VERSION = $(shell sed -n 's/.*"@playwright\/test": "[^0-9]*\([0-9.]*\)".*/\1/p' frontend/package.json | head -1)
# This suite creates and deletes flows, dashboards and users, so the hostname it
# is pointed at is not taken from any file: it is read off the running stack
# (the frontend container's own Traefik rule) and mapped onto the local Traefik
# by address, so DNS never decides. tests/guard.ts is the second line.
test-frontend: ## Run frontend tests (Playwright e2e) against the local stack test-frontend: ## Run frontend tests (Playwright e2e) against the local stack
@ip=$$(docker network inspect proxy \ @ip=$$(docker network inspect proxy \
--format '{{range .Containers}}{{if eq .Name "fluksio-app-proxy-1"}}{{.IPv4Address}}{{end}}{{end}}' \ --format '{{range .Containers}}{{if eq .Name "fluksio-app-proxy-1"}}{{.IPv4Address}}{{end}}{{end}}' \
2>/dev/null | cut -d/ -f1); \ 2>/dev/null | cut -d/ -f1); \
[ -n "$$ip" ] || { echo " ✗ proxy network or Traefik container not found — is the stack up?"; exit 1; }; \ [ -n "$$ip" ] || { echo " ✗ proxy network or Traefik container not found — is the stack up?"; exit 1; }; \
domain=$$(docker inspect fluksio-app 2>/dev/null \
| grep -o 'Host(`app\.[^`]*`)' | head -1 | sed 's/Host(`app\.//;s/`)//'); \
[ -n "$$domain" ] || { echo " ✗ no running app stack to test — 'make dev' first"; exit 1; }; \
docker run --rm --network proxy --ipc=host \ docker run --rm --network proxy --ipc=host \
--user $$(id -u):$$(id -g) -e HOME=/tmp \ --user $$(id -u):$$(id -g) -e HOME=/tmp \
--add-host app.$(DOMAIN):$$ip --add-host api.$(DOMAIN):$$ip \ --add-host app.$$domain:$$ip --add-host api.$$domain:$$ip \
-v $(COMPOSE_ROOT):/app -w /app/frontend \ -v $(COMPOSE_ROOT):/app -w /app/frontend \
-e PLAYWRIGHT_BASE_URL=http://app.$(DOMAIN) \ -e PLAYWRIGHT_BASE_URL=http://app.$$domain \
-e PLAYWRIGHT_API_URL=http://api.$(DOMAIN) \ -e PLAYWRIGHT_API_URL=http://api.$$domain \
-e HOST_RESOLVER_RULES="MAP app.$(DOMAIN) $$ip, MAP api.$(DOMAIN) $$ip" \ -e HOST_RESOLVER_RULES="MAP app.$$domain $$ip, MAP api.$$domain $$ip" \
-e CI=$${CI:-1} \ -e CI=$${CI:-1} \
-e FIRST_SUPERUSER="$$(sed -n 's/^FIRST_SUPERUSER=//p' $(COMPOSE_ROOT)/.env | head -1)" \ -e FIRST_SUPERUSER="$$(sed -n 's/^FIRST_SUPERUSER=//p' $(COMPOSE_ROOT)/.env | head -1)" \
-e FIRST_SUPERUSER_PASSWORD="$$(sed -n 's/^FIRST_SUPERUSER_PASSWORD=//p' $(COMPOSE_ROOT)/.env | head -1)" \ -e FIRST_SUPERUSER_PASSWORD="$$(sed -n 's/^FIRST_SUPERUSER_PASSWORD=//p' $(COMPOSE_ROOT)/.env | head -1)" \
+4 -1
View File
@@ -5,4 +5,7 @@ set -x
coverage run -m pytest tests/ coverage run -m pytest tests/
coverage report coverage report
coverage html --title "${@-coverage}" # The HTML report is a convenience: a container run can leave htmlcov/ owned by
# root, and failing here after a green suite reads like a test failure.
coverage html --title "${@-coverage}" \
|| echo " ! coverage html failed (htmlcov/ not writable? try 'make clean') — tests passed" >&2