Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
75c0eb9
Refactored E2E environment manager
jonatansberg Feb 23, 2026
efe3d6b
Added Tinybird config and build gateway support
jonatansberg Feb 23, 2026
f954d2f
Updated E2E scripts and build-mode workflow
jonatansberg Feb 23, 2026
4086350
Updated CI E2E job for unified manager (host)
jonatansberg Feb 23, 2026
013b809
Fixed flaky member signup attribution tests
jonatansberg Feb 19, 2026
c8c8c28
Updated CI E2E job for Playwright container
jonatansberg Feb 23, 2026
d831d14
Fixed E2E CI gateway config and failure logs
jonatansberg Feb 23, 2026
154a51e
Cleaned up E2E env docs and compose defaults
jonatansberg Feb 24, 2026
28ce1be
Updated E2E CI scripts and Tinybird sync flow
jonatansberg Feb 24, 2026
2280b86
Updated E2E Tinybird prep to run at test start
jonatansberg Feb 24, 2026
3c001da
Cleaned up E2E CI job orchestration
jonatansberg Feb 24, 2026
1fd3d0a
Fixed E2E gateway image passthrough and attribution wait scope
jonatansberg Feb 24, 2026
4286ae8
Improved E2E cleanup container removal resilience
jonatansberg Feb 24, 2026
f46c00a
Polished E2E setup guidance and dev-mode prerequisites
jonatansberg Feb 24, 2026
305facd
Baked dev asset URLs into image and removed E2E duplication
jonatansberg Feb 24, 2026
d5980a7
Cleaned up unused E2E environment helpers
jonatansberg Feb 24, 2026
419726c
Updated E2E docs wording and attribution wait comment
jonatansberg Feb 24, 2026
2c7c313
Adjusted E2E infra wait targets for one-shot services
jonatansberg Feb 24, 2026
05be6e1
Forwarded compose project env into Playwright container
jonatansberg Feb 24, 2026
e44b461
Removed Tinybird event truncation from E2E prep
jonatansberg Feb 25, 2026
13f8c1f
Removed unused legacy E2E compose file
jonatansberg Feb 25, 2026
1370d46
Scoped analytics request enablement to analytics tests
jonatansberg Feb 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1261,27 +1261,28 @@ jobs:
env:
DEPENDENCY_CACHE_KEY: ${{ needs.job_setup.outputs.dependency_cache_key }}

- name: Build public app UMD bundles
run: yarn workspace @tryghost/e2e build:apps

- name: Build E2E image layer
- name: Prepare E2E CI job
env:
GHOST_E2E_BASE_IMAGE: ${{ steps.load.outputs.image-tag }}
run: yarn workspace @tryghost/e2e build:docker
run: bash ./e2e/scripts/prepare-ci-e2e-job.sh

- name: Pull images
- name: Run e2e tests in Playwright container
env:
TEST_WORKERS_COUNT: 1
GHOST_E2E_MODE: build
GHOST_E2E_IMAGE: ghost-e2e:local
run: docker compose -f e2e/compose.yml pull
E2E_SHARD_INDEX: ${{ matrix.shardIndex }}
E2E_SHARD_TOTAL: ${{ matrix.shardTotal }}
E2E_RETRIES: 2
run: bash ./e2e/scripts/run-playwright-container.sh

- name: Setup Playwright
uses: ./.github/actions/setup-playwright
- name: Dump E2E docker logs
if: failure()
run: bash ./e2e/scripts/dump-e2e-docker-logs.sh

- name: Run e2e tests
env:
GHOST_E2E_IMAGE: ghost-e2e:local
TEST_WORKERS_COUNT: 1
run: yarn test:e2e:all --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --retries=2
- name: Stop E2E infra
if: always()
run: yarn workspace @tryghost/e2e infra:down

- name: Upload blob report to GitHub Actions Artifacts
if: failure()
Expand Down
8 changes: 0 additions & 8 deletions compose.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,6 @@ services:
# Redis cache (optional)
adapters__cache__Redis__host: redis
adapters__cache__Redis__port: 6379
# Public app assets - proxied through Caddy gateway to host dev servers
# Using /ghost/assets/* paths
portal__url: /ghost/assets/portal/portal.min.js
comments__url: /ghost/assets/comments-ui/comments-ui.min.js
sodoSearch__url: /ghost/assets/sodo-search/sodo-search.min.js
sodoSearch__styles: /ghost/assets/sodo-search/main.css
signupForm__url: /ghost/assets/signup-form/signup-form.min.js
announcementBar__url: /ghost/assets/announcement-bar/announcement-bar.min.js
depends_on:
mysql:
condition: service_healthy
Expand Down
36 changes: 36 additions & 0 deletions docker/dev-gateway/Caddyfile.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Build mode Caddyfile
# Used for testing pre-built images (local or registry)

{
admin off
}

:80 {
log {
output stdout
format console
}

# Analytics API - proxy to analytics service
# Handles paths like /.ghost/analytics/* or /blog/.ghost/analytics/*
@analytics_paths path_regexp analytics_match ^(.*)/\.ghost/analytics(.*)$
handle @analytics_paths {
rewrite * {re.analytics_match.2}
reverse_proxy {env.ANALYTICS_PROXY_TARGET} {
header_up Host {host}
header_up X-Forwarded-Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
}

# Everything else to Ghost
handle {
reverse_proxy {env.GHOST_BACKEND} {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto https
}
}
}
10 changes: 9 additions & 1 deletion docker/ghost-dev/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,18 @@ RUN --mount=type=cache,target=/usr/local/share/.cache/yarn,id=yarn-cache \
COPY docker/ghost-dev/entrypoint.sh entrypoint.sh
RUN chmod +x entrypoint.sh

# Public app assets are served via /ghost/assets/* in dev mode.
# Caddy forwards these paths to host frontend dev servers.
ENV portal__url=/ghost/assets/portal/portal.min.js \
comments__url=/ghost/assets/comments-ui/comments-ui.min.js \
sodoSearch__url=/ghost/assets/sodo-search/sodo-search.min.js \
sodoSearch__styles=/ghost/assets/sodo-search/main.css \
signupForm__url=/ghost/assets/signup-form/signup-form.min.js \
announcementBar__url=/ghost/assets/announcement-bar/announcement-bar.min.js

# Source code will be mounted from host at /home/ghost/ghost/core
# This allows node --watch to pick up file changes for hot-reload
WORKDIR /home/ghost/ghost/core

ENTRYPOINT ["/home/ghost/entrypoint.sh"]
CMD ["node", "--watch", "--import=tsx", "index.js"]

26 changes: 16 additions & 10 deletions e2e/Dockerfile.e2e
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# E2E test layer: copies locally-built public apps into any Ghost image
# so Ghost serves them at /ghost/assets/{app}/{app}.min.js (same origin, no CORS).
# E2E test layer: copies locally-built public apps into Ghost's content folder
# so Ghost serves them from /content/files/* (same origin, no CORS).
#
# Usage:
# docker build -f e2e/Dockerfile.e2e \
# --build-arg GHOST_IMAGE=ghost-monorepo:latest \
# -t ghost-e2e:local .
#
# Works with any base image (monorepo or production).
# Intended for the production Ghost image built in CI.

ARG GHOST_IMAGE=ghost-monorepo:latest
FROM $GHOST_IMAGE

# Public app UMD bundles — Ghost serves these from /ghost/assets/
COPY apps/portal/umd/portal.min.js core/built/admin/assets/portal/portal.min.js
COPY apps/comments-ui/umd/comments-ui.min.js core/built/admin/assets/comments-ui/comments-ui.min.js
COPY apps/sodo-search/umd/sodo-search.min.js core/built/admin/assets/sodo-search/sodo-search.min.js
COPY apps/sodo-search/umd/main.css core/built/admin/assets/sodo-search/main.css
COPY apps/signup-form/umd/signup-form.min.js core/built/admin/assets/signup-form/signup-form.min.js
COPY apps/announcement-bar/umd/announcement-bar.min.js core/built/admin/assets/announcement-bar/announcement-bar.min.js
# Public app UMD bundles — Ghost serves these from /content/files/
COPY apps/portal/umd /home/ghost/content/files/portal
COPY apps/comments-ui/umd /home/ghost/content/files/comments-ui
COPY apps/sodo-search/umd /home/ghost/content/files/sodo-search
COPY apps/signup-form/umd /home/ghost/content/files/signup-form
COPY apps/announcement-bar/umd /home/ghost/content/files/announcement-bar

ENV portal__url=/content/files/portal/portal.min.js
ENV comments__url=/content/files/comments-ui/comments-ui.min.js
ENV sodoSearch__url=/content/files/sodo-search/sodo-search.min.js
ENV sodoSearch__styles=/content/files/sodo-search/main.css
ENV signupForm__url=/content/files/signup-form/signup-form.min.js
ENV announcementBar__url=/content/files/announcement-bar/announcement-bar.min.js
113 changes: 68 additions & 45 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ yarn test

### Dev Environment Mode (Recommended for Development)

When `yarn dev` is running from the repository root, e2e tests automatically detect it and use a more efficient execution mode:
Dev mode is the default (`GHOST_E2E_MODE=dev`). Start infra with `yarn dev` (or `infra:up`) before running tests:

```bash
# Terminal 1: Start dev environment (from repository root)
Expand All @@ -31,6 +31,44 @@ yarn dev
yarn test
```

If infra is already running, `yarn workspace @tryghost/e2e infra:up` is safe to run again.
For dev-mode test runs, `infra:up` also ensures required local Ghost/gateway dev images exist.

### Analytics Development Flow

When working on analytics locally, use:

```bash
# Terminal 1 (repo root)
yarn dev:analytics

# Terminal 2
yarn workspace @tryghost/e2e test:analytics
```

E2E test scripts automatically sync Tinybird tokens when Tinybird is running.

### Build Mode (Prebuilt Image)

Use build mode when you don’t want to run dev servers. It uses a prebuilt Ghost image and serves public assets from `/content/files`.

```bash
# From repository root
yarn build
yarn workspace @tryghost/e2e build:apps
GHOST_E2E_BASE_IMAGE=<ghost-image> yarn workspace @tryghost/e2e build:docker
GHOST_E2E_MODE=build yarn workspace @tryghost/e2e infra:up

# Run tests
GHOST_E2E_MODE=build GHOST_E2E_IMAGE=ghost-e2e:local yarn workspace @tryghost/e2e test
```

For a CI-like local preflight (pulls Playwright + gateway images and starts infra), run:

```bash
yarn workspace @tryghost/e2e preflight:build
```


### Running Specific Tests

Expand Down Expand Up @@ -149,43 +187,22 @@ For example, a `ghostInstance` fixture creates a new Ghost instance with its own

Test isolation is extremely important to avoid flaky tests that are hard to debug. For the most part, you shouldn't have to worry about this when writing tests, because each test gets a fresh Ghost instance with its own database.

#### Standalone Mode (Default)

When dev environment is not running, tests use full container isolation:

- Global setup (`tests/global.setup.ts`):
- Starts shared services (MySQL, Tinybird, etc.)
- Runs Ghost migrations to create a template database
- Saves a snapshot of the template database using `mysqldump`
- Before each test (`helpers/playwright/fixture.ts`):
- Creates a new database by restoring from the template snapshot
- Starts a new Ghost container connected to the new database
- After each test (`helpers/playwright/fixture.ts`):
- Stops and removes the Ghost container
- Drops the test database
- Global teardown (`tests/global.teardown.ts`):
- Stops and removes shared services

#### Dev Environment Mode (When `yarn dev` is running)

When dev environment is detected, tests use a more efficient approach:

- Global setup:
- Creates a database snapshot in the existing `ghost-dev-mysql`
- Worker setup (once per Playwright worker):
- Creates a Ghost container for the worker
- Creates a Caddy gateway container for routing
- Before each test:
- Clones database from snapshot
- Restarts Ghost container with new database
- After each test:
- Drops the test database
- Worker teardown:
- Removes worker's Ghost and gateway containers
- Global teardown:
- Cleans up all e2e containers (namespace: `ghost-dev-e2e`)

All e2e containers use the `ghost-dev-e2e` project namespace for easy identification and cleanup.
Infrastructure (MySQL, Redis, Mailpit, Tinybird) must already be running before tests start. Use `yarn dev` or `yarn workspace @tryghost/e2e infra:up`.

Global setup (`tests/global.setup.ts`) does:
- Cleans up e2e containers and test databases
- Creates a base database, starts Ghost, waits for health, snapshots the DB

Per-test (`helpers/playwright/fixture.ts`) does:
- Clones a new database from the snapshot
- Restarts Ghost with the new database and waits for readiness

Global teardown (`tests/global.teardown.ts`) does:
- Cleans up e2e containers and test databases (infra services stay running)

Modes:
- Dev mode: Ghost mounts source code and proxies assets to host dev servers
- Build mode: Ghost uses a prebuilt image and serves assets from `/content/files`

### Best Practices

Expand All @@ -202,13 +219,12 @@ Tests run automatically in GitHub Actions on every PR and commit to `main`.

### CI Process

1. **Setup**: Ubuntu runner with Node.js and MySQL
2. **Docker Build & Push**: Build Ghost image and push to GitHub Container Registry
3. **Pull Images**: Pull Ghost, MySQL, Tinybird, etc. images
4. **Test Execution**:
- Wait for Ghost to be ready
- Run Playwright tests
- Upload test artifacts
1. **Setup**: Ubuntu runner with Node.js and Docker
2. **Build Assets**: Build server/admin assets and public app UMD bundles
3. **Build E2E Image**: `yarn workspace @tryghost/e2e build:docker` (layers public apps into `/content/files`)
4. **Prepare E2E Runtime**: Pull Playwright/gateway images in parallel, start infra, and sync Tinybird state (`yarn workspace @tryghost/e2e preflight:build`)
5. **Test Execution**: Run Playwright E2E tests inside the official Playwright container
6. **Artifacts**: Upload Playwright traces and reports on failure

## Available Scripts

Expand All @@ -218,6 +234,13 @@ Within the e2e directory:
# Run all tests
yarn test

# Start/stop test infra (MySQL/Redis/Mailpit/Tinybird)
yarn infra:up
yarn infra:down

# CI-like preflight for build mode (pulls images + starts infra)
yarn preflight:build

# Debug failed tests (keeps containers)
PRESERVE_ENV=true yarn test

Expand Down
Loading
Loading