TecNimbus

Let’s build better software—and a more balanced life—together.

,

Building a Scalable API Test Automation Framework with Playwright (JavaScript)

In this article, I’ll walk through how I built a Playwright-based API automation framework using JavaScript, using the Swagger Petstore API as an example.

🚀 Why Playwright for API Testing?

Playwright is widely known for UI testing, but it also provides powerful API testing capabilities:

  • Built-in API request support
  • No need for external libraries like Axios
  • Integrated with test runner and reporting
  • Supports fixtures and parallel execution

📁 Project Structure

Here’s the folder structure used in this framework:

playwright/
├── api/                # API layer (service classes)
├── tests/              # Test specs
├── fixtures/           # Custom Playwright fixtures
├── utils/              # Helpers (logger, data loader)
├── config/             # Environment configs
├── constants/          # Endpoints and constants
├── test-data/          # JSON payloads
├── playwright.config.js

🔧 API Layer Design

Instead of writing HTTP calls directly in tests, we use a service layer.

Example: PetAPI
export class PetAPI {
  constructor(request, baseURL) {
    this.request = request;
    this.baseURL = baseURL;
  }

  async createPet(payload) {
    return await this.request.post('/pet', {
      data: payload
    });
  }
}

🧪 Writing Clean Test Cases

test('Create a pet', async ({ apiContext }) => {
  const response = await apiContext.post('/pet', {
    data: {
      id: Date.now(),
      name: 'Doggie',
      status: 'available'
    }
  });

  expect(response.status()).toBe(200);
});

With fixtures, we inject a reusable API context into tests.

🔌 Custom API Fixture

Playwright fixtures allow us to centralize configuration:

const test = base.extend({
  apiContext: async ({ request }, use) => {
    const apiContext = await request.newContext({
      baseURL: 'https://petstore.swagger.io/v2'
    });

    await use(apiContext);
    await apiContext.dispose();
  },
});

🌍 Environment Management

We support multiple environments (dev, stage, UAT) using an environment manager:

const ENV = process.env.ENV || 'dev';

Each environment has its own config file:

export default {
  name: 'dev',
  baseURL: 'https://petstore.swagger.io/v2'
};

📦 Test Data Management

Instead of hardcoding payloads, we store them in JSON files:

{
  "id": "{{id}}",
  "name": "Doggie",
  "status": "available"
}

Then dynamically replace values:

const payload = replacePlaceholders(data, {
  id: Date.now()
});

📊 Central Logging

A reusable logger helps debug tests easily:

logRequest('POST', url, payload);
logResponse(response);
Sample Output:
--- API REQUEST ---
POST https://petstore.swagger.io/v2/pet

🧠 Key Design Principles

  • Separation of concerns (tests vs API vs data)
  • Reusability and modular design
  • Environment-driven configuration
  • Clean and readable test cases

🎯 Conclusion

Building a scalable API test framework is not just about writing tests—it’s about designing a system that is:

  • Maintainable
  • Extendable
  • Easy to debug

Using Playwright, we can achieve all of this with minimal dependencies and a clean architecture.

🔗 Resources