diff --git a/tests/ui/purchase/paddle-sandbox-full.test.ts b/tests/ui/purchase/paddle-sandbox-full.test.ts index e73b63f..02efd24 100644 --- a/tests/ui/purchase/paddle-sandbox-full.test.ts +++ b/tests/ui/purchase/paddle-sandbox-full.test.ts @@ -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 { + 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 { + 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 { + for (const selector of selectors) { + if ((await scope.locator(selector).count()) > 0) { + return true; + } + } + + return false; +} + +async function hasAnyText(page: Page, matcher: RegExp): Promise { + 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 | null, rawBody: string): string | null { if (payload) { const directUrl = payload.checkout_url ?? payload.url ?? payload.checkoutUrl;