tests: run the E2E suite against a standalone Francis runtime

Adds a matrix entry that starts a SQLite-backed Francis runtime next to
Pocket ID and points FRANCIS_HOST at it, so the same Playwright suite runs
with the actor state, alarms, and placement owned by the runtime instead of
embedded in Pocket ID.

The suite needs no changes to work in that topology: the E2E reset seeds
every actor through actors.Service() and deliberately leaves the actor
store alone, so it behaves the same whichever side owns it.

The CLI spec is the exception, since export and import are the two commands
whose behaviour genuinely differs. It now picks the right Compose file,
expects an export to carry no francis.bin, feeds the import an archive
without one, and gains a case asserting that an archive that does carry one
is refused.

The runtime is reached over the Compose network on its UDP port, so nothing
is published to the host, and the cluster CA is left unpinned, which
exercises the same trust-on-first-use path an operator gets without
FRANCIS_CA. Pinning is covered by a unit test instead.
This commit is contained in:
Alessandro (Ale) Segala
2026-08-19 06:30:27 +00:00
parent 2e48aaf0a4
commit 22cf4eab93
7 changed files with 243 additions and 8 deletions

View File

@@ -25,17 +25,26 @@ jobs:
strategy:
fail-fast: false
matrix:
# "francis" selects where the actor runtime lives: embedded in Pocket ID, or a standalone runtime it connects to
include:
- db: sqlite
storage: filesystem
francis: embedded
- db: postgres
storage: filesystem
francis: embedded
- db: sqlite
storage: s3
francis: embedded
- db: sqlite
storage: database
francis: embedded
- db: postgres
storage: database
francis: embedded
- db: sqlite
storage: filesystem
francis: remote
steps:
- name: Checkout code
@@ -109,6 +118,31 @@ jobs:
if: matrix.storage == 's3' && steps.s3-cache.outputs.cache-hit == 'true'
run: docker load < /tmp/localstack-s3-image.tar
- name: Resolve Francis runtime image
if: matrix.francis == 'remote'
id: francis-image
working-directory: ./tests/setup
# The Compose file is the single source of truth for the version, so the cache key follows it automatically
run: |
IMAGE=$(grep -oP '(?<=image: )ghcr\.io/italypaleale/francis:\S+' docker-compose-francis.yml)
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
echo "key=$(echo "$IMAGE" | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- name: Cache Francis runtime Docker image
if: matrix.francis == 'remote'
uses: actions/cache@v5
id: francis-cache
with:
path: /tmp/francis-image.tar
key: ${{ steps.francis-image.outputs.key }}-${{ runner.os }}
- name: Pull and save Francis runtime image
if: matrix.francis == 'remote' && steps.francis-cache.outputs.cache-hit != 'true'
run: |
docker pull "${{ steps.francis-image.outputs.image }}"
docker save "${{ steps.francis-image.outputs.image }}" > /tmp/francis-image.tar
- name: Load Francis runtime image
if: matrix.francis == 'remote' && steps.francis-cache.outputs.cache-hit == 'true'
run: docker load < /tmp/francis-image.tar
- name: Install test dependencies
run: pnpm --filter pocket-id-tests install --frozen-lockfile
@@ -128,7 +162,9 @@ jobs:
SCIM_SERVICE_PROVIDER_URL_INTERNAL=http://scim-test-server:8080/v2
EOF
if [ "${{ matrix.db }}" = "postgres" ]; then
if [ "${{ matrix.francis }}" = "remote" ]; then
DOCKER_COMPOSE_FILE=docker-compose-francis.yml
elif [ "${{ matrix.db }}" = "postgres" ]; then
DOCKER_COMPOSE_FILE=docker-compose-postgres.yml
elif [ "${{ matrix.storage }}" = "s3" ]; then
DOCKER_COMPOSE_FILE=docker-compose-s3.yml
@@ -150,6 +186,10 @@ jobs:
done
} &
if [ "${{ matrix.francis }}" = "remote" ]; then
docker compose -f "$DOCKER_COMPOSE_FILE" logs -f --no-log-prefix francis-runtime > /tmp/francis-runtime.log 2>&1 &
fi
- name: Run Playwright tests
working-directory: ./tests
run: pnpm exec playwright test
@@ -158,7 +198,7 @@ jobs:
uses: actions/upload-artifact@v7
if: always() && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: playwright-report-${{ matrix.db }}-${{ matrix.storage }}
name: playwright-report-${{ matrix.db }}-${{ matrix.storage }}-francis-${{ matrix.francis }}
path: tests/.report
include-hidden-files: true
retention-days: 15
@@ -167,7 +207,16 @@ jobs:
uses: actions/upload-artifact@v7
if: always() && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: backend-${{ matrix.db }}-${{ matrix.storage }}
name: backend-${{ matrix.db }}-${{ matrix.storage }}-francis-${{ matrix.francis }}
path: /tmp/backend.log
include-hidden-files: true
retention-days: 15
- name: Upload Francis Runtime Report
uses: actions/upload-artifact@v7
if: always() && matrix.francis == 'remote' && github.event.pull_request.head.ref != 'i18n_crowdin'
with:
name: francis-runtime-${{ matrix.db }}-${{ matrix.storage }}
path: /tmp/francis-runtime.log
include-hidden-files: true
retention-days: 15