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
197 lines
8.0 KiB
YAML
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
|