Payment Testing Guide

How to Test Google Pay on Web with Playwright: Payment Request API, Tokenization, and Transaction Callbacks

A scenario-by-scenario walkthrough of testing Google Pay web integration with Playwright. Payment Request API mocking, tokenization stubs, TEST environment configuration, Google Pay button rendering, and transaction callback verification in CI.

150M+

Google Pay is used by over 150 million users in 40+ countries, and its web integration via the Payment Request API is the primary checkout method for thousands of merchants globally.

Google, 2025 Annual Report

0Payment scenarios covered
0API layers to mock
0%Fewer lines with Assrt
0sAvg test execution time

Google Pay Web Transaction Flow

BrowserYour AppGoogle Pay JSGoogle ServersPayment GatewayClick Google Pay buttonloadPaymentData()Authenticate + tokenizePaymentData with tokenResolve promiseSend token to gatewayCharge confirmedShow success receipt

1. Why Testing Google Pay on Web Is Harder Than It Looks

Google Pay on the web operates through a layered architecture that creates multiple points of friction for automated tests. At the lowest level, the browser exposes the Payment Request API, a native browser feature that presents a system-level payment sheet outside the DOM. Playwright cannot interact with this native sheet directly, because it is not part of the page content. On top of that, Google provides the google.payments.api.PaymentsClient JavaScript library, which wraps the Payment Request API and adds Google's own authentication, tokenization, and card selection flows. These flows load Google-hosted iframes and pop-up windows that your test has no control over in a standard Playwright configuration.

The tokenization layer adds another dimension of complexity. When a user completes the Google Pay sheet, Google returns a PaymentData object containing a payment token. This token is generated by Google's servers using the merchant's gateway configuration and is cryptographically signed. In production, your backend sends this token to your payment gateway (Stripe, Braintree, Adyen, etc.) for processing. In tests, you cannot generate real tokens because Google's tokenization requires a real user session with a real saved card. You must mock the token response.

There are five structural reasons this flow resists straightforward end-to-end testing. First, the Google Pay button itself is rendered inside a shadow DOM within a Google-hosted iframe, making it invisible to standard Playwright locators. Second, the payment sheet is a browser-native or Google-hosted overlay that sits outside the page DOM entirely. Third, the loadPaymentData() promise only resolves after the user interacts with the Google Pay sheet, which cannot be automated without mocking. Fourth, the TEST environment has behavioral differences from production, including dummy token formats and different isReadyToPay responses across browsers. Fifth, transaction callbacks from your payment gateway are asynchronous and may arrive via webhooks, requiring your test to coordinate between browser assertions and server-side state.

Google Pay Web Integration Layers

🌐

Your Checkout Page

Renders GPay button

⚙️

Google Pay JS SDK

PaymentsClient init

isReadyToPay()

Check browser + card

📦

Google Pay Button

Shadow DOM iframe

💳

loadPaymentData()

Opens payment sheet

🔒

Tokenization

Google servers sign token

⚙️

Your Backend

Process via gateway

Why Standard Locators Fail

🌐

Page DOM

Your app's HTML

📦

Google iframe

Cross-origin frame

🔒

Shadow DOM

Encapsulated button

💳

Payment Sheet

Native browser UI

↪️

Google Auth

Pop-up window

The practical approach is not to test Google's code. Google tests their own payment sheet. Your job is to test that your integration calls the right methods with the right parameters, handles the token response correctly, sends it to your gateway, and displays the right confirmation or error UI. The scenarios below demonstrate how to mock the Google Pay SDK at each integration boundary so you can test your code with confidence.

2. Setting Up a Reliable Test Environment

Google Pay provides a TEST environment that returns dummy tokens without charging real cards. Your first step is to configure your checkout page to use the TEST environment when running under Playwright. The Google Pay API accepts an environment parameter of either "TEST" or "PRODUCTION". In TEST mode, isReadyToPay() always returns true regardless of whether the user has a saved card, and loadPaymentData() returns a dummy token without displaying the real payment sheet.

Google Pay Test Environment Setup Checklist

  • Register as a Google Pay merchant in the Google Pay Business Console
  • Set environment to 'TEST' in PaymentsClient constructor for all test runs
  • Configure your payment gateway's test/sandbox mode (Stripe test keys, Braintree sandbox, etc.)
  • Create a Playwright fixture that injects a Google Pay SDK mock before page load
  • Set up environment variables for gateway credentials and merchant ID
  • Prepare mock PaymentData responses matching your gateway's expected token format
  • Configure route interception for Google Pay script CDN (optional, for offline CI)
  • Disable Content Security Policy in test mode if it blocks script injection

Environment Variables

.env.test

Core Google Pay Mock Fixture

The most reliable approach is to intercept the Google Pay JavaScript SDK before it loads and replace it with a mock that your tests control. This eliminates dependency on Google's servers entirely. The mock implements the same interface as the real PaymentsClient: isReadyToPay(), createButton(), and loadPaymentData(). You control what each method returns so you can test happy paths, declines, and error states.

test/fixtures/google-pay-mock.ts

Playwright Configuration for Payment Tests

playwright.config.ts
Install Dependencies

3. Scenario: Google Pay Button Rendering and Readiness

Before any transaction can happen, the Google Pay button must render on your checkout page. This is not as trivial as it sounds. The button only appears if isReadyToPay() returns true, which depends on the browser, the environment configuration, and whether the user has a valid payment method available. In TEST mode, this always returns true, but your code must handle the false case gracefully in production. Testing both branches ensures your checkout page does not show a broken or missing button when Google Pay is unavailable.

1

Google Pay Button Renders When Available

Straightforward

Goal

Verify that your checkout page calls isReadyToPay(), receives a positive response, and renders the Google Pay button in the expected container.

Preconditions

  • App running at APP_BASE_URL
  • Google Pay mock fixture injected with isReadyToPay: true
  • Checkout page has at least one item in the cart

Playwright Implementation

gpay-button.spec.ts

Button Rendering: Playwright vs Assrt

import { test, expect } from '../fixtures/google-pay-mock';

test('Google Pay button renders when available', async ({ page }) => {
  await page.goto('/checkout');

  const gpayButton = page.getByRole('button', {
    name: /buy with google pay/i,
  });
  await expect(gpayButton).toBeVisible({ timeout: 5_000 });

  const mockCalls = await page.evaluate(
    () => window.__gpayMockCalls
  );
  const readyCall = mockCalls.find(
    c => c.method === 'isReadyToPay'
  );
  expect(readyCall).toBeDefined();
  expect(
    readyCall.args[0]
      .allowedPaymentMethods[0]
      .parameters.allowedCardNetworks
  ).toContain('VISA');
});
50% fewer lines

4. Scenario: Successful Payment with Mocked Token

The core happy path for Google Pay web testing involves clicking the Google Pay button, receiving a mocked PaymentData response, sending the token to your backend, and verifying the order confirmation screen. Because the real Google Pay sheet cannot be automated (it is a native browser overlay or a Google-hosted iframe), you mock the loadPaymentData() response and focus your test on everything your code does after receiving the token. This is where the majority of integration bugs live: incorrect token parsing, missing fields in the gateway request, wrong currency codes, and unhandled promise rejections.

2

Successful Google Pay Transaction

Moderate

Goal

Complete a Google Pay purchase from button click through order confirmation, verifying that the correct token reaches your backend and the UI updates accordingly.

Preconditions

  • Google Pay mock returning a valid PaymentData object
  • Backend running with Stripe test keys (or equivalent gateway sandbox)
  • Cart contains at least one item with a known total

Playwright Implementation

gpay-happy-path.spec.ts

What to Assert Beyond the UI

Happy Path Verification Points

  • Token format matches your gateway's expected structure
  • Correct currency code (e.g. USD) in the loadPaymentData request
  • Total price in the request matches the displayed cart total
  • Backend returns a 200 with an order ID
  • Order confirmation page shows the correct card last four digits
  • No JavaScript console errors during the transaction flow

Happy Path Payment: Playwright vs Assrt

import { test, expect } from '../fixtures/google-pay-mock';

test('successful Google Pay transaction', async ({ page }) => {
  await page.goto('/products/test-widget');
  await page.getByRole('button', { name: /add to cart/i }).click();
  await page.goto('/checkout');

  const gpayButton = page.getByRole('button', {
    name: /buy with google pay/i,
  });
  await expect(gpayButton).toBeVisible();

  const paymentPromise = page.waitForRequest(req =>
    req.url().includes('/api/payments/process')
    && req.method() === 'POST'
  );
  await gpayButton.click();

  const paymentRequest = await paymentPromise;
  const body = paymentRequest.postDataJSON();
  const token = JSON.parse(
    body.paymentMethodData.tokenizationData.token
  );
  expect(token.id).toMatch(/^tok_test_gpay_/);
  expect(token.card.last4).toBe('1234');

  await page.waitForURL(/\/order-confirmation/);
  await expect(page.getByText(/order confirmed/i)).toBeVisible();
});
49% fewer lines

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

5. Scenario: Payment Request API Interception

Some Google Pay integrations use the browser's native Payment Request API directly instead of (or in addition to) the Google Pay JavaScript SDK. The Payment Request API is a W3C standard that allows the browser to mediate payments through a built-in UI. When your code calls new PaymentRequest(), Chromium presents a native payment sheet that Playwright cannot interact with. The solution is to intercept the PaymentRequest constructor before your page loads and replace it with a mock that immediately resolves with the payment details you control.

3

Payment Request API Interception

Complex

Playwright Implementation

payment-request-api.spec.ts

The key insight is that the addInitScript call runs before any page JavaScript executes. This means your mock PaymentRequest constructor is in place before your checkout code tries to create a real one. The mock's show() method resolves immediately instead of opening a native payment sheet, so your test never blocks on user interaction with browser chrome.

6. Scenario: Declined Card and Error Handling

A well-tested checkout handles failures gracefully. Google Pay can fail at two levels: the loadPaymentData() promise can reject (user cancelled, browser error, or network failure), or the token can reach your gateway and the charge can be declined. Both paths need tests. The first verifies your frontend handles a rejected promise without crashing. The second verifies your backend correctly propagates a decline to the UI.

4

User Cancellation and Declined Card

Moderate

Playwright Implementation

gpay-errors.spec.ts

Error Handling: Playwright vs Assrt

test('payment gateway declines the card', async ({ page }) => {
  await page.route('**/api/payments/process', route => {
    route.fulfill({
      status: 402,
      contentType: 'application/json',
      body: JSON.stringify({
        error: 'card_declined',
        message: 'Your card was declined.',
        decline_code: 'insufficient_funds',
      }),
    });
  });

  await page.goto('/checkout');
  const gpayButton = page.getByRole('button', {
    name: /buy with google pay/i,
  });
  await gpayButton.click();

  await expect(page.getByText(/card was declined/i))
    .toBeVisible({ timeout: 10_000 });

  const retryButton = page.getByRole('button', {
    name: /try again|use another method/i,
  });
  await expect(retryButton).toBeVisible();
  expect(page.url()).toContain('/checkout');
});
50% fewer lines

7. Scenario: Dynamic Price Updates and Shipping Options

Google Pay supports dynamic price updates through the paymentDataCallbacks mechanism. When a user selects a different shipping address or shipping option inside the Google Pay sheet, your code receives a callback and must return updated transaction details (recalculated tax, updated shipping cost, possibly new shipping options). Testing this requires your mock to simulate the callback chain: the user selects a shipping option, your callback runs, and the final PaymentData reflects the updated total.

5

Dynamic Shipping and Price Recalculation

Complex

Playwright Implementation

gpay-dynamic-pricing.spec.ts

8. Scenario: Gateway Callback and Order Confirmation

In many integrations, the payment gateway sends an asynchronous webhook after the charge succeeds. Your backend creates a pending order when it receives the token, then confirms the order when the webhook arrives. Testing this async flow requires coordinating between the browser (which polls for order status) and the server (which processes the webhook). The most reliable approach is to intercept the gateway webhook endpoint and simulate it with a known payload.

6

Gateway Webhook and Async Order Confirmation

Complex

Playwright Implementation

gpay-webhook.spec.ts
Google Pay Test Suite Run

9. Common Pitfalls That Break Google Pay Test Suites

Google Pay web testing has a distinct set of failure modes that differ from standard payment form testing. These are sourced from real issues reported on the Google Pay SDK GitHub repository, Stack Overflow, and the Google Pay developer community forums.

Pitfall 1: Testing Against the Real Google Pay Sheet

The most common mistake is trying to automate the actual Google Pay payment sheet. The sheet is either a browser-native Payment Request UI or a Google-hosted iframe overlay. Both are inaccessible to Playwright's DOM manipulation. Tests that attempt to frameLocator into the Google Pay sheet or page.click() on native browser controls will fail silently or time out. The correct approach is to mock loadPaymentData() as shown in the scenarios above and test your integration code, not Google's payment sheet.

Pitfall 2: Forgetting addInitScript Timing

If you inject the Google Pay mock after page.goto(), the real Google Pay SDK may have already loaded and cached a reference to the real PaymentsClient. Your mock will not take effect. Always call page.addInitScript() before any navigation. The init script runs in the page context before any other JavaScript, guaranteeing your mock is in place when the checkout code initializes.

Common addInitScript Timing Error

Pitfall 3: Token Format Mismatches Between Gateways

Each payment gateway expects the Google Pay token in a slightly different format. Stripe expects the token string to be passed directly as payment_method_data[token]. Braintree expects the entire paymentData object serialized as JSON. Adyen expects the token under paymentMethod.googlePayToken. If your mock returns a token format that does not match what your gateway integration actually sends, your tests will pass but production will fail. Always generate your mock token to match the exact format your gateway expects, including nested JSON structures and field names.

Pitfall 4: Ignoring the CANCELED Status Code

When a user closes the Google Pay sheet without completing payment, the loadPaymentData() promise rejects with a status code of CANCELED. Many applications treat all promise rejections as errors and show an error banner. The correct behavior is to detect the CANCELED status and silently return the user to the checkout page without showing an error. Test this explicitly, as shown in Section 6.

Pitfall 5: Content Security Policy Blocking Mock Injection

If your checkout page has a strict Content Security Policy that restricts script-src, the addInitScript mock may fail silently. Check your browser console for CSP violation errors. In test mode, either relax the CSP header or use Playwright's page.route() to intercept the Google Pay CDN script and replace it with your mock implementation served from your test server.

Google Pay Testing Anti-patterns

  • Attempting to automate the native Google Pay payment sheet directly
  • Injecting mocks after page.goto() instead of using addInitScript
  • Using a generic token format that does not match your gateway's expected structure
  • Treating CANCELED status as an error instead of a normal user action
  • Skipping CSP configuration for test environments
  • Testing only the happy path without decline and cancellation scenarios
  • Hardcoding amounts instead of reading from the cart or DOM
  • Not verifying that loadPaymentData receives the correct currency and total

10. Writing These Scenarios in Plain English with Assrt

Every scenario above requires substantial boilerplate: mock setup, init script injection, request interception, token parsing, and assertion chains. A single Google Pay test file can easily reach 200 lines. When Google updates their SDK, renames a field in the PaymentData object, or changes the PaymentsClient constructor signature, every one of those tests needs manual updates. Assrt lets you describe the intent of each scenario in plain English and handles the mock wiring, selector resolution, and assertion generation behind the scenes.

The happy path payment scenario from Section 4 is a good example. In raw Playwright, you need to know the exact mock structure, the request interception pattern, how to parse the nested token JSON, and the correct URL pattern for the confirmation page. In Assrt, you describe what should happen and let the framework figure out the implementation details.

scenarios/gpay-full-suite.assrt

Assrt compiles each scenario block into the same Playwright TypeScript you saw in the preceding sections, committed to your repo as real tests you can read, run, and modify. When Google changes the PaymentData schema or your payment gateway updates their token format, Assrt detects the failure, analyzes the new structure, and opens a pull request with the updated mock setup and assertions. Your scenario files stay untouched.

Start with the button rendering scenario. Once it is green in your CI, add the happy path transaction, then the cancellation and decline tests, then the dynamic pricing flow, then the webhook confirmation. In a single afternoon you can have comprehensive Google Pay coverage that most production applications never achieve by hand, and that stays current as both Google's API and your integration evolve.

Related Guides

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk