Skip to main content

Configuring the HTTP client

To interact with an HTTP-based API, your Serenity/JS actor needs the ability to CallAnApi, which wraps the Axios HTTP client enabling the low-level interactions.

In addition to wrapping the Axios client, the ability to CallAnApi also adds automatic proxy server support based on your environment variables or configuration.

Configuring API-only test scenarios​

You can integrate Serenity/JS directly with Cucumber, Mocha, or Jasmine to write API-only test scenarios without the overhead of managing a web browser. In those cases, you should add the ability to CallAnApi yourself when configuring the cast of actors:

spec/example.spec.ts
import { before, describe, it } from 'mocha'
import { engage, Cast } from '@serenity-js/core'
import { CallAnApi } from '@serenity-js/rest'

describe('GitHub Status API v2', () => {

before(async () => {
engage(Cast.where(actor => actor.whoCan(
CallAnApi.at('https://www.githubstatus.com/api/v2/')
)))
})
})

Make sure to review the examples and reference implementations, as well as Serenity/JS REST API testing Project Templates to see how to apply the above configuration to an existing project.

Configuring blended test scenarios​

You can use Serenity/JS with Playwright Test, WebdriverIO, or Protractor to write blended test scenarios interacting with both the web UI and APIs. In those cases, the ability to CallAnApi is configured for you automatically by the respective Serenity/JS test runner adapter and uses the base URL defined in your test runner configuration file. To override the base URL, provide a full URL when performing Send interactions.

spec/healthcheck.spec.ts
import { describe, it, test } from '@serenity-js/playwright-test'
import { Send, GetRequest, LastResponse } from '@serenity-js/rest'
import { Ensure, equals, startsWith } from '@serenity-js/assertions'
import { Navigate, Page } from '@serenity-js/web'

describe('GitHub', () => {

// Set the baseURL directly in the test or in playwright.config.ts, for example:
test.use({
baseURL: 'https://github.com/'
})

describe('Sponsors', () => {

it('lets developers support Serenity/JS', async ({ actor }) => {

await actor.attemptsTo(
// Perform API-based interactions
Send.a(GetRequest.to('https://www.githubstatus.com/api/v2/summary.json')),
Ensure.that(LastResponse.status(), equals(200)),
Ensure.that(
LastResponse.body().status.indicator,
equals('none')
),

// Perform any web-based interactions
Navigate.to('/sponsors/serenity-js'),
Ensure.that(
Page.current().title(),
startsWith('Sponsor @serenity-js on GitHub Sponsors')
),
)
})
})
})

Learn more:

Dynamically configuring the base URL​

If you need to dynamically change the base URL or port of the API you're interacting with, or set extra HTTP headers, use the task to ChangeApiConfig. This approach works for both API-only and blended test scenarios, with any config overrides returning to their original state before the next scenario starts:

import { ChangeApiConfig } from '@serenity-js/rest';

await actorCalled('Apisitt').attemptsTo(
ChangeApiConfig.setUrlTo('https://example.org/secure/oauth'),

// ... perform some API calls
)

Dynamically overriding abilities​

You can add or override the ability to CallAnApi on a per-scenario / per-actor basis using actor.whoCan. This approach works for both API-only and blended test scenarios, with any config overrides returning to their original state before the next scenario starts:

spec/product-search.spec.ts
import { describe, it, before } from 'mocha'
import { engage, Cast, actorCalled } from '@serenity-js/core'
import { CallAnApi, Send, GetRequest, PostRequest, LastResponse } from '@serenity-js/rest'
import { Ensure, equals } from '@serenity-js/assertions'

describe('My Shop', () => {

// Assume there are 2 web services:
// - test data service, responsible for populating an example online shop with test data
// - search service, using the test data created by the test data service
const testDataServiceUrl = 'https://test-data.example.org/api/v1/';
const searchServiceUrl = 'https://search.example.org/api/v1/';

before(() => {
// Configure the cast so that all actor can interact with the search service
engage(
Cast.where(actor => actor.whoCan(CallAnApi.at(searchServiceUrl)))
)
})

describe('Product Search', () => {

it('finds products based on their name', async () => {

// Override the CallAnApi ability
await actorCalled('Admin')
.whoCan(CallAnApi.at(testDataServiceUrl))
.attemptsTo(
// Perform API-based interactions to set up the test data
Send.a(PostRequest.to('products').with({ name: 'Apples', price: '£2.50' })),
Ensure.that(LastResponse.status(), equals(201)),
)

await actorCalled('Barbara')
.attemptsTo(
// Perform API-based interactions with the search service
Send.a(GetRequest.to('search?name=Apples')),
Ensure.that(LastResponse.status(), equals(200)),
Ensure.that(LastResponse.body(), equals([
{ name: 'Apples', price: '£2.50' }
])),
)
})
})
})