API Contract Testing on Frontend with Playwright
Sometimes, as a test engineer, business requirements for testing may be quite weird, and you have to adopt different types of testing in one suite.
Contract testing is a type of software testing that focuses on verifying the interaction between separate components/services (more often, it is two microservices). When two microservices interact via the API, one service sends requests in a predefined format, and another responds in a predefined format. This format is called a «contract» — an agreement between services (and even dev teams) on how they commit to communicating with each other.
A «contract» can be an API specification, but more often, it is just a request and response bodies’ schemas as JSON files, which are shared between two services, and both of them test their APIs against these schemas — this approach is even distinguished into a separate testing method «schema-based contract testing».
In the case of client-server architecture, the frontend can act as a consumer or provider for various APIs and vice versa:
In many articles (see links at the end of the article), contract testing is opposed to integration or end-to-end testing. But in this article, I want to show that contract testing can be a part of end-to-end testing — it can be just a tool for specific checks.
This occasion can happen in the case of specific business requirements for frontend autotests, for example, to check that your frontend makes specific requests to third-party APIs in a particular format. In other words, to ensure that UI sends correct data.
Moreover, these third-party APIs may not allowed to be requested during the tests. That looks like an obvious necessity to «close» these third-party APIs by mocks, but what if you don’t have any complex mocking infrastructure on your test project (and/or don’t want to have)? It can be so if your tests run against a live staging environment. In such cases, you can «close» requests to third-party APIs on a network level by Playwright.
It was not a problem to find a similar project on the Internet. There are a lot of DeFi startups who use open APIs for their infrastructure, but these APIs are mostly GraphQL and JSON-RPC — which adds a little bit of complexity to the example. The description of their differences from REST API is not the topic of this article.
At least I found Sushi cryptocurrency swap page, whose frontend does just a few desired POST requests to third-party API (API’s URL is different from the current website):
The same case in a scheme representation looks like this:
Let me remind you that I focus on third-party APIs because checking an internal API is not the case for this article — you can check your internal APIs by your internal API tests and/or integration ones.
In contract testing, it is implied that each component/service is isolated from each other. Here, you can easily isolate frontend from third-party API with Playwright’s network capabilities:
- Roughly abort request — the request will not be sent to an external API;
- Or mock it.
For the second case, you can modify the response with the abortion of the request if you use only fulfill()
class. But if you use fulfill()
with fetch()
, the request will be sent to the external API. In both ways, when you fulfill the response body with JSON — you make contract testing (checking that the client is processing the fulfilled response) if this JSON scheme is the same as the one used for testing on the side of the external API.
For both cases, you intercept the request by waitForRequest()
class for testing POST’s request body against your contract (and, of course, for PUT or PATCH methods):
If your request body is in JSON format (I think this will happen 90 percent of the time), you can instantly use postDataJSON()
class for comparison JSON schemes by your favorite tool: Ajv, Zod, or use toEqual()
assertion, if for some reason you decide to compare the two JSON objects head-on.
While you are checking only the request’s contract, you may not need the response and may simply abort it (attention, the right behavior depends on your application, and maybe you have to mock the response to prevent the application’s crash):
Here is the code example of such a test:
import { expect, type Page, test } from '@playwright/test';
import { z } from 'zod';
// Contract
const schema = z.object({
jsonrpc: z.string(),
id: z.number(),
method: z.string(),
params: z.array(z.union([z.string(), z.boolean()])),
});
let page: Page;
test.beforeAll(async ({ browser }) => {
const context = await browser.newContext();
page = await context.newPage();
await page.route(/.+lb\.drpc\.org\/ogrpc\?network=ethereum.+/, async (route) => {
if (route.request().method() === 'POST') {
await route.abort();
return;
}
},
);
});
test('Open Sushi Swap', async () => {
// Waiting for a request should be before .goto() method,
// because desired request can be done before the page is fully loaded.
const requestPromise = page.waitForRequest(
(request) =>
request.url().includes('lb.drpc.org/ogrpc?network=ethereum') &&
request.method() === 'POST',
);
await page.goto('/swap');
const request = await requestPromise;
await expect(
() => schema.parse(request.postDataJSON()),
'Should have a request by the contract',
).not.toThrowError();
});
Where,
const schema
is a scheme declaration in Zod’s format;- In
beforeAll
hook, all POST requests to URLs matchhttps://lb.drpc.org/ogrpc?network=ethereum&dkey=Ak765fp4zUm6uVwKu4annC8M80dnCZkR7pAEsm6XXi_w
are blocking; const requestPromise
receives data from the first request matcheshttps://lb.drpc.org/ogrpc?network=ethereum&dkey=Ak765fp4zUm6uVwKu4annC8M80dnCZkR7pAEsm6XXi_w
;- In
expect()
assertion, the reference scheme is parsed against the request’s data. The test passes if the parsing/validating process does not fail —toThrowError()
.
The test presented above may contain more steps and checks because the contract’s check may be just a part of the end-to-end suite.
Read more about contract testing:
Furthermore, in theory, the same approach to mocking can be applied to all HTTP API requests on frontend:
Read more about how Playwright mocks API.
This article was featured in:
- Appium Interceptor, Automation Trends, Playwright and More by Automation Testing with Joe Colantonio;
- Contract Testing in Playwright using Zod by Uncle Aaroh Testing.