Multistep Check Construct
import { MultiStepCheck, Frequency } from 'checkly/constructs'
import * as path from 'path'
new MultiStepCheck('multistep-check-1', {
name: 'Multistep Check #1',
runtimeId: '2025.04',
frequency: Frequency.EVERY_10M,
locations: ['us-east-1', 'eu-west-1'],
code: {
entrypoint: path.join(__dirname, 'onboarding.spec.ts')
}
})
Test Script
Multistep checks use Playwright’s test.step()
function to organize sequential operations:
import { test, expect } from '@playwright/test' // 1
const headers = { // 2
Authorization: `Bearer ${process.env.API_KEY}`,
'x-checkly-account': process.env.ACCOUNT_ID,
}
const baseUrl = process.env.API_URL ?? 'https://api.checklyhq.com/v1'
test('create and delete a check group', async ({ request }) => { // 3
const createdGroup = await test.step('POST /check-groups', async () => { // 4
const response = await request.post(`${baseUrl}/check-groups`, {
data: {
locations: ['eu-west-1'],
name: 'exampleCheckGroup',
},
headers,
})
expect(response).toBeOK() // 5
return response.json() // 6
})
await test.step('DELETE /check-group/{id}', async () => { // 7
const response = await request.delete(`${baseUrl}/check-groups/${createdGroup.id}`, {
headers,
})
expect(response).toBeOK()
})
})
Test Structure
Let’s look at the code above step-by-step, as it determines what our Multistep check will do.
1. Initial declarations: Import the Playwright test framework.
2. Define our headers: In many cases, you will have to authenticate when requesting data by providing authorization headers. Use environment variables to avoid having any confidential data in your test.
3. Establish environment: Create a new test and leverage the Playwright request
fixture to make API requests in the test steps.
4. Declare our first test.step
: The test step uses the request
to perform a post
request, using the headers we defined earlier.
Always use await
before test.step
, otherwise the test will fail.
5. Define our assertion: Use the expect(response)
method to assert if the response was successful (the response code is in the range of 200 - 299) with toBeOK()
. Should the request return anything outside of the ‘OK’ range, the check will fail and in a production scenario, trigger any configured alerts.
6. Return the response for future usage: Return the request response in JSON format, so we can use it in the next test step.
7. Declare our second test.step
: In order to remove the test group we just created, and avoid cluttering our system with test data, remove it by sending a delete
request using the group ID that was returned in our earlier test step. Use the same toBeOK()
assertion as in the previous test step.
If you want to build on the above example, you can add additional assertions, ensuring that the data returned is correct and contains a specific check, or add a PUT
and GET
test step to verify more of the /check-groups
functionality.
API Request Capabilities
HTTP Methods Support
GET
POST
PUT/PATCH
DELETE
const response = await request.get('/api/users/123')
Request Configuration
Query Parameters
Form Data
JSON Data
const response = await request.get('/api/search', {
params: {
q: 'javascript',
limit: 10,
offset: 0
}
})
Data Flow Between Steps
Variable Passing
test('E-commerce purchase workflow', async ({ request }) => {
let authToken, cartId, orderId
await test.step('User authentication', async () => {
const response = await request.post('/api/auth/login', {
data: {
email: process.env.TEST_USER_EMAIL,
password: process.env.TEST_USER_PASSWORD
}
})
const auth = await response.json()
authToken = auth.access_token
})
await test.step('Create shopping cart', async () => {
const response = await request.post('/api/cart', {
headers: { 'Authorization': `Bearer ${authToken}` },
data: { items: [] }
})
const cart = await response.json()
cartId = cart.id
})
await test.step('Add products to cart', async () => {
const response = await request.post(`/api/cart/${cartId}/items`, {
headers: { 'Authorization': `Bearer ${authToken}` },
data: {
productId: 'PROD_123',
quantity: 2,
price: 29.99
}
})
expect(response.status()).toBe(201)
})
await test.step('Checkout and create order', async () => {
const response = await request.post(`/api/cart/${cartId}/checkout`, {
headers: { 'Authorization': `Bearer ${authToken}` },
data: {
paymentMethod: 'test_card',
shippingAddress: {
street: '123 Main St',
city: 'Boston',
zipCode: '02101'
}
}
})
const order = await response.json()
orderId = order.id
expect(order.status).toBe('confirmed')
})
await test.step('Verify order processing', async () => {
const response = await request.get(`/api/orders/${orderId}`, {
headers: { 'Authorization': `Bearer ${authToken}` }
})
const order = await response.json()
expect(order.paymentStatus).toBe('paid')
expect(order.fulfillmentStatus).toBe('pending')
})
})
await test.step('Extract and validate response data', async () => {
const response = await request.get('/api/complex-data')
const data = await response.json()
// Extract nested data
const userId = data.user.profile.id
const permissions = data.user.roles.map(role => role.permissions).flat()
const lastLoginDate = new Date(data.user.lastLogin)
// Validate extracted data
expect(userId).toBeGreaterThan(0)
expect(permissions).toContain('read:profile')
expect(lastLoginDate).toBeInstanceOf(Date)
// Store for next steps
return { userId, permissions, lastLoginDate }
})