Two Linux runners live on the same host. Every Web E2E job first frees "stale" listeners on the fixed ports 3110, 3111, 5180 and 4321 with fuser -k, so a job starting on the second runner killed the Vite server of the job already running on the first one. The victim died with SIGKILL (exit 137), and its remaining tests failed on reloads that could no longer reach a server. This is what made the Web E2E run on this PR red, and it explains the recurring reload failures on main whenever two runs overlapped. The workflow now derives API_PORT, WEB_PORT and WWW_PORT from the runner name (suffix -N means an offset of N times 100), and the port-freeing script follows those settings instead of a hard-coded list. The harness, Vite, the API and the marketing site already read these variables. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
195 lines
7.9 KiB
YAML
195 lines
7.9 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.
|
|
run: |
|
|
set -euo pipefail
|
|
suffix="${RUNNER_NAME##*-}"
|
|
offset=0
|
|
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
|