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.
โ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
Square Web Payments SDK Flow
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
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 Number | Brand | Result |
|---|---|---|
| 4532 7597 3454 5858 | Visa | Success |
| 5104 0600 0000 0008 | Mastercard | Success |
| 3400 000000 00009 | Amex | Success |
| 4000 0000 0000 0002 | Visa | Generic Decline |
| 4000 0000 0000 0010 | Visa | CVV Failure |
| 4000 0000 0000 0036 | Visa | Postal 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.
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.
Card Form Rendering and Readiness
ModerateGoal
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_URLwith Square SDK loaded - Sandbox Application ID configured in
SQUARE_APP_ID - Valid sandbox Location ID
Playwright Implementation
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.
Successful Card Payment (Happy Path)
ModerateGoal
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
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();
});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.
Declined Card and Error Handling
ModeratePlaywright Implementation
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 });
});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.
Apple Pay / Google Pay Capability Detection
ComplexPlaywright Implementation
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).
Tokenization Lifecycle and Network Verification
ComplexPlaywright Implementation
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.
Gift Card and Split Payment Flow
ComplexPlaywright Implementation
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
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.
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 boilerplateRelated Guides
How to Test Apple Pay on Web
A practical, scenario-by-scenario guide to testing Apple Pay on the web with Playwright....
How to Test BigCommerce Checkout
A scenario-by-scenario guide to testing BigCommerce checkout with Playwright. Covers...
How to Test Google Pay on Web
Step-by-step guide to testing Google Pay web integration with Playwright. Covers Payment...
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.