Skip to main content

Custom API Client

Integrate your own API client with wdio-api-runner. This is useful when you have an existing API client with custom authentication logic, specific response handling, or integration with other services.

Using an Existing Client​

If you already have an API client, you can use it directly:

// wdio.conf.ts
import ApiClient from './helpers/api.helper.js'

export const config: WebdriverIO.Config = {
runner: 'api',

apiRunner: {
client: ApiClient
}
}

Your tests will use your client:

// test/api/users.spec.ts
describe('Users API', () => {
it('should fetch user', async () => {
// Uses your ApiClient methods!
const response = await api.get('/users/1', authToken)
expect(response.status).toBe(200)
})
})

Using a Factory Function​

For dynamic client creation based on config:

// wdio.conf.ts
import { ApiClient } from './helpers/api.helper.js'

export const config: WebdriverIO.Config = {
runner: 'api',

apiRunner: {
clientFactory: (config) => {
const client = new ApiClient()
client.setBaseUrl(config.baseUrl)
client.setTimeout(config.apiRunner?.timeout || 30000)
return client
}
}
}

Integration with wdio-openapi-service​

// helpers/api.helper.js
import { apiClient } from 'wdio-openapi-service'

class ApiClient {
async get(path, authToken, params = {}, options = {}) {
const headers = authToken
? { Authorization: `Bearer ${authToken}` }
: {}

return apiClient.get(this.url(path), { headers, params })
}

async post(path, authToken, data = {}, options = {}) {
const headers = authToken
? { Authorization: `Bearer ${authToken}` }
: {}

return apiClient.post(this.url(path), data, { headers })
}

url(path) {
return `${this.baseUrl}${path}`
}

setBaseUrl(url) {
this.baseUrl = url
}
}

export default new ApiClient()
// wdio.conf.ts
import ApiClient from './helpers/api.helper.js'

export const config: WebdriverIO.Config = {
runner: 'api',
apiRunner: {
client: ApiClient
}
}

Custom Global Variable Name​

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

apiRunner: {
client: ApiClient,
globalName: 'apiClient' // Access via `apiClient` instead of `api`
}
}
// test/api/users.spec.ts
describe('Users API', () => {
it('should fetch user', async () => {
const response = await apiClient.get('/users/1', token)
// `api` also works as an alias
})
})

Hybrid Approach​

Use both the built-in client and your custom client:

// wdio.conf.ts
import CustomApiClient from './helpers/api.helper.js'

export const config: WebdriverIO.Config = {
runner: 'api',

apiRunner: {
// Built-in client config
baseUrl: 'https://api.example.com',
timeout: 30000
}
}

// In your test setup
before(async () => {
// Make custom client available alongside built-in
global.customApi = CustomApiClient
})
// test/api/mixed.spec.ts
describe('Mixed API Testing', () => {
it('uses built-in client for simple requests', async () => {
const response = await api.get('/health')
expect(response.ok).toBe(true)
})

it('uses custom client for complex auth', async () => {
const response = await customApi.hawkRequest(
'GET',
'/secure/resource',
hawkCredentials
)
expect(response.status).toBe(200)
})
})

Client Interface​

Your custom client should implement methods your tests need. There's no strict interface requirement:

// Minimal example
class MinimalClient {
async get(url: string): Promise<Response> {
return fetch(url)
}

async post(url: string, data: any): Promise<Response> {
return fetch(url, {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
})
}
}

Axios-Based Client​

import axios, { AxiosInstance } from 'axios'

class AxiosApiClient {
private client: AxiosInstance

constructor(baseURL: string) {
this.client = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json'
}
})
}

async get<T>(url: string, config = {}) {
const response = await this.client.get<T>(url, config)
return {
status: response.status,
data: response.data,
headers: response.headers,
ok: response.status >= 200 && response.status < 300
}
}

async post<T>(url: string, data?: any, config = {}) {
const response = await this.client.post<T>(url, data, config)
return {
status: response.status,
data: response.data,
headers: response.headers,
ok: response.status >= 200 && response.status < 300
}
}

setHeader(name: string, value: string) {
this.client.defaults.headers.common[name] = value
}
}

export default new AxiosApiClient(process.env.API_BASE_URL || '')