Files
fotospiel-app/tests/e2e/checkout-payment.test.ts

156 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test';
import { execSync } from 'child_process';
const LOGIN_EMAIL = 'checkout-e2e@example.com';
const LOGIN_PASSWORD = 'Password123!';
test.describe('Checkout Payment Step Paddle flow', () => {
test.beforeAll(async () => {
execSync(
`php artisan tenant:add-dummy --email=${LOGIN_EMAIL} --password=${LOGIN_PASSWORD} --first_name=Checkout --last_name=Tester --address="Playwrightstr. 1" --phone="+4912345678"`
);
execSync(
`php artisan tinker --execute="App\\\\Models\\\\User::where('email', '${LOGIN_EMAIL}')->update(['email_verified_at' => now()]);"`
);
});
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', LOGIN_EMAIL);
await page.fill('input[name="password"]', LOGIN_PASSWORD);
await page.getByRole('button', { name: /Anmelden|Login/ }).click();
await expect(page).toHaveURL(/dashboard/);
});
test('opens Paddle checkout and shows success notice', async ({ page }) => {
await page.route('**/paddle/create-checkout', async (route) => {
const request = route.request();
const postData = request.postDataJSON() as { inline?: boolean } | null;
const inline = Boolean(postData?.inline);
if (inline) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
mode: 'inline',
items: [
{ priceId: 'pri_123', quantity: 1 },
],
custom_data: {
tenant_id: '1',
package_id: '2',
checkout_session_id: 'cs_123',
},
customer: {
email: LOGIN_EMAIL,
},
}),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
checkout_url: 'https://paddle.test/checkout/success',
}),
});
});
await page.route('https://cdn.paddle.com/paddle/v2/paddle.js', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `
window.Paddle = {
Environment: { set: function(env) { window.__paddleEnv = env; } },
Initialize: function(opts) { window.__paddleInit = opts; },
Checkout: {
open: function(config) {
window.__paddleOpenConfig = config;
}
}
};
`,
});
});
await openCheckoutPaymentStep(page);
await page.evaluate(() => {
window.__openedUrls = [];
window.open = (url: string, target?: string | null, features?: string | null) => {
window.__openedUrls.push({ url, target: target ?? null, features: features ?? null });
return null;
};
});
await page.getByRole('button', { name: /Continue with Paddle|Weiter mit Paddle/ }).click();
await expect(
page.locator(
'text=/Paddle checkout is running in a secure overlay|Der Paddle-Checkout läuft jetzt in einem Overlay/'
)
).toBeVisible();
await expect.poll(async () => {
return page.evaluate(() => window.__paddleOpenConfig?.items?.[0]?.priceId ?? null);
}).toBe('pri_123');
await expect.poll(async () => {
return page.evaluate(() => window.__openedUrls?.length ?? 0);
}).toBe(0);
});
test('shows error state when Paddle checkout creation fails', async ({ page }) => {
await page.route('**/paddle/create-checkout', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ message: 'test-error' }),
});
});
await openCheckoutPaymentStep(page);
await page.getByRole('button', { name: /Continue with Paddle|Weiter mit Paddle/ }).click();
await expect(
page.locator('text=/Paddle checkout could not be started|Paddle-Checkout konnte nicht gestartet werden/')
).toBeVisible();
});
});
async function openCheckoutPaymentStep(page: import('@playwright/test').Page) {
await page.goto('/packages');
const checkoutLink = page.locator('a[href^="/checkout/"]').first();
const href = await checkoutLink.getAttribute('href');
if (!href) {
throw new Error('No checkout link found on packages page.');
}
await page.goto(href);
const nextButton = page.getByRole('button', {
name: /Weiter zum Zahlungsschritt|Continue to Payment/,
});
if (await nextButton.isVisible()) {
await nextButton.click();
}
await page.waitForSelector('text=/Zahlung|Payment/');
}
declare global {
interface Window {
__openedUrls?: Array<{ url: string; target?: string | null; features?: string | null }>;
__paddleOpenConfig?: { url?: string; items?: Array<{ priceId: string; quantity: number }>; settings?: { displayMode?: string } };
__paddleEnv?: string;
__paddleInit?: Record<string, unknown>;
}
}