name: E2E Headed Chrome on: # E2E removed from `pull_request` to keep PR feedback under ~2 minutes; PR-time # protection is the CI workflow (typecheck / unit / lint / adapter / build). # E2E still guards `main` directly, runs nightly, and on release tag push so # protocol/CDP/extension contract regressions are caught before they ship. push: branches: [main, dev] paths: - 'extension/**' - 'src/browser/**' - 'src/daemon.ts' - 'src/execution.ts' - 'src/interceptor.ts' - 'tests/e2e/**' - 'tests/smoke/**' - '.github/actions/setup-chrome/**' - '.github/workflows/e2e-headed.yml' tags: ['v*'] schedule: # Daily 08:00 UTC — catch flake / Chrome-version drift even when no commits # touched the watched paths recently. - cron: '0 8 * * *' workflow_dispatch: concurrency: group: e2e-${{ github.ref }} cancel-in-progress: true jobs: e2e-headed: runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # Gate placement by what each runner can run deterministically: # - the real-browser extension smoke needs a Chrome that reliably runs # an MV3 extension, which only Linux+xvfb provides on hosted runners # (headed macOS crashes on Mach port rendezvous outside an Aqua # session; headless does not connect the extension SW there); # - the daemon transport contracts need no browser and run blocking on # every OS, so macOS/Windows get a real gate, not a skipped one. # macOS pinned to 15 while the macOS 26 image stabilizes. os: [ubuntu-latest, macos-15, windows-latest] timeout-minutes: 20 steps: - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version: '22' cache: 'npm' - name: Install dependencies run: npm ci # Linux runs the extension smoke and macOS runs the full real-site e2e # suite; both need a real Chrome. Windows runs only the browser-free # transport gate, and the setup-chrome action hangs on Windows anyway. - name: Setup Chrome if: runner.os != 'Windows' uses: ./.github/actions/setup-chrome id: setup-chrome - name: Build run: npm run build - name: Build extension run: npm run build --prefix extension # Real-browser extension smoke: Linux under xvfb is the one hosted # environment where a real Chrome reliably starts an MV3 extension, so # this is the release-blocking browser gate. Headed (not headless): # headless does not connect the extension service worker on hosted # runners. See the matrix comment for why macOS/Windows don't run it. - name: Run AX Chrome smoke (Linux, real extension via xvfb) if: runner.os == 'Linux' env: CHROME_PATH: ${{ steps.setup-chrome.outputs.chrome-path }} OPENCLI_AX_E2E: '1' OPENCLI_E2E_HEADED: '1' run: | xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \ npx vitest run --project e2e tests/e2e/browser-ax-chrome.test.ts --reporter=verbose # Transport contract E2E: real daemon process + scripted fake extension. # Pins the cross-layer contracts (waiter attach, deadline 408, dispatched # disconnect, profile fallback, graceful shutdown) end to end with the # actual daemon binary — no browser required, so this is the blocking # gate on EVERY OS, including macOS and Windows. - name: Run daemon transport contract E2E run: npx vitest run --project e2e-fixed-port tests/e2e/daemon-transport.test.ts --reporter=verbose - name: Run E2E tests (Linux, via xvfb) if: runner.os == 'Linux' env: OPENCLI_AX_E2E: '0' run: | xvfb-run --auto-servernum --server-args="-screen 0 1280x720x24" \ npx vitest run tests/e2e/ --reporter=verbose # Real-site adapter e2e stays on Linux/macOS; Windows runs the two # deterministic gates above (unit coverage in ci.yml already spans it). - name: Run E2E tests (macOS) if: runner.os == 'macOS' env: OPENCLI_AX_E2E: '0' run: npx vitest run tests/e2e/ --reporter=verbose