1
0
Fork 0
mirror of https://github.com/s-frick/effigenix.git synced 2026-03-28 17:04:49 +01:00

init e2e ui tests base skeleton

This commit is contained in:
Janosch 2026-03-27 09:41:35 +01:00
parent 83c7321c8f
commit c84629cc4e
16 changed files with 1219 additions and 0 deletions

View file

@ -0,0 +1,20 @@
FROM mcr.microsoft.com/playwright:v1.51.0-noble
WORKDIR /app
# pnpm installieren
RUN npm install -g pnpm@9
# Workspace-Root und e2e-Package-Manifest kopieren
COPY frontend/package.json frontend/pnpm-workspace.yaml ./
COPY test-automation/web-ui/package.json ./test-automation/web-ui/
# pnpm Install (nur Prod + Dev-Deps des e2e-Pakets)
RUN pnpm install --frozen-lockfile
# Test-Code und Konfiguration kopieren
COPY test-automation/web-ui/ ./test-automation/web-ui/
WORKDIR /app/test-automation/web-ui
ENTRYPOINT ["pnpm", "exec", "playwright", "test"]

View file

@ -0,0 +1,34 @@
import { test as base, expect } from '@playwright/test';
type AuthFixtures = {
adminToken: string;
viewerToken: string;
};
export const test = base.extend<AuthFixtures>({
adminToken: async ({ request }, use) => {
const res = await request.post('/api/auth/login', {
data: {
username: process.env.TEST_USER_ADMIN ?? 'admin',
password: process.env.TEST_USER_ADMIN_PASS ?? 'admin123',
},
});
expect(res.status()).toBe(200);
const { token } = await res.json();
await use(token);
},
viewerToken: async ({ request }, use) => {
const res = await request.post('/api/auth/login', {
data: {
username: process.env.TEST_USER_VIEWER ?? 'viewer',
password: process.env.TEST_USER_VIEWER_PASS ?? 'viewer123',
},
});
expect(res.status()).toBe(200);
const { token } = await res.json();
await use(token);
},
});
export { expect };

View file

@ -0,0 +1,29 @@
import { test as base } from '@playwright/test';
import { ApiClient } from '../helpers/api-client.js';
type SeedFixtures = {
apiClient: ApiClient;
};
/**
* Seed-Fixture: stellt einen authentifizierten ApiClient bereit.
*
* Strategie (zu klären in Phase 1):
* Option A DB-Reset vor jeder Suite via Spring Boot test-Profile + Liquibase
* Option B Test-Daten mit zufälligen Namen (UUID-Suffix) zur Isolation
*
* Aktuell: Option B als pragmatischer Einstieg.
*/
export const test = base.extend<SeedFixtures>({
apiClient: async ({ request }, use) => {
const res = await request.post('/api/auth/login', {
data: {
username: process.env.TEST_USER_ADMIN ?? 'admin',
password: process.env.TEST_USER_ADMIN_PASS ?? 'admin123',
},
});
const { token } = await res.json();
const client = new ApiClient(request, token);
await use(client);
},
});

View file

@ -0,0 +1,45 @@
import type { APIRequestContext, APIResponse } from '@playwright/test';
/**
* Typisierter API-Wrapper für Playwright-Tests.
* Ergänzt den raw request-Context um Auth-Header und JSON-Defaults.
*/
export class ApiClient {
constructor(
private readonly request: APIRequestContext,
private readonly token: string,
) {}
private get authHeaders() {
return { Authorization: `Bearer ${this.token}` };
}
async get(path: string): Promise<APIResponse> {
return this.request.get(path, { headers: this.authHeaders });
}
async post(path: string, body: unknown): Promise<APIResponse> {
return this.request.post(path, {
data: body,
headers: this.authHeaders,
});
}
async put(path: string, body: unknown): Promise<APIResponse> {
return this.request.put(path, {
data: body,
headers: this.authHeaders,
});
}
async patch(path: string, body: unknown): Promise<APIResponse> {
return this.request.patch(path, {
data: body,
headers: this.authHeaders,
});
}
async delete(path: string): Promise<APIResponse> {
return this.request.delete(path, { headers: this.authHeaders });
}
}

View file

@ -0,0 +1,17 @@
{
"name": "@effigenix/e2e",
"version": "0.1.0",
"private": true,
"description": "Playwright E2E-Tests für Effigenix ERP (Web UI + API)",
"scripts": {
"test": "playwright test",
"test:api": "playwright test --project=api",
"test:web": "playwright test --project=web-chromium",
"report": "playwright show-report playwright-report"
},
"devDependencies": {
"@playwright/test": "^1.51.0",
"@types/node": "^20.0.0",
"typescript": "^5.4.0"
}
}

View file

@ -0,0 +1,31 @@
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'test-results/junit.xml' }],
],
use: {
baseURL: process.env.BASE_URL ?? 'http://localhost:8080',
extraHTTPHeaders: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
projects: [
{
name: 'api',
testMatch: 'tests/api/**/*.spec.ts',
// Kein Browser nötig reiner API-Test via request context
},
{
name: 'web-chromium',
testMatch: 'tests/web/**/*.spec.ts',
use: { browserName: 'chromium' },
},
],
});

View file

@ -0,0 +1,30 @@
import { test, expect } from '../../../fixtures/auth.fixture.js';
/**
* TC-AUTH Autorisierung Masterdata
* Quelle: GitHub Issue #67
*/
test.describe('TC-AUTH: Autorisierung', () => {
test('TC-AUTH-01: Unauthentifizierter Zugriff wird abgelehnt', async ({ request }) => {
const res = await request.get('/api/suppliers');
expect([401, 403]).toContain(res.status());
});
test('TC-AUTH-02: Admin darf Lieferant erstellen', async ({ request, adminToken }) => {
const res = await request.post('/api/suppliers', {
data: { name: `Auth-Test-${Date.now()}`, phone: '+49 30 00000' },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(201);
});
test('TC-AUTH-03: Viewer darf keine Lieferanten erstellen', async ({ request, viewerToken }) => {
const res = await request.post('/api/suppliers', {
data: { name: `Viewer-Test-${Date.now()}`, phone: '+49 30 00001' },
headers: { Authorization: `Bearer ${viewerToken}` },
});
expect(res.status()).toBe(403);
});
// TODO: Weitere ACs aus Issue #67 ergänzen
});

View file

@ -0,0 +1,47 @@
import { test, expect } from '../../../fixtures/auth.fixture.js';
/**
* TC-CAT Produktkategorien
* Quelle: GitHub Issue #62
*/
test.describe('TC-CAT: Produktkategorien', () => {
test('TC-CAT-01: Kategorie erstellen Happy Path', async ({ request, adminToken }) => {
const res = await request.post('/api/product-categories', {
data: { name: 'Obst & Gemüse', description: 'Frische Produkte' },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(201);
const body = await res.json();
expect(body.name).toBe('Obst & Gemüse');
expect(body.description).toBe('Frische Produkte');
});
test('TC-CAT-02: Kategorie erstellen ohne Beschreibung', async ({ request, adminToken }) => {
const res = await request.post('/api/product-categories', {
data: { name: `Milchprodukte-${Date.now()}` },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(201);
});
test('TC-CAT-04: Doppelter Name wird abgelehnt', async ({ request, adminToken }) => {
const name = `Duplikat-${Date.now()}`;
await request.post('/api/product-categories', {
data: { name },
headers: { Authorization: `Bearer ${adminToken}` },
});
const res = await request.post('/api/product-categories', {
data: { name },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect([409, 422]).toContain(res.status());
});
test('TC-CAT-06: Leerer Name wird abgelehnt', async ({ request, adminToken }) => {
const res = await request.post('/api/product-categories', {
data: { name: '' },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(400);
});
});

View file

@ -0,0 +1,47 @@
import { test, expect } from '../../../fixtures/auth.fixture.js';
/**
* TC-SUP Lieferanten
* Quelle: GitHub Issue #63
*/
test.describe('TC-SUP: Lieferanten', () => {
test('TC-SUP-01: Lieferant erstellen Pflichtfelder', async ({ request, adminToken }) => {
const res = await request.post('/api/suppliers', {
data: { name: `Frisch AG ${Date.now()}`, phone: '+49 30 12345' },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(201);
const body = await res.json();
expect(body.status).toBe('AKTIV');
});
test('TC-SUP-02: Lieferant erscheint in Liste nach Erstellung', async ({ request, adminToken }) => {
const name = `ListTest-${Date.now()}`;
await request.post('/api/suppliers', {
data: { name, phone: '+49 30 99999' },
headers: { Authorization: `Bearer ${adminToken}` },
});
const res = await request.get('/api/suppliers', {
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(200);
const body = await res.json();
const found = (Array.isArray(body) ? body : body.content ?? []).some(
(s: { name: string }) => s.name === name,
);
expect(found).toBe(true);
});
test('TC-SUP-03: Lieferant ohne Name wird abgelehnt', async ({ request, adminToken }) => {
const res = await request.post('/api/suppliers', {
data: { phone: '+49 30 12345' },
headers: { Authorization: `Bearer ${adminToken}` },
});
expect(res.status()).toBe(400);
});
// TODO: Weitere TCs aus Issue #63 nach Implementierung hinzufügen
// TC-SUP-04: Doppelter Name abgelehnt
// TC-SUP-05: Lieferant deaktivieren
// TC-SUP-06: Filter nach Name
});

View file

@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["tests/**/*.ts", "fixtures/**/*.ts", "helpers/**/*.ts", "playwright.config.ts"]
}