Files
fotospiel-app/tests/ui/purchase/checkout-payment.test.ts
Codex Agent 10c99de1e2
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
Migrate billing from Paddle to Lemon Squeezy
2026-02-03 10:59:54 +01:00

176 lines
5.7 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, expectFixture as expect } from '../helpers/test-fixtures';
const demoTenantCredentials = {
email: process.env.E2E_DEMO_TENANT_EMAIL ?? 'tenant-demo@fotospiel.app',
password: process.env.E2E_DEMO_TENANT_PASSWORD ?? 'Demo1234!',
};
test.describe('Checkout Payment Step Lemon Squeezy flow', () => {
test('opens Lemon Squeezy checkout and shows success notice', async ({ page }) => {
await page.route('**/lemonsqueezy/create-checkout', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
checkout_url: 'https://fotospiel.lemonsqueezy.com/checkout/success',
}),
});
});
await page.route('https://app.lemonsqueezy.com/js/lemon.js', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `
window.createLemonSqueezy = function() {};
window.LemonSqueezy = {
Setup: function(options) { window.__lemonEventHandler = options?.eventHandler || null; },
Url: {
Open: function(url) {
window.__lemonOpenedUrl = url;
}
}
};
`,
});
});
await openCheckoutPaymentStep(page, demoTenantCredentials);
await acceptCheckoutTerms(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 Lemon Squeezy|Weiter mit Lemon Squeezy/ }).first().click();
await expect(
page.locator(
'text=/secure overlay|Overlay|neuen Tab|new tab/i'
)
).toBeVisible();
let mode: 'inline' | 'hosted' | null = null;
for (let i = 0; i < 8; i++) {
const state = await page.evaluate(() => ({
inline: Boolean(window.__lemonOpenedUrl),
opened: window.__openedUrls?.length ?? 0,
}));
if (state.inline) {
mode = 'inline';
break;
}
if (state.opened > 0) {
mode = 'hosted';
break;
}
await page.waitForTimeout(300);
}
expect(mode).not.toBeNull();
if (mode === 'inline') {
const inlineUrl = await page.evaluate(() => window.__lemonOpenedUrl ?? null);
expect(inlineUrl).not.toBeNull();
}
if (mode === 'hosted') {
await expect.poll(async () => {
return page.evaluate(() => window.__openedUrls?.[0]?.url ?? null);
}).toContain('lemonsqueezy');
}
});
test('shows error state when Lemon Squeezy checkout creation fails', async ({ page }) => {
await page.route('**/lemonsqueezy/create-checkout', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ message: 'test-error' }),
});
});
await page.route('https://app.lemonsqueezy.com/js/lemon.js', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/javascript',
body: `
window.createLemonSqueezy = function() {};
window.LemonSqueezy = {
Setup: function(options) { window.__lemonEventHandler = options?.eventHandler || null; },
Url: {
Open: function() {
throw new Error('forced Lemon Squeezy failure');
}
}
};
`,
});
});
await openCheckoutPaymentStep(page, demoTenantCredentials);
await acceptCheckoutTerms(page);
await page.getByRole('button', { name: /Continue with Lemon Squeezy|Weiter mit Lemon Squeezy/ }).first().click();
await expect(
page.locator('text=/Lemon Squeezy-Checkout konnte nicht gestartet werden|Lemon Squeezy checkout could not be started/i')
).toBeVisible();
});
});
async function openCheckoutPaymentStep(
page: import('@playwright/test').Page,
credentials: { email: string; password: string }
) {
await page.goto('/packages');
const detailsButtons = page.getByRole('button', {
name: /Details ansehen|Details anzeigen|View details/i,
});
await expect(detailsButtons.first()).toBeVisible();
await detailsButtons.first().click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible();
await dialog.getByRole('link', { name: /Jetzt bestellen|Order now|Jetzt buchen/i }).click();
await expect(page).toHaveURL(/\/(bestellen|checkout)\/\d+/);
await page.getByRole('button', { name: /^Weiter$/ }).first().click();
const continueButton = page.getByRole('button', { name: /Weiter zur Zahlung|Continue to Payment/i });
if (await continueButton.isVisible()) {
await continueButton.click();
} else {
await page.getByRole('button', { name: /^Anmelden$/ }).first().click();
await expect(page.locator('input[name="identifier"]')).toBeVisible();
await page.fill('input[name="identifier"]', credentials.email);
await page.fill('input[name="password"]', credentials.password);
await page.getByRole('button', { name: /^Anmelden$/ }).last().click();
}
await expect(page.getByPlaceholder(/Gutscheincode/i)).toBeVisible();
}
async function acceptCheckoutTerms(page: import('@playwright/test').Page) {
const termsCheckbox = page.locator('#checkout-terms-hero');
await expect(termsCheckbox).toBeVisible();
await termsCheckbox.click();
}
declare global {
interface Window {
__openedUrls?: Array<{ url: string; target?: string | null; features?: string | null }>;
__lemonOpenedUrl?: string | null;
__lemonEventHandler?: ((event: { event: string; data?: unknown }) => void) | null;
}
}