Skip to content

ci: self-skip schema-parity + verify-non-root so they're safe to require (#1237) #742

ci: self-skip schema-parity + verify-non-root so they're safe to require (#1237)

ci: self-skip schema-parity + verify-non-root so they're safe to require (#1237) #742

Workflow file for this run

name: frontend-e2e
# Runs only when a PR carries the `ui` label. Adds a browser test layer for
# UI changes; gated to avoid spending ~5 min of CI on every backend PR.
# Trigger manually by adding the `ui` label to a PR.
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
jobs:
e2e:
if: contains(github.event.pull_request.labels.*.name, 'ui')
runs-on: ubuntu-latest
timeout-minutes: 25
defaults:
run:
working-directory: src/frontend
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: src/frontend/package-lock.json
- name: Install npm dependencies
run: npm ci
- name: Cache Playwright browsers
uses: actions/cache@v5
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('src/frontend/package-lock.json') }}
- name: Install Playwright browser
run: npx playwright install --with-deps chromium
- name: Generate CI admin password
# I-01 fix (cso-diff-2026-05-17): replace the previous hardcoded
# `CiTestPassword!1` fallback with a per-run random value so the
# repo never publishes a default admin credential, even one only
# reachable from the runner sandbox. Honours an explicit
# E2E_ADMIN_PASSWORD secret override when set. The literal `Aa1!`
# suffix guarantees OWASP ASVS 2.1 complexity classes on the
# random branch (upper / lower / digit / special) — `tr -d`
# strips base64 chars that would break form-encoded curl bodies
# later.
env:
OVERRIDE: ${{ secrets.E2E_ADMIN_PASSWORD }}
run: |
set -euo pipefail
if [ -n "${OVERRIDE:-}" ]; then
PASS="$OVERRIDE"
else
PASS="$(openssl rand -base64 24 | tr -d '/+=\n')Aa1!"
fi
echo "::add-mask::$PASS"
echo "ADMIN_PASSWORD=$PASS" >> "$GITHUB_ENV"
- name: Start Trinity stack
working-directory: ${{ github.workspace }}
env:
# ADMIN_PASSWORD is inherited from GITHUB_ENV (see the
# "Generate CI admin password" step above).
ANTHROPIC_API_KEY: ${{ secrets.E2E_ANTHROPIC_API_KEY || 'placeholder' }}
run: |
# Generate the minimum .env needed to boot. Real secrets are not
# required for e2e — login uses ADMIN_PASSWORD only.
cp .env.example .env
echo "ADMIN_PASSWORD=$ADMIN_PASSWORD" >> .env
echo "ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY" >> .env
echo "SECRET_KEY=$(openssl rand -hex 32)" >> .env
./scripts/deploy/start.sh
- name: Wait for backend health
run: |
for i in {1..60}; do
if curl -fsS http://localhost:8000/health >/dev/null 2>&1; then
echo "backend healthy"; exit 0
fi
sleep 2
done
echo "backend never became healthy"; exit 1
# Issue #874 regression guards (`verify-non-root`,
# `verify-prod-frontend-uid`) moved to .github/workflows/container-security.yml
# so they run unconditionally on infrastructure PRs (this workflow
# is `ui`-label gated and would otherwise skip them on backend PRs).
- name: Skip first-time setup wizard
working-directory: ${{ github.workspace }}
run: |
# On a fresh DB the admin user is bootstrapped from ADMIN_PASSWORD,
# but `setup_completed` stays false (the backfill migration runs
# before admin creation). Without this flag the frontend redirects
# every route to /setup. Mark setup complete directly.
docker exec trinity-backend python3 -c "from database import db; db.set_setting('setup_completed', 'true'); print('setup_completed=true')"
- name: Run Playwright smoke tests
env:
# ADMIN_PASSWORD is inherited from GITHUB_ENV (see the "Generate
# CI admin password" step).
E2E_BASE_URL: http://localhost
# Only @smoke-tagged specs run in CI. @visual / @interactive specs
# are local-only until their flake/baseline story is sorted (#596).
run: npm run test:e2e:smoke
- name: Collect Trinity logs on failure
if: failure()
working-directory: ${{ github.workspace }}
run: |
docker compose logs --no-color --tail=200 backend frontend mcp-server > trinity-logs.txt || true
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: src/frontend/e2e/playwright-report/
retention-days: 14
- name: Upload failure artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: e2e-failure-artifacts
path: |
src/frontend/e2e/test-results/
trinity-logs.txt
retention-days: 14