Skip to content

🤖 refactor: simplify CI and tests folder structure #5762

🤖 refactor: simplify CI and tests folder structure

🤖 refactor: simplify CI and tests folder structure #5762

Workflow file for this run

name: CI
on:
pull_request:
branches: ["**"]
merge_group:
workflow_dispatch:
inputs:
test_filter:
description: 'Optional test filter (e.g., "workspace", "tests/file.test.ts", or "-t pattern")'
required: false
type: string
# This filter is passed to unit tests, integration tests, e2e tests, and storybook tests
# to enable faster iteration when debugging specific test failures in CI
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# Detect what files changed to enable selective test execution
changes:
name: Detect Changes
runs-on: ubuntu-latest
outputs:
docs-only: ${{ steps.filter.outputs.docs-only }}
browser-only: ${{ steps.filter.outputs.browser-only }}
node-backend: ${{ steps.filter.outputs.node-backend }}
ci-config: ${{ steps.filter.outputs.ci-config }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Detect file changes
uses: dorny/paths-filter@v3
id: filter
with:
filters: |
docs-only:
- 'docs/**'
- '*.md'
- 'LICENSE'
- '.vscode/**'
browser-only:
- 'src/browser/**'
- 'src/styles/**'
- 'src/components/**'
- 'src/hooks/**'
- '*.stories.tsx'
- '.storybook/**'
node-backend:
- 'src/node/**'
- 'src/cli/**'
- 'src/desktop/**'
- 'tests/integration/**'
ci-config:
- '.github/**'
- 'jest.config.cjs'
- 'babel.config.cjs'
- 'package.json'
- 'bun.lockb'
- 'tsconfig*.json'
static-check:
name: Static Checks (lint + typecheck + fmt)
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- uses: ./.github/actions/setup-mux
- name: Generate version file
run: ./scripts/generate-version.sh
- name: Cache shfmt
id: cache-shfmt
uses: actions/cache@v4
with:
path: ~/.local/bin/shfmt
# We install latest via webinstall; reflect that in the cache key to avoid pinning mismatches
key: ${{ runner.os }}-shfmt-latest
restore-keys: |
${{ runner.os }}-shfmt-
- name: Install and setup shfmt
run: |
# Install shfmt if not cached or if cached binary is broken
if [[ ! -f "$HOME/.local/bin/shfmt" ]] || ! "$HOME/.local/bin/shfmt" --version >/dev/null 2>&1; then
curl -sS https://webinstall.dev/shfmt | bash
fi
echo "$HOME/.local/bin" >> $GITHUB_PATH
# Verify shfmt is available
"$HOME/.local/bin/shfmt" --version
- name: Install Nix
uses: cachix/install-nix-action@v27
with:
extra_nix_config: |
experimental-features = nix-command flakes
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Add uv to PATH
run: echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Run static checks
run: make -j3 static-check
test:
name: Unit Tests
needs: [changes]
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Skip (docs-only changes)
if: needs.changes.outputs.docs-only == 'true' && needs.changes.outputs.node-backend != 'true' && needs.changes.outputs.browser-only != 'true' && needs.changes.outputs.ci-config != 'true'
run: |
echo "::notice::Skipping unit tests - docs-only changes"
echo "## Unit Tests Skipped" >> $GITHUB_STEP_SUMMARY
echo "Only documentation files changed." >> $GITHUB_STEP_SUMMARY
- uses: ./.github/actions/setup-mux
if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
- name: Build worker files
if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
run: make build-main
- name: Run tests with coverage
if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
run: bun test --coverage --coverage-reporter=lcov ${{ github.event.inputs.test_filter || 'src' }}
- name: Upload coverage to Codecov
if: needs.changes.outputs.docs-only != 'true' || needs.changes.outputs.node-backend == 'true' || needs.changes.outputs.browser-only == 'true' || needs.changes.outputs.ci-config == 'true'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
flags: unit-tests
fail_ci_if_error: false
integration-test:
name: Integration Tests
needs: [changes]
timeout-minutes: 10
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
# Skip conditions: docs-only OR browser-only (no backend changes)
- name: Check if tests should run
id: should-run
run: |
DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
# Run if: manual filter provided, OR node-backend changed, OR ci-config changed
# Skip if: docs-only OR (browser-only AND NOT node-backend AND NOT ci-config)
if [[ -n "${{ github.event.inputs.test_filter }}" ]]; then
echo "run=true" >> $GITHUB_OUTPUT
echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
elif [[ "$NODE_BACKEND" == "true" ]] || [[ "$CI_CONFIG" == "true" ]]; then
echo "run=true" >> $GITHUB_OUTPUT
echo "reason=Backend or CI config changes detected" >> $GITHUB_OUTPUT
elif [[ "$DOCS_ONLY" == "true" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
elif [[ "$BROWSER_ONLY" == "true" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Browser-only changes (no backend impact)" >> $GITHUB_OUTPUT
else
# Default: run tests (safety fallback)
echo "run=true" >> $GITHUB_OUTPUT
echo "reason=Default - running all tests" >> $GITHUB_OUTPUT
fi
- name: Skip integration tests
if: steps.should-run.outputs.run != 'true'
run: |
echo "::notice::Skipping integration tests - ${{ steps.should-run.outputs.reason }}"
echo "## Integration Tests Skipped" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
- uses: ./.github/actions/setup-mux
if: steps.should-run.outputs.run == 'true'
- name: Build worker files
if: steps.should-run.outputs.run == 'true'
run: make build-main
- name: Run integration tests
if: steps.should-run.outputs.run == 'true'
# --silent suppresses per-test output (17+ test files × workers = overwhelming logs)
run: TEST_INTEGRATION=1 bun x jest --coverage --maxWorkers=100% --silent ${{ github.event.inputs.test_filter || 'tests' }}
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- name: Upload coverage to Codecov
if: steps.should-run.outputs.run == 'true'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
flags: integration-tests
fail_ci_if_error: false
storybook-test:
name: Storybook Interaction Tests
needs: [changes]
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Check if tests should run
id: should-run
run: |
DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
MANUAL_FILTER="${{ github.event.inputs.test_filter }}"
# Skip if manual filter (user wants specific tests) or docs-only
if [[ -n "$MANUAL_FILTER" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
else
echo "run=true" >> $GITHUB_OUTPUT
fi
- name: Skip storybook tests
if: steps.should-run.outputs.run != 'true'
run: |
echo "::notice::Skipping storybook tests - ${{ steps.should-run.outputs.reason }}"
echo "## Storybook Tests Skipped" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
- uses: ./.github/actions/setup-mux
if: steps.should-run.outputs.run == 'true'
- uses: ./.github/actions/setup-playwright
if: steps.should-run.outputs.run == 'true'
- name: Build Storybook
if: steps.should-run.outputs.run == 'true'
run: make storybook-build
- name: Serve Storybook
if: steps.should-run.outputs.run == 'true'
run: |
bun x http-server storybook-static -p 6006 &
sleep 5
- name: Run Storybook tests
if: steps.should-run.outputs.run == 'true'
run: make test-storybook
e2e-test:
name: E2E Tests (${{ matrix.os }})
needs: [changes]
strategy:
fail-fast: false
matrix:
include:
# Linux: comprehensive E2E tests
- os: linux
runner: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
test_scope: "all"
# macOS: window lifecycle and platform-dependent tests only
- os: macos
runner: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest' }}
test_scope: "window-lifecycle"
runs-on: ${{ matrix.runner }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Check if tests should run
id: should-run
run: |
DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
BROWSER_ONLY="${{ needs.changes.outputs.browser-only }}"
NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
MANUAL_FILTER="${{ github.event.inputs.test_filter }}"
# Skip if manual filter or docs-only
if [[ -n "$MANUAL_FILTER" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Manual test filter provided" >> $GITHUB_OUTPUT
elif [[ "$DOCS_ONLY" == "true" ]] && [[ "$BROWSER_ONLY" != "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
else
echo "run=true" >> $GITHUB_OUTPUT
fi
- name: Skip E2E tests
if: steps.should-run.outputs.run != 'true'
run: |
echo "::notice::Skipping E2E tests - ${{ steps.should-run.outputs.reason }}"
echo "## E2E Tests Skipped" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
- uses: ./.github/actions/setup-mux
if: steps.should-run.outputs.run == 'true'
- name: Install xvfb (Linux)
if: steps.should-run.outputs.run == 'true' && matrix.os == 'linux'
run: |
sudo apt-get update
sudo apt-get install -y xvfb
- uses: ./.github/actions/setup-playwright
if: steps.should-run.outputs.run == 'true'
- name: Run comprehensive e2e tests (Linux)
if: steps.should-run.outputs.run == 'true' && matrix.os == 'linux'
run: xvfb-run -a make test-e2e
env:
ELECTRON_DISABLE_SANDBOX: 1
- name: Run window lifecycle e2e tests (macOS)
if: steps.should-run.outputs.run == 'true' && matrix.os == 'macos'
run: make test-e2e PLAYWRIGHT_ARGS="tests/e2e/scenarios/windowLifecycle.spec.ts"
docker-smoke-test:
name: Docker Smoke Test
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
# Only run in merge queue (Docker builds are slow)
if: github.event_name == 'merge_group'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: .
load: true
tags: mux-server:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Start container
run: |
docker run -d --name mux-test -p 3000:3000 mux-server:test
# Wait for server to be ready
for i in {1..30}; do
if curl -sf http://localhost:3000/health; then
echo "Server is ready"
break
fi
echo "Waiting for server... ($i/30)"
sleep 1
done
- name: Health check
run: |
response=$(curl -sf http://localhost:3000/health)
echo "Health response: $response"
if ! echo "$response" | grep -q '"status":"ok"'; then
echo "Health check failed"
exit 1
fi
- name: Version check
run: |
response=$(curl -sf http://localhost:3000/version)
echo "Version response: $response"
# Verify response contains expected fields
if ! echo "$response" | grep -q '"mode":"server"'; then
echo "Version check failed: missing mode=server"
exit 1
fi
if ! echo "$response" | grep -q '"git_describe"'; then
echo "Version check failed: missing git_describe field"
exit 1
fi
- name: Show container logs on failure
if: failure()
run: docker logs mux-test
- name: Cleanup
if: always()
run: docker rm -f mux-test || true
mux-server-smoke-test:
name: Mux Server Smoke Test
needs: [changes]
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Check if tests should run
id: should-run
run: |
DOCS_ONLY="${{ needs.changes.outputs.docs-only }}"
NODE_BACKEND="${{ needs.changes.outputs.node-backend }}"
CI_CONFIG="${{ needs.changes.outputs.ci-config }}"
# Skip if docs-only, run if backend or CI changes
if [[ "$DOCS_ONLY" == "true" ]] && [[ "$NODE_BACKEND" != "true" ]] && [[ "$CI_CONFIG" != "true" ]]; then
echo "run=false" >> $GITHUB_OUTPUT
echo "reason=Docs-only changes" >> $GITHUB_OUTPUT
else
echo "run=true" >> $GITHUB_OUTPUT
fi
- name: Skip smoke test
if: steps.should-run.outputs.run != 'true'
run: |
echo "::notice::Skipping mux server smoke test - ${{ steps.should-run.outputs.reason }}"
echo "## Mux Server Smoke Test Skipped" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.should-run.outputs.reason }}" >> $GITHUB_STEP_SUMMARY
- uses: ./.github/actions/setup-mux
if: steps.should-run.outputs.run == 'true'
- name: Build application
if: steps.should-run.outputs.run == 'true'
run: make build
- name: Pack npm package
if: steps.should-run.outputs.run == 'true'
run: npm pack
- name: Run smoke test
if: steps.should-run.outputs.run == 'true'
env:
SERVER_PORT: 3001
SERVER_HOST: localhost
STARTUP_TIMEOUT: 30
run: |
# Find the actual tarball name (version may vary)
TARBALL=$(ls mux-*.tgz | head -1)
PACKAGE_TARBALL="$TARBALL" ./scripts/smoke-test.sh
- name: Upload server logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: mux-server-smoke-test-logs
path: /tmp/tmp.*/server.log
if-no-files-found: warn
retention-days: 7
check-codex-comments:
name: Check Codex Comments
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git describe to find tags
- name: Check for unresolved Codex comments
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
./scripts/check_codex_comments.sh ${{ github.event.pull_request.number }}
# Trigger CI run