How to Test Apple Pay, Google Pay, and PayPal Flows
Short answer
Wallet checkout converts on mobile but cannot run fully in Linux headless CI—Apple Pay needs Safari on Apple hardware; Google Pay availability varies; PayPal uses popups or redirects. Split coverage: probe Assert for order creation on every PR, wallet UI on macOS/browser matrix or manual/nightly jobs, and explicit fallback-to-card scenarios when the wallet sheet is dismissed.
Part of Testing Guides by business flow.
Who this is for
Ecommerce and SaaS teams accepting Apple Pay, Google Pay, Link, or PayPal alongside cards—often via Stripe Payment Request Button, Stripe wallets on Payment Element, or direct PayPal SDK integration.
Why testing wallet payments matters
- Revenue loss — wallet button visible but broken on Safari iOS; PayPal return URL drops session cookie; order never created while user saw success sheet.
- Conversion — mobile wallet users are high-intent; fallback to card must work when wallet cancelled.
- Support load — "Paid with Apple Pay but no confirmation email" usually means webhook gap, not wallet bug—still needs E2E proof.
Wallet APIs differ from card Elements—do not assume one spec covers all.
Complexity map
| Scenario | Edge case | Why tests break | Approach |
|---|---|---|---|
| Apple Pay | Not on Linux CI | Sheet never opens | macOS job or probe-only PR path |
| Google Pay | Chrome headless | Button hidden | Headed Chrome or API Arrange |
| PayPal popup | Blocked in headless | Hang forever | page.waitForEvent('popup') + sandbox login |
| PayPal redirect | Full page leave | Session lost | Persist cart id; probe after return |
| Stripe wallet | Payment Element mount | Same iframe rules | frameLocator + wallet button visibility |
| Cancel wallet | User dismisses sheet | Orphan pending order | Probe no paid order until card fallback |
| Shipping in PRB | Required contact fields | Validation flake | Seed address in Arrange |
| Domain verification | Apple Pay domain file | Works staging not prod | Separate smoke on prod domain |
| Currency/country | Wallet PM availability | Button missing | Seed geo; document uncovered slices |
| Webhook | Same as card | Success sheet ≠ paid | Probe order status |
| Express checkout | Guest vs logged-in | Wrong customer link | Probe customer id on order |
| Refund wallet charge | PM type specific | Refund API differs | See returns guide |
Tiered testing strategy
| Tier | Coverage | When |
|---|---|---|
| Probe + card path | Order logic, webhooks | Every PR |
| PayPal sandbox redirect | Full wallet flow | PR if stable in CI |
| Apple Pay UI | Safari sheet | macOS nightly or manual |
| TrueCoverage | wallet_type prod share | Prioritize tiers when slice grows |
Stripe wallets (Apple Pay / Google Pay)
When using Stripe Payment Element with wallets enabled:
// Wallets may not appear in Linux CI — skip UI with test annotation
test('card fallback when wallet unavailable', async ({ page, request }) => {
await page.goto(`/checkout?runId=${runId}`);
const walletButton = page.getByRole('button', { name: /apple pay|google pay/i });
if (await walletButton.isVisible().catch(() => false)) {
test.info().annotations.push({ type: 'wallet', description: 'wallet visible—run on macOS for full flow' });
}
// Card path always runnable
const frame = page.frameLocator('iframe[name^="__privateStripeFrame"]').first();
await frame.getByLabel(/card number/i).fill('4242424242424242');
await page.getByRole('button', { name: 'Pay' }).click();
await expect.poll(async () => {
const res = await request.get(`/api/test/probes/orders/${runId}`);
return (await res.json()).status;
}).toBe('paid');
});
Stripe documents wallet testing in test mode—register test domain for Apple Pay on staging.
PayPal sandbox
Use PayPal sandbox buyer/seller accounts in CI secrets.
test('PayPal sandbox checkout', async ({ page, context, request }) => {
await page.goto(`/checkout?runId=${runId}`);
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'PayPal' }).click();
const popup = await popupPromise;
await popup.getByLabel('Email').fill(process.env.PAYPAL_SANDBOX_BUYER!);
await popup.getByLabel('Password').fill(process.env.PAYPAL_SANDBOX_PASSWORD!);
await popup.getByRole('button', { name: 'Log In' }).click();
await popup.getByRole('button', { name: 'Pay Now' }).click();
await expect(page).toHaveURL(/order-confirmation/, { timeout: 60_000 });
await expect.poll(async () => {
const res = await request.get(`/api/test/probes/orders/${runId}`);
return (await res.json()).status;
}).toBe('paid');
});
Assert probe, not PayPal success page alone—your capture webhook must fire.
Fallback when wallet cancelled
await page.getByRole('button', { name: 'Apple Pay' }).click();
// Simulate dismiss — Esc or cancel if sheet appears in headed run
await page.keyboard.press('Escape');
await expect.poll(async () => {
const res = await request.get(`/api/test/probes/orders/${runId}`);
return (await res.json()).status;
}).not.toBe('paid');
// Complete with card
Arrange / Act / Assert summary
- Arrange — seed cart,
runId, sandbox credentials; HTTPS origin (wallets require secure context). - Act — shortest wallet path available in current runner; card fallback on Linux.
- Assert — probe order
paid+ payment method type if stored; webhook processed.
Anti-patterns
| Anti-pattern | Why it fails | Better approach |
|---|---|---|
| Only Apple Pay in suite | Linux CI skips forever | Card + probe every PR |
| Assert PayPal popup title | UI changes | Probe order |
| No cancel-wallet test | Ghost pending orders | Probe unpaid after dismiss |
| Live PayPal in CI | Real money risk | Sandbox only |
| Skip webhook Assert | Sheet success, no order | Same probe as card |
Example scenario
Situation: Shopper taps Apple Pay then cancels the wallet sheet.
Expected outcome: No paid order; checkout remains completable via card.
Why UI-only automation breaks: Checkout page shows error state but backend created pending PaymentIntent.
- Arrange: Seed cart for runId on HTTPS staging or localhost with valid cert.
- Act: Open wallet sheet and cancel (headed/macOS).
- Assert: Probe order not paid; complete card path; probe paid.
TestChimp workflow: Compare wallet_type × device_class in TrueCoverage—prioritize macOS job if Apple Pay dominates prod.
Same Arrange/Act/Assert pattern as expired-coupon checkout.
Connect scenarios to your QA workflow
Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).
Related scenarios
- Stripe payments — Elements, webhooks, 3DS
- Ecommerce checkout — cart → pay
- Stripe webhooks — capture events
External references
Frequently asked questions
Can I test Apple Pay in GitHub Actions Linux runners?
Apple Pay requires Safari on Apple hardware—most Linux CI cannot open the wallet sheet. Run a small macOS job for wallet UI, or rely on probe Assert for order logic and manual session capture for wallet UX.
How do I test PayPal checkout in Playwright?
Use PayPal sandbox credentials, handle popup or redirect flow with waitForEvent popup, and assert via backend probe—not PayPal UI alone. Store sandbox buyer login in CI secrets.
Should wallet tests use the same webhook Assert as cards?
Yes—wallet authorization success is not order fulfillment. Poll probe until paid after PayPal return or Stripe payment_intent.succeeded.
Google Pay button never appears in CI—what do I do?
Google Pay requires eligible browser, user, and merchant config. Test card fallback every PR; run headed Chrome wallet spec on scheduled macOS/Windows agents.
How do I test wallet cancel without flaking?
Probe that no paid order exists after dismiss, then complete card checkout in same spec. Run cancel path in headed mode where sheet exists.
Do I need HTTPS for wallet E2E?
Apple Pay and many wallet APIs require secure context. Use staging HTTPS, mkcert localhost, or Playwright webServer with TLS for local wallet dev tests.
How do we track wallet vs card usage in testing?
Compare wallet_type × device_class distributions in TrueCoverage. When prod wallet share grows, run /testchimp evolve to add macOS or PayPal scenarios. Link SmartTests with // @Scenario: tags.
Apply these patterns in your repo
Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.