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:
- Mocha (API-only)
- Jasmine (API-only)
- Cucumber (API-only)
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/')
)))
})
})
import 'jasmine';
import { engage, Cast } from '@serenity-js/core'
import { CallAnApi } from '@serenity-js/rest'
describe('GitHub Status API v2', () => {
beforeAll(async () => {
engage(Cast.where(actor => actor.whoCan(
CallAnApi.at('https://www.githubstatus.com/api/v2/')
)))
})
})
import { BeforeAll } from '@cucumber/cucumber'
import { engage, Cast } from '@serenity-js/core'
import { CallAnApi } from '@serenity-js/rest'
BeforeAll(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.
- Playwright Test (UI+API)
- WebdriverIO (UI+API)
- Protractor (UI+API)
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')
),
)
})
})
})
import { describe, it } from 'mocha'
import { actorCalled } from '@serenity-js/core'
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 in wdio.conf.ts, for example:
//
// baseUrl: 'https://jsonplaceholder.typicode.com/'
describe('Sponsors', () => {
it('lets developers support Serenity/JS', async ({ }) => {
await actorCalled('Apisitt').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')
),
)
})
})
})
import { describe, it } from 'mocha'
import { actorCalled } from '@serenity-js/core'
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 in protractor.conf.js, for example:
//
// baseUrl: 'https://jsonplaceholder.typicode.com/'
describe('Sponsors', () => {
it('lets developers support Serenity/JS', async ({ }) => {
await actorCalled('Apisitt').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:
- Mocha (API-only)
- Playwright Test (UI+API)
- WebdriverIO (UI+API)
- Protractor (UI+API)
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' }
])),
)
})
})
})
import { describe, it, test } from '@serenity-js/playwright-test'
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/';
// Configure the cast so that all actor can interact with the search service
test.use({
baseURL: searchServiceUrl
})
describe('Product Search', () => {
it('finds products based on their name', async ({ actorCalled }) => {
// 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' }
])),
)
})
})
})
import { describe, it } from 'mocha'
import { 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
// Set the baseUrl of the search service available to all actors in wdio.conf.ts, for example:
//
// baseUrl: 'https://search.example.org/api/v1/'
// URLs of other services can be set in the test itself, for example:
const testDataServiceUrl = 'https://test-data.example.org/api/v1/';
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' }
])),
)
})
})
})
import { describe, it } from 'mocha'
import { 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
// Set the baseUrl of the search service available to all actors in protractor.conf.js, for example:
//
// baseUrl: 'https://search.example.org/api/v1/'
// URLs of other services can be set in the test itself, for example:
const testDataServiceUrl = 'https://test-data.example.org/api/v1/';
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' }
])),
)
})
})
})