Skip to main content
Ask AI

API Testing

API testing with TestRelic captures analytics about HTTP requests, responses, timing, and test results. The SDK tracks API calls made through the request fixture without requiring a browser.

What does API testing mode track?

API tests use the request fixture from TestRelic to enable tracking of:

  • Request Details — HTTP method, URL, headers, body
  • Response Data — Status code, headers, body, timing
  • API Chaining — Sequential API calls with data dependencies
  • Error Handling — Failed requests, timeout errors, validation failures
No Browser Required

API tests run without launching a browser, making them faster and more lightweight than E2E tests.

How do I set up API testing with TestRelic?

How do I set up API testing using an AI assistant?

Copy one of the prompts below and paste it into your AI coding assistant.


Full setup (one prompt, does everything)

AI Prompt — API full setup
Set up @testrelic/playwright-analytics for API testing in this project:

1. Install: npm install @testrelic/playwright-analytics
2. Open playwright.config.ts and add the TestRelic reporter to the reporter array:
['@testrelic/playwright-analytics', {
outputPath: './test-results/analytics-timeline.json',
includeStackTrace: true,
includeCodeSnippets: true,
}]
3. In my API test files, extend the base test fixture using testRelicApiFixture:

import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';
const test = base.extend(testRelicApiFixture);

Keep the existing 'list' reporter if it is already there. Do not change any test logic.

Step-by-step prompts (one at a time)

Step 1 — Install the package:

AI Prompt — Step 1: Install
Run the following in my project terminal:
npm install @testrelic/playwright-analytics

Step 2 — Configure the reporter:

AI Prompt — Step 2: Configure reporter
Open playwright.config.ts and add @testrelic/playwright-analytics to the reporter array.
Use this configuration:

['@testrelic/playwright-analytics', {
outputPath: './test-results/analytics-timeline.json',
includeStackTrace: true,
includeCodeSnippets: true,
}]

Keep any existing reporters (like 'list'). Show me the updated file.

Step 3 — Set up the API fixture:

AI Prompt — Step 3: Set up API fixture
In my API test files, set up the TestRelic API fixture:

import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

Do not change any test logic, only change the fixture setup.

Step 4 — Run the tests:

AI Prompt — Step 4: Run tests
Run my Playwright tests with:
npx playwright test

Then tell me where the analytics report was written.

How do I write API tests?

What does a basic API test look like?

tests/api/posts.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

// Extend base test with TestRelic API fixture
const test = base.extend(testRelicApiFixture);

test('fetch posts', { tag: ['@api'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts');

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

const posts = await response.json();
expect(posts).toBeInstanceOf(Array);
expect(posts.length).toBeGreaterThan(0);
});
Test Tags

Use the { tag: ['@api'] } syntax to categorize API tests separately from E2E tests.

How do I write CRUD tests?

How do I write a GET request test?

tests/api/get.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('GET - fetch all posts', { tag: ['@api', '@get'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts');

expect(response.status()).toBe(200);
expect(response.headers()['content-type']).toContain('application/json');

const posts = await response.json();
expect(posts.length).toBe(100);
});

test('GET - fetch single post', { tag: ['@api', '@get'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts/1');

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

const post = await response.json();
expect(post).toHaveProperty('id', 1);
expect(post).toHaveProperty('userId');
expect(post).toHaveProperty('title');
expect(post).toHaveProperty('body');
});

test('GET - fetch with query parameters', { tag: ['@api', '@get'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts', {
params: {
userId: 1,
},
});

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

const posts = await response.json();
expect(posts.every(post => post.userId === 1)).toBe(true);
});

How do I write a POST request test?

tests/api/post.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('POST - create new post', { tag: ['@api', '@post'] }, async ({ request }) => {
const newPost = {
title: 'Test Post',
body: 'This is a test post created by TestRelic',
userId: 1,
};

const response = await request.post('https://jsonplaceholder.typicode.com/posts', {
data: newPost,
});

expect(response.status()).toBe(201);

const createdPost = await response.json();
expect(createdPost).toHaveProperty('id');
expect(createdPost.title).toBe(newPost.title);
expect(createdPost.body).toBe(newPost.body);
expect(createdPost.userId).toBe(newPost.userId);
});

test('POST - with custom headers', { tag: ['@api', '@post'] }, async ({ request }) => {
const response = await request.post('https://jsonplaceholder.typicode.com/posts', {
data: {
title: 'Test',
body: 'Body',
userId: 1,
},
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'test-value',
},
});

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

How do I write PUT and PATCH request tests?

tests/api/put.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('PUT - update existing post', { tag: ['@api', '@put'] }, async ({ request }) => {
const updatedPost = {
id: 1,
title: 'Updated Title',
body: 'Updated body content',
userId: 1,
};

const response = await request.put('https://jsonplaceholder.typicode.com/posts/1', {
data: updatedPost,
});

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

const result = await response.json();
expect(result.id).toBe(1);
expect(result.title).toBe(updatedPost.title);
expect(result.body).toBe(updatedPost.body);
});

test('PATCH - partial update', { tag: ['@api', '@patch'] }, async ({ request }) => {
const response = await request.patch('https://jsonplaceholder.typicode.com/posts/1', {
data: {
title: 'Patched Title',
},
});

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

const result = await response.json();
expect(result.title).toBe('Patched Title');
expect(result).toHaveProperty('body'); // Other fields remain
});

How do I write a DELETE request test?

tests/api/delete.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('DELETE - remove post', { tag: ['@api', '@delete'] }, async ({ request }) => {
const response = await request.delete('https://jsonplaceholder.typicode.com/posts/1');

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

test('DELETE - verify removal', { tag: ['@api', '@delete'] }, async ({ request }) => {
// First, verify the post exists
const getResponse = await request.get('https://jsonplaceholder.typicode.com/posts/1');
expect(getResponse.status()).toBe(200);

// Delete the post
const deleteResponse = await request.delete('https://jsonplaceholder.typicode.com/posts/1');
expect(deleteResponse.status()).toBe(200);

// In a real API, verify it's gone
// (JSONPlaceholder doesn't actually delete data)
});

How do I chain multiple API calls together?

tests/api/chaining.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('API chaining - create and update', { tag: ['@api', '@chain'] }, async ({ request }) => {
// Step 1: Create a new post
const createResponse = await request.post('https://jsonplaceholder.typicode.com/posts', {
data: {
title: 'New Post',
body: 'Initial content',
userId: 1,
},
});

expect(createResponse.status()).toBe(201);
const createdPost = await createResponse.json();
const postId = createdPost.id;

// Step 2: Fetch the created post
const getResponse = await request.get(`https://jsonplaceholder.typicode.com/posts/${postId}`);
expect(getResponse.status()).toBe(200);

// Step 3: Update the post
const updateResponse = await request.put(`https://jsonplaceholder.typicode.com/posts/${postId}`, {
data: {
id: postId,
title: 'Updated Title',
body: 'Updated content',
userId: 1,
},
});

expect(updateResponse.status()).toBe(200);
const updatedPost = await updateResponse.json();
expect(updatedPost.title).toBe('Updated Title');

// Step 4: Delete the post
const deleteResponse = await request.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`);
expect(deleteResponse.status()).toBe(200);
});

test('API chaining - user and posts', { tag: ['@api', '@chain'] }, async ({ request }) => {
// Step 1: Fetch user
const userResponse = await request.get('https://jsonplaceholder.typicode.com/users/1');
expect(userResponse.status()).toBe(200);
const user = await userResponse.json();

// Step 2: Fetch user's posts
const postsResponse = await request.get('https://jsonplaceholder.typicode.com/posts', {
params: { userId: user.id },
});

expect(postsResponse.status()).toBe(200);
const posts = await postsResponse.json();

// Verify all posts belong to the user
expect(posts.every(post => post.userId === user.id)).toBe(true);

// Step 3: Fetch comments for first post
const commentsResponse = await request.get(
`https://jsonplaceholder.typicode.com/posts/${posts[0].id}/comments`
);

expect(commentsResponse.status()).toBe(200);
const comments = await commentsResponse.json();
expect(comments.length).toBeGreaterThan(0);
});

How do I validate response structure?

tests/api/validation.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('validate post structure', { tag: ['@api', '@validation'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts/1');
expect(response.status()).toBe(200);

const post = await response.json();

// Validate structure
expect(post).toHaveProperty('id');
expect(post).toHaveProperty('userId');
expect(post).toHaveProperty('title');
expect(post).toHaveProperty('body');

// Validate types
expect(typeof post.id).toBe('number');
expect(typeof post.userId).toBe('number');
expect(typeof post.title).toBe('string');
expect(typeof post.body).toBe('string');

// Validate values
expect(post.id).toBeGreaterThan(0);
expect(post.title.length).toBeGreaterThan(0);
});

How do I test error scenarios?

tests/api/errors.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('handle 404 not found', { tag: ['@api', '@error'] }, async ({ request }) => {
const response = await request.get('https://jsonplaceholder.typicode.com/posts/99999');

expect(response.status()).toBe(404);
expect(response.ok()).toBe(false);
});

test('handle timeout', { tag: ['@api', '@error'] }, async ({ request }) => {
// Set a very short timeout
const response = await request.get('https://jsonplaceholder.typicode.com/posts', {
timeout: 1, // 1ms - will likely timeout
}).catch((error) => {
expect(error.message).toContain('timeout');
return null;
});

if (response === null) {
// Timeout occurred as expected
expect(true).toBe(true);
}
});

test('handle network errors', { tag: ['@api', '@error'] }, async ({ request }) => {
const response = await request.get('https://invalid-domain-that-does-not-exist.com/api', {
timeout: 5000,
}).catch((error) => {
expect(error).toBeDefined();
return null;
});

expect(response).toBeNull();
});

How do I handle authentication in API tests?

How do I use Basic Auth?

tests/api/auth-basic.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('basic authentication', { tag: ['@api', '@auth'] }, async ({ request }) => {
const response = await request.get('https://httpbin.org/basic-auth/user/pass', {
headers: {
'Authorization': 'Basic ' + Buffer.from('user:pass').toString('base64'),
},
});

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

How do I use Bearer Token auth?

tests/api/auth-token.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('bearer token authentication', { tag: ['@api', '@auth'] }, async ({ request }) => {
const token = 'your-api-token-here';

const response = await request.get('https://api.example.com/protected', {
headers: {
'Authorization': `Bearer ${token}`,
},
});

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

How do I use an API key?

tests/api/auth-apikey.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);

test('API key authentication', { tag: ['@api', '@auth'] }, async ({ request }) => {
const apiKey = process.env.API_KEY || 'test-api-key';

const response = await request.get('https://api.example.com/data', {
headers: {
'X-API-Key': apiKey,
},
});

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

Always use environment variables for sensitive data like API keys, tokens, and passwords. Configure redactHeaders and redactBodyFields in TestRelic to prevent sensitive data from appearing in reports.

What configuration options are available for API tests?

Use the standard TestRelic reporter options in your playwright.config.ts. See the full configuration reference for all available options.

playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
reporter: [
['@testrelic/playwright-analytics', {
outputPath: './test-results/api-analytics.json',
includeStackTrace: true,
includeCodeSnippets: true,
redactPatterns: [
/api_key=[^&\s]+/gi,
/password=[^&\s]+/gi,
],
}],
],
});
Sensitive Data

Use redactPatterns to scrub sensitive values from error output and stack traces. Built-in patterns already cover AWS keys, Bearer tokens, and private keys.

How do I run API tests?

How do I run all API tests?

npx playwright test --grep @api

How do I run a specific test file?

npx playwright test tests/api/posts.spec.ts

How do I run tests with specific tags?

npx playwright test --grep "@api.*@get"

How do I run the API example project?

cd examples/api-testing
npm install
npx playwright test

How do I analyze the API report?

After running API tests, analyze the generated report:

cat test-results/api-analytics.json | jq .

Key metrics to look for:

  • Request/response timing
  • Status codes
  • Request/response headers
  • Request/response bodies (with redaction)
  • API call sequence
  • Error details
Report Visualization

See the API Report Documentation for detailed information on report structure and analysis.

What does a real-world API test suite look like?

examples/api-testing/tests/jsonplaceholder.spec.ts
import { test as base } from '@playwright/test';
import { testRelicApiFixture } from '@testrelic/playwright-analytics/api-fixture';
import { expect } from '@testrelic/playwright-analytics/fixture';

const test = base.extend(testRelicApiFixture);
const BASE_URL = 'https://jsonplaceholder.typicode.com';

test.describe('JSONPlaceholder API', () => {
test('complete CRUD workflow', { tag: ['@api', '@crud'] }, async ({ request }) => {
// CREATE
const createRes = await request.post(`${BASE_URL}/posts`, {
data: { title: 'Test', body: 'Content', userId: 1 },
});
expect(createRes.status()).toBe(201);
const post = await createRes.json();

// READ
const readRes = await request.get(`${BASE_URL}/posts/${post.id}`);
expect(readRes.status()).toBe(200);

// UPDATE
const updateRes = await request.put(`${BASE_URL}/posts/${post.id}`, {
data: { ...post, title: 'Updated' },
});
expect(updateRes.status()).toBe(200);

// DELETE
const deleteRes = await request.delete(`${BASE_URL}/posts/${post.id}`);
expect(deleteRes.status()).toBe(200);
});

test('nested resources', { tag: ['@api', '@nested'] }, async ({ request }) => {
// Get post
const postRes = await request.get(`${BASE_URL}/posts/1`);
const post = await postRes.json();

// Get post comments
const commentsRes = await request.get(`${BASE_URL}/posts/${post.id}/comments`);
expect(commentsRes.status()).toBe(200);
const comments = await commentsRes.json();
expect(comments.length).toBeGreaterThan(0);

// Get user
const userRes = await request.get(`${BASE_URL}/users/${post.userId}`);
expect(userRes.status()).toBe(200);
});
});

What are the best practices for API tests?

How do I keep credentials secure?

Store API credentials as environment variables:

const apiKey = process.env.API_KEY;
const baseUrl = process.env.API_BASE_URL || 'https://api.example.com';

How do I validate response shapes thoroughly?

Always validate the shape of responses:

const response = await request.get('/api/users/1');
const user = await response.json();

expect(user).toHaveProperty('id');
expect(user).toHaveProperty('email');
expect(user).toHaveProperty('name');

How do I test error scenarios, not just happy paths?

test('handles invalid user ID', async ({ request }) => {
const response = await request.get('/api/users/invalid');
expect(response.status()).toBe(400);
});

How do I create reusable test data with fixtures?

const testUser = {
name: 'Test User',
email: 'test@example.com',
username: 'testuser',
};

test('create user', async ({ request }) => {
const response = await request.post('/api/users', { data: testUser });
expect(response.status()).toBe(201);
});

How do I clean up test data after each test?

test('user lifecycle', async ({ request }) => {
// Create
const createRes = await request.post('/api/users', { data: testUser });
const user = await createRes.json();

try {
// Test operations
// ...
} finally {
// Clean up
await request.delete(`/api/users/${user.id}`);
}
});

Where do I go next?

Additional Resources

Was this doc helpful?
Last updated on by Srivishnu Ayyagari