Payment Testing Guide

How to Test Square Web Payments SDK with Playwright: Complete 2026 Guide

A scenario-by-scenario walkthrough of testing Square Web Payments SDK with Playwright. Tokenization iframes, card form rendering, Apple Pay fallback, sandbox application IDs, error states, and the pitfalls that break real payment test suites.

$200B+

โ€œSquare processed over $200 billion in gross payment volume in 2024, powering millions of merchants from small businesses to enterprise sellers worldwide.โ€

Block, Inc. 2024 Annual Report

0+Nested iframes per card form
0Payment scenarios covered
0%Fewer lines with Assrt
0sAvg tokenization round-trip

Square Web Payments SDK Flow

BrowserYour AppSquare SDK (iframe)Square APIYour ServerLoad checkout pageInitialize payments(appId, locationId)Render card form in iframeUser enters card detailstokenize() requestReturn payment token (cnon:...)Token result to your appPOST /pay with tokenCreatePayment API callPayment completedSuccess responseShow confirmation

1. Why Testing Square Web Payments Is Harder Than It Looks

Square Web Payments SDK renders its card input fields inside nested iframes hosted on Square's own domain. When you call payments.card()and attach it to a container element, the SDK injects a series of cross-origin iframes for the card number, expiration date, CVV, and postal code fields. These iframes are deliberately sandboxed to isolate sensitive card data from your application code. From Playwright's perspective, this means you cannot use standard page.fill() or page.getByLabel() calls directly. You must use frameLocator() to drill into each nested iframe before interacting with the input fields inside them.

The complexity compounds from there. The card form is initialized asynchronously. After you call card.attach('#card-container'), Square's JavaScript must download additional resources, render the iframe DOM, and signal readiness before the inputs accept focus. If your test tries to interact with the card fields before the SDK has finished initializing, the inputs will silently ignore your keystrokes. There is no loading spinner or visible indicator by default; you must wait for specific DOM conditions inside the iframe.

There are four structural reasons this flow is hard to test reliably. First, the tokenization iframe enforces cross-origin isolation, which prevents direct DOM access from the parent page. Second, Apple Pay and Google Pay require specific browser capabilities and merchant domain verification, meaning they fail silently in headless browsers unless you mock or intercept the capability check. Third, the sandbox application ID must match a specific Square developer account, and tests that use a production application ID will charge real cards. Fourth, the card form renders custom styled inputs that do not expose standard HTML attributes like name or id, so you must target them by their iframe structure and internal CSS selectors.

Square Card Form Initialization Flow

๐ŸŒ

Load SDK

Script tag on your page

โš™๏ธ

Initialize

payments(appId, locationId)

๐Ÿ“ฆ

Create Card

payments.card()

๐ŸŒ

Attach

card.attach('#container')

๐Ÿ“ฆ

Iframe Render

Cross-origin card inputs

โœ…

Ready

Inputs accept focus

Tokenization and Payment Flow

๐ŸŒ

User Fills Card

Inside SDK iframe

๐ŸŒ

Click Pay

Triggers tokenize()

โš™๏ธ

SDK Tokenizes

Card data to Square API

โœ…

Token Returned

cnon:... nonce

โš™๏ธ

Server Payment

CreatePayment with nonce

โœ…

Confirmation

Payment succeeded

A comprehensive Square Web Payments test suite must handle all of these surfaces. The sections below walk through each scenario you need, with runnable Playwright TypeScript code you can copy into your project.

2. Setting Up a Reliable Test Environment

Before writing any test code, you need a properly configured Square sandbox environment. Square provides a sandbox that mirrors the production API with test credentials, test card numbers, and a separate application ID. Never run end-to-end payment tests against production credentials; Square sandbox transactions are free and fully isolated.

Square Sandbox Setup Checklist

  • Create a Square Developer account at developer.squareup.com
  • Create a sandbox application and note the Application ID
  • Copy the sandbox Access Token from the Credentials tab
  • Note your sandbox Location ID from the Locations API
  • Add your test domain to the Web Payments SDK allowed domains
  • Verify the sandbox application uses the sandbox base URL (connect.squareupsandbox.com)
  • Disable any production webhook endpoints to avoid confusion
  • Install @square/web-payments-sdk-types for TypeScript support

Environment Variables

.env.test

Square Sandbox Test Card Reference

Square provides specific test card numbers for their sandbox environment. Unlike Stripe where 4242 4242 4242 4242 works universally, Square sandbox cards have their own distinct numbers. Using the wrong test card number is the most common reason sandbox payments fail silently.

Card NumberBrandResult
4532 7597 3454 5858VisaSuccess
5104 0600 0000 0008MastercardSuccess
3400 000000 00009AmexSuccess
4000 0000 0000 0002VisaGeneric Decline
4000 0000 0000 0010VisaCVV Failure
4000 0000 0000 0036VisaPostal Code Mismatch

Playwright Configuration for Square Iframes

Square Web Payments SDK renders card inputs inside cross-origin iframes hosted on *.squarecdn.com. Playwright needs generous timeouts for iframe rendering, and your configuration should account for the async initialization of the SDK.

playwright.config.ts
Verify Square Sandbox Credentials

3. Scenario: Card Form Rendering and Readiness

Before you can test any payment flow, the Square card form must be fully rendered and ready to accept input. This is the most common failure point in Square payment tests: the test attempts to fill card details before the SDK has finished injecting its iframes. The card form goes through several initialization stages, and your test must wait for the final โ€œreadyโ€ state before interacting with any input field.

1

Card Form Rendering and Readiness

Moderate

Goal

Navigate to the checkout page, wait for the Square Web Payments SDK to fully initialize, confirm that all card input iframes are rendered and accepting focus, and verify the Pay button is enabled.

Preconditions

  • App running at APP_BASE_URL with Square SDK loaded
  • Sandbox Application ID configured in SQUARE_APP_ID
  • Valid sandbox Location ID

Playwright Implementation

square-card-form.spec.ts

What to Assert Beyond the UI

  • All four card input iframes are present (card number, expiration, CVV, postal code)
  • Each iframe is sourced from squarecdn.com
  • The SDK did not throw initialization errors (check the browser console)
  • The pay button transitions from disabled to enabled after SDK readiness

4. Scenario: Successful Card Payment (Happy Path)

This is the core smoke test for any Square integration. A customer fills in valid card details, clicks Pay, the SDK tokenizes the card, your server creates a payment using the token, and the user sees a confirmation page. If this test breaks, no payments are working and you want to know within minutes.

The key challenge here is interacting with the card input fields inside the nested iframe structure. You must use Playwright's frameLocator() to drill through the outer Square iframe and then into each individual field iframe. The SDK also performs client-side validation before allowing tokenization, so your test data must pass Luhn checks and format validation.

2

Successful Card Payment (Happy Path)

Moderate

Goal

Complete an end-to-end payment using a Square sandbox test card, verify the tokenization succeeds, confirm the server processes the payment, and assert the confirmation page displays the correct amount.

Playwright Implementation

square-payment-happy-path.spec.ts

Successful Payment: Playwright vs Assrt

import { test, expect } from '@playwright/test';

test('successful card payment', async ({ page }) => {
  await page.goto('/checkout?item=test-product&amount=1000');
  await page.waitForSelector('#card-container iframe', {
    state: 'attached', timeout: 15_000,
  });

  const outerFrame = page.frameLocator(
    '#card-container iframe[src*="squarecdn.com"]'
  );
  const cardFrame = outerFrame.frameLocator('iframe[title="Card Number"]');
  await cardFrame.locator('input[autocomplete="cc-number"]')
    .fill('4532759734545858');

  const expFrame = outerFrame.frameLocator('iframe[title="Expiration"]');
  await expFrame.locator('input[autocomplete="cc-exp"]').fill('12/30');

  const cvvFrame = outerFrame.frameLocator('iframe[title="CVV"]');
  await cvvFrame.locator('input[autocomplete="cc-csc"]').fill('111');

  const zipFrame = outerFrame.frameLocator('iframe[title="Postal Code"]');
  await zipFrame.locator('input[autocomplete="postal-code"]').fill('94103');

  await page.getByRole('button', { name: /pay/i }).click();
  await page.waitForURL(/\/confirmation/, { timeout: 30_000 });
  await expect(page.getByText('$10.00')).toBeVisible();
});
57% fewer lines

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started โ†’

5. Scenario: Declined Card and Error Handling

Testing the unhappy path is just as important as the happy path. When a card is declined, the Square SDK returns a specific error from the tokenization step or the server-side CreatePayment call fails. Your application needs to handle both types of failure gracefully: client-side validation errors (invalid card number format, missing fields) and server-side decline responses (insufficient funds, CVV mismatch).

Square sandbox provides dedicated test card numbers that trigger specific failure modes. The card 4000 0000 0000 0002 triggers a generic decline, while 4000 0000 0000 0010 fails CVV verification. Your test should verify that the error message displayed to the user is helpful and that the card form remains interactive for retry.

3

Declined Card and Error Handling

Moderate

Playwright Implementation

square-declined-card.spec.ts

Declined Card Error: Playwright vs Assrt

test('declined card: shows error', async ({ page }) => {
  await page.goto('/checkout?item=test-product&amount=1000');
  const outerFrame = page.frameLocator(
    '#card-container iframe[src*="squarecdn.com"]'
  );
  const cardFrame = outerFrame.frameLocator('iframe[title="Card Number"]');
  await cardFrame.locator('input[autocomplete="cc-number"]')
    .fill('4000000000000002');
  // ... fill expiration, CVV, postal code ...
  await page.getByRole('button', { name: /pay/i }).click();

  await expect(
    page.getByText(/payment declined/i)
  ).toBeVisible({ timeout: 15_000 });
  expect(page.url()).toContain('/checkout');
  await expect(
    page.getByRole('button', { name: /pay/i })
  ).toBeEnabled({ timeout: 5_000 });
});
55% fewer lines

6. Scenario: Apple Pay / Google Pay Fallback

Square Web Payments SDK supports Apple Pay on Safari and Google Pay on Chrome through the same payments.applePay() and payments.googlePay() methods. However, testing these digital wallet flows in a headless Playwright environment is notoriously difficult. Apple Pay requires a registered merchant domain, a valid Apple Pay merchant certificate, and a Safari browser with a real Apple Pay wallet configured. Google Pay requires the Payment Request API to be available, which headless Chromium does not fully support.

The practical testing strategy has two parts. First, test the capability detection: verify that your checkout page correctly shows or hides the Apple Pay / Google Pay buttons based on browser capabilities. Second, intercept the SDK's capability check at the network level to simulate a supported environment, then verify your application handles the tokenization callback correctly.

4

Apple Pay / Google Pay Capability Detection

Complex

Playwright Implementation

square-digital-wallets.spec.ts

7. Scenario: Tokenization Lifecycle and Network Interception

When the user clicks Pay, the Square SDK calls its internal tokenize()method, which sends the card data from the iframe to Square's servers and returns a payment token (nonce) in the format cnon:.... This token is then passed to your server, which calls the Square Payments API to create the actual charge. Testing this lifecycle requires intercepting network requests to verify the tokenization call is made, the nonce is received, and your server forwards it correctly.

Playwright's page.route() lets you intercept the server-side payment request to verify the nonce format and the amount being charged. You can also monitor the network waterfall to ensure the tokenization happens at the right time (after form submission, before your server call).

5

Tokenization Lifecycle and Network Verification

Complex

Playwright Implementation

square-tokenization.spec.ts

8. Scenario: Gift Card and Split Payments

Square Web Payments SDK supports gift card payments through the payments.giftCard() method, which renders a separate input for gift card numbers. Split payments, where a customer pays partially with a gift card and the remainder with a credit card, are a common real-world scenario that many test suites overlook. The complexity here is that your server must orchestrate two separate tokenization calls and two CreatePayment API requests.

6

Gift Card and Split Payment Flow

Complex

Playwright Implementation

square-gift-card.spec.ts

9. Common Pitfalls That Break Square Payment Test Suites

Using Production Application IDs in Tests

The single most dangerous mistake is using a production Square Application ID in your test suite. Unlike sandbox credentials, production Application IDs will process real charges against real cards. Square sandbox Application IDs always start with sandbox-sq0idb-. Add a startup assertion to your test suite that verifies the Application ID prefix before running any payment tests. If the ID does not start with sandbox-, abort the entire suite immediately.

Racing Against Iframe Initialization

The Square SDK's iframe rendering is asynchronous and can take anywhere from 500ms to 5 seconds depending on network conditions, CDN latency, and browser performance. Tests that attempt to fill card fields immediately after navigating to the checkout page will intermittently fail because the iframe inputs have not yet been injected into the DOM. Always wait for the specific iframe and its internal input element before attempting any interaction. The waitFor({ state: 'visible' }) call on the nested input is the most reliable readiness signal.

Wrong Sandbox Test Card Numbers

Square's sandbox test card numbers are different from Stripe's. Using 4242 4242 4242 4242 (Stripe's universal test card) against Square sandbox will fail with a generic tokenization error. Square's sandbox Visa success card is 4532 7597 3454 5858. This is a frequent source of confusion for teams migrating from Stripe, and the error message from the SDK does not indicate that the card number itself is the problem.

Ignoring the Nested Iframe Structure

Square Web Payments SDK uses a two-level iframe architecture. The outer iframe (sourced from squarecdn.com) contains the card form layout, and each individual field (card number, expiration, CVV, postal code) lives inside its own nested iframe within the outer one. Tests that use a single frameLocator() call will fail because the inputs are not direct children of the first iframe. You need chained frameLocator() calls to reach each field.

Location ID Mismatch

Every Square payment is associated with a Location. If you initialize the SDK with a Location ID that does not belong to the Application ID's merchant account, the SDK will fail silently or throw a cryptic initialization error. In sandbox, each developer account has a default test location. Use the Locations API to retrieve the correct Location ID programmatically in your test setup, rather than hardcoding it.

Square Payment Test Anti-Pattern Checklist

  • Using production Application ID in test environment
  • Filling card fields before iframe is fully rendered
  • Using Stripe test card numbers against Square sandbox
  • Single frameLocator() instead of nested frameLocator() chain
  • Hardcoding Location ID instead of fetching from API
  • Missing timeout on iframe visibility waits
  • Not verifying sandbox- prefix on Application ID at test startup
  • Assuming Apple Pay works in headless Chromium
Square Payment Test Suite Run
Common Square SDK Initialization Error

10. Writing These Scenarios in Plain English with Assrt

Every scenario above requires navigating nested iframes, chaining frameLocator() calls, and waiting for asynchronous SDK initialization. The card form rendering scenario alone needs four separate iframe traversals just to verify the inputs are visible. When Square updates their SDK, changes the iframe structure, or modifies the internal input selectors, every test in your suite breaks simultaneously. This is exactly the class of maintenance burden that Assrt eliminates.

With Assrt, you describe the payment scenario in plain English. The framework resolves the iframe nesting, finds the correct input elements, handles the async initialization timing, and generates the equivalent Playwright TypeScript automatically. When Square ships an SDK update that changes the iframe title attributes or input autocomplete values, Assrt detects the failure, analyzes the new DOM structure, and opens a pull request with updated locators. Your scenario files stay untouched.

Here is the complete happy path scenario from Section 4, compiled into an Assrt file. Notice how the iframe complexity is completely abstracted: you write โ€œfill the card numberโ€ and Assrt handles the nested frameLocator() chain under the hood.

scenarios/square-payments-full-suite.assrt

Assrt compiles each scenario block into the Playwright TypeScript shown in the preceding sections. The generated code includes the nested frameLocator() chains, the iframe title selectors, the autocomplete attribute targeting, and the timeout values. When Square changes their SDK internals, Assrt regenerates the locators. Your scenario descriptions remain stable.

Start with the card form rendering check. Once that passes in CI, add the happy path payment, then the decline scenario, then the tokenization verification. In a single afternoon you can have complete Square Web Payments coverage that handles iframe nesting, async initialization, and sandbox test card validation without maintaining any brittle selectors by hand.

Full Suite: Playwright vs Assrt

// 8 test files, ~180 lines of Playwright TypeScript
// Each test must manually:
// - Chain frameLocator() calls for nested iframes
// - Wait for async SDK initialization
// - Use correct iframe[title="..."] selectors
// - Handle timeout values for each interaction
// - Know the exact input autocomplete attributes

import { test, expect } from '@playwright/test';

test('card form renders', async ({ page }) => {
  await page.goto('/checkout');
  const outer = page.frameLocator('#card-container iframe[src*="squarecdn"]');
  const card = outer.frameLocator('iframe[title="Card Number"]');
  await expect(card.locator('input[autocomplete="cc-number"]'))
    .toBeVisible({ timeout: 15_000 });
  // ... repeat for expiration, CVV, postal code
});

test('successful payment', async ({ page }) => {
  // ... 40+ lines of iframe navigation and card filling
});

test('declined card', async ({ page }) => {
  // ... 35+ lines with different test card number
});

// ... 5 more test files with similar boilerplate
71% fewer lines

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