Compare commits
No commits in common. "314a977cedd572e3d43a8ad8b1609350f25b89ae" and "f761f4a791485ac45224d724b0ad0dc5a817ce22" have entirely different histories.
314a977ced
...
f761f4a791
8 changed files with 0 additions and 260 deletions
|
|
@ -50,30 +50,8 @@ services:
|
||||||
# Reliable file-watching under the bind-mount.
|
# Reliable file-watching under the bind-mount.
|
||||||
CHOKIDAR_USEPOLLING: "true"
|
CHOKIDAR_USEPOLLING: "true"
|
||||||
tty: true
|
tty: true
|
||||||
|
|
||||||
# Playwright e2e runner (browsers + libs preinstalled). On-demand only — opt
|
|
||||||
# in with `--profile e2e`, so it never starts with a plain `dev-up`. Reaches
|
|
||||||
# the SPA by service name on the compose network (frontend:5173), so no host
|
|
||||||
# browsers are needed (NixOS host can't run Chromium natively). Image tag is
|
|
||||||
# pinned to the @playwright/test version in e2e/package.json — keep in sync.
|
|
||||||
playwright:
|
|
||||||
image: mcr.microsoft.com/playwright:v1.49.0-jammy
|
|
||||||
profiles: [e2e]
|
|
||||||
working_dir: /e2e
|
|
||||||
volumes:
|
|
||||||
- ./e2e:/e2e
|
|
||||||
- e2e_node_modules:/e2e/node_modules
|
|
||||||
environment:
|
|
||||||
# Target the SPA by compose service name, not localhost.
|
|
||||||
E2E_BASE_URL: "http://frontend:5173"
|
|
||||||
depends_on:
|
|
||||||
- frontend
|
|
||||||
# Install deps then run the suite; the image already has the browsers.
|
|
||||||
command: sh -c "npm install && npm test"
|
|
||||||
|
|
||||||
###> symfony/mercure-bundle ###
|
###> symfony/mercure-bundle ###
|
||||||
###< symfony/mercure-bundle ###
|
###< symfony/mercure-bundle ###
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
frontend_node_modules:
|
frontend_node_modules:
|
||||||
e2e_node_modules:
|
|
||||||
|
|
|
||||||
5
e2e/.gitignore
vendored
5
e2e/.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
node_modules
|
|
||||||
/test-results
|
|
||||||
/playwright-report
|
|
||||||
/blob-report
|
|
||||||
/.cache
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
# E2E smoke tests (Playwright)
|
|
||||||
|
|
||||||
End-to-end smoke tests that drive a real browser against the **live dev stack**.
|
|
||||||
These verify the cross-stack behavior the component tests mock away: the real
|
|
||||||
session cookie, the Vite `/api` → `php:80` proxy, and the actual API contract.
|
|
||||||
|
|
||||||
## Runs in a container, not the host
|
|
||||||
|
|
||||||
Playwright needs real browser binaries. Rather than installing them on the host
|
|
||||||
(the NixOS host can't launch Chromium — missing shared libraries), the suite
|
|
||||||
runs in the **`playwright` compose service** (`mcr.microsoft.com/playwright`,
|
|
||||||
browsers + libs preinstalled). It's gated behind a compose profile so it never
|
|
||||||
starts with a plain `dev-up`.
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
1. Bring the stack up: `dev-up` (needs `frontend`, `php`, `database` services).
|
|
||||||
2. Run the suite via the `e2e` profile (installs deps + runs the tests in the
|
|
||||||
container; the image already has the browsers):
|
|
||||||
```
|
|
||||||
podman-compose -f compose.yaml -f compose.override.yaml --profile e2e run --rm playwright
|
|
||||||
```
|
|
||||||
The runner targets the SPA by compose service name (`http://frontend:5173`,
|
|
||||||
set via `E2E_BASE_URL`) — container-to-container, no host browsers involved.
|
|
||||||
|
|
||||||
## Version coupling
|
|
||||||
|
|
||||||
`@playwright/test` in `package.json` is pinned to an **exact** version that must
|
|
||||||
match the `mcr.microsoft.com/playwright:vX.Y.Z-jammy` image tag in
|
|
||||||
`compose.override.yaml`. A mismatch makes Playwright refuse to launch (the
|
|
||||||
runner and the image's bundled browsers must agree). Bump both together.
|
|
||||||
76
e2e/package-lock.json
generated
76
e2e/package-lock.json
generated
|
|
@ -1,76 +0,0 @@
|
||||||
{
|
|
||||||
"name": "buckets-e2e",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "buckets-e2e",
|
|
||||||
"devDependencies": {
|
|
||||||
"@playwright/test": "1.49.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@playwright/test": {
|
|
||||||
"version": "1.49.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz",
|
|
||||||
"integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"playwright": "1.49.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"playwright": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fsevents": {
|
|
||||||
"version": "2.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
|
||||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"os": [
|
|
||||||
"darwin"
|
|
||||||
],
|
|
||||||
"engines": {
|
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/playwright": {
|
|
||||||
"version": "1.49.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz",
|
|
||||||
"integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"playwright-core": "1.49.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"playwright": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"optionalDependencies": {
|
|
||||||
"fsevents": "2.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/playwright-core": {
|
|
||||||
"version": "1.49.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz",
|
|
||||||
"integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"playwright-core": "cli.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"name": "buckets-e2e",
|
|
||||||
"private": true,
|
|
||||||
"description": "Playwright end-to-end smoke tests. Runs in the `playwright` compose service (browsers preinstalled) against the live stack, targeting frontend:5173. NOTE: @playwright/test version must match the image tag in compose.override.yaml.",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"test": "playwright test",
|
|
||||||
"test:headed": "playwright test --headed"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@playwright/test": "1.49.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import { defineConfig, devices } from '@playwright/test'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Playwright config for the host-run e2e smoke suite.
|
|
||||||
*
|
|
||||||
* These tests drive a real browser against the LIVE dev stack (the `frontend`,
|
|
||||||
* `php`, and `database` compose services must be up — `dev-up`). The frontend
|
|
||||||
* proxies /api to the backend, so the e2e exercises the real cookie-session
|
|
||||||
* flow end to end.
|
|
||||||
*
|
|
||||||
* Runs in the `playwright` compose service (browsers preinstalled in the image)
|
|
||||||
* via `--profile e2e`, NOT on the host — the NixOS host can't launch Chromium.
|
|
||||||
* baseURL defaults to the compose service name (http://frontend:5173) via
|
|
||||||
* E2E_BASE_URL; the localhost fallback is only for an ad-hoc host run.
|
|
||||||
*/
|
|
||||||
export default defineConfig({
|
|
||||||
testDir: './tests',
|
|
||||||
fullyParallel: true,
|
|
||||||
forbidOnly: !!process.env.CI,
|
|
||||||
retries: process.env.CI ? 2 : 0,
|
|
||||||
reporter: 'list',
|
|
||||||
use: {
|
|
||||||
baseURL: process.env.E2E_BASE_URL ?? 'http://localhost:5173',
|
|
||||||
trace: 'on-first-retry',
|
|
||||||
},
|
|
||||||
projects: [
|
|
||||||
{
|
|
||||||
name: 'chromium',
|
|
||||||
use: { ...devices['Desktop Chrome'] },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import { expect, test } from '@playwright/test'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The auth happy path end to end against the live stack: register a brand-new
|
|
||||||
* account, log in with it, land on the authenticated app, then log out.
|
|
||||||
*
|
|
||||||
* Uses a unique email per run because this hits the real database (a duplicate
|
|
||||||
* email would 422 on register).
|
|
||||||
*/
|
|
||||||
test('register, log in, then log out', async ({ page }) => {
|
|
||||||
const email = `e2e-${Date.now()}@example.com`
|
|
||||||
const password = 'correct-horse-battery-staple'
|
|
||||||
|
|
||||||
// --- Register -----------------------------------------------------------
|
|
||||||
await page.goto('/register')
|
|
||||||
await page.getByLabel(/email/i).fill(email)
|
|
||||||
await page.getByLabel(/password/i).fill(password)
|
|
||||||
await page.getByRole('button', { name: /register/i }).click()
|
|
||||||
|
|
||||||
// Register does not auto-login — it redirects to the login screen.
|
|
||||||
await expect(page).toHaveURL(/\/login$/)
|
|
||||||
await expect(page.getByRole('button', { name: /log in/i })).toBeVisible()
|
|
||||||
|
|
||||||
// --- Log in -------------------------------------------------------------
|
|
||||||
await page.getByLabel(/email/i).fill(email)
|
|
||||||
await page.getByLabel(/password/i).fill(password)
|
|
||||||
await page.getByRole('button', { name: /log in/i }).click()
|
|
||||||
|
|
||||||
// Lands on the authenticated app (the scenarios placeholder + the logout
|
|
||||||
// control in the authed layout).
|
|
||||||
await expect(page.getByText(/emergency fund/i)).toBeVisible()
|
|
||||||
await expect(page.getByRole('button', { name: /log out/i })).toBeVisible()
|
|
||||||
|
|
||||||
// --- Log out ------------------------------------------------------------
|
|
||||||
await page.getByRole('button', { name: /log out/i }).click()
|
|
||||||
|
|
||||||
// Back to the login screen; the authed content is gone.
|
|
||||||
await expect(page).toHaveURL(/\/login$/)
|
|
||||||
await expect(page.getByText(/emergency fund/i)).toHaveCount(0)
|
|
||||||
})
|
|
||||||
41
shell.nix
41
shell.nix
|
|
@ -155,44 +155,6 @@ pkgs.mkShell {
|
||||||
$COMPOSE exec frontend npm run build "$@"
|
$COMPOSE exec frontend npm run build "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
dev-e2e() {
|
|
||||||
# Run the Playwright e2e smoke suite in the `playwright` compose service
|
|
||||||
# (browsers preinstalled). Needs the stack up (dev-up). NOT the Vitest
|
|
||||||
# suite — that's dev-fe-test. Pass-through args, e.g.: dev-e2e --headed
|
|
||||||
PODMAN_USERNS=keep-id $COMPOSE --profile e2e run --rm playwright npm test -- "$@"
|
|
||||||
}
|
|
||||||
|
|
||||||
dev-check() {
|
|
||||||
# Fast pre-commit gate: all static/unit checks, back + front. Fail-fast
|
|
||||||
# (stops at the first failure). No coverage, no e2e — use dev-check-all
|
|
||||||
# for the thorough version.
|
|
||||||
echo "▶ backend: tests" && dev-test \
|
|
||||||
&& echo "▶ backend: phpstan" && dev-stan \
|
|
||||||
&& echo "▶ backend: cs" && dev-cs \
|
|
||||||
&& echo "▶ frontend: tests" && dev-fe-test \
|
|
||||||
&& echo "▶ frontend: lint" && dev-fe-lint \
|
|
||||||
&& echo "▶ frontend: cs" && dev-fe-cs \
|
|
||||||
&& echo "▶ frontend: build" && dev-fe-build \
|
|
||||||
&& echo "✓ dev-check passed"
|
|
||||||
}
|
|
||||||
|
|
||||||
dev-check-all() {
|
|
||||||
# Thorough gate: everything in dev-check, but with COVERAGE instead of the
|
|
||||||
# plain test runs, plus the Playwright e2e. Slow; needs the stack up.
|
|
||||||
# NOTE: frontend coverage (dev-fe-coverage) is not wired yet — added in #62
|
|
||||||
# alongside the @vitest/coverage-v8 setup; until then this runs the plain
|
|
||||||
# frontend test suite for the frontend leg.
|
|
||||||
echo "▶ backend: coverage" && dev-coverage \
|
|
||||||
&& echo "▶ backend: phpstan" && dev-stan \
|
|
||||||
&& echo "▶ backend: cs" && dev-cs \
|
|
||||||
&& echo "▶ frontend: tests (coverage pending #62)" && dev-fe-test \
|
|
||||||
&& echo "▶ frontend: lint" && dev-fe-lint \
|
|
||||||
&& echo "▶ frontend: cs" && dev-fe-cs \
|
|
||||||
&& echo "▶ frontend: build" && dev-fe-build \
|
|
||||||
&& echo "▶ e2e" && dev-e2e \
|
|
||||||
&& echo "✓ dev-check-all passed"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ===================
|
# ===================
|
||||||
# DATABASE / MIGRATION COMMANDS
|
# DATABASE / MIGRATION COMMANDS
|
||||||
# ===================
|
# ===================
|
||||||
|
|
@ -291,9 +253,6 @@ pkgs.mkShell {
|
||||||
echo " dev-fe-cs [args] Check frontend formatting (Prettier)"
|
echo " dev-fe-cs [args] Check frontend formatting (Prettier)"
|
||||||
echo " dev-fe-cs-fix Apply frontend formatting"
|
echo " dev-fe-cs-fix Apply frontend formatting"
|
||||||
echo " dev-fe-build Build the frontend (tsc + vite build)"
|
echo " dev-fe-build Build the frontend (tsc + vite build)"
|
||||||
echo " dev-e2e [args] Run Playwright e2e smoke suite (container)"
|
|
||||||
echo " dev-check Fast gate: all back+front tests/stan/cs/build"
|
|
||||||
echo " dev-check-all Thorough gate: dev-check + coverage + e2e"
|
|
||||||
echo " dev-migrate-diff Generate a migration from entity mappings"
|
echo " dev-migrate-diff Generate a migration from entity mappings"
|
||||||
echo " dev-migrate Apply migrations to dev + test DBs"
|
echo " dev-migrate Apply migrations to dev + test DBs"
|
||||||
echo " dev-migrate-prev Roll back one migration on dev + test DBs"
|
echo " dev-migrate-prev Roll back one migration on dev + test DBs"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue