* ci: add Product CI merge gate and Autopilot auto-merge path
Aggregate lint, typecheck, builds, unit, Postgres journeys, and Web E2E
into a single Product CI check for branch protection. Soft-fail artifact
uploads and skip headless macOS screenshots so infra noise does not block
merge. Add autopilot/auto-merge workflow and MERGE_GATES.md.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): keep autopilot auto-merge inert without label
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): run autopilot auto-merge on self-hosted runner
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(ci): free stale E2E ports before Playwright bind on Linux
Self-hosted evoo-ci runners can retain listeners on harness ports
(3110 API, 5180 web, 4321 marketing, 3111 device control) after a
failed run, causing EADDRINUSE before the next job. Add a guarded
free-e2e-ports.sh that uses fuser on Linux only and never touches
live dev ports 3100/5173. Wire it into the Playwright workflow and
the E2E harness with clear logging.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): stabilize message More menu Escape in hover E2E
The desktop hover spec assumed Escape always closed the first page-level
menu within one keypress. On self-hosted Chromium the Copy menu could stay
open while the rail remained visible, failing getByRole('menu') count.
Wait for the Copy item to prove the menu opened, then retry Escape until
that item hides while asserting the More control stays on the hover rail.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
* fix(web): retry sign-up navigation in Playwright helpers
Late-suite specs can miss the sign-up heading when Vite is still
warming on self-hosted runners. Retry goto until the form renders
instead of failing once at the default timeout.
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
179 lines
7.2 KiB
YAML
179 lines
7.2 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: 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
|