diff --git a/.gitea/workflows/playwright.yml b/.gitea/workflows/playwright.yml index 10a2e21..7c37de7 100644 --- a/.gitea/workflows/playwright.yml +++ b/.gitea/workflows/playwright.yml @@ -87,13 +87,16 @@ jobs: - name: Tear down if: always() 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 if: ${{ !cancelled() }} - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: blob-report-${{ matrix.shardIndex }} path: frontend/blob-report - include-hidden-files: true retention-days: 1 merge-reports: @@ -108,19 +111,23 @@ jobs: - uses: oven-sh/setup-bun@v2 - name: Install dependencies 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 - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v3 with: path: frontend/all-blob-reports - pattern: blob-report-* - merge-multiple: true + - name: Flatten the per-shard directories + 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 run: bunx playwright merge-reports --reporter html ./all-blob-reports working-directory: frontend - name: Upload HTML report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v3 with: name: html-report--attempt-${{ github.run_attempt }} path: frontend/playwright-report - include-hidden-files: true retention-days: 7 diff --git a/Makefile b/Makefile index 396ac17..e3df5de 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # Convenience targets for development, testing, linting, and deployment. # 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 \ lint-frontend format-frontend umami build docs docs-serve clean help @@ -31,6 +31,14 @@ help: ## Show available targets # ── 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) $(COMPOSE_DEV) up --build proxy mailcatcher @@ -45,6 +53,15 @@ dev-lan: ## Same, plus the app on http://:$(APP_PORT) (no DNS needed) DOMAIN=$${DOMAIN:-localhost} ENVIRONMENT=$${ENVIRONMENT:-local} APP_PORT=$(APP_PORT) \ $(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 $(COMPOSE_PROD_RUN) up --build -d @@ -115,25 +132,27 @@ test-backend: ## Run backend tests (pytest + coverage) # nothing running and touches no development data. 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) +# 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 @ip=$$(docker network inspect proxy \ --format '{{range .Containers}}{{if eq .Name "fluksio-app-proxy-1"}}{{.IPv4Address}}{{end}}{{end}}' \ 2>/dev/null | cut -d/ -f1); \ [ -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 \ --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 \ - -e PLAYWRIGHT_BASE_URL=http://app.$(DOMAIN) \ - -e PLAYWRIGHT_API_URL=http://api.$(DOMAIN) \ - -e HOST_RESOLVER_RULES="MAP app.$(DOMAIN) $$ip, MAP api.$(DOMAIN) $$ip" \ + -e PLAYWRIGHT_BASE_URL=http://app.$$domain \ + -e PLAYWRIGHT_API_URL=http://api.$$domain \ + -e HOST_RESOLVER_RULES="MAP app.$$domain $$ip, MAP api.$$domain $$ip" \ -e CI=$${CI:-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)" \ diff --git a/backend/scripts/test.sh b/backend/scripts/test.sh index 38c3e89..b080e38 100755 --- a/backend/scripts/test.sh +++ b/backend/scripts/test.sh @@ -5,4 +5,7 @@ set -x coverage run -m pytest tests/ 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