Skip to content

CICD - Unit | Integration | E2E #9

CICD - Unit | Integration | E2E

CICD - Unit | Integration | E2E #9

Workflow file for this run

# =============================================================================
# CI/CD Pipeline
# =============================================================================
#
# Two-stage pipeline that gates merges to main:
# Stage 1 (test): Unit + integration tests on GH Actions runner
# [TODO] Stage 2 (e2e): Playwright E2E against a real staging deployment
#
# Both stages must pass before a PR can be merged to main.
# Merging to main triggers production deploy via Coolify (existing setup).
#
# REQUIRED BRANCH PROTECTION RULES ON main:
# - Require status checks for unit-integration and e2e
# - Require branches to be up to date before merging
# =============================================================================
name: CI Pipeline
on:
pull_request:
branches: [main]
# =============================================================================
# WHY NO WORKFLOW-LEVEL CONCURRENCY:
#
# We need different concurrency strategies per job:
# - unit-integration: cancel-in-progress per PR (fast feedback on new pushes)
# - e2e: queue globally (staging is a shared single resource)
#
# Workflow-level concurrency would force one strategy for both.
# =============================================================================
jobs:
# ===========================================================================
# STAGE 1: Unit & Integration Tests
# ===========================================================================
#
# Runs Vitest against a local Supabase subset.
#
# Concurrency: cancel-in-progress PER PR. If you push again to the same PR
# while tests are running, the old run is killed immediately. No point
# finishing tests on stale code.
# ===========================================================================
unit-integration:
name: Unit & Integration Tests
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
concurrency:
group: test-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
# -------------------------------------------------------------------------
# maximum amount of time a job or step can run before GitHub automatically cancels it
# -------------------------------------------------------------------------
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
# RLIMIT ISSUE temp fix
# this step is only necessary because of an issue with Docker and Supabase where it cannot set the `ulimit`.
# this step sets the `ulimit` higher than the Supabase supavisor container's 100000, to prevent an "Operation not permitted"
# error stopping the supavisor from starting.
# https://github.com/supabase/cli/issues/4443
- name: Configure system limits
run: |
echo "This is due to a bug with Supabase and GitHub runner: https://github.com/supabase/cli/issues/4443"
# Set ulimit for system (higher than Supabase's 100000)
# Run ulimit within a root shell since it's a builtin and cannot be run like `sudo ulimit`
sudo bash -c 'ulimit -n 200000 && ulimit -u 16384'
# Configure Docker daemon with merged config
DOCKER_CONFIG="/etc/docker/daemon.json"
# Read existing config or start with empty object
if [ -f "$DOCKER_CONFIG" ]; then
EXISTING_CONFIG=$(cat "$DOCKER_CONFIG")
else
EXISTING_CONFIG="{}"
fi
# Merge new limits with existing config using jq (higher than Supabase's 100000)
echo "$EXISTING_CONFIG" | jq '. + {
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Hard": 200000,
"Soft": 200000
},
"nproc": {
"Name": "nproc",
"Hard": 16384,
"Soft": 16384
}
}
}' | sudo tee "$DOCKER_CONFIG" > /dev/null
# Restart Docker to apply changes
sudo systemctl restart docker
# Wait for Docker to be ready
timeout 30 sh -c 'until docker info > /dev/null 2>&1; do sleep 1; done'
- uses: actions/setup-node@v4
# use Node version enforced for the project
# see https://github.com/actions/setup-node
with:
node-version-file: '.nvmrc'
cache: "npm"
- name: Install dependencies
run: npm ci
# load the env variables for the sveltekit app
# (slightly) different from .env.ci
- name: Load app env
run: cat cicd/.env.app.ci >> "$GITHUB_ENV"
# -----------------------------------------------------------------------
- name: Run Unit Tests
run: npm run test:unit
- name: Run Integration Tests
if: always()
run: npm run test:integration
- name: Upload unit coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-unit
path: reports/coverage/unit
retention-days: 7
- name: Upload integration coverage
if: always()
uses: actions/upload-artifact@v4
with:
name: coverage-integration
path: reports/coverage/integration
retention-days: 7
# https://github.com/davelosert/vitest-coverage-report-action
- name: Report Unit Coverage
# Set if: always() to also generate the report if tests are failing
# Only works if you set `reportOnFailure: true` in your vite config as specified above
if: always()
uses: davelosert/vitest-coverage-report-action@v2
with:
name: Unit
json-summary-path: reports/coverage/unit/coverage-summary.json
json-final-path: reports/coverage/unit/coverage-final.json
vite-config-path: vitest.config.unit.ts
- name: Report Integration Coverage
if: always()
uses: davelosert/vitest-coverage-report-action@v2
with:
name: Integration
json-summary-path: reports/coverage/integration/coverage-summary.json
json-final-path: reports/coverage/integration/coverage-final.json
vite-config-path: vitest.config.integration.ts