name: E2E Tests on: push: branches: [main] paths: - "apps/backend/**" - "apps/web/**" - "apps/packages/**" - "apps/pnpm-lock.yaml" - ".github/workflows/e2e-tests.yml" - "!**/*.md" pull_request: branches: [main] paths: - "apps/backend/**" - "apps/web/**" - "apps/packages/**" - "apps/pnpm-lock.yaml" - ".github/workflows/e2e-tests.yml" - "!**/*.md" # Manual trigger for verifying CI on draft PRs and ad-hoc reruns. The # default sharded run uses the same ref the user picks in the dispatch UI. workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: read jobs: build: name: Build runs-on: ubuntu-latest timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version-file: apps/backend/go.mod cache-dependency-path: apps/backend/go.sum - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: "9" - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install dependencies working-directory: apps run: pnpm install --frozen-lockfile - name: Build backend and web run: make build-backend build-web # Linux/amd64 helper binaries (agentctl + mock-agent) bind-mounted into # containers by the Docker E2E project. Cheap to build (~1s each) so we # always do it; the regular E2E job ignores them. - name: Build Docker E2E helper binaries run: make build-backend-linux-helpers - name: Upload backend build uses: actions/upload-artifact@v4 with: name: e2e-backend-build path: apps/backend/bin/ retention-days: 1 # Tar the .next directory to preserve pnpm symlinks in the standalone build. # upload-artifact dereferences symlinks which breaks the Node.js module resolution. - name: Package web build run: tar -cf apps/web/next-build.tar -C apps/web .next - name: Upload web build uses: actions/upload-artifact@v4 with: name: e2e-web-build path: apps/web/next-build.tar retention-days: 1 e2e: name: E2E Shard ${{ matrix.shard }}/10 needs: build runs-on: ubuntu-latest timeout-minutes: 10 strategy: fail-fast: false matrix: shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: "9" - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install dependencies working-directory: apps run: pnpm install --frozen-lockfile - name: Install Playwright browsers working-directory: apps/web run: npx playwright install chromium --with-deps - name: Download backend build uses: actions/download-artifact@v4 with: name: e2e-backend-build path: apps/backend/bin/ - name: Make backend binaries executable run: chmod +x apps/backend/bin/* - name: Download web build uses: actions/download-artifact@v4 with: name: e2e-web-build path: apps/web/ - name: Extract web build working-directory: apps/web run: tar -xf next-build.tar && rm next-build.tar # Pinned to chromium + mobile-chrome so the dedicated `e2e-docker` job # below owns the real-Docker project. Without --project the docker spec # would run here too, fail seeding (KANDEV_DOCKER_ENABLED is off), and # spam every shard. - name: Run E2E tests (shard ${{ matrix.shard }}/10) working-directory: apps/web run: npx playwright test --config e2e/playwright.config.ts --project=chromium --project=mobile-chrome --shard=${{ matrix.shard }}/10 - name: Upload blob report if: always() uses: actions/upload-artifact@v4 with: name: blob-report-${{ matrix.shard }} path: apps/web/e2e/blob-report/ retention-days: 3 - name: Upload test results if: failure() uses: actions/upload-artifact@v4 with: name: test-results-${{ matrix.shard }} path: apps/web/e2e/test-results/ retention-days: 7 # Real-Docker E2E: exercises the local_docker executor end-to-end. ubuntu-latest # ships with Docker preinstalled, so no service container is required. The # spec count is small (currently 1 active + 2 fixme) so we don't shard. # Cleans up any leftover kandev-managed containers at the end so a leaky # process doesn't poison the next run on the same self-hosted runner. e2e-docker: name: E2E Docker needs: build runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout code uses: actions/checkout@v4 - name: Verify Docker is available run: docker info - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: "9" - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install dependencies working-directory: apps run: pnpm install --frozen-lockfile - name: Install Playwright browsers working-directory: apps/web run: npx playwright install chromium --with-deps - name: Download backend build uses: actions/download-artifact@v4 with: name: e2e-backend-build path: apps/backend/bin/ - name: Make backend binaries executable run: chmod +x apps/backend/bin/* - name: Download web build uses: actions/download-artifact@v4 with: name: e2e-web-build path: apps/web/ - name: Extract web build working-directory: apps/web run: tar -xf next-build.tar && rm next-build.tar - name: Run Docker E2E tests working-directory: apps/web env: KANDEV_E2E_DOCKER: "1" run: npx playwright test --config e2e/playwright.config.ts --project=docker - name: Cleanup leftover kandev containers if: always() run: docker ps -aq --filter label=kandev.managed=true | xargs -r docker rm -f # Named with the same `blob-report-*` prefix the merge job globs for # so the docker results land in the unified HTML report alongside the # sharded chromium/mobile-chrome runs. - name: Upload Docker blob report if: always() uses: actions/upload-artifact@v4 with: name: blob-report-docker path: apps/web/e2e/blob-report/ retention-days: 3 - name: Upload Docker E2E results if: failure() uses: actions/upload-artifact@v4 with: name: docker-e2e-test-results path: apps/web/e2e/test-results/ retention-days: 7 e2e-report: name: Merge E2E Reports needs: [e2e, e2e-docker] if: always() runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up pnpm uses: pnpm/action-setup@v4 with: version: "9" - name: Set up Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install dependencies working-directory: apps run: pnpm install --frozen-lockfile - name: Download blob reports uses: actions/download-artifact@v4 with: path: apps/web/e2e/blob-reports pattern: blob-report-* merge-multiple: true - name: Merge reports working-directory: apps/web run: npx playwright merge-reports --reporter=html ./e2e/blob-reports - name: Upload merged report uses: actions/upload-artifact@v4 with: name: playwright-report path: apps/web/playwright-report/ retention-days: 14