Claude Skills Guide

Claude Code Playwright API Testing Workflow Tutorial

API testing is a critical part of modern software development. Whether you’re validating REST endpoints, checking GraphQL resolvers, or verifying microservice contracts, having a robust testing workflow saves time and prevents bugs from reaching production. In this tutorial, we’ll explore how to use Claude Code with Playwright to create an efficient, maintainable API testing workflow.

Why Combine Claude Code with Playwright for API Testing?

Playwright is primarily known for end-to-end browser testing, but its API testing capabilities are equally powerful. When you combine Playwright with Claude Code, you get an intelligent partner that can:

Claude Code acts as your testing companion, understanding your codebase context and helping you write better, more thorough API tests.

Setting Up Your Testing Environment

Before diving into the workflow, let’s set up a proper testing environment with Playwright configured for API testing.

Installation and Configuration

First, initialize a new project and install Playwright:

mkdir api-testing-workflow && cd api-testing-workflow
npm init -y
npm install -D @playwright/test
npx playwright install

Create a playwright.config.ts file optimized for API testing:

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: process.env.API_URL || 'http://localhost:3000',
    trace: 'on-first-retry',
    extraHTTPHeaders: {
      'Content-Type': 'application/json',
    },
  },
  projects: [
    {
      name: 'api-tests',
      testMatch: /api.*\.spec\.ts$/,
    },
  ],
});

Environment Variables Management

Create a .env file to manage your API endpoints and authentication tokens:

API_URL=https://api.yourapp.com
API_KEY=your_test_api_key
TEST_USER_EMAIL=test@example.com
TEST_USER_PASSWORD=secure_password_here

Load these in your tests using the dotenv package or Playwright’s built-in environment variable support.

Creating Your First API Test

Now let’s create a practical API test that demonstrates key testing patterns. We’ll test a hypothetical user management API.

Basic API Request Testing

Create a test file tests/api/users.spec.ts:

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

test.describe('Users API', () => {
  let authToken: string;

  test.beforeAll(async ({ request }) => {
    // Set up: Get authentication token
    const response = await request.post('/api/auth/login', {
      data: {
        email: process.env.TEST_USER_EMAIL,
        password: process.env.TEST_USER_PASSWORD,
      },
    });

    expect(response.ok()).toBeTruthy();
    const body = await response.json();
    authToken = body.token;
  });

  test('GET /api/users returns user list', async ({ request }) => {
    const response = await request.get('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
    });

    expect(response.ok()).toBeTruthy();
    expect(response.status()).toBe(200);

    const users = await response.json();
    expect(Array.isArray(users)).toBeTruthy();
    expect(users.length).toBeGreaterThan(0);
  });

  test('GET /api/users/:id returns specific user', async ({ request }) => {
    const response = await request.get('/api/users/1', {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
    });

    expect(response.ok()).toBeTruthy();
    const user = await response.json();
    expect(user.id).toBe(1);
    expect(user).toHaveProperty('email');
  });

  test('POST /api/users creates new user', async ({ request }) => {
    const newUser = {
      name: 'Test User',
      email: `test-${Date.now()}@example.com`,
      role: 'user',
    };

    const response = await request.post('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
      data: newUser,
    });

    expect(response.ok()).toBeTruthy();
    const createdUser = await response.json();
    expect(createdUser.email).toBe(newUser.email);
  });

  test('PUT /api/users/:id updates user', async ({ request }) => {
    const updateData = {
      name: 'Updated Name',
    };

    const response = await request.put('/api/users/1', {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
      data: updateData,
    });

    expect(response.ok()).toBeTruthy();
    const updatedUser = await response.json();
    expect(updatedUser.name).toBe(updateData.name);
  });

  test('DELETE /api/users/:id removes user', async ({ request }) => {
    // First create a user to delete
    const createResponse = await request.post('/api/users', {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
      data: {
        name: 'To Be Deleted',
        email: `delete-${Date.now()}@example.com`,
      },
    });

    const created = await createResponse.json();

    // Now delete it
    const deleteResponse = await request.delete(`/api/users/${created.id}`, {
      headers: {
        Authorization: `Bearer ${authToken}`,
      },
    });

    expect(deleteResponse.ok()).toBeTruthy();
    expect(deleteResponse.status()).toBe(204);
  });
});

Advanced Testing Patterns

Testing Error Cases and Edge Cases

Comprehensive API testing includes error scenarios:

test.describe('Error Handling', () => {
  test('GET /api/users/:id returns 404 for non-existent user', async ({ request }) => {
    const response = await request.get('/api/users/99999');
    expect(response.status()).toBe(404);
    
    const body = await response.json();
    expect(body).toHaveProperty('error');
  });

  test('POST /api/users returns 400 for invalid email', async ({ request }) => {
    const response = await request.post('/api/users', {
      data: {
        name: 'Test',
        email: 'invalid-email',
      },
    });

    expect(response.status()).toBe(400);
    const body = await response.json();
    expect(body.errors).toBeDefined();
  });

  test('API returns 401 without authentication', async ({ request }) => {
    const response = await request.get('/api/users');
    expect(response.status()).toBe(401);
  });
});

Response Validation with Schema Testing

Use JSON schema validation to ensure API responses match expected structures:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  email: z.string().email(),
  name: z.string(),
  role: z.enum(['user', 'admin', 'moderator']),
  createdAt: z.string().datetime(),
});

test('GET /api/users returns properly formatted response', async ({ request }) => {
  const response = await request.get('/api/users');
  const body = await response.json();

  // Validate each user matches the schema
  body.users.forEach((user: unknown) => {
    const result = UserSchema.safeParse(user);
    expect(result.success).toBeTruthy();
  });
});

Optimizing Your Workflow with Claude Code

Using Claude for Test Generation

When working with Claude Code, you can use its understanding of your API to generate comprehensive tests. Simply describe your endpoint and ask for test coverage:

“Generate Playwright API tests for a /api/products endpoint that supports CRUD operations, including validation for required fields, pagination parameters, and error handling for unauthorized requests.”

Claude will generate tests following best practices, including proper setup/teardown, authentication handling, and edge case coverage.

Debugging Failed Tests

When tests fail, Claude Code can analyze the failure context and suggest fixes. Share the test output and error messages, and ask Claude to:

Maintaining Tests Over Time

As your API evolves, tests need updates. Claude Code can help by:

Best Practices for API Testing Workflows

  1. Use meaningful test names: Describe what you’re testing and expected outcomes
  2. Test both success and failure paths: Don’t just test happy paths
  3. Isolate tests: Each test should be independent and not rely on other tests’ state
  4. Use proper HTTP methods: GET for reading, POST for creating, PUT/PATCH for updating, DELETE for removing
  5. Validate response schemas: Don’t just check values—ensure structure is correct
  6. Handle authentication properly: Use test accounts and rotate credentials if needed
  7. Log important information: Add console logs for debugging purposes

Running Your Tests

Execute your API tests with:

# Run all API tests
npx playwright test --project=api-tests

# Run specific test file
npx playwright test tests/api/users.spec.ts

# Run with UI for debugging
npx playwright test --ui

# Run with trace viewer
npx playwright test --trace on

Conclusion

Building an effective API testing workflow with Claude Code and Playwright combines powerful testing capabilities with intelligent assistance. Playwright provides a robust framework for HTTP request testing, while Claude Code acts as your coding partner—helping generate tests, debug failures, and maintain test suites as your API grows.

Start with basic endpoint testing, then gradually add more sophisticated patterns like schema validation, error case coverage, and integration tests. The investment in comprehensive API testing pays dividends in code quality and developer confidence.

Remember: the best test is one that catches bugs before they reach production while remaining maintainable as your codebase evolves.

Built by theluckystrike — More at zovo.one