name: check-test on: push: branches: - dev - beta pull_request: types: [opened, synchronize, reopened, ready_for_review] workflow_dispatch: inputs: quarantine: description: Run only the quarantined browser tests (packages/app/e2e/QUARANTINE.md) type: boolean default: false concurrency: # A new push to a pull request cancels its stale run. Every push and manual run keeps its own group, so each # dev and beta commit gets a verdict and a quarantine run never cancels a normal one. group: ${{ case(github.event_name == 'pull_request', format('{0}-{1}', github.workflow, github.event.pull_request.number), format('{0}-{1}', github.workflow, github.run_id)) }} cancel-in-progress: true permissions: contents: read checks: write env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: affected: name: affected packages if: ${{ !github.event.pull_request.draft }} runs-on: blacksmith-4vcpu-ubuntu-2404 outputs: e2e: ${{ steps.packages.outputs.app }} steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version-file: package.json - name: Find affected packages id: packages env: TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} TURBO_SCM_HEAD: ${{ github.sha }} run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "app=true" >> "$GITHUB_OUTPUT" exit 0 fi # packages/stories hosts the component tests, but no app package depends on it. bun x turbo@2.10.2 ls --affected --filter=@opencode/app --filter=@opencode/stories --output=json > affected.json bun -e 'const result = await Bun.file("affected.json").json(); console.log(`app=${result.packages.count > 0}`)' >> "$GITHUB_OUTPUT" unit: name: unit (${{ matrix.settings.name }}) if: ${{ !inputs.quarantine && !github.event.pull_request.draft }} strategy: fail-fast: false matrix: settings: - name: linux host: blacksmith-4vcpu-ubuntu-2404 - name: windows host: blacksmith-4vcpu-windows-2025 runs-on: ${{ matrix.settings.host }} defaults: run: shell: bash steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "24" - name: Setup Bun uses: ./.github/actions/setup-bun - name: Test Effect simplification rules if: runner.os == 'Linux' run: bun run test:effect-simplification-rules - name: Check Effect simplifications if: runner.os == 'Linux' run: bun run lint:effect-simplifications - name: Configure git identity run: | git config --global user.email "bot@opencode.ai" git config --global user.name "opencode" - name: Run unit tests timeout-minutes: 20 run: | # The runners have four vCPUs, and each Bun test process performs its own concurrent work. if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then GITHUB_ACTIONS=false bun turbo test --concurrency=3 exit 0 fi GITHUB_ACTIONS=false bun turbo test --affected --concurrency=3 env: OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }} TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} TURBO_SCM_HEAD: ${{ github.sha }} - name: Verify packed codemode package if: runner.os == 'Linux' working-directory: packages/codemode run: bun run pack - name: Verify packed workerd SDK if: runner.os == 'Linux' timeout-minutes: 15 run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then bun turbo verify:package --filter=@opencode/sdk exit 0 fi bun turbo verify:package --affected --filter=@opencode/sdk env: TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} TURBO_SCM_HEAD: ${{ github.sha }} - name: Check generated client if: runner.os == 'Linux' working-directory: packages/client run: bun run check:generated - name: Check generated OpenAPI document if: runner.os == 'Linux' working-directory: packages/protocol run: bun run check:generated build: name: build (${{ matrix.settings.name }}) if: ${{ !inputs.quarantine && !github.event.pull_request.draft }} strategy: fail-fast: false matrix: settings: - name: linux host: blacksmith-4vcpu-ubuntu-2404 - name: windows host: blacksmith-4vcpu-windows-2025 runs-on: ${{ matrix.settings.host }} defaults: run: shell: bash steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} # Pull requests diff against the merge's first parent; pushes diff against the previous dev head. fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Setup Bun uses: ./.github/actions/setup-bun - name: Verify compiled service lifecycle timeout-minutes: 10 working-directory: packages/cli env: NODE_OPTIONS: ${{ runner.os == 'Windows' && '--max-old-space-size=4096' || '' }} run: | bun run script/build.ts --single --skip-install bun run script/service-smoke.ts # Unit tests import workspace sources and never build the libraries they use, so this checks those builds. The # CLI and app closures hold every library a tested package depends on. The builds are tsc and Bun.build with no # platform-specific steps, and the libraries publish from Linux, so Windows skips them. - name: Build workspace libraries if: ${{ !cancelled() && runner.os == 'Linux' }} timeout-minutes: 10 run: | if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then bun turbo build --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' exit 0 fi bun turbo build --affected --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...' env: TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }} TURBO_SCM_HEAD: ${{ github.sha }} - name: Setup Node build runtime if: ${{ !cancelled() }} uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: "26.4.0" - name: Verify Node build if: ${{ !cancelled() }} timeout-minutes: 15 working-directory: packages/cli env: NODE_OPTIONS: ${{ runner.os == 'Windows' && '--max-old-space-size=4096' || '' }} run: | bun run script/build-node.ts --single --skip-install --outdir=dist/node bun run script/service-smoke.ts --node e2e-suite: name: e2e (${{ matrix.settings.name }}, ${{ matrix.suite.id }}${{ matrix.quarantine && ', quarantine' || '' }}) needs: affected if: needs.affected.outputs.e2e == 'true' # Quarantined tests are known red; their legs report without failing the e2e checks. continue-on-error: ${{ matrix.quarantine }} strategy: fail-fast: false matrix: # Beta PRs gate on Linux; Windows runs on pushes and manual runs (nightly-beta.yml dispatches beta). settings: ${{ fromJSON(github.base_ref == 'beta' && '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"}]' || '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"},{"name":"windows","host":"blacksmith-4vcpu-windows-2025"}]') }} quarantine: ${{ fromJSON(inputs.quarantine && '[true]' || '[false]') }} # Playwright splits whole spec files across shards by test count, not duration. Every app-N # entry must use the same total, and the entries must cover 1..total. The service worker suite # reuses shard 2's production build. Each component suite runs once per OS in its own leg, so the # two suites' story servers and test time are not added up on one runner. suite: - id: app-1 shard: 1/2 - id: app-2 shard: 2/2 - id: session-ui-components - id: app-components runs-on: ${{ matrix.settings.host }} env: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers # Beta only (packages/app/e2e/QUARANTINE.md): gate legs skip quarantined tests, retry once and stop after 8 # failures; quarantine legs run only those tests, to the end. Other branches keep the config defaults. PLAYWRIGHT_ARGS: ${{ matrix.quarantine && '--grep @quarantine --pass-with-no-tests --retries=1' || (github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && '--grep-invert @quarantine --max-failures=8 --retries=1' || '' }} defaults: run: shell: bash steps: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: # Playwright 1.59 hangs while extracting Chromium with Node 24.16. node-version: "24.15" - name: Setup Bun uses: ./.github/actions/setup-bun - name: Read Playwright version id: playwright-version run: | version=$(node -e 'console.log(require("./package.json").workspaces.catalog["@playwright/test"])') echo "version=$version" >> "$GITHUB_OUTPUT" - name: Cache Playwright browsers id: playwright-cache uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ github.workspace }}/.playwright-browsers key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright-version.outputs.version }}-chromium - name: Install Playwright system dependencies if: runner.os == 'Linux' working-directory: packages/app run: bunx playwright install-deps chromium - name: Install Playwright browsers if: steps.playwright-cache.outputs.cache-hit != 'true' working-directory: packages/app run: bunx playwright install chromium - name: Run app e2e tests against production build if: matrix.suite.shard run: bun --cwd packages/app test:e2e:built --shard=${{ matrix.suite.shard }} ${{ env.PLAYWRIGHT_ARGS }} env: CI: true timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Verify service worker precaching and upgrades if: matrix.suite.shard == '2/2' && !matrix.quarantine working-directory: packages/app run: bunx playwright test --config e2e/service-worker/playwright.config.ts timeout-minutes: 5 - name: Run session UI component tests if: matrix.suite.id == 'session-ui-components' run: bun --cwd packages/session-ui test:components ${{ env.PLAYWRIGHT_ARGS }} env: CI: true timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Run app component tests if: matrix.suite.id == 'app-components' run: bun --cwd packages/app test:components ${{ env.PLAYWRIGHT_ARGS }} env: CI: true timeout-minutes: ${{ (matrix.quarantine || github.base_ref == 'beta' || github.ref == 'refs/heads/beta') && 10 || 15 }} - name: Upload Playwright artifacts if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: playwright-${{ matrix.settings.name }}-${{ matrix.suite.id }}${{ matrix.quarantine && '-quarantine' || '' }}-${{ github.run_attempt }} if-no-files-found: ignore retention-days: 7 path: | packages/app/e2e/test-results packages/app/e2e/playwright-report packages/session-ui/component-tests/test-results packages/session-ui/component-tests/playwright-report packages/app/component-tests/test-results packages/app/component-tests/playwright-report # Branch rules require one e2e check per OS. These keep those names and pass only when every # leg above passed, or when no leg was needed; a failing leg on either OS fails both checks. e2e: name: e2e (${{ matrix.os }}) needs: [affected, e2e-suite] if: ${{ !cancelled() && !inputs.quarantine && !github.event.pull_request.draft }} strategy: fail-fast: false matrix: os: [linux, windows] runs-on: blacksmith-4vcpu-ubuntu-2404 steps: - name: Check browser test results env: AFFECTED: ${{ needs.affected.result }} SUITES: ${{ needs.e2e-suite.result }} EXPECTED: ${{ needs.affected.outputs.e2e == 'true' && 'success' || 'skipped' }} run: | if [ "${{ matrix.os }}" = windows ] && [ "${{ github.base_ref }}" = beta ]; then echo "Windows browser tests run on beta pushes and nightly, not on beta pull requests" exit 0 fi echo "affected packages: $AFFECTED, browser test legs: $SUITES (expected $EXPECTED)" test "$AFFECTED" = success && test "$SUITES" = "$EXPECTED"