funktionierender stand, purchasewizard noch nicht optimiert.

This commit is contained in:
Codex Agent
2025-10-04 16:49:21 +02:00
parent bc6a75961a
commit 3c0bbb688b
15 changed files with 400 additions and 1867 deletions

View File

@@ -1,153 +1,69 @@
import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
import { execSync } from 'child_process'; // Für artisan seed
const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:8000';
function seedTestUser() {
execSync('php artisan tenant:add-dummy --email=test@example.com --password=password123 --first_name=Test --last_name=User --address="Teststr. 1" --phone="+49123"', { stdio: 'ignore' });
execSync('php artisan tinker --execute="App\\Models\\User::where(\'email\', \'test@example.com\')->update([\'email_verified_at\' => now()]);"', { stdio: 'ignore' });
}
test.describe('Marketing Purchase Wizard', () => {
test.beforeAll(() => {
seedTestUser();
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('guest users see registration step after package selection', async ({ page }) => {
await page.goto(`${BASE_URL}/purchase-wizard/1`);
test('Free-Paket-Flow (ID=1, Starter)', async ({ page }) => {
await page.goto('http://localhost:8000/de'); // Lokaler Server (vite dev)
await expect(page).toHaveTitle(/Fotospiel/);
await page.screenshot({ path: 'free-step1-home.png', fullPage: true });
await page.getByRole('button', { name: /Weiter/i }).click();
// Paketauswahl
await page.getByRole('link', { name: 'Alle Packages ansehen' }).click();
await expect(page).toHaveURL(/\/de\/packages/);
await page.screenshot({ path: 'free-step2-packages.png', fullPage: true });
await page.getByRole('button', { name: 'Details anzeigen' }).first().click(); // Erstes Paket (Free)
await expect(page.locator('dialog')).toBeVisible();
await page.screenshot({ path: 'free-step3-modal.png', fullPage: true });
await page.getByRole('tab', { name: 'Kaufen' }).click();
await page.getByRole('link', { name: 'Registrieren & Kaufen' }).click();
await expect(page).toHaveURL(/\/de\/register\?package_id=1/);
await page.screenshot({ path: 'free-step4-register.png', fullPage: true });
await expect(page.getByText(/Registrieren/i)).toBeVisible();
await expect(page.getByText(/Anmelden/i)).toBeVisible();
// Registrierung (Test-Daten, aber seedet vorab hier Login simulieren falls nötig)
// Da seeded: Verwende Login statt neuer Registrierung für Test
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.getByRole('button', { name: 'Anmelden' }).click(); // Falls Login-Form nach Redirect
await expect(page).toHaveURL(/\/buy-packages\/1/);
await page.screenshot({ path: 'free-step5-buy.png', fullPage: true });
// Kauf (Free: Direkte Success)
await expect(page.locator('text=Free package assigned')).toContainText('success'); // API-Response oder Page-Text
await page.goto('/marketing/success');
await expect(page).toHaveURL(/\/marketing\/success/);
await page.screenshot({ path: 'free-step6-success.png', fullPage: true });
await expect(page).toHaveURL(/\/admin/); // Redirect
await page.screenshot({ path: 'free-step7-admin.png', fullPage: true });
await expect(page.locator('text=Remaining Photos')).toContainText('300'); // Limits aus package-flow.test.ts integriert
});
test('authenticated users skip auth and can finish PayPal flow', async ({ page }) => {
await page.route('https://js.stripe.com/v3', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `window.Stripe = function(){
return {
elements: function(){
return {
create: function(){
return {
mount: function(){},
destroy: function(){},
on: function(){},
update: function(){},
unmount: function(){},
};
},
getElement: function(){
return {
clear: function(){},
};
}
};
},
confirmCardPayment: async function(){
return { paymentIntent: { id: 'pi_test', status: 'succeeded' } };
}
};
};`
});
test('Paid-Paket-Flow (ID=2, Pro mit Stripe-Test)', 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/);
// Mock Stripe
await page.route('https://checkout.stripe.com/**', async route => {
await route.fulfill({ status: 200, body: '<html>Mock Stripe Success</html>' });
});
// Simuliere Checkout: Fill Test-Karte
await page.fill('[name="cardNumber"]', '4242424242424242');
await page.fill('[name="cardExpiry"]', '12/25');
await page.fill('[name="cardCvc"]', '123');
await page.click('[name="submit"]');
await page.waitForURL(/\/marketing\/success/); // Nach Webhook
await page.screenshot({ path: 'paid-step6-success.png', fullPage: true });
await page.route('https://www.paypal.com/sdk/js?**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `window.paypal = {
Buttons: function(options){
return {
render: function(container){
const target = typeof container === 'string' ? document.querySelector(container) : container;
if (!target) return;
const btn = document.createElement('button');
btn.type = 'button';
btn.textContent = 'PayPal Test Button';
btn.addEventListener('click', async () => {
try {
const orderId = await options.createOrder();
await options.onApprove({ orderID: orderId });
} catch (error) {
if (options.onError) options.onError(error);
}
});
target.innerHTML = '';
target.appendChild(btn);
},
close: function(){}
};
}
};`
});
});
await page.route('**/purchase/auth/login', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
status: 'authenticated',
user: { id: 1, email: 'test@example.com', name: 'Test User', pending_purchase: false, email_verified: true },
next_step: 'payment',
needs_verification: false,
}),
}));
await page.route('**/purchase/auth/register', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
status: 'registered',
user: { id: 2, email: 'new@example.com', name: 'New User', pending_purchase: true, email_verified: false },
next_step: 'payment',
}),
}));
await page.route('**/purchase/stripe/intent', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ client_secret: 'pi_secret', payment_intent_id: 'pi_test' }),
}));
await page.route('**/purchase/stripe/complete', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'completed' }),
}));
await page.route('**/purchase/paypal/order', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ order_id: 'ORDER-TEST', status: 'CREATED' }),
}));
await page.route('**/purchase/paypal/capture', (route) => route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ status: 'captured' }),
}));
await page.goto(`${BASE_URL}/de/login`);
await page.fill('input[name="login"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.getByRole('button', { name: /Anmelden/i }).click();
await expect(page).toHaveURL(/dashboard|admin/i, { timeout: 10000 });
await page.goto(`${BASE_URL}/purchase-wizard/2`);
await page.getByRole('button', { name: /Weiter/i }).click();
await expect(page.getByRole('button', { name: 'Stripe' })).toBeVisible();
await expect(page.getByRole('button', { name: 'PayPal' })).toBeVisible();
await page.getByRole('button', { name: 'PayPal' }).click();
await page.getByRole('button', { name: 'PayPal Test Button' }).click();
await expect(page.getByText(/Willkommen/i)).toBeVisible();
await expect(page.getByRole('button', { name: /Dashboard/i })).toBeVisible();
// Integration: Limits-Check wie in package-flow.test.ts
await expect(page.locator('text=Remaining Photos')).toContainText('Unbegrenzt'); // Pro-Limit
});
});
});