Files
Spoolman/.github/workflows/ci.yml
T
Donkie 17f8b9fee1 client_v2: add Playwright frontend integration tests
Mirrors tests_frontend, but drives the Svelte client instead of the legacy
React one: same production image, same PostgreSQL-backed compose stack, same
"only the first navigation uses a URL" discipline.

- smoke.spec.ts clicks through every top-bar tab, asserting each route's title
  and the app shell, with no browser console errors; a second test proves the
  library reached the API rather than falling into its offline state.
- crud.spec.ts walks the first-use happy path. client_v2 folds the legacy
  client's three create forms into one "Add spools" modal, so a manufacturer,
  a filament and a spool are created by a single submit, then verified back in
  the library list and on the locations page.

Two client_v2-specific helpers earn their keep: the top bar renders its nav
twice (desktop + mobile, one hidden by CSS) so nav locators need narrowing to
the visible one, and NumberInput's accessible name picks up its stepper and
help buttons, so numeric fields are found via their form item instead of
getByLabel.

The suite publishes on host port 8001 so it can run alongside the legacy one.
CI gains a test-frontend-v2 job cloned from test-frontend, gating
publish-images alongside it.

Also fixes tests_frontend/run.py: it only built client/dist, but the Dockerfile
copies client_v2/build unconditionally, so the image could not be built from a
clean checkout.
2026-07-29 19:08:12 +02:00

611 lines
17 KiB
YAML

name: CI
on:
push:
branches:
- master
tags:
- "v*"
pull_request:
branches:
- master
permissions:
contents: read
packages: read
env:
GHCR_IMAGE: donkie/spoolman
jobs:
#
# Linting
#
style:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install node
uses: actions/setup-node@v4.0.3
with:
node-version: '20'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.26"
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"
- name: Install lefthook
run: uv add --dev lefthook
- name: Install client dependencies
run: |
cd client
npm ci
- name: Run pre-commit
run: uv run lefthook run ci
#
# Build the Spoolman client for baking into the docker images
# This is done on native hardware to speed things up, vite is very slow on emulated hardware
#
build-client:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install node
uses: actions/setup-node@v4.0.3
with:
node-version: '20'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "0.9.26"
- name: "Set up Python"
uses: actions/setup-python@v6
with:
python-version-file: "pyproject.toml"
- name: Install client dependencies
run: |
cd client
npm ci
- name: Build
run: |
cd client
rm -f .env && echo "VITE_APIURL=/api/v1" > .env.production
npm run build
# Build the new Svelte client (client_v2), served by default at runtime.
# No VITE_APIURL is set: the client resolves the API relative to its base path.
- name: Install client_v2 dependencies
run: |
cd client_v2
npm ci
- name: Build client_v2
run: |
cd client_v2
npm run build
- name: Write build info
run: |
echo "GIT_COMMIT=$(git rev-parse --short HEAD)" > build.txt
echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> build.txt
# Remove client sources and keep only the built output for each client
- name: Clean
run: |
mv client/dist dist
rm -rf client
mkdir client
mv dist client/dist
mv client_v2/build build_v2
rm -rf client_v2
mkdir client_v2
mv build_v2 client_v2/build
rm -rf .git
rm -rf .venv
- name: Fix permissions
run: |
chmod +x scripts/*.sh
- name: Upload client Spoolman artifact
uses: actions/upload-artifact@v4.4.0
with:
name: client
path: client/dist
- name: Upload client_v2 Spoolman artifact
uses: actions/upload-artifact@v4.4.0
with:
name: client_v2
path: client_v2/build
- name: Upload full Spoolman artifact
uses: actions/upload-artifact@v4.4.0
with:
include-hidden-files: true
name: spoolman
path: .
#
# Build native image for integration tests
#
build-amd64:
needs: [build-client]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download client
uses: actions/download-artifact@v4.1.7
with:
name: client
path: client/dist
- name: Download client_v2
uses: actions/download-artifact@v4.1.7
with:
name: client_v2
path: client_v2/build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Spoolman
if: ${{ github.event_name != 'pull_request' }}
uses: docker/build-push-action@v6.7.0
with:
context: .
tags: ${{ env.GHCR_IMAGE }}:test
outputs: type=docker,dest=/tmp/spoolman.tar
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64,mode=max
# Separate build for pull requests since PRs dont have access to store cache
- name: Build Spoolman (PR)
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6.7.0
with:
context: .
tags: ${{ env.GHCR_IMAGE }}:test
outputs: type=docker,dest=/tmp/spoolman.tar
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
- name: Upload artifact
uses: actions/upload-artifact@v4.4.0
with:
name: spoolman-image
path: /tmp/spoolman.tar
#
# Build tester image for integration tests
#
build-tester:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Spoolman Tester
if: ${{ github.event_name != 'pull_request' }}
uses: docker/build-push-action@v6.7.0
with:
context: ./tests_integration
tags: ${{ env.GHCR_IMAGE }}-tester:latest
outputs: type=docker,dest=/tmp/spoolman-tester.tar
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester,mode=max
# Separate build for pull requests since PRs dont have access to store cache
- name: Build Spoolman Tester (PR)
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6.7.0
with:
context: ./tests_integration
tags: ${{ env.GHCR_IMAGE }}-tester:latest
outputs: type=docker,dest=/tmp/spoolman-tester.tar
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-tester
- name: Upload artifact
uses: actions/upload-artifact@v4.4.0
with:
name: spoolman-tester-image
path: /tmp/spoolman-tester.tar
#
# Perform integration tests
#
test:
needs: [build-amd64, build-tester]
strategy:
matrix:
dbtype: ["postgres", "sqlite", "mariadb", "cockroachdb"]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download built images
uses: actions/download-artifact@v4.1.7
with:
pattern: spoolman*-image
path: /tmp
- name: Load built images
run: |
docker load --input /tmp/spoolman-image/spoolman.tar
docker load --input /tmp/spoolman-tester-image/spoolman-tester.tar
- name: Perform integration tests
uses: hoverkraft-tech/compose-action@v2.0.1
with:
compose-file: "./tests_integration/docker-compose-${{ matrix.dbtype }}.yml"
up-flags: "--abort-on-container-exit"
down-flags: "--volumes"
#
# Perform frontend (browser) integration tests against the real image
#
test-frontend:
needs: [build-amd64]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download built image
uses: actions/download-artifact@v4.1.7
with:
name: spoolman-image
path: /tmp/spoolman-image
- name: Load built image
run: docker load --input /tmp/spoolman-image/spoolman.tar
- name: Install node
uses: actions/setup-node@v4.0.3
with:
node-version: '20'
- name: Install Playwright
run: |
cd tests_frontend
npm ci
npx playwright install --with-deps chromium
- name: Start Spoolman
run: docker compose -f tests_frontend/docker-compose.yml up -d --wait
- name: Run frontend integration tests
run: |
cd tests_frontend
npx playwright test
- name: Dump Spoolman logs on failure
if: failure()
run: docker compose -f tests_frontend/docker-compose.yml logs spoolman
- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4.4.0
with:
name: playwright-report
path: tests_frontend/playwright-report
retention-days: 7
- name: Stop Spoolman
if: always()
run: docker compose -f tests_frontend/docker-compose.yml down -v
#
# Perform frontend (browser) integration tests for client_v2 against the real image
#
test-frontend-v2:
needs: [build-amd64]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download built image
uses: actions/download-artifact@v4.1.7
with:
name: spoolman-image
path: /tmp/spoolman-image
- name: Load built image
run: docker load --input /tmp/spoolman-image/spoolman.tar
- name: Install node
uses: actions/setup-node@v4.0.3
with:
node-version: '20'
- name: Install Playwright
run: |
cd tests_frontend_v2
npm ci
npx playwright install --with-deps chromium
- name: Start Spoolman
run: docker compose -f tests_frontend_v2/docker-compose.yml up -d --wait
- name: Run client_v2 frontend integration tests
run: |
cd tests_frontend_v2
npx playwright test
- name: Dump Spoolman logs on failure
if: failure()
run: docker compose -f tests_frontend_v2/docker-compose.yml logs spoolman
- name: Upload Playwright report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4.4.0
with:
name: playwright-report-v2
path: tests_frontend_v2/playwright-report
retention-days: 7
- name: Stop Spoolman
if: always()
run: docker compose -f tests_frontend_v2/docker-compose.yml down -v
#
# Build arm64 image
# Don't run this for pull requests
# Only push to buildcache and let release job push to registry
#
build-arm64:
if: ${{ github.event_name != 'pull_request' }}
needs: [build-client]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download client
uses: actions/download-artifact@v4.1.7
with:
name: client
path: client/dist
- name: Download client_v2
uses: actions/download-artifact@v4.1.7
with:
name: client_v2
path: client_v2/build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and cache Docker image
uses: docker/build-push-action@v6.7.0
with:
context: .
platforms: linux/arm64
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64,mode=max
#
# Build arm/v7 image
# Don't run this for pull requests
# Only push to buildcache and let release job push to registry
#
build-armv7:
if: ${{ github.event_name != 'pull_request' }}
needs: [build-client]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download client
uses: actions/download-artifact@v4.1.7
with:
name: client
path: client/dist
- name: Download client_v2
uses: actions/download-artifact@v4.1.7
with:
name: client_v2
path: client_v2/build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and cache Docker image
uses: docker/build-push-action@v6.7.0
with:
context: .
platforms: linux/arm/v7
cache-from: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7
cache-to: type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7,mode=max
#
# Release images GHCR and Docker Hub if tests pass
# Don't run this for pull requests
#
publish-images:
if: ${{ github.event_name != 'pull_request' }}
needs: [test, test-frontend, test-frontend-v2, build-amd64, build-arm64, build-armv7, style, build-client]
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download client
uses: actions/download-artifact@v4.1.7
with:
name: client
path: client/dist
- name: Download client_v2
uses: actions/download-artifact@v4.1.7
with:
name: client_v2
path: client_v2/build
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.6.1
- name: Log in to the Github Container registry
uses: docker/login-action@v3.3.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for GHCR
id: meta-ghcr
uses: docker/metadata-action@v5.5.1
with:
images: |
ghcr.io/${{ env.GHCR_IMAGE }}
tags: |
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Extract metadata (tags, labels) for Docker Hub
id: meta-dh
uses: docker/metadata-action@v5.5.1
with:
images: |
${{ vars.REMOTE_REGISTRY_URL }}/${{ vars.REMOTE_REGISTRY_NAMESPACE }}/${{ vars.REMOTE_REGISTRY_REPOSITORY }}
tags: |
type=edge
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Log in to DockerHub
uses: docker/login-action@v3.3.0
with:
registry: ${{ vars.REMOTE_REGISTRY_URL }}
username: ${{ secrets.REMOTE_REGISTRY_USERNAME }}
password: ${{ secrets.REMOTE_REGISTRY_PASSWORD }}
- name: Store git commit and build date
run: |
echo "GIT_COMMIT=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
echo "BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$GITHUB_ENV"
- name: Build and push Docker images
uses: docker/build-push-action@v6.7.0
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: |
${{ steps.meta-ghcr.outputs.tags }}
${{ steps.meta-dh.outputs.tags }}
labels: ${{ steps.meta-ghcr.outputs.labels }}
build-args: |
GIT_COMMIT
BUILD_DATE
cache-from: |
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-amd64
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-arm64
type=registry,ref=ghcr.io/${{ env.GHCR_IMAGE }}:buildcache-armv7
#
# Create Github Release if CI was triggered by a tag
#
publish-release:
if: ${{ (github.event_name != 'pull_request') && startsWith(github.event.ref, 'refs/tags/v') }}
needs: [publish-images]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download built spoolman
uses: actions/download-artifact@v4.1.7
with:
name: spoolman
path: /tmp/spoolman
- name: Generate release info
run: |
cd /tmp/spoolman
echo '{
"project_name": "Spoolman",
"project_owner": "Donkie",
"version": "${{ github.ref_name }}"
}' > release_info.json
- name: Zip
run: |
cd /tmp/spoolman
zip -r spoolman.zip .
- name: Create release and upload build
uses: softprops/action-gh-release@v1
id: create-release
with:
draft: true
name: ${{ github.ref_name }}
tag_name: ${{ github.ref_name }}
files: /tmp/spoolman/spoolman.zip
token: ${{ secrets.GITHUB_TOKEN }}
body: "⚠️ TODO ⚠️"