The Comprehensive Guide to Playwright API Testing
The software development lifecycle (SDLC) outlines a systematic approach to designing, developing, and testing software. Test automation is a key component of this process, offering several advantages: Automating repetitive tasks speeds up testing cycles, minimizes human error, and seamlessly integrates with CI/CD pipelines.
This article provides a comprehensive guide to testing your APIs with Playwright, an end-to-end testing framework. We also include practical examples and best practices to help you start writing automated tests. A basic understanding of JavaScript/TypeScript and NPM is beneficial since Playwright is a Node.js-based tool, but this is not required.
Summary of key Playwright API testing best practices
The following table outlines five best practices for API testing using Playwright and test automation in general. In the subsequent sections, we delve deeper into each best practice, providing specific examples and recommendations for effective and thorough API testing.
{{banner-large-dark-2="/banners"}}
Establish an API testing approach
Understand your API
Before diving into testing, it's crucial to understand your API thoroughly. This includes understanding endpoints, request methods, parameters, response formats, and error codes. The table below lays out some key components, considerations, and actions to keep in mind.
Network and proxy considerations
Once you understand your API, it's time to explore your chosen testing framework/tool. For this article, of course, we’re using Playwright. Let’s set up Playwright with proper network and proxy configurations, such as configuring proxies to test location-dependent services and handling API rate limits.
Network factors can introduce new requirements that must be considered:
- If your application uses location-dependent services, such as displaying localized content or recommendations, you must use proxies to simulate different geographic locations to ensure correct functionality.
- If your application has region-specific access restrictions, you’ll need to use proxies to bypass (or test) these restrictions and test from different locations. Playwright can be configured to use an HTTPS or SOCKS5 proxy, which allows you to route network traffic through a proxy server.
- Some APIs have a rate limit to prevent abuse. You can simulate multiple users accessing an API by launching multiple Playwright instances and configuring them to use the same proxy.
API-only or mixed testing?
Identifying the appropriate testing tools and methodologies early on allows for more efficient resource allocation and prevents unnecessary delays. Deciding whether to stick to API or mixed testing exclusively is essential. The table below should give you a rough idea of how to proceed.
Establishing testing preconditions
Creating a controlled environment that mirrors real-world scenarios is essential when conducting API testing. This involves determining the in-app state details and then setting up preconditions accordingly. Preconditions ensure that each test starts from a known, stable state—they simplify the debugging process because you know exactly what state the system was in when the test was conducted. This makes it easier to trace failures back to their source.
Deciding on in-app state details
To ensure accurate and meaningful test results, determine the specific in-app state details that should be simulated for each test. This involves identifying the necessary data, configurations, or user states that should be present at the beginning of a test.
Here are a few in-app state details to consider:
- User roles: Simulating different user roles (e.g., admin, customer, guest) to test access controls and permissions
- Data existence: Ensuring the presence of specific data entities (e.g., products, orders) in the database
- Application settings: Configuring application settings (e.g., language, theme) to match specific user preferences
- Network conditions: Simulating different network conditions (e.g., latency or errors) to test the API's resilience (see the earlier discussion of network and proxy considerations).
Project-level vs. test-level preconditions
Preconditions should be established on two levels: project and test.
Project-level preconditions are applied across multiple test cases and are generally less likely to change frequently. They often involve setting up the testing environment or configuring external dependencies. Here are some examples:
- Installing required dependencies or libraries
- Configuring internal/external services, such as database connections, payment gateways, email providers, etc.
- Creating test users with predefined roles and permissions
- Ensuring that data exists in a shopping cart when that endpoint is being tested
Test-level preconditions are tailored to individual test cases and may change frequently. They often involve setting up specific data or simulating user interactions to create the desired initial state. For example:
- Logging in as a specific user role (e.g., admin, user, custom role, etc.)
- Creating or updating specific data entities (e.g., products, customers)
- Simulating network conditions (e.g., an API is only called from mobile browsers, so we mock devices)
{{banner-small-3="/banners"}}
Isolate testing environments
Test isolation ensures that each test operates independently, preventing interference from other tests. Playwright ensures test isolation by running each test in a separate browser context. It's similar to opening a new and separate web browsing session (e.g., an incognito window), providing a clean slate for each set of tests. This prevents cascading failures and enhances test reproducibility.
Each context has its own independent storage, cookies, and session data, ensuring that tests don't interfere with one another. Playwright efficiently creates a new context for every test and a default page within that context.
The APIRequestContext method allows you to interact with API endpoints, configure microservices, and prepare the environment or service for your end-to-end tests. Every Playwright browser context has an associated APIRequestContext instance that shares cookie storage with the browser context. You can access this instance using browserContext.request or page.request. Additionally, you can create new APIRequestContext instances manually by calling apiRequest.newContext().
In the example below, we iterate through an array of users representing different login scenarios (for example, to test the authentication of different user roles). We create a new context for each user using browser.newContext(), which ensures a clean environment with no existing session data. After testing login success/failure, we close the context using context.close() to release resources.
// Simulate multiple login attempts with different users
async function testLogin() {
for (const user of users) {
// Create a new browser context for each user (clean slate)
const context = await browser.newContext();
const page = await context.newPage();
// Login with specific user credentials
await page.goto('https://app.qualiti.ai/auth/login');
await page.fill('#username', user.username);
await page.fill('#password', user.password);
await page.click('#submit-button');
// Assert login success/failure based on user credentials
// ...
// Close the context after each login attempt
await context.close();
}
}
In the other example below, we create a new context but don't close it immediately. We simulate a successful login using predefined credentials to perform actions that require a logged-in user (such as editing a user’s profile) and leverage existing session data.
async function testUserActions() {
// Assuming login is successful in a previous test
const context = await browser.newContext();
const page = await context.newPage();
// Login to Qualiti using another user
await page.goto('https://app.qualiti.ai/auth/login');
await page.fill('#username', 'valid_user');
await page.fill('#password', 'valid_password');
await page.click('#submit-button');
// User is now logged in within the existing context
// Perform actions requiring a logged-in user (eg., edit profile)
await page.goto('https://your-app.com/profile');
await page.click('#edit-profile-button');
// Close the context when finished
await context.close();
}
Playwright also provides a powerful mechanism for simulating interactions from multiple users or devices simultaneously within a single test using multiple browser contexts. This is particularly useful for scenarios that require the following:
- Concurrent user interactions: Testing how the application handles multiple users accessing different parts of the application simultaneously
- Device compatibility: Verifying that the application works as expected across different browsers and devices
- User role testing: Simulating interactions from users with different roles or permissions
The following snippet simulates two users interacting with a chat application by creating a separate context for each user:
const { chromium } = require('playwright');
// Create a Chromium browser instance
const browser = await chromium.launch();
// Create two isolated browser contexts
const user1Context = await browser.newContext();
const user2Context = await browser.newContext();
// Create pages for each user
const user1Page = await user1Context.newPage();
const user2Page = await user2Context.newPage();
// Simulate two users interacting with a chat application
async function testChat() {
// User 1 sends a message
await user1Page.goto('https://your-chat-app/room');
await user1Page.fill('#message-input', 'Hello, user 2!');
await user1Page.click('#send-button');
// User 2 receives the message and replies
await user2Page.goto('https://your-chat-app/room');
await user2Page.waitForSelector('#message-list', { state: 'attached' });
const message = await user2Page.locator('#message-list').first();
expect(message.textContent()).toContain('Hello, user 2!');
await user2Page.fill('#message-input', 'Hi, user 1!');
await user2Page.click('#send-button');
}
// Run the test
testChat();
These simple examples showcase the benefits of using Playwright's context management for maintaining test isolation and handling different testing scenarios.
Mock external network traffic
While the primary goal of API testing is to verify the functionality of an API, mocking API requests can provide significant advantages in specific scenarios. Here are a few example scenarios where mocking external network traffic is needed.
Granular control over response data:
- Custom scenarios: Mocking allows you to create precisely tailored scenarios with specific data sets, error responses, or edge cases. This enables you to test your application's behavior thoroughly under various conditions.
- Data validation: You can provide controlled input to verify that your application correctly handles different data types, formats, and edge cases.
- Error handling: Test how your application responds to expected and unexpected API errors, ensuring graceful error handling and a good user experience.
Isolation and focus on application logic:
- Decoupling: Mocking the API isolates your application from the actual API, allowing you to concentrate solely on testing the application's logic and UI rendering.
- Reduced dependencies: By decoupling your application from the API, you can test independently, reducing dependencies and simplifying the testing process.
- Faster feedback loops: Mocking can accelerate testing cycles by eliminating the need to wait for the API to be available or respond reliably.
Early development and testing:
- Parallel development: Mocking APIs can enable parallel front-end and back-end development, allowing you to iterate on the UI and application logic without waiting for the API to be fully functional.
- Reduced risk: By testing early, you can identify and address potential issues before they become more difficult to resolve.
How to mock external network traffic with Playwright
Playwright allows you to intercept, modify, and mock HTTP and HTTPS network traffic. This includes simulating or altering any requests made by a page, such as XHRs and fetch requests. Additionally, Playwright enables you to mock network interactions using HTTP archive (HAR) files, a standardized format for recording and storing detailed information about network requests and responses. They provide a comprehensive snapshot of all network activity during a web page's loading process.
Let’s look into the three different methods of mocking external network traffic.
Mocking API requests
- Purpose: Intercepts and replaces actual API requests with custom responses.
- Use case: Testing API-dependent functionality without relying on the actual API, simulating error scenarios, or controlling response data.
- Example: This test demonstrates mocking an API request and verifying its impact on the application. We want to intercept all calls to */**/api/v1/fruits and return a custom response containing a single fruit (“Strawberry”) instead of querying the actual API.
test('mocks a fruit without calling the api', async ({ page }) => {
// Mock the API route before navigation
await page.route('*/**/api/v1/fruits', async (route) => {
const mockData = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json: mockData });
});
// Navigate to the application page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the mocked Strawberry fruit is displayed
await expect(page.getByText('Strawberry')).toBeVisible();
});
Modifying API responses
- Purpose: Intercepts API requests, modifies the response data, and fulfills the request with the modified response.
- Use case: Testing the application's behavior with different API responses, simulating edge cases, or injecting specific data.
- Example: In the example below, we intercept the call to the fruit API and modify the data with a new fruit: “Orange.” We then navigate to the URL and verify the presence of this newly added fruit.
test('gets the json from api and adds a new fruit', async ({ page }) => {
// Get the response and add to it
await page.route('*/**/api/v1/fruits', async route => {
const response = await route.fetch();
const json = await response.json();
json.push({ name: Orange, id: 100 });
// Fulfill using the original response while patching the response body
// with the given JSON object.
await route.fulfill({ response, json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the new fruit is visible
await expect(page.getByText('Orange', { exact: true })).toBeVisible();
});
Mocking with HAR files
- Purpose: Using a recorded HAR file to simulate network requests and responses.
- Use case: Testing an application's behavior with prerecorded network interactions. You can capture HAR files of problematic scenarios and replay them later.
- Example: The examples below demonstrate how to record, modify, and replay a HAR file:
- To record a HAR file during a specific test scenario, use Playwright's page.routeFromHAR() or browserContext.routeFromHAR().
- To modify a HAR file, open the hashed .txt file inside your hars folder and edit the JSON. This file should be kept under source control.
- To replay a HAR file, turn off or simply remove the update option. This will test against the HAR file instead of hitting the API.
test('gets the json from HAR and checks the new fruit has been added', async ({ page }) => {
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.routeFromHAR('./hars/fruit.har', {
url: '*/**/api/v1/fruits',
update: false,
});
// ...
Use CI/CD tools
To effectively leverage Playwright API testing within your software development lifecycle, integrate your test plans with your existing continuous integration (CI) and continuous delivery (CD) pipeline. This integration offers several key benefits in terms of both test execution frequency and test suite maintenance:
- Early detection: Running tests on every pull request or commit enables you to identify and address issues early in development. However, there might be situations where this isn't practical, such as when tests are computationally expensive.
- Continuous quality assurance: Regular testing ensures that your API maintains a high level of quality throughout its development lifecycle.
- Relevance: Regularly review and update your test suites to ensure that they remain relevant to the evolving API and application.
- Effectiveness: Ensure that your tests cover all critical scenarios and provide meaningful feedback.
Use AI testing tools
AI integrations are transforming the testing process in several key ways. Automated testing solutions use repeated processes to remove rote work while maintaining code quality.
Traditionally, developers write tests covering all aspects of a feature as it is created. As more features are added, they update previous test cases to streamline the code across the entire application. This continues indefinitely, and testing becomes a bigger overhead as app complexity increases.
AI saves developers time by performing automated code analysis on a given codebase. As code changes are committed and tests are run through CI/CD, faulty tests can be identified and immediately resolved.
AI-enhanced tools like Qualiti use intelligent analysis to:
- Triage tests and automatically fix faulty tests
- Categorize and re-organize tests as code changes are introduced
- Analyse documents, such as the ability to scan code documentation and retain contextual information
Tools like Qualiti allow users to generate end-to-end Playwright test scripts from simple English prompts, making writing and running tests easier, even for teams with less technical expertise. This speeds up the testing process and ensures more reliable outcomes. For instance, when using Qualiti, you can write a prompt like “Call the notifications endpoint in my API” in the “Type here” box, and the AI will generate the necessary code to script the task.
As an end-to-end tool, Playwright excels in many use cases. Playwright natively supports API testing, and other back-end testing operations. Qualiti builds on this further by allowing testers to create structured prompts to validate responses and endpoints. With natural language prompts, non-technical personnel can be more involved in testing, which can transform team dynamics.
How it works:
1. Natural Language Prompts: Write what you want to test in plain English, and Qualiti generates the appropriate Playwright code. You can break up your test workflow into actions and assertions. Action steps run code operations, while assertion steps ensure test expectations are met.

2. AI-Assisted Debugging: Assess test results with the Trace Viewer, which allows you to visually step through each action, ensuring that every interaction functions as expected. Use this to troubleshoot failing tests in production or use it to verify test results in other environments.

3. Production Integration: After generating scripts, you can customize them within Playwright, ensuring full control over the tests.
a. Integrating a dedicated production environment into your Qualiti workspace allows you to monitor it.

AI tooling allows you to delegate minor testing decisions, freeing time to focus on real issues and alerts. This reduces time to market and eliminates one of the engineering world's most notorious challenges: flaky tests.
{{banner-small-4="/banners"}}
Last thoughts
Understanding an API's structure, security mechanisms, and network considerations can help you create comprehensive and efficient test suites with high coverage. Leveraging Playwright's capabilities, including mocking, HAR file manipulation, and integration with CI/CD tools, can streamline your API testing and delivery process.
Starting small is crucial for automation because it allows teams to gradually build and validate their processes. By first establishing and verifying a well-defined testing plan, including preconditions and configurations, they can ensure a solid foundation before scaling up. From there, test coverage can be gradually expanded as needed. Continuously refining and improving your test suite in response to feedback and evolving requirements ensures that your automation remains effective and relevant over time.
By leveraging Qualiti’s AI-powered testing tools, you can automate the creation and maintenance of your test suite, freeing up your team to focus on higher-value tasks. Quickly set up your testing environment and generate detailed tests by adding context for the AI, all while maintaining control over the testing process.