evOo-local/.github/workflows/playwright.yml
Claude ddf5febdb3
ci: keep every runner off the default E2E ports
Branches that still free the fixed ports 3110, 3111, 5180 and 4321 keep
running on the shared host (main, the nightly run, other open PRs) until
they carry the per-runner ports. A job of ours on the runner without a
numeric suffix used exactly those defaults, so such a neighbour could
still kill it, and its own port freeing could kill the neighbour. The
unsuffixed runner now starts at offset 100, so no job from this workflow
touches the defaults in either direction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
2026-10-02 16:44:57 +00:00

197 lines
8.0 KiB
YAML

name: Playwright
on:
workflow_dispatch:
inputs:
sandbox_provider:
description: Sandbox provider
required: true
type: choice
options:
- fake
- e2b
- daytona
- box
default: fake
workflow_call:
inputs:
sandbox_provider:
description: Sandbox provider
required: false
type: string
default: fake
publish_report:
description: Publish this run to the persistent visual dashboard
required: false
type: boolean
default: false
upload_artifacts:
description: Retain the report and diagnostics as GitHub artifacts
required: false
type: boolean
default: false
secrets:
S3_ACCESS_KEY_ID:
description: Access key used to publish visual reports
required: false
S3_SECRET_ACCESS_KEY:
description: Secret key used to publish visual reports
required: false
E2B_API_KEY:
description: E2B API key used only when the E2B sandbox is selected
required: false
DAYTONA_API_KEY:
description: Daytona API key used only when the Daytona sandbox is selected
required: false
BOX_API_KEY:
description: Box API key used only when the Box sandbox is selected
required: false
permissions:
contents: read
jobs:
playwright:
name: Web E2E
runs-on: [self-hosted, evoo-ci, Linux]
timeout-minutes: 30
concurrency:
group: ${{ inputs.publish_report && 'playwright-publication' || format('playwright-{0}', github.run_id) }}
cancel-in-progress: false
steps:
- uses: actions/checkout@v5
- uses: pnpm/action-setup@v4
id: pnpm-setup
with:
dest: ${{ runner.temp }}/setup-pnpm-${{ github.run_id }}-${{ github.job }}
- name: Isolate pnpm store and PATH
shell: bash
run: |
set -euo pipefail
bin="${{ steps.pnpm-setup.outputs.bin_dest }}"
dest="${{ steps.pnpm-setup.outputs.dest }}"
if [[ -z "$bin" || -z "$dest" ]]; then
echo "pnpm setup did not report install paths" >&2
exit 1
fi
echo "PNPM_HOME=${dest}" >> "$GITHUB_ENV"
echo "${bin}" >> "$GITHUB_PATH"
echo "${dest}" >> "$GITHUB_PATH"
store="${RUNNER_TOOL_CACHE}/pnpm-store"
mkdir -p "$store"
echo "npm_config_store_dir=${store}" >> "$GITHUB_ENV"
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Clear stale Vite dependency cache
run: rm -rf apps/web/node_modules/.vite node_modules/.vite
- uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('pnpm-lock.yaml') }}
- name: Install Chromium
timeout-minutes: 5
# Skip --with-deps: evo Linux runners keep Chromium's OS libraries installed,
# and apt-get can stall until the job timeout on persistent self-hosted machines.
run: pnpm --filter @rakazo/web exec playwright install chromium
- name: Validate E2B credentials
if: inputs.sandbox_provider == 'e2b'
env:
E2B_API_KEY: ${{ secrets.E2B_API_KEY }}
run: test -n "$E2B_API_KEY"
- name: Validate Daytona credentials
if: inputs.sandbox_provider == 'daytona'
env:
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
run: test -n "$DAYTONA_API_KEY"
- name: Validate Box credentials
if: inputs.sandbox_provider == 'box'
env:
BOX_API_KEY: ${{ secrets.BOX_API_KEY }}
run: test -n "$BOX_API_KEY"
- name: Pick E2E ports for this runner
# Several runners share one host. Each runner gets its own port set so the
# port-freeing steps and the harness never touch another job's servers. No runner
# uses the harness defaults: jobs from branches that still free the fixed ports
# cannot kill ours, and we cannot kill theirs.
run: |
set -euo pipefail
suffix="${RUNNER_NAME##*-}"
offset=100
if [[ "$suffix" =~ ^[0-9]+$ ]]; then
offset=$((suffix * 100))
fi
{
echo "API_PORT=$((3110 + offset))"
echo "WEB_PORT=$((5180 + offset))"
echo "WWW_PORT=$((4321 + offset))"
} >> "$GITHUB_ENV"
echo "E2E ports for ${RUNNER_NAME}: api $((3110 + offset)), web $((5180 + offset)), www $((4321 + offset))"
- name: Free stale E2E ports
run: bash scripts/free-e2e-ports.sh
- name: Run Playwright tests
id: playwright_tests
env:
SANDBOX_TEST_PROVIDER: ${{ inputs.sandbox_provider }}
E2B_API_KEY: ${{ inputs.sandbox_provider == 'e2b' && secrets.E2B_API_KEY || '' }}
DAYTONA_API_KEY: ${{ inputs.sandbox_provider == 'daytona' && secrets.DAYTONA_API_KEY || '' }}
BOX_API_KEY: ${{ inputs.sandbox_provider == 'box' && secrets.BOX_API_KEY || '' }}
run: pnpm test:e2e -- --sandbox="$SANDBOX_TEST_PROVIDER"
# Marketing homepage only: separate config so product chromium does not start Astro.
# Runs after product e2e so shared apps/web/test-results is not wiped by the product run.
- name: Free stale marketing E2E ports
if: always() && (steps.playwright_tests.outcome == 'success' || steps.playwright_tests.outcome == 'failure')
run: bash scripts/free-e2e-ports.sh
- name: Run marketing homepage E2E
id: www_playwright_tests
if: always() && (steps.playwright_tests.outcome == 'success' || steps.playwright_tests.outcome == 'failure')
run: pnpm --filter @rakazo/www e2e
- name: Record Playwright result
if: always() && inputs.upload_artifacts
env:
PLAYWRIGHT_RESULT: ${{ steps.playwright_tests.outcome }}
WWW_PLAYWRIGHT_RESULT: ${{ steps.www_playwright_tests.outcome }}
run: |
mkdir -p test-report/e2e
if [[ "$PLAYWRIGHT_RESULT" == "success" && "$WWW_PLAYWRIGHT_RESULT" == "success" ]]; then
printf 'success\n' > test-report/e2e/outcome.txt
elif [[ "$PLAYWRIGHT_RESULT" == "success" && ( "$WWW_PLAYWRIGHT_RESULT" == "skipped" || -z "$WWW_PLAYWRIGHT_RESULT" ) ]]; then
printf 'success\n' > test-report/e2e/outcome.txt
else
printf 'failure\n' > test-report/e2e/outcome.txt
fi
- name: Upload Playwright artifacts
if: always() && inputs.upload_artifacts
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: playwright-artifacts-${{ github.run_id }}-${{ github.run_attempt }}
path: |
playwright-report
playwright-report-www
apps/web/test-results
test-report/e2e
retention-days: 7
if-no-files-found: warn
- name: Publish Playwright visual report
if: always() && inputs.publish_report
env:
AWS_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
AWS_DEFAULT_REGION: ${{ vars.S3_REGION }}
AWS_EC2_METADATA_DISABLED: "true"
S3_BUCKET: ${{ vars.S3_BUCKET }}
S3_ENDPOINT: ${{ vars.S3_ENDPOINT }}
PLAYWRIGHT_PUBLIC_BASE_URL: ${{ vars.PLAYWRIGHT_PUBLIC_BASE_URL }}
PLAYWRIGHT_RESULT: ${{ steps.playwright_tests.outcome == 'success' && steps.www_playwright_tests.outcome == 'success' && 'success' || 'failure' }}
PLAYWRIGHT_RUN_ATTEMPT: ${{ github.run_attempt }}
PLAYWRIGHT_RUN_ID: ${{ github.run_id }}
PLAYWRIGHT_RUN_NUMBER: ${{ github.run_number }}
PLAYWRIGHT_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
PLAYWRIGHT_SHA: ${{ github.sha }}
PLAYWRIGHT_EVENT: ${{ github.event_name }}
PLAYWRIGHT_BRANCH: ${{ github.ref_name }}
run: bash scripts/publish-playwright-report.sh