Skip to content

fix(release): embed inline release notes in appcast.xml #684

fix(release): embed inline release notes in appcast.xml

fix(release): embed inline release notes in appcast.xml #684

Workflow file for this run

name: UI E2E
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-ui-${{ github.ref }}
cancel-in-progress: true
jobs:
paths-filter:
runs-on: arc-general
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4
id: filter
with:
filters: |
code:
- .github/workflows/ci-ui.yml
- "frontend/**"
- "tests/ui/**"
- "src/persona_forge/**"
- requirements-dev.txt
frontend:
- .github/workflows/ci-ui.yml
- "frontend/**"
frontend-lint:
name: frontend-lint
needs: paths-filter
if: needs.paths-filter.outputs.frontend == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
runs-on: arc-general
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "24"
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Oxlint (warnings are errors)
working-directory: frontend
run: npm run lint
e2e:
name: e2e
needs: paths-filter
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
runs-on: arc-general
timeout-minutes: 30
env:
PYTHONPATH: src:src/export
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: No relevant changes
if: needs.paths-filter.outputs.code != 'true'
run: echo "No frontend/UI-relevant changes detected, skipping E2E run"
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
if: needs.paths-filter.outputs.code == 'true'
with:
python-version: "3.13"
cache: pip
cache-dependency-path: requirements-dev.txt
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: needs.paths-filter.outputs.code == 'true'
with:
node-version: "24"
- name: Install Python test dependencies
if: needs.paths-filter.outputs.code == 'true'
run: python -m pip install -r requirements-dev.txt
- name: Environment and import smoke test
if: needs.paths-filter.outputs.code == 'true'
run: |
echo "=== Environment ==="
python --version
pip list --format=columns | head -30
echo "PYTHONPATH=$PYTHONPATH"
echo "PWD=$(pwd)"
echo "=== Import smoke test ==="
python -c "
import sys
sys.path.insert(0, 'src')
sys.path.insert(0, 'src/export')
# Install fake model before importing app (no torch on runner)
from tests.fixtures.fake_runtime import FakeModelRuntime
rt = FakeModelRuntime()
rt.install()
errors = []
def try_import(name):
try:
__import__(name)
except Exception as e:
errors.append(f'{name}: {e}')
try_import('flask')
try_import('soundfile')
try_import('numpy')
try_import('persona_forge')
try_import('persona_forge.app')
if errors:
print('IMPORT ERRORS:')
for e in errors:
print(' ', e)
sys.exit(1)
else:
print('All critical imports OK')
"
- name: Install frontend dependencies
if: needs.paths-filter.outputs.code == 'true'
working-directory: frontend
run: npm ci
- name: Build frontend
if: needs.paths-filter.outputs.code == 'true'
working-directory: frontend
run: npm run build
- name: Install Playwright test dependencies
if: needs.paths-filter.outputs.code == 'true'
working-directory: tests/ui
run: npm ci
- name: Cache Playwright browsers
if: needs.paths-filter.outputs.code == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('tests/ui/package-lock.json') }}
- name: Install Playwright browsers
if: needs.paths-filter.outputs.code == 'true'
working-directory: tests/ui
run: npx playwright install --with-deps chromium
- name: Start fake_model_server
if: needs.paths-filter.outputs.code == 'true'
run: |
# Seed the same disposable libraries used by local E2E, then launch the
# fake-only Python server. This avoids relying on child-process environment
# propagation from the Node harness on the self-hosted runner.
E2E_DATA_DIR="$(mktemp -d)"
mkdir -p "$E2E_DATA_DIR/voices" "$E2E_DATA_DIR/segments"
cp -R tests/ui/fixtures/capture-data/voices/. "$E2E_DATA_DIR/voices/"
cp -R tests/ui/fixtures/capture-data/segments/. "$E2E_DATA_DIR/segments/"
export VOICE_LIBRARY_DIR="$E2E_DATA_DIR/voices"
export SEGMENT_LIBRARY_DIR="$E2E_DATA_DIR/segments"
export FRONTEND_DIST_DIR="$PWD/frontend/dist"
export PERSONA_FORGE_TEST_PORT=8319
python tests/ui/fixtures/fake_model_server.py > fake_model_server.log 2>&1 &
SERVER_PID=$!
echo "fake_model_server PID=$SERVER_PID"
echo "=== Waiting for fake_model_server to become healthy ==="
deadline=$((SECONDS + 10))
while [ $SECONDS -lt $deadline ]; do
sleep 1
set +e
response=$(curl -s -w "\n%{http_code}" http://127.0.0.1:8319/health 2>&1 || true)
code=$(echo "$response" | tail -1)
set -e
if [ "$code" = "200" ]; then
echo "fake_model_server is healthy after $((SECONDS))s"
echo "health response:"
echo "$response"
segment_count=$(curl -fsS http://127.0.0.1:8319/omnivoice/segments | python -c "import json,sys; print(len(json.load(sys.stdin).get('segments', [])))")
echo "fake segment fixture count: $segment_count"
test "$segment_count" -gt 0
exit 0
fi
done
echo "=== fake_model_server did not become healthy ==="
echo "Server PID alive?"
kill -0 "$SERVER_PID" 2>/dev/null && echo "YES" || echo "NO"
echo "--- fake_model_server logs (full) ---"
cat fake_model_server.log || echo "(empty or unreadable)"
echo "--- end logs ---"
exit 1
- name: Run Playwright E2E tests
if: needs.paths-filter.outputs.code == 'true'
working-directory: tests/ui
env:
PYTHONPATH: src:src/export
PERSONA_FORGE_UI_URL: http://127.0.0.1:8319
run: npm run test:ci
- name: Upload Playwright report
if: failure() && needs.paths-filter.outputs.code == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: tests/ui/playwright-report
retention-days: 1
- name: Upload Playwright test results
if: failure() && needs.paths-filter.outputs.code == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-test-results
path: tests/ui/test-results
if-no-files-found: warn
retention-days: 1
- name: Upload fake_model_server log
if: failure() && needs.paths-filter.outputs.code == 'true'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fake_model_server-log
path: fake_model_server.log
retention-days: 1