Skip to main content

How to Test Mobile Web and Responsive Checkout

Short answer

Responsive checkout E2E fails when teams run desktop-only CI, use mouse clicks on touch-only controls, or assert wallet buttons that never appear in headless Linux. Emulate mobile viewports and hasTouch, split wallet vs card coverage, probe order rows on every PR, and follow Mobile testing (Mobilewright) for native app parity—not just narrow desktop windows.

Part of Testing Guides by UI patterns.

Who this is for

Ecommerce, SaaS billing, and marketplace teams where 60%+ revenue is mobile web—sticky CTAs, drawer carts, Payment Request / Apple Pay, address autofill, and breakpoint-specific layouts (hamburger nav, bottom sheets).

Why mobile checkout needs different Arrange/Assert

LayerPitfallE2E fix
ViewportDesktop grid hides mobile bugtest.use({ viewport, isMobile, hasTouch })
Touchclick() vs tap targetstap(); min 44px hit targets
Wallet payNo Apple Pay in Linux CIProbe order path + macOS nightly wallet job
Sticky headerCTA covered by cookie bannerDismiss banners in Arrange
Input zoomiOS zoom on focus breaks layoutFont-size ≥16px or probe-only field checks
OrientationLandscape cart drawer clipSeparate landscape spec slice

Mobile web is not "desktop but narrow"—scroll containers, 100vh, and safe-area insets differ.

Complexity map

ScenarioEdge caseWhy tests breakApproach
iPhone 14 viewportNot mobile user-agentWrong CSS branchdevices['iPhone 14'] preset
Android ChromeDifferent font metricsOverflow clipPixel 7 preset + screenshot on fail
Touch scrollElement not in viewporttap timeoutlocator.scrollIntoViewIfNeeded()
Drawer cartOff-canvas not visibleAssert zero itemsOpen cart drawer first
Bottom sheet checkoutz-index overlayClick interceptedgetByRole in dialog scope
Apple Pay sheetUnavailable headlessButton missing—not bugTiered: probe PR + wallet nightly
Google PayChrome headless hiddenSkip wronglyDocument coverage matrix
Autofill addressBrowser heuristicsFlaky fillSeed via API; minimal UI fill
Promo code accordionCollapsed on mobileField not foundExpand section before fill
3DS on mobileNested iframe scrollChallenge off-screenframeLocator + scroll into view
Network slow 3GTimeoutsFalse failRoute probe Assert before UI
PWA standalonedisplay-mode CSSUntested modeemulateMedia or dedicated spec
Safe area notchFixed footer overlapCTA untappableVisual regression optional
Rotate landscapeTwo-column breakpointLayout jumpsetViewportSize mid-test
Cookie consentBlocks pay buttonRandom failSeed consent cookie in Arrange

Viewport and touch fixture

// playwright.config.ts (excerpt)
import { devices } from '@playwright/test';

export default defineConfig({
projects: [
{ name: 'desktop-chrome', use: { ...devices['Desktop Chrome'] } },
{
name: 'mobile-safari',
use: {
...devices['iPhone 14'],
// hasTouch and isMobile included in preset
},
},
{
name: 'mobile-chrome',
use: { ...devices['Pixel 7'] },
},
],
});
// tests/checkout/mobile-guest.spec.ts
// @Scenario: checkout/mobile-guest-card
import { test, expect } from '../fixtures/run';

test.use({ ...test.info().project.use }); // inherits device preset

test('guest completes card checkout on mobile', async ({ page, request, runId }) => {
await request.post('/api/test/seed-cart', {
data: { runId, sku: 'tee-mobile', qty: 1 },
});

await page.goto('/cart');
await page.getByRole('button', { name: 'Checkout' }).tap();

// Dismiss sticky cookie banner if present
const accept = page.getByRole('button', { name: 'Accept cookies' });
if (await accept.isVisible()) await accept.tap();

await page.getByLabel('Email').fill(`guest+${runId}@test.local`);
await page.getByRole('button', { name: 'Pay now' }).tap();

const cardFrame = page.frameLocator('iframe[name^="__privateStripeFrame"]');
await cardFrame.getByLabel('Card number').fill('4242424242424242');
await cardFrame.getByLabel('Expiration').fill('12/34');
await cardFrame.getByLabel('CVC').fill('123');
await page.getByRole('button', { name: 'Place order' }).tap();

await expect.poll(async () => {
const res = await request.get(`/api/test/probe-order?runId=${runId}`);
return (await res.json()).status;
}, { timeout: 30_000 }).toBe('paid');
});

Wallet pay tiered strategy

Full wallet UI rarely runs in default Linux CI—mirror wallet payments guide:

TierCoverageWhen
PRCard checkout mobile + probe orderEvery commit
Nightly macOSApple Pay sheet SafariScheduled
Manual / device farmGoogle Pay biometricsRelease gate

On PR: assert wallet button visible where supported OR card fallback path completes—probe order either way.

Responsive breakpoint matrix

Do not duplicate every desktop spec—prioritize by TrueCoverage mobile sessions:

  1. Cart → checkout → pay (critical path)
  2. Nav → PLP → add to cart (discovery)
  3. Account → order history (post-purchase)

Use page.setViewportSize({ width: 390, height: 844 }) for quick breakpoint probes inside desktop project when adding one-off regression tests.

Native mobile vs mobile web

Mobile web Playwright covers responsive sites in mobile browsers. For native iOS/Android apps, use Mobile testing (Mobilewright)—shared // @Scenario links and markdown plans, different driver. Do not assume one spec runs both.

TestChimp workflow

// @Scenario: checkout/mobile-guest-card links to markdown plans. /testchimp test repairs tap selectors when responsive refactors move CTAs to bottom sheets. /testchimp init scaffolds probe-order routes. ExploreChimp highlights mobile-only screen states when markScreenState fixtures annotate checkout steps.

Anti-patterns

Anti-patternWhy it failsBetter approach
Desktop-only CIMobile layout bugs shipmobile-safari + mobile-chrome projects
click() everywhereTouch event paths untestedtap() on mobile projects
Assert wallet in LinuxFalse failuresTiered wallet + probe order
320px onlyMiss tablet breakpointPixel + iPad slice
Full page screenshot assertBrittleProbe order + targeted roles
Ignore cookie bannerIntercepted tapsDismiss or seed consent
Same spec both orientationsDoubles flakeSplit only if prod data shows usage
Skip 3DS mobileHighest fraud segmentframeLocator mobile spec

External references

Example scenario

Situation: Guest on iPhone completes checkout with card after dismissing cookie banner.

Expected outcome: Order paid; confirmation reachable without horizontal scroll trap.

Why UI-only automation breaks: Thank-you page shows while order row still pending—mobile redirect raced webhook.

  1. Arrange: Seed cart with runId via API; set iPhone 14 project.
  2. Act: Tap checkout; fill guest email; complete Stripe card in frame; place order.
  3. Assert: Poll probe-order status paid; optional confirmation heading visible.

TestChimp workflow: // @Scenario: links mobile checkout to markdown; /testchimp test preserves tap + probe Assert when bottom sheet CSS changes.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Frequently asked questions

Which mobile devices should Playwright emulate?

Minimum: iPhone 14 (Safari) and Pixel 7 (Chrome). Add iPad if tablet checkout differs. Match top TrueCoverage devices over time.

click vs tap in Playwright?

Use tap() on mobile projects with hasTouch. click() may pass but miss touch-specific handlers.

Can Apple Pay run in GitHub Actions?

Not on Linux workers. Run wallet UI on macOS nightly; keep probe-order Assert on every PR with card path.

How to test sticky mobile headers?

scrollIntoViewIfNeeded before tap; dismiss overlays. Optional visual snapshot on failure only.

Mobile web vs native app testing?

Mobile web uses Playwright device presets. Native apps use Mobilewright—see /smart-tests/mobile-testing for shared scenario links.

Responsive vs separate m. subdomain?

Same probe strategy. If m. subdomain, add baseURL project per host; assert canonical cart IDs via probe.

iOS input zoom on focus?

Ensure 16px+ font on inputs or accept zoom behavior. Prefer probe for validation errors over pixel asserts.

TestChimp with mobile checkout?

/testchimp init scaffolds probe-order; markScreenState annotates mobile screen states for ExploreChimp; /testchimp test repairs specs when responsive refactors move CTAs.

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.

Start free on TestChimp · Book a demo