Breeze uses three layers of testing to ensure quality across both backend and frontend code.
| Layer | Framework | Location | Requires DB | CI Workflow |
|---|---|---|---|---|
| PHP Unit / Integration | PHPUnit 10 | tests/ |
No (mocks) | .github/workflows/php.yml |
| React Component | Vitest + Testing Library | src/**/*.test.tsx |
No | .github/workflows/ui.yml |
| End-to-End (E2E) | Playwright | e2e/tests/ |
Docker | .github/workflows/e2e.yml |
- PHP 8.3+
- Composer dependencies installed (
composer install)
# Run all PHP tests (lint check + PHPUnit + PHPStan)
composer test
# Run PHPUnit only
composer phpunit
# Run a specific test suite
./breezeVendor/bin/phpunit tests/Repository
./breezeVendor/bin/phpunit tests/Service
./breezeVendor/bin/phpunit tests/Controller
# Run integration tests
./breezeVendor/bin/phpunit tests/Integration
# Generate HTML coverage report
composer coverage
# Output: tests/log/coverage/make install # Install PHP dependencies
make test # Setup test DB + run all tests (lint + phpunit + phpstan)
make lint # Auto-fix PHP (PHP-CS-Fixer) + UI (Biome) code style
make coverage # Generate HTML coverage report (tests/log/coverage/)Tests are organized by architectural layer:
tests/
├── Controller/ # Controller tests
├── Entity/ # Entity tests
├── Event/ # Event handler tests
├── Integration/ # Integration tests (mocked services)
├── Repository/ # Repository layer tests
├── Service/ # Service layer tests
├── Traits/ # Trait tests
├── Util/ # Utility tests
├── Validate/ # Validation tests
└── bootstrap.php # PHPUnit bootstrap
All unit tests use mocks and stubs — no database required.
# PHPStan (level configured in phpstan.neon)
composer phpstan
# PHP-CS-Fixer lint check
composer lint:check
# PHP-CS-Fixer auto-fix
composer lint- Node.js 22+
- npm dependencies installed (
npm ci)
# Run tests in watch mode
npm test
# Run once (CI mode)
npm run test:run
# Run with interactive UI
npm run test:uimake ui-install # Install npm dependencies
make ui-test # Run VitestComponent tests are co-located with their source files:
src/
├── Wall.test.tsx
├── components/
│ ├── Editor.test.tsx
│ ├── Like.test.tsx
│ ├── Tabs.test.tsx
│ └── ...
└── __fixtures__/ # Shared test fixtures
├── permissions.ts
├── statusData.ts
└── userData.ts
- Config:
vitest.config.ts - Setup:
vitest.setup.ts(loads@testing-library/jest-dom/vitest) - Environment: jsdom
- Linting: Biome (
biome.json) — do not use ESLint or Prettier
E2E tests run the full app stack inside Docker containers using a mock PHP API.
- Docker and Docker Compose
The docker-compose.e2e.yml file orchestrates four services:
- db — MySQL 8.0 (host port 3307)
- api — PHP mock server (host port 8080)
- app — Vite dev server (host port 3001)
- e2e — Playwright runner (runs tests then exits)
The mock API (e2e/api/) returns fixture data matching the real Breeze API
contract so tests run without a full SMF installation.
Build and start the database, mock API, and Vite dev server.
The --wait flag blocks until every container reports healthy.
docker compose -f docker-compose.e2e.yml up -d --build --wait db api appFirst run pulls the MySQL and Node images, so it may take a few minutes. Subsequent runs reuse cached layers and start in ~15 seconds.
# Run all E2E tests
docker compose -f docker-compose.e2e.yml run --rm e2e
# Run a single test file
docker compose -f docker-compose.e2e.yml run --rm e2e \
npx playwright test tests/wall.spec.ts --config=playwright.config.ts
# Run tests matching a grep pattern
docker compose -f docker-compose.e2e.yml run --rm e2e \
npx playwright test -g "displays mock statuses" --config=playwright.config.tsBecause e2e/tests/ is volume-mounted, you can edit or add test files
without rebuilding the container — just re-run step 2.
After a run, Playwright writes an HTML report to e2e/playwright-report/
(volume-mounted to the host). Open it in your browser:
open e2e/playwright-report/index.html # macOS
xdg-open e2e/playwright-report/index.html # LinuxFailure screenshots are saved to e2e/test-results/.
# Stop containers and remove volumes
docker compose -f docker-compose.e2e.yml down -v# Full cycle: start → test → stop
docker compose -f docker-compose.e2e.yml up -d --build --wait db api app
docker compose -f docker-compose.e2e.yml run --rm e2e
docker compose -f docker-compose.e2e.yml down -v| Problem | Fix |
|---|---|
app container not healthy |
Check docker compose -f docker-compose.e2e.yml logs app — usually a port conflict on 3001 |
| Tests time out waiting for statuses | Verify the mock API is running: curl http://localhost:8080/index.php?action=breezeStatus&sa=wall |
net::ERR_CONNECTION_REFUSED in tests |
Make sure VITE_APP_DEV_URL in docker-compose.e2e.yml points to http://api:8000/index.php (Docker internal hostname) |
| Stale containers from a previous run | Run docker compose -f docker-compose.e2e.yml down -v before starting again |
For local visual debugging, run Playwright on the host instead of inside
Docker. The host browser hits the Docker services through port-mapped localhost.
Note: The headless Docker runner uses Docker-internal hostnames (
http://api:8000). When running locally, the browser needslocalhostURLs instead. OverrideVITE_APP_DEV_URLon the app container for this.
One-time setup:
cd e2e
npm init -y
npm install @playwright/test
npx playwright install chromium
cd ..Start the stack with localhost API URL:
VITE_APP_DEV_URL=http://localhost:8080/index.php \
docker compose -f docker-compose.e2e.yml up -d --build --wait db api appRun tests with a visible browser:
cd e2e
# Headed mode — opens a Chromium window
npx playwright test tests/wall.spec.ts --headed --config=playwright.config.ts
# UI mode — interactive panel with timeline, DOM snapshot, step-by-step
npx playwright test --ui --config=playwright.config.ts
# Debug mode — pauses at each step with Playwright Inspector
npx playwright test tests/wall.spec.ts --debug --config=playwright.config.ts
baseURLdefaults tohttp://localhost:3001and the API override maps tohttp://localhost:8080— both are host-mapped Docker ports.
Place test files in e2e/tests/. The directory is volume-mounted so you
don't need to rebuild the container when adding or editing tests.
// e2e/tests/wall.spec.ts
import { test, expect } from '@playwright/test';
test('wall loads and displays statuses', async ({ page }) => {
await page.goto('/');
await expect(page.locator('.status')).toHaveCount(3);
});- Playwright config:
e2e/playwright.config.ts - Dockerfile:
e2e/Dockerfile(based onmcr.microsoft.com/playwright) - Base URL (Docker):
http://app:3000· Base URL (local):http://localhost:3001 - Reports:
e2e/playwright-report/(volume-mounted to host) - Screenshots on failure:
e2e/test-results/
The mock API (e2e/api/) handles the same endpoints as the real Breeze backend:
| Action | Sub-action | What it returns |
|---|---|---|
breezeStatus |
profile / wall |
3 mock statuses with comments |
breezeStatus |
postStatus |
New status (201) |
breezeStatus |
deleteStatus |
Empty (204) |
breezeComment |
postComment |
New comment (201) |
breezeComment |
deleteComment |
Empty (204) |
breezeLike |
like |
Like info (201) |
| Workflow | File | Trigger | What it runs |
|---|---|---|---|
| PHP | .github/workflows/php.yml |
push, PR | PHPUnit + PHPStan + lint |
| UI | .github/workflows/ui.yml |
push, PR | Vitest component tests |
| E2E | .github/workflows/e2e.yml |
push, PR | Docker Compose + Playwright |
# All PHP checks
composer test
# All React tests
npm run test:run
# All E2E tests
docker compose -f docker-compose.e2e.yml up -d --build --wait db api app
docker compose -f docker-compose.e2e.yml run --rm e2e
docker compose -f docker-compose.e2e.yml down -v