Add Playwright scaffolding and example E2E test.

This commit is contained in:
antanst 2025-08-08 18:53:45 +03:00 committed by Chris
parent 25d2aee4b7
commit 9af587d918
9 changed files with 325 additions and 0 deletions

7
e2e/.env.example Normal file
View file

@ -0,0 +1,7 @@
# Base URL of the running frontend app
#APP_URL=http://localhost:8080
# Test user credentials
# Create with: TUDUDI_USER_EMAIL=... TUDUDI_USER_PASSWORD=... npm run backend:start
E2E_EMAIL=test@tududi.com
E2E_PASSWORD=password123

46
e2e/README.md Normal file
View file

@ -0,0 +1,46 @@
## tududi E2E (Playwright)
End-to-end tests live in this isolated `e2e/` folder.
The suite uses Playwright and assumes the app is running locally.
### Quick start
`npm run test:ui`
1) Backend (with a test user):
```bash
TUDUDI_USER_EMAIL=test@tududi.com TUDUDI_USER_PASSWORD=password123 npm run backend:start
```
2) Frontend dev server:
```bash
npm run frontend:dev
```
3) E2E tests:
```bash
cd e2e
npm ci
npm run install-browsers
APP_URL=http://localhost:8080 E2E_EMAIL=test@tududi.com E2E_PASSWORD=password123 npm test
```
- Only Chromium:
```bash
npx playwright test --project=Chromium
```
- UI mode:
```bash
npm run test:ui
```
### Notes
- Base URL defaults to `http://localhost:8080`. Override with `APP_URL`.
- The login smoke test fills Email/Password and expects redirect to `/today`.
- Ensure a user exists. The backend start step above auto-creates/updates the user via env vars.

105
e2e/bin/run-e2e.sh Executable file
View file

@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -euo pipefail
# Config
APP_URL_DEFAULT="http://localhost:8080"
BACKEND_URL="http://localhost:3002"
BACKEND_HEALTH="${BACKEND_URL}/api/health"
FRONTEND_URL="${APP_URL:-$APP_URL_DEFAULT}"
# Colors
red() { printf "\033[31m%s\033[0m\n" "$*"; }
green() { printf "\033[32m%s\033[0m\n" "$*"; }
yellow() { printf "\033[33m%s\033[0m\n" "$*"; }
# Ensure dependencies in e2e/
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
E2E_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
ROOT_DIR="$(cd "$E2E_DIR/.." && pwd)"
cd "$E2E_DIR"
if [ ! -f package.json ]; then
red "e2e/package.json not found"
exit 1
fi
# Install e2e deps and browsers
if [ ! -d node_modules ]; then
yellow "Installing e2e dependencies..."
npm ci
fi
if ! npx playwright --version >/dev/null 2>&1; then
yellow "Installing Playwright browsers..."
npm run install-browsers
fi
# Start backend and frontend
cd "$ROOT_DIR"
yellow "Starting backend..."
TUDUDI_USER_EMAIL="${E2E_EMAIL:-test@tududi.com}" \
TUDUDI_USER_PASSWORD="${E2E_PASSWORD:-password123}" \
npm run backend:start &
BACKEND_PID=$!
cleanup() {
yellow "Stopping background processes..."
# Attempt graceful group termination
if [ -n "${FRONTEND_PID:-}" ]; then kill -TERM -$FRONTEND_PID >/dev/null 2>&1 || true; fi
if [ -n "${BACKEND_PID:-}" ]; then kill -TERM -$BACKEND_PID >/dev/null 2>&1 || true; fi
# Kill by known ports (best-effort)
if command -v lsof >/dev/null 2>&1; then
FRONTEND_PIDS_KILL=$(lsof -ti tcp:8080 || true)
BACKEND_PIDS_KILL=$(lsof -ti tcp:3002 || true)
if [ -n "${FRONTEND_PIDS_KILL:-}" ]; then kill ${FRONTEND_PIDS_KILL} >/dev/null 2>&1 || true; fi
if [ -n "${BACKEND_PIDS_KILL:-}" ]; then kill ${BACKEND_PIDS_KILL} >/dev/null 2>&1 || true; fi
fi
# Direct child processes as fallback
if [ -n "${FRONTEND_PID:-}" ] && ps -p $FRONTEND_PID >/dev/null 2>&1; then kill $FRONTEND_PID || true; fi
if [ -n "${BACKEND_PID:-}" ] && ps -p $BACKEND_PID >/dev/null 2>&1; then kill $BACKEND_PID || true; fi
}
trap cleanup EXIT INT TERM
# Wait for backend health
yellow "Waiting for backend to be ready at ${BACKEND_HEALTH}..."
for i in {1..60}; do
if curl -sf "$BACKEND_HEALTH" >/dev/null; then
green "Backend is ready"
break
fi
sleep 1
if [ "$i" -eq 60 ]; then
red "Backend did not become ready in time"
exit 1
fi
done
yellow "Starting frontend dev server..."
npm run frontend:dev &
FRONTEND_PID=$!
# Wait for frontend
yellow "Waiting for frontend at ${FRONTEND_URL}..."
for i in {1..60}; do
if curl -sf "$FRONTEND_URL" >/dev/null; then
green "Frontend is ready"
break
fi
sleep 1
if [ "$i" -eq 60 ]; then
red "Frontend did not become ready in time"
exit 1
fi
done
# Run tests
cd "$E2E_DIR"
yellow "Running Playwright tests..."
APP_URL="$FRONTEND_URL" \
E2E_EMAIL="${E2E_EMAIL:-test@tududi.com}" \
E2E_PASSWORD="${E2E_PASSWORD:-password123}" \
npm test

92
e2e/package-lock.json generated Normal file
View file

@ -0,0 +1,92 @@
{
"name": "tududi-e2e",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tududi-e2e",
"version": "0.1.0",
"devDependencies": {
"@playwright/test": "^1.47.2",
"dotenv": "^16.5.0"
}
},
"node_modules/@playwright/test": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz",
"integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.54.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"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.54.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz",
"integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.54.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz",
"integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}

16
e2e/package.json Normal file
View file

@ -0,0 +1,16 @@
{
"name": "tududi-e2e",
"private": true,
"version": "0.1.0",
"description": "End-to-end tests for tududi using Playwright",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"codegen": "playwright codegen ${APP_URL:-http://localhost:8080}",
"install-browsers": "playwright install --with-deps"
},
"devDependencies": {
"@playwright/test": "^1.47.2",
"dotenv": "^16.5.0"
}
}

26
e2e/playwright.config.ts Normal file
View file

@ -0,0 +1,26 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
dotenv.config({ path: process.cwd() + '/.env' });
const baseURL = process.env.APP_URL || 'http://localhost:8080';
export default defineConfig({
testDir: './tests',
timeout: 60_000,
expect: { timeout: 10_000 },
fullyParallel: true,
reporter: [['list']],
use: {
baseURL,
trace: 'on-first-retry',
video: 'retain-on-failure',
screenshot: 'only-on-failure',
viewport: { width: 1280, height: 800 },
},
projects: [
{ name: 'Chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'Firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'WebKit', use: { ...devices['Desktop Safari'] } },
],
});

View file

@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}

28
e2e/tests/login.spec.ts Normal file
View file

@ -0,0 +1,28 @@
import { test, expect } from '@playwright/test';
// Simple smoke test: user can log in and gets redirected to Today page
// Requires backend and frontend dev servers running locally. By default:
// - Frontend: http://localhost:8080 (webpack dev server)
// - Backend: http://localhost:3002 (proxied by webpack dev server)
// Set APP_URL to override base URL if needed.
test('user can login and reach Today page', async ({ page, baseURL }) => {
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
// Go directly to login page
await page.goto(appUrl + '/login');
// Fill credentials. Ensure a user exists, e.g. TUDUDI_USER_EMAIL/TUDUDI_USER_PASSWORD or seed.
const email = process.env.E2E_EMAIL || 'test@tududi.com';
const password = process.env.E2E_PASSWORD || 'password123';
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill(password);
await page.getByRole('button', { name: /login/i }).click();
// Expect redirect to Today view
await expect(page).toHaveURL(/\/today$/);
// Basic sanity check: page shows some Today UI elements
await expect(page.getByText(/Today/i)).toBeVisible();
});

View file

@ -11,6 +11,7 @@
"dev": "npm run frontend:dev", "dev": "npm run frontend:dev",
"build": "npm run frontend:build", "build": "npm run frontend:build",
"test": "npm run backend:test", "test": "npm run backend:test",
"test:ui": "bash e2e/bin/run-e2e.sh",
"test:watch": "npm run frontend:test:watch", "test:watch": "npm run frontend:test:watch",
"test:coverage": "npm run frontend:test:coverage && npm run backend:test:coverage", "test:coverage": "npm run frontend:test:coverage && npm run backend:test:coverage",
"frontend:dev": "webpack serve --config webpack.config.js --hot", "frontend:dev": "webpack serve --config webpack.config.js --hot",