name: Playwright Tests on: push: branches: - main paths: - backend/** - frontend/** - docker/compose*.yml - .env.example - .gitea/workflows/playwright.yml pull_request: types: - opened - synchronize paths: - backend/** - frontend/** - docker/compose*.yml - .env.example - .gitea/workflows/playwright.yml # A new push supersedes the run still queued for the commit before it. Without # this each commit leaves its own run in the queue and a runner that was offline # for a while works through a backlog of results nobody will read. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: # The playwright runner service comes from the dev overlay. It loads the SPA # from the nginx `frontend` service and reaches the API as http://backend:8000 # on the project network — both wired up in compose.ci.yml. COMPOSE_FILE: docker/compose.yml:docker/compose.dev.yml:docker/compose.ci.yml # frontend/playwright.config.ts keys the blob reporter, the retries and # forbidOnly off CI, and compose.dev.yml forwards it into the container. CI: "true" jobs: test-playwright: runs-on: ubuntu-latest timeout-minutes: 60 strategy: fail-fast: false matrix: # Nine spec files under tests/, plus tests/auth.setup.ts, which every # shard reruns as its setup project. Split two ways: each shard pays a # full compose stack's startup, so more of them would spend more time # booting than testing. shardIndex: [1, 2] shardTotal: [2] env: # One stack per shard, so a `down -v` cannot reach the other shard's # volumes. compose.ci.yml above drops the fixed container_names and host # ports, which the project name does not cover — without it two shards on # the same runner host collide on the daemon-global names. COMPOSE_PROJECT_NAME: fluksio-app-ci-pw-${{ matrix.shardIndex }} steps: - name: Checkout uses: actions/checkout@v4 - name: Write .env # Not committed; compose interpolates it and the runner container reads # FIRST_SUPERUSER / FIRST_SUPERUSER_PASSWORD out of it (tests/config.ts). run: cp .env.example .env - run: docker compose build backend frontend playwright - run: docker compose down -v --remove-orphans # PLAYWRIGHT_BASE_URL points the browser at `frontend`, which is not a # dependency of the playwright service, so bring it up explicitly. - run: docker compose up -d --wait backend frontend # Named rather than --rm: the report is written inside the container, and # compose.ci.yml drops the mount that would have put it on this # filesystem — under a sibling daemon that mount lands somewhere this job # cannot read. The teardown below removes the container either way. - name: Run Playwright tests run: >- docker compose run --name "$COMPOSE_PROJECT_NAME-pw" playwright bunx playwright test --trace=retain-on-failure --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} # Before the teardown, and on a failed run too — a red shard's report is # the one worth reading. - name: Collect the blob report if: ${{ !cancelled() }} run: | docker cp "$COMPOSE_PROJECT_NAME-pw:/app/frontend/blob-report" frontend/ \ || echo "no blob report to collect" - name: Tear down if: always() run: docker compose down -v --remove-orphans - name: Upload blob report if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shardIndex }} path: frontend/blob-report include-hidden-files: true retention-days: 1 merge-reports: needs: - test-playwright # Merge even when a shard failed — that report is the one worth reading. if: ${{ !cancelled() }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - name: Install dependencies run: bun install --frozen-lockfile - name: Download blob reports uses: actions/download-artifact@v4 with: path: frontend/all-blob-reports pattern: blob-report-* merge-multiple: true - 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 with: name: html-report--attempt-${{ github.run_attempt }} path: frontend/playwright-report include-hidden-files: true retention-days: 7