Skip to content

Install GitHub CLI in CI Workflow #1620

Install GitHub CLI in CI Workflow

Install GitHub CLI in CI Workflow #1620

name: PR Quality Gate - Standalone
on:
pull_request:
branches: ['leader']
workflow_dispatch:
inputs:
branch:
description: 'Branch to check'
required: true
default: 'leader'
concurrency:
group: pr-quality-standalone-${{ github.event.pull_request.number || github.ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
checks: write # Required for dorny/test-reporter
actions: read # Required for artifact downloads
pull-requests: write # Required for gh pr comment
issues: write # Required for posting comments
env:
FORCE_COLOR: 1
# Unique session identifier for concurrent runs
SESSION_ID: ${{ github.event.pull_request.number || github.ref_name || github.run_id }}
# Maximum bytes to include from each log file in failure comments
LOG_TRUNCATE_BYTES: 20000
jobs:
# ===========================================================================
# SINGLE JOB: SETUP AND ALL TESTS
# Sets up environment and runs all quality checks, tests in one job
# ===========================================================================
test:
name: 🧪 Setup & All Tests
runs-on: self-hosted
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 0
clean: true # 🧹 Wipes previous run artifacts to ensure a fresh start
- name: Install GitHub CLI
uses: actions4gh/setup-gh@v1
- name: Define Dynamic Cache Path
shell: bash
# This expands $HOME (e.g., /home/ari) and saves it to the workflow env
run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/ms-playwright" >> $GITHUB_ENV
- name: Sanitize SESSION_ID
shell: bash
run: |
# Replace / with - in SESSION_ID to prevent invalid artifact names
SAFE_SESSION_ID=$(echo "${{ env.SESSION_ID }}" | tr '/' '-')
echo "SESSION_ID=$SAFE_SESSION_ID" >> $GITHUB_ENV
# --- GLOBAL PERSISTENCE CONFIG ---
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Configure Persistent Store
shell: bash
run: |
mkdir -p ~/.local/share/pnpm/store
# Use session-specific cache directories to prevent conflicts
mkdir -p ~/.cache/pnpm-${{ env.SESSION_ID }}
pnpm config set store-dir ~/.local/share/pnpm/store
pnpm config set cache-dir ~/.cache/pnpm-${{ env.SESSION_ID }}
pnpm config set side-effects-cache true
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '20.x'
# ---------------------------------
- name: Create Runtime Envs
run: |
mkdir -p logs coverage playwright-report test-results
echo "NEXTAUTH_URL=http://127.0.0.1:3000" > .env.local
echo "NEXTAUTH_SECRET=ci-secret-${{ env.SESSION_ID }}" >> .env.local
- name: Run Setup Script
run: |
chmod +x ./scripts/setup.sh
./scripts/setup.sh
# This installs dependencies into ./node_modules
# 1. Get version for cache key
- name: Get installed Playwright version
id: playwright-version
shell: bash
run: echo "VERSION=$(npx playwright --version | cut -d ' ' -f 2)" >> $GITHUB_OUTPUT
# 2. Cache the browser binaries in the HOME directory
#- name: Cache Playwright Browsers
# id: playwright-cache
# uses: actions/cache@v4
# with:
# path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }}
# key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.VERSION }}
# restore-keys: |
# ${{ runner.os }}-playwright-
# 3. Install Browsers (ONLY if cache miss)
# NOTE: If running on a new server, install OS deps manually once with:
# sudo env PATH="$(dirname $(which node)):$PATH" npx playwright install-deps
- name: Install Playwright Browsers
# if: steps.playwright-cache.outputs.cache-hit != 'true'
# CHANGE: Removed --with-deps (System deps are already on your server)
run: pnpm exec playwright install chromium
- name: Cache Next.js Build
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-
# ADD THIS LINE: Matches any previous Next.js cache
${{ runner.os }}-nextjs-
- name: Lint Code
id: lint
run: |
set -o pipefail
pnpm run lint 2>&1 | tee logs/lint-output.log
- name: Verify Build
id: build
run: |
set -o pipefail
pnpm run build 2>&1 | tee logs/build-output.log
- name: Run Infrastructure Tests and Generate JUnit Report
id: infra_tests
# Run tests and output the report file to a specific path, capture output
run: |
set -o pipefail
PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-results/infra-results.xml pnpm run test:infra --reporter=junit 2>&1 | tee logs/infra-output.log
# continue-on-error allows subsequent steps to run for failure reporting
continue-on-error: true
- name: Publish Infra Test Results to GitHub Checks
# This action reads the generated XML and displays it in the Checks/Summary tab
uses: dorny/test-reporter@v1
if: always() && steps.infra_tests.outcome != 'skipped' # Ensures the report is published even if tests failed, but not if skipped
with:
name: Infrastructure Tests Report # The name of the check run
path: test-results/infra-results.xml # Path to the generated XML file
reporter: java-junit
fail-on-error: true # Fails the overall workflow job if any tests failed
- name: Run Unit Tests
id: unit_tests
run: |
set -o pipefail
pnpm run test:unit 2>&1 | tee logs/unit-output.log
continue-on-error: true
- name: Publish Unit Test Results to GitHub Checks
# This action reads the generated XML and displays it in the Checks/Summary tab
uses: dorny/test-reporter@v1
if: always() && steps.unit_tests.outcome != 'skipped' # Ensures the report is published even if tests failed, but not if skipped
with:
name: Unit Tests Report # The name of the check run
path: test-results/unit-results.xml # Path to the generated XML file
reporter: java-junit
fail-on-error: true # Fails the overall workflow job if any tests failed
- name: Start Application for Performance Test
if: always() && steps.build.outcome == 'success'
run: pnpm run start
- name: Run Performance Test
id: perf_tests
if: always() && steps.build.outcome == 'success'
run: |
set -o pipefail
PLAYWRIGHT_JUNIT_OUTPUT_NAME=test-results/perf-results.xml pnpm run test:perf --reporter=junit 2>&1 | tee logs/performance-output.log
continue-on-error: true
- name: Publish Performance Test Results to GitHub Checks
uses: dorny/test-reporter@v1
if: always() && steps.perf_tests.outcome != 'skipped'
with:
name: Performance Tests Report
path: test-results/perf-results.xml
reporter: java-junit
fail-on-error: true
- name: Upload Performance Metrics
if: always()
uses: actions/upload-artifact@v4
with:
name: performance-metrics
path: test-results/performance-metrics.json
- name: Stop Application
if: always()
run: pnpm run kill-all || true
- name: Run Visual Tests
id: visual_tests
run: |
set -o pipefail
pnpm run test:visual 2>&1 | tee logs/visual-output.log
# NOTE: This generates files in the default ./blob-report directory
continue-on-error: true
- name: Publish Visual Test Results to GitHub Checks
uses: dorny/test-reporter@v1
if: always() && steps.visual_tests.outcome != 'skipped'
with:
name: Visual Tests Report
path: test-results/results.xml
reporter: java-junit
fail-on-error: true
- name: Merge Playwright Reports
if: always() && steps.visual_tests.outcome != 'skipped'
run: npx playwright merge-reports --reporter html ./blob-report
# The command now correctly points to the default directory containing blob reports
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results-${{ env.SESSION_ID }}
path: |
coverage/
playwright-report/
test-results/
logs/
reports/performance/
retention-days: 7
# Post comment on PR when tests fail
- name: Report Failure to Jules
if: always() && (steps.lint.outcome == 'failure' || steps.build.outcome == 'failure' || steps.infra_tests.outcome == 'failure' || steps.unit_tests.outcome == 'failure' || steps.visual_tests.outcome == 'failure' || steps.perf_tests.outcome == 'failure')
env:
GH_TOKEN: ${{ secrets.ARI_PAT }}
run: |
# 1. Identify the PR Number
# Try getting it from the event (pull_request)
PR_NUMBER="${{ github.event.pull_request.number }}"
# If triggered by workflow_dispatch, find the PR associated with this branch
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" == "null" ]; then
PR_NUMBER=$(gh pr list --head "${{ github.ref_name }}" --json number --jq '.[0].number')
fi
if [ -z "$PR_NUMBER" ] || [ "$PR_NUMBER" == "null" ]; then
echo "::warning::No Pull Request found for this branch. Skipping comment."
exit 0
fi
# 2. Build the list of failed checks and collect logs to temp files
FAILED_CHECKS=""
# Define comment file path in workspace
COMMENT_FILE="test-results/pr-comment.md"
# Ensure test-results exists (it should, but safety first)
mkdir -p test-results
# Initialize the comment file with header
# We construct the content directly into the final file to avoid temp file issues
# 1. Collect failed checks
if [ "${{ steps.lint.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Lint, "
fi
if [ "${{ steps.build.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Build, "
fi
if [ "${{ steps.infra_tests.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Infrastructure Tests, "
fi
if [ "${{ steps.unit_tests.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Unit Tests, "
fi
if [ "${{ steps.visual_tests.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Visual Tests, "
fi
if [ "${{ steps.perf_tests.outcome }}" == "failure" ]; then
FAILED_CHECKS="${FAILED_CHECKS}Performance Tests, "
fi
FAILED_CHECKS="${FAILED_CHECKS%, }"
# 2. Write Header
echo "@jules fix ${FAILED_CHECKS}" > "$COMMENT_FILE"
echo "" >> "$COMMENT_FILE"
echo "<details>" >> "$COMMENT_FILE"
echo "<summary><strong>Failed Test Report Log</strong></summary>" >> "$COMMENT_FILE"
# 3. Append Logs
append_log() {
local log_file="$1"
local test_name="$2"
if [ -f "$log_file" ]; then
echo "" >> "$COMMENT_FILE"
echo "### ${test_name} Log" >> "$COMMENT_FILE"
echo '```' >> "$COMMENT_FILE"
tail -c "${{ env.LOG_TRUNCATE_BYTES }}" "$log_file" | sed -r 's/\x1b\[[0-9;]*m//g' >> "$COMMENT_FILE"
echo '```' >> "$COMMENT_FILE"
else
echo "" >> "$COMMENT_FILE"
echo "### ${test_name} Log" >> "$COMMENT_FILE"
echo "_Log file not found: ${log_file}_" >> "$COMMENT_FILE"
fi
}
if [ "${{ steps.lint.outcome }}" == "failure" ]; then
append_log "logs/lint-output.log" "Lint"
fi
if [ "${{ steps.build.outcome }}" == "failure" ]; then
append_log "logs/build-output.log" "Build"
fi
if [ "${{ steps.infra_tests.outcome }}" == "failure" ]; then
append_log "logs/infra-output.log" "Infrastructure Tests"
fi
if [ "${{ steps.unit_tests.outcome }}" == "failure" ]; then
append_log "logs/unit-output.log" "Unit Tests"
fi
if [ "${{ steps.visual_tests.outcome }}" == "failure" ]; then
append_log "logs/visual-output.log" "Visual Tests"
fi
if [ "${{ steps.perf_tests.outcome }}" == "failure" ]; then
append_log "logs/performance-output.log" "Performance Test Output"
# Also attach the specific background logs if they exist
append_log "logs/ws-stress-client.log" "WebSocket Stress Client"
append_log "logs/frontend-perf-server.log" "Frontend Perf Server"
fi
# 4. Close Details
echo "</details>" >> "$COMMENT_FILE"
# 5. Post the Comment
if [ -s "$COMMENT_FILE" ]; then
gh pr comment "$PR_NUMBER" --body-file "$COMMENT_FILE"
else
echo "::error::Comment file is empty, skipping comment."
exit 1
fi
- name: Checkout
uses: actions/checkout@v6
if: always()
with:
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 0
# CHANGED: Set to false to keep .next/cache and node_modules on disk
clean: false
- name: Clean Workspace (Preserve Cache)
if: always()
run: |
# Delete everything EXCEPT .next and node_modules
find . -maxdepth 1 -mindepth 1 \
-not -name '.next' \
-not -name 'node_modules' \
-not -name '.git' \
-not -name '.cache' \
-exec rm -rf {} +
# Reset git state to ensure code is fresh
git reset --hard HEAD
git clean -fdx --exclude .next --exclude node_modules --exclude .eslintcache --exclude .cache
# Fail the job if any tests failed (must be at the very end)
- name: Fail Workflow on Test Failures
if: always() && (steps.lint.outcome == 'failure' || steps.build.outcome == 'failure' || steps.infra_tests.outcome == 'failure' || steps.unit_tests.outcome == 'failure' || steps.visual_tests.outcome == 'failure' || steps.perf_tests.outcome == 'failure')
run: |
echo "One or more tests failed. Failing the workflow."
exit 1