API Automation Testing Playwright QA Automation

Debugging API Test while using Playwright with TypeScript 

Debugging API Test while using Playwright with TypeScript

API testing ensures the quality, performance, and security of critical application functionalities. APIs handle core business logic and data exchanges. So, they are vital for seamless operations. Testing APIs finds defects early. This cuts costs and prevents issues from reaching the UI. API tests can be run independently of the UI, making testing faster and more efficient. It also covers more. It includes edge cases and data scenarios that UI tests may miss. Additionally, API testing validates security, ensuring data protection and application integrity. Performance testing ensures APIs can scale effectively and handle high traffic loads.

Why Debugging APIs is Crucial?

Debugging APIs is vital. It helps find problems that can cause system failures, data issues, or security risks. Since APIs enable communication between different services, bugs can affect multiple components. Effective debugging finds the root cause of errors. These may be due to data handling, authentication, or logic failures. It also prevents these errors from escalating. This improves system reliability, and user experience, and reduces downtime. 

How API debugging can be done using Playwright with TypeScript

Playwright with TypeScript helps you debug API efficiently. It has features for intercepting requests and logging responses. You can inspect API calls, log requests, and responses, and identify issues quickly. The playwright also supports mocking APIs to test edge cases. It can simulate different scenarios. By automating API tests with Playwright and TypeScript, debugging will be faster. This will ensure APIs work as expected under various conditions. 

API testing is key to ensuring your backend services are reliable and fast. Playwright, being a powerful end-to-end testing framework, supports API testing with robust capabilities.   

However, debugging API tests can be a bit challenging.   

This guide below will help you. It covers effective strategies for debugging API tests in Playwright with TypeScript. It uses a real-world example. 

Setting Up Playwright for API Testing: 

Before we dive into debugging techniques, let’s set up Playwright for API testing. 

1. Install Playwright and TypeScript:

npm install playwright typescript ts-node 

2. Configure TypeScript: Create a tsconfig.json file

{ 
"compilerOptions": {
"target": "ESNext",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}

3. Create a Test File: Create a file named api.test.ts

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

// A basic test to perform a GET request to an example API endpoint
test('GET request to example API', async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts/1');
expect(response.status()).toBe(200); // Expect the status code to be 200
const data = await response.json();
expect(data.id).toBe(1); // Expect the id field in the response to be 1
});

Let’s consider an authentication example: 

Suppose we have a user authentication API with the following endpoints:

  • POST /api/login: Authenticates a user and returns a JWT token. 
  • GET /api/profile: Fetches the user profile using the JWT token. 

Step-by-Step Debugging Guide:

 1. Logging Information 

Logging is the simplest way to debug and understand what’s happening in your test. 

Test File: auth.test.ts 

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

test.describe('User Authentication API', () => {
test('Login and fetch profile', async () => {
console.log('Starting login test...'); // Log the start of the login test

// Perform a POST request to the login endpoint
const loginResponse = await request.post('https://example.com/api/login', {
data: {
username: 'testuser',
password: 'password123'
}
});

console.log('Login response status:', loginResponse.status()); // Log the response status
expect(loginResponse.status()).toBe(200); // Expect the status code to be 200

const loginData = await loginResponse.json();
console.log('Login response data:', loginData); // Log the response data
const token = loginData.token;
expect(token).toBeTruthy(); // Expect the token to be truthy

// Perform a GET request to the profile endpoint using the token
const profileResponse = await request.get('https://example.com/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});

console.log('Profile response status:', profileResponse.status()); // Log the response status
expect(profileResponse.status()).toBe(200); // Expect the status code to be 200

const profileData = await profileResponse.json();
console.log('Profile response data:', profileData); // Log the response data
expect(profileData.username).toBe('testuser'); // Expect the username in the response to be 'testuser'
});
});

2. Using Breakpoints 

If you use an IDE like Visual Studio Code, set breakpoints. They will pause execution and let you inspect variables. 

  1. Open auth.test.ts in VSCode. 
  2. Click in the gutter next to the line number to set a breakpoint. 
  3. Run your test in debug mode 
npx playwright test --debug 

3. Inspecting Responses in Detail

Sometimes, inspecting headers, status codes, and response bodies in detail helps understand issues. 

test.describe('User Authentication API', () => { 
test('Login and fetch profile', async () => {
// Perform a POST request to the login endpoint
const loginResponse = await request.post('https://example.com/api/login', {
data: {
username: 'testuser',
password: 'password123'
}
});

console.log('Login response headers:', loginResponse.headers()); // Log the response headers
console.log('Login response status:', loginResponse.status()); // Log the response status
const loginData = await loginResponse.json();
console.log('Login response data:', loginData); // Log the response data
const token = loginData.token;
expect(token).toBeTruthy(); // Expect the token to be truthy

// Perform a GET request to the profile endpoint using the token
const profileResponse = await request.get('https://example.com/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});

console.log('Profile response headers:', profileResponse.headers()); // Log the response headers
console.log('Profile response status:', profileResponse.status()); // Log the response status
const profileData = await profileResponse.json();
console.log('Profile response data:', profileData); // Log the response data
expect(profileData.username).toBe('testuser'); // Expect the username in the response to be 'testuser'
});
});

4. Error Handling and Assertions 

Robust error handling and assertions can help identify where the failure occurs. 

test.describe('User Authentication API', () => { 
test('Login and fetch profile', async () => {
try {
// Perform a POST request to the login endpoint
const loginResponse = await request.post('https://example.com/api/login', {
data: {
username: 'testuser',
password: 'password123'
}
});
expect(loginResponse.status()).toBe(200); // Expect the status code to be 200

const loginData = await loginResponse.json();
const token = loginData.token;
expect(token).toBeTruthy(); // Expect the token to be truthy

// Perform a GET request to the profile endpoint using the token
const profileResponse = await request.get('https://example.com/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
expect(profileResponse.status()).toBe(200); // Expect the status code to be 200

const profileData = await profileResponse.json();
expect(profileData.username).toBe('testuser'); // Expect the username in the response to be 'testuser'
} catch (error) {
console.error('Test failed:', error); // Log any errors that occur
throw error; // Rethrow the error to fail the test
}
});
});

5. Custom Logging Utility 

Create a custom logging utility for consistent log formatting and easier debugging. 

// Utility function to log response details 
function logResponse(response: any, label: string) {
console.log(`${label} response status:`, response.status()); // Log the response status
console.log(`${label} response headers:`, response.headers()); // Log the response headers
response.json().then((data: any) => console.log(`${label} response data:`, data)); // Log the response data
}

test.describe('User Authentication API', () => {
test('Login and fetch profile', async () => {
// Perform a POST request to the login endpoint
const loginResponse = await request.post('https://example.com/api/login', {
data: {
username: 'testuser',
password: 'password123'
}
});
logResponse(loginResponse, 'Login'); // Log the login response
const loginData = await loginResponse.json();
const token = loginData.token;

// Perform a GET request to the profile endpoint using the token
const profileResponse = await request.get('https://example.com/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
logResponse(profileResponse, 'Profile'); // Log the profile response
const profileData = await profileResponse.json();
expect(profileData.username).toBe('testuser'); // Expect the username in the response to be 'testuser'
});
});

Advanced Debugging Techniques:

 1. Playwright Debug Mode

Playwright has a built-in debug mode. It lets you pause the test and inspect the browser. 

npx playwright test --debug  

This mode is better for UI tests. It can also help API tests by allowing you to step through TypeScript code. 

2. Mocking API Responses 

Isolate and test specific scenarios by mocking API responses. 

test.describe('User Authentication API', () => { 
test('Login and fetch profile with mock', async ({ request }) => {
// Mock login response
const mockLoginResponse = {
status: 200,
body: JSON.stringify({ token: 'mock-token' })
};
// Mock profile response
const mockProfileResponse = {
status: 200,
body: JSON.stringify({ username: 'testuser' })
};

// Mock the POST and GET requests
request.post = jest.fn().mockResolvedValue(mockLoginResponse);
request.get = jest.fn().mockResolvedValue(mockProfileResponse);

// Perform the login request
const loginResponse = await request.post('https://example.com/api/login', {
data: {
username: 'testuser',
password: 'password123'
}
});
const loginData = await loginResponse.json();
const token = loginData.token;

// Perform the profile request
const profileResponse = await request.get('https://example.com/api/profile', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const profileData = await profileResponse.json();
expect(profileData.username).toBe('testuser'); // Expect the username in the response to be 'testuser'
});
});

Conclusion:

Debugging API tests in Playwright with TypeScript requires basic and advanced techniques. Use console logs, breakpoints, response inspections, and error handling. They can help you find and fix issues. Also, using best practices in test data and authentication can improve your debugging. These strategies will make your API tests robust, reliable, and maintainable. 

sandeep-chouhan

Senior SDET