Files
claude-skills-reference/engineering-team/playwright-pro/templates/checkout/payment.md
Alireza Rezvani d33d03da50 feat: add playwright-pro plugin — production-grade Playwright testing toolkit (#254)
Complete Claude Code plugin with:
- 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report)
- 3 specialized agents (test-architect, test-debugger, migration-planner)
- 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility)
- TestRail MCP server (TypeScript) — 8 tools for bidirectional sync
- BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing
- Smart hooks (auto-validate tests, auto-detect Playwright projects)
- 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests)
- Leverages Claude Code built-ins (/batch, /debug, Explore subagent)
- Zero-config for core features; TestRail/BrowserStack via env vars
- Both TypeScript and JavaScript support throughout

Co-authored-by: Leo <leo@openclaw.ai>
2026-03-05 13:50:05 +01:00

5.4 KiB

Payment Template

Tests card form entry, validation, and payment processing.

Prerequisites

  • Cart with items, shipping filled
  • Test card numbers: {{testCardNumber}} (success), {{declinedCardNumber}} (decline)
  • App running at {{baseUrl}}

TypeScript

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

async function fillCardForm(page: Page, card: {
  number: string; expiry: string; cvc: string; name: string;
}): Promise<void> {
  // Stripe/Braintree iframes — adapt frame locator to your provider
  const cardFrame = page.frameLocator('[data-testid="card-number-frame"]');
  await cardFrame.getByRole('textbox', { name: /card number/i }).fill(card.number);
  const expiryFrame = page.frameLocator('[data-testid="expiry-frame"]');
  await expiryFrame.getByRole('textbox', { name: /expiry/i }).fill(card.expiry);
  const cvcFrame = page.frameLocator('[data-testid="cvc-frame"]');
  await cvcFrame.getByRole('textbox', { name: /cvc|cvv/i }).fill(card.cvc);
  await page.getByRole('textbox', { name: /cardholder name/i }).fill(card.name);
}

test.describe('Payment', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('{{baseUrl}}/checkout/payment');
  });

  // Happy path: successful payment
  test('completes payment with valid card', async ({ page }) => {
    await fillCardForm(page, {
      number: '{{testCardNumber}}',
      expiry: '12/28',
      cvc: '123',
      name: '{{cardholderName}}',
    });
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page).toHaveURL(/\/order-confirmation|\/success/);
    await expect(page.getByRole('heading', { name: /order confirmed|thank you/i })).toBeVisible();
  });

  // Happy path: processing state shown
  test('shows processing state while payment is pending', async ({ page }) => {
    await fillCardForm(page, {
      number: '{{testCardNumber}}',
      expiry: '12/28',
      cvc: '123',
      name: '{{cardholderName}}',
    });
    const payBtn = page.getByRole('button', { name: /pay|place order/i });
    await payBtn.click();
    await expect(payBtn).toBeDisabled();
    await expect(page.getByText(/processing|please wait/i)).toBeVisible();
  });

  // Error case: declined card
  test('shows decline error for rejected card', async ({ page }) => {
    await fillCardForm(page, {
      number: '{{declinedCardNumber}}',
      expiry: '12/28',
      cvc: '123',
      name: '{{cardholderName}}',
    });
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page.getByRole('alert')).toContainText(/declined|card.*not accepted/i);
    await expect(page).toHaveURL(/\/checkout\/payment/);
  });

  // Error case: invalid card number format
  test('shows inline error for invalid card number', async ({ page }) => {
    const cardFrame = page.frameLocator('[data-testid="card-number-frame"]');
    await cardFrame.getByRole('textbox', { name: /card number/i }).fill('1234');
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page.getByText(/invalid.*card number/i)).toBeVisible();
  });

  // Error case: expired card
  test('shows error for expired card', async ({ page }) => {
    await fillCardForm(page, {
      number: '{{testCardNumber}}',
      expiry: '01/20',
      cvc: '123',
      name: '{{cardholderName}}',
    });
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page.getByRole('alert')).toContainText(/expired|invalid.*expiry/i);
  });

  // Edge case: 3DS authentication required
  test('handles 3DS challenge and completes payment', async ({ page }) => {
    await fillCardForm(page, {
      number: '{{threeDsCardNumber}}',
      expiry: '12/28',
      cvc: '123',
      name: '{{cardholderName}}',
    });
    await page.getByRole('button', { name: /pay|place order/i }).click();
    // 3DS modal appears
    const challengeFrame = page.frameLocator('[data-testid="3ds-challenge-frame"]');
    await challengeFrame.getByRole('button', { name: /complete authentication/i }).click();
    await expect(page).toHaveURL(/\/order-confirmation|\/success/);
  });
});

JavaScript

const { test, expect } = require('@playwright/test');

test.describe('Payment', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('{{baseUrl}}/checkout/payment');
  });

  test('completes payment with valid card', async ({ page }) => {
    const cardFrame = page.frameLocator('[data-testid="card-number-frame"]');
    await cardFrame.getByRole('textbox', { name: /card number/i }).fill('{{testCardNumber}}');
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page).toHaveURL(/\/order-confirmation/);
  });

  test('shows decline error for rejected card', async ({ page }) => {
    const cardFrame = page.frameLocator('[data-testid="card-number-frame"]');
    await cardFrame.getByRole('textbox', { name: /card number/i }).fill('{{declinedCardNumber}}');
    await page.getByRole('button', { name: /pay|place order/i }).click();
    await expect(page.getByRole('alert')).toContainText(/declined/i);
  });
});

Variants

Variant Description
Successful payment Valid test card → order confirmation
Processing state Button disabled + spinner during processing
Declined card Error alert, stays on payment page
Invalid card number Inline validation from provider
Expired card Expiry error
3DS challenge Modal completed, payment succeeds