Skip to main content

HAR Logging

Record HTTP requests to HAR (HTTP Archive) format for debugging, replay, and analysis. HAR files are an industry-standard format that can be imported into Chrome DevTools, Postman, Charles Proxy, and many other tools.

Overview​

HAR logging provides complete visibility into your API test traffic:

FeatureDescription
Full Request DetailsURL, method, headers, body
Full Response DetailsStatus, headers, body, timing
Timing BreakdownDNS, connect, send, wait, receive
Industry StandardCompatible with browser devtools and analysis tools

Configuration​

Enable HAR logging in your config:

// wdio.conf.ts
export const config: WebdriverIO.Config = {
runner: 'api',

apiRunner: {
logging: {
enabled: true,
outputDir: './har-logs',
filename: 'api-requests.har'
}
}
}

Recording Requests​

Automatic Recording​

With logging enabled, all requests are automatically recorded:

it('should create user', async () => {
const response = await api.post('/users', {
name: 'John',
email: 'john@example.com'
})

expect(response.status).toBe(201)
// Request is automatically logged to HAR file
})

Manual Recording Control​

import { harLogger } from 'wdio-api-runner'

it('should record specific requests', async () => {
harLogger.startRecording()

await api.get('/users')
await api.post('/users', { name: 'John' })

const har = harLogger.stopRecording()
await harLogger.saveToFile('./test-output/user-tests.har')
})

HAR File Structure​

The generated HAR file follows the HAR 1.2 specification:

{
"log": {
"version": "1.2",
"creator": {
"name": "wdio-api-runner",
"version": "1.0.0"
},
"entries": [
{
"startedDateTime": "2024-01-15T10:30:00.000Z",
"time": 145,
"request": {
"method": "POST",
"url": "https://api.example.com/users",
"headers": [...],
"postData": {
"mimeType": "application/json",
"text": "{\"name\":\"John\"}"
}
},
"response": {
"status": 201,
"statusText": "Created",
"headers": [...],
"content": {
"size": 256,
"mimeType": "application/json",
"text": "{\"id\":1,\"name\":\"John\"}"
}
},
"timings": {
"send": 5,
"wait": 130,
"receive": 10
}
}
]
}
}

Viewing HAR Files​

Chrome DevTools​

  1. Open Chrome DevTools (F12)
  2. Go to Network tab
  3. Right-click → Import HAR file

Online Viewers​

Programmatic Access​

import { harLogger } from 'wdio-api-runner'

const har = harLogger.getHar()

for (const entry of har.log.entries) {
console.log(`${entry.request.method} ${entry.request.url}`)
console.log(` Status: ${entry.response.status}`)
console.log(` Time: ${entry.time}ms`)
}

Filtering​

By URL Pattern​

apiRunner: {
logging: {
enabled: true,
filter: {
includeUrls: ['/api/*'],
excludeUrls: ['/api/health', '/api/metrics']
}
}
}

By Status Code​

apiRunner: {
logging: {
enabled: true,
filter: {
statusCodes: [400, 401, 403, 404, 500] // Only log errors
}
}
}

Sensitive Data​

Redact Headers​

apiRunner: {
logging: {
enabled: true,
redact: {
headers: ['Authorization', 'X-API-Key', 'Cookie']
}
}
}

Redact Body Fields​

apiRunner: {
logging: {
enabled: true,
redact: {
bodyFields: ['password', 'token', 'secret']
}
}
}

Per-Test HAR Files​

import { harLogger } from 'wdio-api-runner'

describe('User API', () => {
beforeEach(() => {
harLogger.startRecording()
})

afterEach(async function() {
const testName = this.currentTest?.title.replace(/\s+/g, '-')
await harLogger.saveToFile(`./har-logs/${testName}.har`)
harLogger.clear()
})

it('should create user', async () => {
await api.post('/users', { name: 'John' })
})
})

Use Cases​

Debugging Failed Tests​

afterEach(async function() {
if (this.currentTest?.state === 'failed') {
const testName = this.currentTest.title.replace(/\s+/g, '-')
await harLogger.saveToFile(`./failed-tests/${testName}.har`)
}
})

Performance Analysis​

const har = harLogger.getHar()
const slowRequests = har.log.entries.filter(e => e.time > 1000)

console.log('Slow requests (>1s):')
for (const entry of slowRequests) {
console.log(` ${entry.request.url}: ${entry.time}ms`)
}

Next Steps​