Files
opencode/.github/workflows/check-test.yml
Dax Raad 342d8f1e3b chore(ci): gate a stable release on the linux browser suites
the windows playwright legs still fail on dev pushes often enough that a
release gate over them would stall shipping. unit tests and builds still cover
both OSes, and dev pushes and nightly runs still cover the windows browser
legs.
2026-11-10 21:47:12 -05:00

356 lines
15 KiB
YAML

name: check-test
# Pull requests run the fast tier: the affected unit tests on Linux. The full tier — unit tests on Windows, the
# compiled builds, and the Playwright suites on both OSes — runs on pushes to dev and beta and on nightly and manual
# runs. A stable release also runs it through release.yml's gate, with the browser suites on Linux only.
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
workflow_call:
inputs:
quarantine:
description: Run only the quarantined browser tests (packages/app/e2e/QUARANTINE.md)
type: boolean
default: false
release_gate:
description: Run the browser suites on Linux only
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
# Push and pull request runs test only what changed. Manual runs and the release gate test everything: they
# have no base commit to diff against.
AFFECTED: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
# Pull requests diff against the merge's first parent; pushes diff against the previous dev head.
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }}
TURBO_SCM_HEAD: ${{ github.sha }}
jobs:
# Only the browser suites need this, and pull requests run no browser suites.
affected:
name: affected packages
if: ${{ !github.event.pull_request.draft && github.event_name != 'pull_request' }}
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 }}
fetch-depth: 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
run: |
if [ "$AFFECTED" != "true" ]; 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:
# Pull requests run Linux only; Windows runs the same tests on pushes, nightly runs and the release gate.
settings: ${{ fromJSON(github.event_name == 'pull_request' && '[{"name":"linux","host":"blacksmith-4vcpu-ubuntu-2404"}]' || '[{"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 [ "$AFFECTED" = "true" ]; then
GITHUB_ACTIONS=false bun turbo test --affected --concurrency=3
exit 0
fi
GITHUB_ACTIONS=false bun turbo test --concurrency=3
env:
OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER: ${{ runner.os == 'Windows' && 'true' || 'false' }}
- 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 [ "$AFFECTED" = "true" ]; then
bun turbo verify:package --affected --filter=@opencode/sdk
exit 0
fi
bun turbo verify:package --filter=@opencode/sdk
- 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 }})
# Compiling the CLI and smoke-testing its service lifecycle is part of the full tier.
if: ${{ !inputs.quarantine && github.event_name != 'pull_request' && !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 }}
fetch-depth: 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 [ "$AFFECTED" = "true" ]; then
bun turbo build --affected --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...'
exit 0
fi
bun turbo build --concurrency=3 --filter='@opencode/cli^...' --filter='@opencode/app^...'
- 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:
# The release gate runs the Linux browser suites only: the Windows legs still fail on dev pushes often
# enough that a stable release must not wait for them. Pushes and nightly runs cover Windows.
settings: ${{ fromJSON(inputs.release_gate && '[{"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.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.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.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.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
# One check per OS for the browser suites: it passes only when every leg above passed, or when no leg was
# needed because the changed packages have no browser tests.
e2e:
name: e2e (${{ matrix.os }})
needs: [affected, e2e-suite]
if: ${{ !cancelled() && !inputs.quarantine && github.event_name != 'pull_request' && !github.event.pull_request.draft }}
strategy:
fail-fast: false
matrix:
os: [linux, windows]
# Aggregates results only; these do not need a paid runner.
runs-on: ubuntu-24.04
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: |
echo "affected packages: $AFFECTED, browser test legs: $SUITES (expected $EXPECTED)"
test "$AFFECTED" = success && test "$SUITES" = "$EXPECTED"