Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ backend/server/.venv/

# Root node_modules if present
node_modules

# Playwright suite: run from a checkout, never from inside the image
frontend/tests
frontend/playwright.config.ts
frontend/test-results
frontend/playwright-report
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Run these commands in order:
- `cd frontend && npm run format` - **6 seconds** - Fix code formatting (ALWAYS run before committing)
- `cd frontend && npm run lint` - **6 seconds** - Check code formatting
- `cd frontend && npm run check` - **12 seconds** - Run Svelte type checking (3 errors, 19 warnings expected)
- `cd frontend && pnpm e2e:up && pnpm test:e2e` - Playwright end-to-end tests against a Docker stack on http://localhost:8017 (see `documentation/docs/install/testing.md`)

**Backend (Django with Python):**
- Backend development requires Docker - local Python pip install fails due to network timeouts
Expand Down
108 changes: 108 additions & 0 deletions .github/workflows/e2e-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
name: E2E Tests

permissions:
contents: read

on:
workflow_dispatch:
pull_request:
paths:
- "backend/**"
- "frontend/**"
- "docker/**"
- "docker-compose.yml"
- ".github/workflows/e2e-test.yml"
push:
branches:
- main
- development
paths:
- "backend/**"
- "frontend/**"
- "docker/**"
- "docker-compose.yml"
- ".github/workflows/e2e-test.yml"

env:
POSTGRES_PASSWORD: e2e-test-pass
COMPOSE_ARGS: -p adventurelog-e2e -f docker-compose.yml -f docker/docker-compose.e2e.yml

jobs:
playwright:
name: Playwright
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v6
with:
version: 10.32.1

- uses: actions/setup-node@v7
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

- name: install dependencies
working-directory: frontend
run: pnpm install --frozen-lockfile

- name: read playwright version
id: playwright
working-directory: frontend
run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"

- name: cache playwright browsers
uses: actions/cache@v6
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ steps.playwright.outputs.version }}

- name: install chromium
working-directory: frontend
run: pnpm exec playwright install --with-deps chromium

- name: build image from source
run: docker build -f docker/Dockerfile --target aio -t adventurelog-e2e:local .

- name: start stack
run: docker compose $COMPOSE_ARGS up -d --wait

- name: wait for health endpoint
run: |
for i in $(seq 1 30); do
if curl -fsS http://localhost:8017/health; then
exit 0
fi
sleep 5
done
echo "Health check failed"
exit 1

- name: run playwright
working-directory: frontend
run: pnpm test:e2e

- name: upload report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: |
frontend/playwright-report
frontend/test-results
retention-days: 7
if-no-files-found: ignore

- name: stack logs
if: failure()
run: |
docker compose $COMPOSE_ARGS ps
docker compose $COMPOSE_ARGS logs

- name: tear down
if: always()
run: docker compose $COMPOSE_ARGS down -v
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,12 @@ When contributing, please try to match the **style and patterns already used in

---

# Testing

Frontend changes that add or change a user flow should come with a Playwright spec under `frontend/tests/e2e`. The [testing guide](documentation/docs/install/testing.md) covers running the suite locally; it also runs in CI on every pull request.

---

# Documentation Changes

If your changes affect:
Expand Down
32 changes: 32 additions & 0 deletions docker/docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Overrides for the Playwright end-to-end stack. Layer it on the Standard
# Deployment compose file:
#
# POSTGRES_PASSWORD=e2e docker compose -p adventurelog-e2e \
# -f docker-compose.yml -f docker/docker-compose.e2e.yml up -d --build --wait
#
# The image is built from the working tree rather than pulled, world data is
# skipped so the stack boots in seconds, and the port, container names and
# project name are distinct so it can run beside a normal local instance.
services:
app:
build:
context: .
dockerfile: docker/Dockerfile
target: aio
image: adventurelog-e2e:local
container_name: adventurelog-e2e
restart: "no"
env_file: !reset []
environment:
SITE_URL: http://localhost:8017
SKIP_WORLD_DATA: "1"
ENABLE_RATE_LIMITS: "false"
DJANGO_ADMIN_USERNAME: admin
DJANGO_ADMIN_PASSWORD: admin
DJANGO_ADMIN_EMAIL: admin@example.com
ports: !override
- "8017:80"

db:
container_name: adventurelog-e2e-db
restart: "no"
1 change: 1 addition & 0 deletions documentation/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ export default defineConfig({
text: "Development Setup",
link: "/docs/install/dev_container_wsl",
},
{ text: "Testing", link: "/docs/install/testing" },
{
text: "Platform Guides",
collapsed: true,
Expand Down
47 changes: 47 additions & 0 deletions documentation/docs/install/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Testing 🧪

The Playwright end-to-end suite lives in `frontend/tests/e2e` and drives the real app in a browser. It runs against the Standard Deployment image built from your working tree, so it exercises the same nginx, SvelteKit and Django wiring users get.

## Prerequisites

- Docker with the Compose plugin
- Node.js 22 and pnpm (`corepack enable` picks up the version pinned in `frontend/package.json`)

## Running the suite

```bash
cd frontend
pnpm install
pnpm exec playwright install chromium

pnpm e2e:up # build the image and start it on http://localhost:8017
pnpm test:e2e # run the suite
pnpm e2e:down # stop the stack and drop its volumes
```

`pnpm e2e:up` layers `docker/docker-compose.e2e.yml` over the root `docker-compose.yml`. The stack uses its own project name, container names and port, so it can run beside a normal local instance. World data is skipped so it boots in seconds, and the usual `admin` / `admin` superuser is created on first boot.

`pnpm test:e2e:ui` opens the Playwright UI for stepping through a spec. A failed run writes an HTML report to `frontend/playwright-report` and traces to `frontend/test-results`.

## Running against another instance

Set `PLAYWRIGHT_BASE_URL` to point the suite somewhere else:

```bash
PLAYWRIGHT_BASE_URL=http://localhost:8015 pnpm test:e2e
```

The instance needs an `admin` / `admin` user and rate limits off (`ENABLE_RATE_LIMITS=false`, the default). Tests delete the data they create, but run them against a throwaway database anyway.

## Writing tests

- Put specs in `frontend/tests/e2e/*.spec.ts` and import `test` and `expect` from `./fixtures`, not from `@playwright/test`.
- Specs start logged in as `admin`; `auth.setup.ts` logs in once and the session is reused. Use `test.use({ storageState: { cookies: [], origins: [] } })` for logged-out flows.
- Seed data through the `api` fixture, which goes through the SvelteKit `/api` proxy with the browser session. Anything it creates is deleted after the test.
- Requests to hosts other than the app are blocked, so specs never depend on map tiles, geocoding or Wikipedia. Prefer the details step of the location modal over the Quick Start map.
- Prefer `getByRole` and `getByLabel` with the English strings from `src/locales/en.json`; the suite pins the browser locale to `en-US`.
- New spec files are type-checked by `pnpm check` and formatted by `pnpm format`.

## CI

`.github/workflows/e2e-test.yml` builds the image, starts the stack and runs the suite on pull requests and pushes that touch the frontend, backend or Docker files. The HTML report is uploaded as a workflow artifact.
5 changes: 5 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Playwright
/test-results
/playwright-report
/tests/e2e/.auth
7 changes: 6 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,19 @@
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check .",
"format": "prettier --write ."
"format": "prettier --write .",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"e2e:up": "cd .. && POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-e2e} docker compose -p adventurelog-e2e -f docker-compose.yml -f docker/docker-compose.e2e.yml up -d --build --wait",
"e2e:down": "cd .. && POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-e2e} docker compose -p adventurelog-e2e -f docker-compose.yml -f docker/docker-compose.e2e.yml down -v"
},
"devDependencies": {
"@event-calendar/core": "^3.12.0",
"@event-calendar/day-grid": "^3.12.0",
"@event-calendar/interaction": "^3.12.0",
"@event-calendar/time-grid": "^3.12.0",
"@iconify-json/mdi": "^1.2.3",
"@playwright/test": "^1.63.0",
"@sveltejs/adapter-node": "^5.2.12",
"@sveltejs/adapter-vercel": "^5.7.0",
"@sveltejs/kit": "^2.49.5",
Expand Down
41 changes: 41 additions & 0 deletions frontend/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { defineConfig, devices } from '@playwright/test';
import { ADMIN_STORAGE_STATE } from './tests/e2e/constants';

// Point PLAYWRIGHT_BASE_URL at a running AdventureLog instance. The default matches
// the e2e compose stack (docker/docker-compose.e2e.yml, started with `pnpm e2e:up`).
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:8017';

export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
timeout: 45_000,
reporter: process.env.CI
? [['list'], ['github'], ['html', { open: 'never' }]]
: [['list'], ['html', { open: 'on-failure' }]],
use: {
baseURL,
locale: 'en-US',
timezoneId: 'UTC',
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: ADMIN_STORAGE_STATE,
// The location modal mounts a MapLibre map; recent Chromium builds refuse
// software WebGL in headless mode without these.
launchOptions: {
args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader']
}
},
dependencies: ['setup']
}
]
});
52 changes: 52 additions & 0 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/src/routes/locations/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@
<div
tabindex="0"
role="button"
aria-label={$t('adventures.create_new')}
class="btn btn-primary btn-circle w-16 h-16 shadow-2xl hover:shadow-primary/25 transition-all duration-200"
>
<Plus class="w-8 h-8" />
Expand Down
Loading
Loading