Capture Paddle sandbox inline network logs
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { Page } from '@playwright/test';
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
import { dismissConsentBanner, expectFixture as expect, test } from '../helpers/test-fixtures';
|
||||
|
||||
@@ -30,93 +31,130 @@ test.describe('Paddle sandbox full flow (staging)', () => {
|
||||
test.skip(!shouldRun, 'Set E2E_PADDLE_SANDBOX=1 to run live sandbox checkout on staging.');
|
||||
test.skip(!tenantEmail || !tenantPassword, 'Set E2E_TENANT_EMAIL and E2E_TENANT_PASSWORD for sandbox flow.');
|
||||
|
||||
test('register, pay via Paddle sandbox, and login to event admin', async ({ page, request }) => {
|
||||
test('register, pay via Paddle sandbox, and login to event admin', async ({ page, request }, testInfo) => {
|
||||
const paddleNetworkLog: string[] = [];
|
||||
|
||||
page.on('response', async (response) => {
|
||||
const url = response.url();
|
||||
if (!/paddle/i.test(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const status = response.status();
|
||||
let bodySnippet = '';
|
||||
if (status >= 400 || /checkout|error/i.test(url)) {
|
||||
try {
|
||||
const text = await response.text();
|
||||
bodySnippet = text.trim().slice(0, 2000);
|
||||
} catch {
|
||||
bodySnippet = '';
|
||||
}
|
||||
}
|
||||
|
||||
const entry = [`[${status}] ${url}`, bodySnippet].filter(Boolean).join('\n');
|
||||
paddleNetworkLog.push(entry);
|
||||
if (paddleNetworkLog.length > 40) {
|
||||
paddleNetworkLog.shift();
|
||||
}
|
||||
});
|
||||
|
||||
await page.addInitScript(() => {
|
||||
Object.defineProperty(navigator, 'webdriver', {
|
||||
get: () => undefined,
|
||||
});
|
||||
});
|
||||
|
||||
// Jump directly into wizard for Standard package (2)
|
||||
await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`);
|
||||
|
||||
await dismissConsentBanner(page);
|
||||
|
||||
await proceedToAccountStep(page);
|
||||
|
||||
await completeRegistrationOrLogin(page, {
|
||||
email: tenantEmail!,
|
||||
password: tenantPassword!,
|
||||
});
|
||||
|
||||
const termsCheckbox = page.locator('#checkout-terms-hero');
|
||||
await expect(termsCheckbox).toBeVisible();
|
||||
await termsCheckbox.click();
|
||||
|
||||
const checkoutCta = page.getByRole('button', { name: /Weiter mit Paddle|Continue with Paddle/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 rawBody = await apiResponse.text();
|
||||
let checkoutPayload: Record<string, unknown> | null = null;
|
||||
try {
|
||||
checkoutPayload = rawBody && rawBody.trim().startsWith('{') ? JSON.parse(rawBody) : null;
|
||||
} catch {
|
||||
checkoutPayload = null;
|
||||
// Jump directly into wizard for Standard package (2)
|
||||
await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`);
|
||||
|
||||
await dismissConsentBanner(page);
|
||||
|
||||
await proceedToAccountStep(page);
|
||||
|
||||
await completeRegistrationOrLogin(page, {
|
||||
email: tenantEmail!,
|
||||
password: tenantPassword!,
|
||||
});
|
||||
|
||||
const termsCheckbox = page.locator('#checkout-terms-hero');
|
||||
await expect(termsCheckbox).toBeVisible();
|
||||
await termsCheckbox.click();
|
||||
|
||||
const checkoutCta = page.getByRole('button', { name: /Weiter mit Paddle|Continue with Paddle/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 rawBody = await apiResponse.text();
|
||||
let checkoutPayload: Record<string, unknown> | null = null;
|
||||
try {
|
||||
checkoutPayload = rawBody && rawBody.trim().startsWith('{') ? JSON.parse(rawBody) : null;
|
||||
} catch {
|
||||
checkoutPayload = null;
|
||||
}
|
||||
|
||||
const inlineMode = checkoutPayload?.mode === 'inline' || checkoutPayload?.inline === true;
|
||||
const checkoutUrl = extractCheckoutUrl(checkoutPayload, rawBody);
|
||||
|
||||
if (!inlineMode) {
|
||||
expect(checkoutUrl).toContain('paddle');
|
||||
}
|
||||
|
||||
// Navigate to Paddle hosted checkout and complete payment.
|
||||
if (inlineMode) {
|
||||
await expect(
|
||||
page.getByText(/Checkout geöffnet|Checkout opened|Paddle-Checkout/i).first()
|
||||
).toBeVisible({ timeout: 20_000 });
|
||||
await waitForPaddleCardInputs(page, ['input[autocomplete="cc-number"]', 'input[name="cardnumber"]', 'input[name="card_number"]']);
|
||||
} else if (checkoutUrl) {
|
||||
await page.goto(checkoutUrl);
|
||||
await expect(page).toHaveURL(/paddle/);
|
||||
} else {
|
||||
throw new Error(`Missing Paddle checkout URL. Response: ${rawBody}`);
|
||||
}
|
||||
|
||||
await completeHostedPaddleCheckout(page, sandboxCard);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const latestCompleted = await request.get('/api/_testing/checkout/sessions/latest', {
|
||||
params: { status: 'completed', email: tenantEmail },
|
||||
});
|
||||
if (!latestCompleted.ok()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const json = await latestCompleted.json();
|
||||
return json?.data?.status ?? null;
|
||||
},
|
||||
{ timeout: 120_000 }
|
||||
)
|
||||
.toBe('completed');
|
||||
|
||||
await page.goto(`${baseUrl}/event-admin/login`);
|
||||
await dismissConsentBanner(page);
|
||||
|
||||
await page.locator('input[name="identifier"]').fill(tenantEmail!);
|
||||
await page.locator('input[name="password"]').fill(tenantPassword!);
|
||||
await page.getByRole('button', { name: /Anmelden|Login/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/event-admin\/mobile\/(dashboard|welcome)/i, { timeout: 30_000 });
|
||||
await expect(page.getByText(/Dashboard|Willkommen/i)).toBeVisible();
|
||||
} finally {
|
||||
if (paddleNetworkLog.length > 0) {
|
||||
const logPath = testInfo.outputPath('paddle-network-log.txt');
|
||||
await fs.writeFile(logPath, paddleNetworkLog.join('\n\n'), 'utf8');
|
||||
await testInfo.attach('paddle-network-log', {
|
||||
path: logPath,
|
||||
contentType: 'text/plain',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const inlineMode = checkoutPayload?.mode === 'inline' || checkoutPayload?.inline === true;
|
||||
const checkoutUrl = extractCheckoutUrl(checkoutPayload, rawBody);
|
||||
|
||||
if (!inlineMode) {
|
||||
expect(checkoutUrl).toContain('paddle');
|
||||
}
|
||||
|
||||
// Navigate to Paddle hosted checkout and complete payment.
|
||||
if (inlineMode) {
|
||||
await expect(
|
||||
page.getByText(/Checkout geöffnet|Checkout opened|Paddle-Checkout/i).first()
|
||||
).toBeVisible({ timeout: 20_000 });
|
||||
await waitForPaddleCardInputs(page, ['input[autocomplete="cc-number"]', 'input[name="cardnumber"]', 'input[name="card_number"]']);
|
||||
} else if (checkoutUrl) {
|
||||
await page.goto(checkoutUrl);
|
||||
await expect(page).toHaveURL(/paddle/);
|
||||
} else {
|
||||
throw new Error(`Missing Paddle checkout URL. Response: ${rawBody}`);
|
||||
}
|
||||
|
||||
await completeHostedPaddleCheckout(page, sandboxCard);
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const latestCompleted = await request.get('/api/_testing/checkout/sessions/latest', {
|
||||
params: { status: 'completed', email: tenantEmail },
|
||||
});
|
||||
if (!latestCompleted.ok()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const json = await latestCompleted.json();
|
||||
return json?.data?.status ?? null;
|
||||
},
|
||||
{ timeout: 120_000 }
|
||||
)
|
||||
.toBe('completed');
|
||||
|
||||
await page.goto(`${baseUrl}/event-admin/login`);
|
||||
await dismissConsentBanner(page);
|
||||
|
||||
await page.locator('input[name="identifier"]').fill(tenantEmail!);
|
||||
await page.locator('input[name="password"]').fill(tenantPassword!);
|
||||
await page.getByRole('button', { name: /Anmelden|Login/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/event-admin\/mobile\/(dashboard|welcome)/i, { timeout: 30_000 });
|
||||
await expect(page.getByText(/Dashboard|Willkommen/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user