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:
parent
83c7321c8f
commit
c84629cc4e
16 changed files with 1219 additions and 0 deletions
20
test-automation/web-ui/Dockerfile
Normal file
20
test-automation/web-ui/Dockerfile
Normal 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"]
|
||||
34
test-automation/web-ui/fixtures/auth.fixture.ts
Normal file
34
test-automation/web-ui/fixtures/auth.fixture.ts
Normal 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 };
|
||||
29
test-automation/web-ui/fixtures/seed.fixture.ts
Normal file
29
test-automation/web-ui/fixtures/seed.fixture.ts
Normal 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);
|
||||
},
|
||||
});
|
||||
45
test-automation/web-ui/helpers/api-client.ts
Normal file
45
test-automation/web-ui/helpers/api-client.ts
Normal 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 });
|
||||
}
|
||||
}
|
||||
17
test-automation/web-ui/package.json
Normal file
17
test-automation/web-ui/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
31
test-automation/web-ui/playwright.config.ts
Normal file
31
test-automation/web-ui/playwright.config.ts
Normal 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' },
|
||||
},
|
||||
],
|
||||
});
|
||||
30
test-automation/web-ui/tests/api/auth/authorization.spec.ts
Normal file
30
test-automation/web-ui/tests/api/auth/authorization.spec.ts
Normal 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
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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
|
||||
});
|
||||
0
test-automation/web-ui/tests/web/.gitkeep
Normal file
0
test-automation/web-ui/tests/web/.gitkeep
Normal file
12
test-automation/web-ui/tsconfig.json
Normal file
12
test-automation/web-ui/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue