trails/playwright.config.ts
Ullrich Schäfer be64e2df5c
test(e2e): journal↔planner save handoff covers Phase A + Phase B
New \`e2e/journal-planner-save.test.ts\` exercises the full save flow:

1. Seed a routeId + JWT via \`/api/e2e/seed\` (journal).
2. POST to \`/api/sessions\` on the planner with \`callbackUrl\` +
   \`callbackToken\` — mirrors what the journal's \`edit-in-planner\`
   action does server-to-server.
3. Open the planner session in a real browser.
4. Click \"Save to Journal\". The button POSTs sessionId+GPX to the
   planner's \`/api/save-to-journal\` action (Phase A); the action
   forwards to the journal callback with the Bearer.

Test 1 asserts:
- The journal's route ends up with geometry (round-trip works).
- The exact JWT string is **never** present in any browser-issued
  request body or \`Authorization\` header — i.e. Phase A correctly
  keeps the token server-side.

Test 2 asserts:
- A second click on Save reuses the same stored JWT, the journal's
  jti consumer rejects it, and the planner UI surfaces the error.
  This is the Phase B replay guard exercised end-to-end through the
  UI rather than just the API.

Added \`journal-planner-save\` Playwright project (no baseURL — the
test navigates both apps using absolute URLs).

Full repo: pnpm typecheck / lint / test all green (cached).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 00:43:56 +02:00

118 lines
2.9 KiB
TypeScript

import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./e2e",
outputDir: "./e2e/results",
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: process.env.CI
? [["github"], ["html", { open: "never" }], ["json", { outputFile: "playwright-results.json" }]]
: "list",
use: {
baseURL: "http://localhost:3000",
trace: "on-first-retry",
screenshot: "only-on-failure",
},
projects: [
{
name: "journal",
testMatch: "journal.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
{
name: "planner",
testMatch: "planner-*.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3001",
},
},
{
name: "auth",
testMatch: "auth.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
{
name: "public-content",
testMatch: "public-content.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
{
name: "demo-bot",
testMatch: "demo-bot.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
{
name: "integration",
testMatch: "integration.test.ts",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "journal-planner-save",
testMatch: "journal-planner-save.test.ts",
use: {
// No baseURL — the test navigates between both apps using
// absolute URLs.
...devices["Desktop Chrome"],
},
},
{
name: "notifications",
testMatch: "notifications.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
{
name: "komoot-import",
testMatch: "komoot-import.test.ts",
use: {
...devices["Desktop Chrome"],
baseURL: "http://localhost:3000",
},
},
],
webServer: process.env.CI
? [
{
command: "npx react-router-serve ./build/server/index.js",
url: "http://localhost:3000",
cwd: "./apps/journal",
reuseExistingServer: false,
},
{
command: "node --experimental-strip-types server.ts",
url: "http://localhost:3001",
cwd: "./apps/planner",
reuseExistingServer: false,
},
]
: [
{
command: "pnpm --filter @trails-cool/journal dev",
url: "http://localhost:3000",
reuseExistingServer: true,
},
{
command: "pnpm --filter @trails-cool/planner dev",
url: "http://localhost:3001",
reuseExistingServer: true,
},
],
});