feat: bind repository to Linear/Jira/Sentry issue watchers #9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | |
| # Required so the containerized build / e2e shards / e2e-report jobs can | |
| # pull the kandev-ci image from GHCR via GITHUB_TOKEN. | |
| packages: read | |
| jobs: | |
| build: | |
| name: Build | |
| runs-on: ubuntu-latest | |
| # Pre-baked image bundles Go, Node 24, pnpm 9.15.9 — see | |
| # .github/docker/ci-base/Dockerfile. Uses the `build` tag (with Go) | |
| # because this job compiles the backend. | |
| container: ghcr.io/kdlbs/kandev-ci:build-latest | |
| timeout-minutes: 10 | |
| # Container jobs default to `sh`; pin to bash for the few shell steps below. | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Mark workspace safe for git | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| # Inside the container the home dir is /root, so the pnpm store lives at | |
| # /root/.local/share/pnpm/store. actions/cache resolves `~` against $HOME | |
| # so the same path string works for both the host and container variants. | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/pnpm/store | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - 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@v5 | |
| with: | |
| name: e2e-backend-build | |
| path: apps/backend/bin/ | |
| retention-days: 1 | |
| # Tar the Vite dist directory so shards can reuse the same static web build. | |
| - name: Package web build | |
| run: tar -cf apps/web/web-dist.tar -C apps/web dist | |
| - name: Upload web build | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: e2e-web-build | |
| path: apps/web/web-dist.tar | |
| retention-days: 1 | |
| e2e: | |
| name: E2E Shard ${{ matrix.shard }}/14 | |
| needs: build | |
| runs-on: ubuntu-latest | |
| # Pre-baked image bundles Node 24, pnpm 9.15.9, and the Playwright browser | |
| # binaries (Chromium etc.) — see .github/docker/ci-base/Dockerfile. This is | |
| # the biggest speedup since `npx playwright install chromium --with-deps` | |
| # alone took ~1.5–2 min per shard. | |
| # | |
| # `--ipc=host` is required for Chromium: the default container `/dev/shm` | |
| # is only ~64MiB, which causes tabs to slow to a crawl and renderers to | |
| # crash mid-test. | |
| # Uses the `runtime` tag — no Go toolchain needed; the shards consume the | |
| # pre-built backend artifact from the `build` job. ~600 MB smaller pull | |
| # than the build image, saves ~20s per shard at startup. | |
| container: | |
| image: ghcr.io/kdlbs/kandev-ci:runtime-latest | |
| options: --ipc=host | |
| # 25 min (was 22) — leaves room for the worst-case "every shard hits a few | |
| # flake-retries" combination without blowing past the cap. Average shard | |
| # runs ~10 min, so this only kicks in when something is going wrong. | |
| timeout-minutes: 25 | |
| # Container jobs default to `sh`; pin to bash. | |
| defaults: | |
| run: | |
| shell: bash | |
| # Keep localhost resolution deterministic for Node fetches used by the | |
| # Playwright fixtures and backend readiness polling. | |
| env: | |
| NODE_OPTIONS: --dns-result-order=ipv4first | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Mark workspace safe for git | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/pnpm/store | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install dependencies | |
| working-directory: apps | |
| run: pnpm install --frozen-lockfile | |
| - name: Download backend build | |
| uses: actions/download-artifact@v5 | |
| 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@v5 | |
| with: | |
| name: e2e-web-build | |
| path: apps/web/ | |
| - name: Extract web build | |
| working-directory: apps/web | |
| run: tar -xf web-dist.tar && rm web-dist.tar | |
| # Pinned to chromium + mobile-chrome so the dedicated `e2e-containers` | |
| # job below owns the real-Docker project (which covers both the Docker | |
| # executor and the SSH executor's sshd target). Without --project the | |
| # container-bound specs would run here too, fail seeding (Docker not | |
| # enabled for these shards), and spam every shard. | |
| - name: Run E2E tests (shard ${{ matrix.shard }}/14) | |
| working-directory: apps/web | |
| run: npx playwright test --config e2e/playwright.config.ts --project=chromium --project=mobile-chrome --shard=${{ matrix.shard }}/14 | |
| - name: Upload blob report | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| 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@v5 | |
| 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. Container-bound tests are slow (~2 min each), so we shard across | |
| # 6 runners — Playwright's --shard splits the project's tests into N groups. | |
| # 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. | |
| # | |
| # This job runs the `containers` Playwright project, which covers both the | |
| # Docker executor and the SSH executor (since the SSH executor's e2e tests | |
| # also rely on Docker to host a real sshd container as the remote target). | |
| # Sharded 6 ways — each shard is its own runner. See apps/web/e2e/README.md. | |
| e2e-containers: | |
| name: E2E Containers Shard ${{ matrix.shard }}/6 | |
| needs: build | |
| # This job intentionally runs on the host ubuntu runner (NOT the kandev-ci | |
| # container image): the containers project needs direct access to the | |
| # host Docker daemon, and a docker-in-docker setup would break the tests' | |
| # host-style bind mounts (paths under os.tmpdir() inside a runner container | |
| # wouldn't exist on the host where the docker daemon resolves them). | |
| # We still avoid the slow `playwright install --with-deps` step by copying | |
| # browsers out of the kandev-ci image — see the extract step below. | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4, 5, 6] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Verify Docker is available | |
| run: docker info | |
| - name: Set up pnpm | |
| uses: pnpm/action-setup@v5 | |
| with: | |
| version: "9.15.9" | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/pnpm/store | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install dependencies | |
| working-directory: apps | |
| run: pnpm install --frozen-lockfile | |
| # Required for the docker pull below to succeed even if the kandev-ci | |
| # package is private on GHCR. The `e2e` / `e2e-report` jobs above don't | |
| # need this because GitHub's `container:` integration handles auth | |
| # implicitly using GITHUB_TOKEN. | |
| - name: Log in to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Skip the Playwright CDN download (~7 min and an outage-prone path) by | |
| # copying the prebaked chromium out of the kandev-ci image, which is | |
| # `FROM mcr.microsoft.com/playwright:...` and already ships the matching | |
| # browser at /ms-playwright. Pulling the `runtime` tag (matches the | |
| # `e2e` / `e2e-report` jobs above) — the `build` tag has the same | |
| # browser layer but is ~300 MB larger because of the Go toolchain. | |
| - name: Extract Playwright browsers from kandev-ci image | |
| run: | | |
| set -euo pipefail | |
| docker pull ghcr.io/kdlbs/kandev-ci:runtime-latest | |
| mkdir -p /tmp/ms-playwright | |
| docker run --rm \ | |
| -v /tmp/ms-playwright:/dest \ | |
| ghcr.io/kdlbs/kandev-ci:runtime-latest \ | |
| cp -a /ms-playwright/. /dest/ | |
| echo "PLAYWRIGHT_BROWSERS_PATH=/tmp/ms-playwright" >> "$GITHUB_ENV" | |
| # Browser binaries come from the image above. GitHub's Ubuntu runners | |
| # usually already have the shared libs Chromium needs, so smoke-test the | |
| # copied browser first and only hit apt as a bounded fallback. | |
| - name: Verify Playwright browser runtime | |
| working-directory: apps/web | |
| run: | | |
| set -euo pipefail | |
| smoke_test() { | |
| node <<'NODE' | |
| const { chromium } = require("@playwright/test"); | |
| (async () => { | |
| const browser = await chromium.launch({ headless: true }); | |
| const page = await browser.newPage(); | |
| await page.goto("data:text/html,ok"); | |
| await browser.close(); | |
| })().catch((error) => { | |
| console.error(error); | |
| process.exit(1); | |
| }); | |
| NODE | |
| } | |
| if smoke_test; then | |
| echo "Playwright Chromium launches with existing runner packages." | |
| exit 0 | |
| fi | |
| echo "::warning::Playwright Chromium failed to launch; installing missing host packages." | |
| timeout 180s npx playwright install-deps chromium | |
| smoke_test | |
| - name: Download backend build | |
| uses: actions/download-artifact@v5 | |
| 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@v5 | |
| with: | |
| name: e2e-web-build | |
| path: apps/web/ | |
| - name: Extract web build | |
| working-directory: apps/web | |
| run: tar -xf web-dist.tar && rm web-dist.tar | |
| - name: Run container-backed E2E tests shard ${{ matrix.shard }}/6 (Docker executor + SSH executor) | |
| working-directory: apps/web | |
| env: | |
| KANDEV_E2E_CONTAINERS: "1" | |
| run: npx playwright test --config e2e/playwright.config.ts --project=containers --shard=${{ matrix.shard }}/6 | |
| - 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 containers results land in the unified HTML report alongside | |
| # the sharded chromium/mobile-chrome runs. | |
| - name: Upload containers blob report | |
| if: always() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: blob-report-containers-${{ matrix.shard }} | |
| path: apps/web/e2e/blob-report/ | |
| retention-days: 3 | |
| - name: Upload containers E2E results | |
| if: failure() | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: containers-e2e-test-results-${{ matrix.shard }} | |
| path: apps/web/e2e/test-results/ | |
| retention-days: 7 | |
| e2e-report: | |
| name: Merge E2E Reports | |
| needs: [e2e, e2e-containers] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| # Pre-baked image bundles Node 24, pnpm 9.15.9, and the Playwright CLI. | |
| # `runtime` tag — no Go needed for merging blob reports. | |
| container: ghcr.io/kdlbs/kandev-ci:runtime-latest | |
| timeout-minutes: 5 | |
| # Container jobs default to `sh`; pin to bash. | |
| defaults: | |
| run: | |
| shell: bash | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Mark workspace safe for git | |
| run: git config --global --add safe.directory "$GITHUB_WORKSPACE" | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/pnpm/store | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('apps/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install dependencies | |
| working-directory: apps | |
| run: pnpm install --frozen-lockfile | |
| - name: Download blob reports | |
| uses: actions/download-artifact@v5 | |
| with: | |
| path: apps/web/e2e/blob-reports | |
| pattern: blob-report-* | |
| merge-multiple: true | |
| - name: Merge reports | |
| working-directory: apps/web | |
| # `-c e2e/playwright.config.ts` is required because the blobs come from | |
| # two different absolute checkout paths: the containerized `e2e` shards | |
| # check out under `/__w/...` while `e2e-containers` runs on the host | |
| # runner and checks out under `/home/runner/work/...`. Playwright | |
| # embeds the absolute testDir in each blob and refuses to merge | |
| # reports recorded with different test directories — passing the | |
| # config makes it normalize against the config's `testDir` instead. | |
| run: npx playwright merge-reports --config e2e/playwright.config.ts --reporter=html ./e2e/blob-reports | |
| - name: Upload merged report | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: playwright-report | |
| path: apps/web/playwright-report/ | |
| retention-days: 14 |