Improve Paddle sandbox inline readiness checks
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-03 22:44:23 +01:00
parent 8764915fcd
commit 6f5aa5e09b

View File

@@ -16,11 +16,26 @@ const sandboxCard = {
postal: process.env.E2E_PADDLE_CARD_POSTAL ?? '10115',
};
test.use({
userAgent:
process.env.E2E_USER_AGENT ??
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
launchOptions: {
args: ['--disable-blink-features=AutomationControlled'],
},
});
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 }) => {
await page.addInitScript(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined,
});
});
// Jump directly into wizard for Standard package (2)
await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`);
@@ -69,6 +84,7 @@ test.describe('Paddle sandbox full flow (staging)', () => {
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/);
@@ -196,6 +212,62 @@ async function completeHostedPaddleCheckout(
await payButton.click();
}
async function waitForPaddleCardInputs(page: Page, selectors: string[], timeoutMs = 30_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (await hasAnySelector(page, selectors)) {
return;
}
if (await hasAnyText(page, /Something went wrong|try again later/i)) {
throw new Error('Paddle inline checkout returned an error in the iframe.');
}
await page.waitForTimeout(500);
}
throw new Error('Paddle card inputs did not appear in time.');
}
async function hasAnySelector(page: Page, selectors: string[]): Promise<boolean> {
if (await hasSelectorInFrame(page, selectors)) {
return true;
}
for (const frame of page.frames()) {
if (await hasSelectorInFrame(frame, selectors)) {
return true;
}
}
return false;
}
async function hasSelectorInFrame(scope: Page | import('@playwright/test').Frame, selectors: string[]): Promise<boolean> {
for (const selector of selectors) {
if ((await scope.locator(selector).count()) > 0) {
return true;
}
}
return false;
}
async function hasAnyText(page: Page, matcher: RegExp): Promise<boolean> {
if ((await page.getByText(matcher).count()) > 0) {
return true;
}
for (const frame of page.frames()) {
if ((await frame.getByText(matcher).count()) > 0) {
return true;
}
}
return false;
}
function extractCheckoutUrl(payload: Record<string, unknown> | null, rawBody: string): string | null {
if (payload) {
const directUrl = payload.checkout_url ?? payload.url ?? payload.checkoutUrl;