added various tests for playwright
This commit is contained in:
30
tests/ui/auth/login-bruteforce.test.ts
Normal file
30
tests/ui/auth/login-bruteforce.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
const shouldRun = process.env.E2E_BRUTEFORCE === '1';
|
||||
|
||||
test.describe('Login brute-force throttle', () => {
|
||||
test.skip(!shouldRun, 'Set E2E_BRUTEFORCE=1 to run brute-force throttle check against the live/staging site.');
|
||||
|
||||
test('repeated bad logins eventually trigger throttle', async ({ request }) => {
|
||||
const attemptPayload = {
|
||||
email: 'nonexistent-user@example.com',
|
||||
password: 'WrongPass123!',
|
||||
};
|
||||
|
||||
const statuses: number[] = [];
|
||||
const bodies: string[] = [];
|
||||
|
||||
for (let i = 0; i < 8; i += 1) {
|
||||
const response = await request.post('/login', {
|
||||
form: attemptPayload,
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
statuses.push(response.status());
|
||||
bodies.push(await response.text());
|
||||
}
|
||||
|
||||
const hitThrottle = statuses.includes(429) || bodies.some((body) => /too many.+attempt/i.test(body));
|
||||
|
||||
expect(hitThrottle).toBeTruthy();
|
||||
});
|
||||
});
|
||||
41
tests/ui/purchase/contact-form-spam.test.ts
Normal file
41
tests/ui/purchase/contact-form-spam.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const shouldRun = process.env.E2E_CONTACT_SPAM === '1';
|
||||
const baseUrl = process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app';
|
||||
|
||||
test.describe('Marketing contact form spam/throttle', () => {
|
||||
test.skip(!shouldRun, 'Set E2E_CONTACT_SPAM=1 to run contact spam/throttle check on staging.');
|
||||
|
||||
test('honeypot rejects bot submission and throttling kicks in', async ({ page }) => {
|
||||
await page.goto(`${baseUrl}/de#contact`);
|
||||
|
||||
const acceptCookies = page.getByRole('button', { name: /akzeptieren|accept/i });
|
||||
if (await acceptCookies.isVisible()) {
|
||||
await acceptCookies.click();
|
||||
}
|
||||
|
||||
// Fill visible fields
|
||||
await page.fill('input[name="name"]', 'Spam Bot');
|
||||
await page.fill('input[name="email"]', 'spam@example.com');
|
||||
await page.fill('textarea[name="message"]', 'Test spam message');
|
||||
|
||||
// Trip honeypot
|
||||
await page.$eval('input[name="nickname"]', (el: HTMLInputElement) => {
|
||||
el.value = 'bot-field';
|
||||
});
|
||||
|
||||
const submit = page.getByRole('button', { name: /senden|absenden|submit/i }).first();
|
||||
await submit.click();
|
||||
|
||||
await expect(page.locator('text=/error|ungültig|invalid/i')).toBeVisible();
|
||||
|
||||
// Rapid resubmits to trigger throttle (best-effort)
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
await submit.click();
|
||||
}
|
||||
|
||||
// Either error message or no success flash should be present
|
||||
const success = page.locator('text=/Danke|Erfolg|success/i');
|
||||
await expect(success).not.toBeVisible({ timeout: 1000 });
|
||||
});
|
||||
});
|
||||
@@ -1,82 +0,0 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { execSync } from 'child_process'; // Für artisan seed
|
||||
|
||||
test.describe('Marketing Package Flow: Auswahl → Registrierung → Kauf (Free & Paid)', () => {
|
||||
test.beforeAll(async () => {
|
||||
// Seed Test-Tenant (einmalig)
|
||||
execSync('php artisan tenant:add-dummy --email=test@example.com --password=password123 --first_name=Test --last_name=User --address="Teststr. 1" --phone="+49123"');
|
||||
// Mock Verifizierung: Update DB (in Test-Env)
|
||||
execSync('php artisan tinker --execute="App\\Models\\User::where(\'email\', \'test@example.com\')->update([\'email_verified_at\' => now()]);"');
|
||||
});
|
||||
|
||||
test('Free-Paket-Flow mit Wizard (ID=1, Starter, eingeloggter User)', async ({ page }) => {
|
||||
// Login first
|
||||
await page.goto('http://localhost:8000/de/login');
|
||||
await page.fill('[name="email"]', 'test@example.com');
|
||||
await page.fill('[name="password"]', 'password123');
|
||||
await page.getByRole('button', { name: 'Anmelden' }).click();
|
||||
await expect(page).toHaveURL(/\/dashboard/);
|
||||
|
||||
// Go to Wizard
|
||||
await page.goto('http://localhost:8000/purchase-wizard/10');
|
||||
await expect(page.locator('text=Sie sind bereits eingeloggt')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Weiter zum Zahlungsschritt' }).click();
|
||||
await expect(page).toHaveURL(/\/purchase-wizard\/1/); // Next step
|
||||
await page.screenshot({ path: 'wizard-logged-in.png', fullPage: true });
|
||||
|
||||
// Payment (Free: Success)
|
||||
await expect(page.locator('text=Free package assigned')).toBeVisible();
|
||||
await page.screenshot({ path: 'wizard-free-success.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard Login-Fehler mit Toast', async ({ page }) => {
|
||||
await page.goto('http://localhost:8000/purchase-wizard/10');
|
||||
// Switch to Login
|
||||
await page.getByRole('button', { name: 'Anmelden' }).click();
|
||||
await page.fill('[name="email"]', 'wrong@example.com');
|
||||
await page.fill('[name="password"]', 'wrong');
|
||||
await page.getByRole('button', { name: 'Anmelden' }).click();
|
||||
await expect(page.locator('[data-testid="toast"]')).toBeVisible(); // Toast for error
|
||||
await expect(page.locator('text=Ungültige Anmeldedaten')).toBeVisible(); // Inline error
|
||||
await page.screenshot({ path: 'wizard-login-error.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard Registrierung-Fehler mit Toast', async ({ page }) => {
|
||||
await page.goto('http://localhost:8000/purchase-wizard/10');
|
||||
// Reg form with invalid data
|
||||
await page.fill('[name="email"]', 'invalid');
|
||||
await page.getByRole('button', { name: 'Registrieren' }).click();
|
||||
await expect(page.locator('[data-testid="toast"]')).toBeVisible();
|
||||
await expect(page.locator('text=Das E-Mail muss eine gültige E-Mail-Adresse sein')).toBeVisible();
|
||||
await page.screenshot({ path: 'wizard-reg-error.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Wizard Erfolgreiche Reg mit Success-Message', async ({ page }) => {
|
||||
await page.goto('http://localhost:8000/purchase-wizard/10');
|
||||
// Fill valid reg data (use unique email)
|
||||
await page.fill('[name="first_name"]', 'TestReg');
|
||||
await page.fill('[name="last_name"]', 'User');
|
||||
await page.fill('[name="email"]', 'testreg@example.com');
|
||||
await page.fill('[name="username"]', 'testreguser');
|
||||
await page.fill('[name="address"]', 'Teststr. 1');
|
||||
await page.fill('[name="phone"]', '+49123');
|
||||
await page.fill('[name="password"]', 'Password123!');
|
||||
await page.fill('[name="password_confirmation"]', 'Password123!');
|
||||
await page.check('[name="privacy_consent"]');
|
||||
await page.getByRole('button', { name: 'Registrieren' }).click();
|
||||
await expect(page.locator('text=Sie sind nun eingeloggt')).toBeVisible(); // Success message
|
||||
await page.waitForTimeout(2000); // Auto-next
|
||||
await expect(page).toHaveURL(/\/purchase-wizard\/1/); // Payment step
|
||||
await page.screenshot({ path: 'wizard-reg-success.png', fullPage: true });
|
||||
});
|
||||
|
||||
test('Paid-Paket-Flow (ID=2, Pro mit Paddle)', async ({ page }) => {
|
||||
// Ähnlich wie Free, aber package_id=2
|
||||
await page.goto('http://localhost:8000/de/packages');
|
||||
await page.getByRole('button', { name: 'Details anzeigen' }).nth(1).click(); // Zweites Paket (Paid)
|
||||
// ... (Modal, Register/Login wie oben)
|
||||
await expect(page).toHaveURL(/\/buy-packages\/2/);
|
||||
|
||||
await expect(page.getByAltText('Paddle')).toBeVisible();
|
||||
});
|
||||
});
|
||||
44
tests/ui/purchase/marketing-smoke.test.ts
Normal file
44
tests/ui/purchase/marketing-smoke.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Marketing frontend smoke', () => {
|
||||
test('landing renders without console errors and CTA leads to packages', async ({ page }) => {
|
||||
const consoleErrors: string[] = [];
|
||||
page.on('console', (msg) => {
|
||||
if (msg.type() === 'error') {
|
||||
consoleErrors.push(msg.text());
|
||||
}
|
||||
});
|
||||
|
||||
const response = await page.goto('/');
|
||||
expect(response?.ok()).toBeTruthy();
|
||||
|
||||
const acceptCookies = page.getByRole('button', { name: /akzeptieren|accept/i });
|
||||
if (await acceptCookies.isVisible()) {
|
||||
await acceptCookies.click();
|
||||
}
|
||||
|
||||
await expect(page.getByRole('heading', { level: 1, name: /Dein Event|Fotospiel/i })).toBeVisible();
|
||||
const heroCta = page.getByRole('link', { name: /paket|packages|starten|ausprobieren/i }).first();
|
||||
await expect(heroCta).toBeVisible();
|
||||
|
||||
await heroCta.click();
|
||||
await expect(page).toHaveURL(/\/packages/);
|
||||
|
||||
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
|
||||
|
||||
expect(consoleErrors).toEqual([]);
|
||||
});
|
||||
|
||||
test('packages page lists packages and register CTAs', async ({ page }) => {
|
||||
const response = await page.goto('/packages');
|
||||
expect(response?.ok()).toBeTruthy();
|
||||
|
||||
const acceptCookies = page.getByRole('button', { name: /akzeptieren|accept/i });
|
||||
if (await acceptCookies.isVisible()) {
|
||||
await acceptCookies.click();
|
||||
}
|
||||
|
||||
const packageCards = page.locator('section >> text=/Starter|Standard|Premium/');
|
||||
await expect(packageCards.first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
38
tests/ui/purchase/paddle-sandbox-checkout.test.ts
Normal file
38
tests/ui/purchase/paddle-sandbox-checkout.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
const shouldRun = process.env.E2E_PADDLE_SANDBOX === '1';
|
||||
|
||||
test.describe('Paddle sandbox checkout (staging)', () => {
|
||||
test.skip(!shouldRun, 'Set E2E_PADDLE_SANDBOX=1 to run live sandbox checkout on staging.');
|
||||
|
||||
test('creates Paddle checkout session from packages page', async ({ page }) => {
|
||||
const base = process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app';
|
||||
|
||||
await page.goto(`${base}/packages`);
|
||||
|
||||
const acceptCookies = page.getByRole('button', { name: /akzeptieren|accept/i });
|
||||
if (await acceptCookies.isVisible()) {
|
||||
await acceptCookies.click();
|
||||
}
|
||||
|
||||
const checkoutButtons = page.locator('a:has-text("Paket") , a:has-text("Checkout"), a:has-text("Jetzt"), button:has-text("Checkout")');
|
||||
const count = await checkoutButtons.count();
|
||||
|
||||
if (count === 0) {
|
||||
test.skip('No checkout CTA found on packages page');
|
||||
}
|
||||
|
||||
const [requestPromise] = await Promise.all([
|
||||
page.waitForRequest('**/paddle/create-checkout'),
|
||||
checkoutButtons.first().click(),
|
||||
]);
|
||||
|
||||
const checkoutRequest = await requestPromise.response();
|
||||
expect(checkoutRequest, 'Expected paddle/create-checkout request to resolve').toBeTruthy();
|
||||
expect(checkoutRequest!.status()).toBeLessThan(400);
|
||||
|
||||
const body = await checkoutRequest!.json();
|
||||
const checkoutUrl = body.checkout_url ?? body.url ?? '';
|
||||
expect(checkoutUrl).toContain('paddle');
|
||||
});
|
||||
});
|
||||
79
tests/ui/purchase/paddle-sandbox-full.test.ts
Normal file
79
tests/ui/purchase/paddle-sandbox-full.test.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { test as base } from '../helpers/test-fixtures';
|
||||
|
||||
const shouldRun = process.env.E2E_PADDLE_SANDBOX === '1';
|
||||
const baseUrl = process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app';
|
||||
const sandboxEmail = process.env.E2E_PADDLE_EMAIL ?? 'playwright-buyer@example.com';
|
||||
|
||||
test.describe('Paddle sandbox full flow (staging)', () => {
|
||||
test.skip(!shouldRun, 'Set E2E_PADDLE_SANDBOX=1 to run live sandbox checkout on staging.');
|
||||
|
||||
test('create checkout, simulate webhook completion, and verify session completion', async ({ page, request }) => {
|
||||
// Jump directly into wizard for Standard package (2)
|
||||
await page.goto(`${baseUrl}/purchase-wizard/2`);
|
||||
|
||||
const acceptCookies = page.getByRole('button', { name: /akzeptieren|accept/i });
|
||||
if (await acceptCookies.isVisible()) {
|
||||
await acceptCookies.click();
|
||||
}
|
||||
|
||||
// If login/register step is present, choose guest path or continue
|
||||
const continueButtons = page.getByRole('button', { name: /weiter|continue/i });
|
||||
if (await continueButtons.first().isVisible()) {
|
||||
await continueButtons.first().click();
|
||||
}
|
||||
|
||||
// Fill minimal registration form to reach payment step
|
||||
await page.fill('input[name="first_name"]', 'Play');
|
||||
await page.fill('input[name="last_name"]', 'Wright');
|
||||
await page.fill('input[name="email"]', sandboxEmail);
|
||||
await page.fill('input[name="address"]', 'Teststrasse 1, 12345 Berlin');
|
||||
await page.fill('input[name="phone"]', '+49123456789');
|
||||
await page.fill('input[name="username"]', 'playwright-buyer');
|
||||
await page.fill('input[name="password"]', 'Password123!');
|
||||
await page.fill('input[name="password_confirmation"]', 'Password123!');
|
||||
|
||||
const checkoutCta = page.getByRole('button', { name: /weiter zum zahlungsschritt|continue to payment|Weiter/i }).first();
|
||||
await expect(checkoutCta).toBeVisible({ timeout: 20000 });
|
||||
|
||||
const [apiResponse] = await Promise.all([
|
||||
page.waitForResponse((resp) => resp.url().includes('/paddle/create-checkout') && resp.status() < 500),
|
||||
checkoutCta.click(),
|
||||
]);
|
||||
|
||||
const checkoutPayload = await apiResponse.json();
|
||||
const checkoutUrl: string = checkoutPayload.checkout_url ?? checkoutPayload.url ?? '';
|
||||
|
||||
expect(checkoutUrl).toContain('paddle');
|
||||
|
||||
// Navigate to checkout to ensure it loads (hosted page). Use sandbox card data if needed later.
|
||||
await page.goto(checkoutUrl);
|
||||
await expect(page).toHaveURL(/paddle/);
|
||||
|
||||
// Fetch latest session for this buyer
|
||||
const latestSession = await request.get('/api/_testing/checkout/sessions/latest', {
|
||||
params: { email: sandboxEmail },
|
||||
});
|
||||
|
||||
expect(latestSession.status()).toBe(200);
|
||||
const sessionJson = await latestSession.json();
|
||||
const sessionId: string | undefined = sessionJson?.data?.id;
|
||||
expect(sessionId, 'checkout session id').toBeTruthy();
|
||||
|
||||
// Simulate Paddle webhook completion
|
||||
const simulate = await request.post(`/api/_testing/checkout/sessions/${sessionId}/simulate-paddle`, {
|
||||
data: {
|
||||
status: 'completed',
|
||||
transaction_id: 'txn_playwright_' + Date.now(),
|
||||
},
|
||||
});
|
||||
|
||||
expect(simulate.status()).toBe(200);
|
||||
|
||||
// Confirm session is marked completed
|
||||
const latestCompleted = await request.get('/api/_testing/checkout/sessions/latest', {
|
||||
params: { status: 'completed', email: sandboxEmail },
|
||||
});
|
||||
expect(latestCompleted.status()).toBe(200);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user