If you've been using WebdriverIO for API testing, you know the pain — every test spins up a browser session even when you're just hitting REST endpoints. That's wasted time, memory, and CI minutes. wdio-api-runner solves this by bypassing browser session creation entirely, giving you the full power of WebdriverIO's test orchestration for pure API and backend testing.
Why wdio-api-runner?
Traditional WebdriverIO testing creates browser sessions for every test, even when you're just testing APIs. Here's how the API runner compares:
| Metric | Browser Runner | API Runner |
|---|---|---|
| Startup time | ~2-5 seconds | ~50ms |
| Memory per worker | ~200-500MB | ~30MB |
| Dependencies | Browser + Driver | None |
| CI/CD complexity | High | Minimal |
Features
- Zero Browser Overhead — No WebDriver, no browser binaries, no session management
- Native Fetch API Client — Modern, Promise-based HTTP client with full TypeScript support
- Fluent Assertions — Chain assertions for status, headers, body, and JSON schema validation
- Authentication Helpers — Built-in Basic, API Key, Bearer/JWT, and OAuth2 support
- GraphQL Support — Full support for queries, mutations, and subscriptions
- HAR Logging — Record requests to HAR format for debugging and replay
- Performance Metrics — p50/p95/p99 latency tracking with threshold checking
- All WDIO Features — Parallel execution, reporters, services, and frameworks work seamlessly
Installation
npm install wdio-api-runner --save-devQuick Start
Step 1: Configure WebdriverIO
Set runner: 'api' in your WebdriverIO config file — that's the key change. Everything else stays familiar:
// wdio.conf.ts
export const config: WebdriverIO.Config = {
runner: 'api',
specs: ['./test/api/**/*.spec.ts'],
framework: 'mocha',
reporters: ['spec'],
baseUrl: 'https://api.example.com',
apiRunner: {
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
}
}Step 2: Write Your First Test
The api object is globally available in your tests, providing familiar HTTP methods:
describe('Users API', () => {
it('should fetch user by ID', async () => {
const response = await api.get('/users/1')
expect(response.status).toBe(200)
expect(response.data).toHaveProperty('id', 1)
expect(response.data).toHaveProperty('email')
})
it('should create a new user', async () => {
const response = await api.post('/users', {
name: 'John Doe',
email: 'john@example.com'
})
expect(response.status).toBe(201)
})
})Step 3: Run Your Tests
npx wdio run wdio.conf.tsThat's it — no browser downloads, no driver setup, just fast API tests.
Fluent Assertions
One of the most powerful features is the fluent assertion API. Chain multiple assertions for readable, expressive test code:
import { assertResponse } from 'wdio-api-runner'
assertResponse(response)
.toBeSuccess()
.and.toHaveContentType('application/json')
.and.toHaveBodyProperty('users')
.and.toRespondWithin(500)This is much cleaner than writing individual expect() statements for each check. The chain reads like a specification.
Authentication
Real-world APIs need authentication. The runner provides built-in helpers for common auth patterns:
Bearer Token
import { bearerAuth } from 'wdio-api-runner'
api.addRequestInterceptor(bearerAuth({
token: async () => await getToken()
}).interceptor)OAuth2 Client Credentials
import { oauth2ClientCredentials } from 'wdio-api-runner'
api.addRequestInterceptor(oauth2ClientCredentials({
tokenUrl: 'https://auth.example.com/token',
clientId: 'my-client',
clientSecret: 'secret'
}).interceptor)The auth helpers handle token refresh, caching, and retry logic automatically — you don't need to manage tokens manually in your tests.
GraphQL Support
For teams working with GraphQL APIs, the runner includes a dedicated GraphQL client:
import { createGraphQLClient } from 'wdio-api-runner'
const graphql = createGraphQLClient({
endpoint: 'https://api.example.com/graphql'
})
const response = await graphql.query(`
query GetUser($id: ID!) {
user(id: $id) { id name email }
}
`, { id: '123' })The GraphQL client supports queries, mutations, and subscriptions with full TypeScript type inference.
Performance Metrics
The API runner tracks response times automatically. You can set thresholds and get p50/p95/p99 latency metrics:
// Assert response time is within threshold
assertResponse(response).toRespondWithin(500)
// Track metrics across multiple requests
const metrics = api.getPerformanceMetrics()
console.log(metrics.p95) // 95th percentile latencyCI Integration
Since there are no browser dependencies, CI integration is dramatically simpler. No need for Chrome/Firefox binaries, no Xvfb for headless mode, no driver management:
# GitHub Actions example
jobs:
api-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- run: npm ci
- run: npx wdio run wdio.conf.tsThat's a complete CI pipeline for API tests — no Docker containers, no browser setup, no complexity.
When to Use wdio-api-runner
Use the API runner when you:
- Have a dedicated API/backend test suite separate from UI tests
- Want to reuse WebdriverIO's ecosystem (reporters, services, parallel execution) for API testing
- Need fast feedback loops in CI — startup in ~50ms vs ~2-5 seconds
- Want consistent tooling between your UI and API test teams
- Are testing microservices, GraphQL APIs, or REST endpoints
Keep the browser runner for tests that actually need a browser — UI tests, visual regression, and E2E flows that involve both UI and API interactions.
Full Documentation
This post covers the basics. For the full documentation including advanced configuration, custom interceptors, HAR logging, and more, visit the official documentation site.
You can also find the source code and contribute on GitHub.
Have questions about setting up wdio-api-runner? Feel free to reach out or open an issue on GitHub.