When working with third party API's it is better to mock the API call rather than hit the API especially when they are API's that you do not control. You might also want to mock an API when in development mode and the API hasn't been written yet. Mocking the API allows you to finish developing your component or app and write the tests and then when the API is ready you can just swap out the mock for the real API call.
With Playwright you don't need any additional libraries to mock an API call. You can use the page.route
method to intercept the API call and return a mock response. This means that instead of hitting the real API the browser will return the mocked response.
Mocking the API call
In the example below we are intercepting an API call to */**/api/v1/fruits
and returning a mock response of [{ name: 'Strawberry', id: 21 }]
by using the fulfill
method, which fulfills a route's request with a given response.
await page.route('*/**/api/v1/fruits', async (route) => {
const json = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json });
});
Let's take a look at this with a real example. We have an app that fetches and renders a list of fruit. We want to test that the page renders a fruit but we don't want to hit the API each time we run the test.
We can intercept the browser API call to */**/api/v1/fruits
and pass in a mock response that we want to be fulfilled by our route.
We then go to the page and assert that the page has a text of 'Strawberry', the mocked data that we created.
test('mocks a fruit and does not call api', async ({ page }) => {
// Mock the api call before navigating
await page.route('*/**/api/v1/fruits', async (route) => {
const json = [{ name: 'Strawberry', id: 21 }];
await route.fulfill({ json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the Strawberry fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});
When running our test with UI Mode, or with the view traces option from the VS Code extension, we can inspect the network tab and see that our API call of 'fruits' has the 'fulfilled' tag next to it. This means that the API call has been intercepted and the mock response has been returned.
Modifying the API call
Sometimes you might need to modify the API call to add more data to the response until it has been implemented in the API.
Instead of mocking the API we can intercept the route just like in the example above but instead we will use the route.fetch()
method. This performs the request and fetches the result, so that the response can be modified and then fulfilled using the route.fulfill()
method.
In the fulfill
method we pass in the response
argument, which is the API response to fulfill the route's request with, and the json
argument, which will contain the new fruit which we will call 'Playwright'.
await page.route('*/**/api/v1/fruits', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.push({ name: "Playwright", id: 100 });
await route.fulfill({ response, json });
});
In our complete example it will look something like this where we fetch the API response and add Playwright as the new fruit. We then go to the page and assert that the Playwright text exits.
test('gets the json from api and adds a new fruit', async ({ page }) => {
// Get the response and add to it
await page.route('*/**/api/v1/fruits', async (route) => {
const response = await route.fetch();
const json = await response.json();
json.push({ name: "Playwright", id: 100 });
// Fulfill using the original response, while patching the response body
// with the given JSON object.
await route.fulfill({ response, json });
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the new fruit is visible
await expect(page.getByText('Playwright', { exact: true })).toBeVisible();
});
When we run our test we can now see that our new fruit which we called 'Playwright' is visible on the page.
If we run our test with with UI Mode, or with the view traces option from the VS Code extension, we can see that we are using route.fetch
. If we open the network tab on the page.goto
step we can see our 'fruits' call has the 'api' tag next to it, followed by the 'fulfilled' tag. This means that the API has been called and the response has been modified.
Mocking with HAR files
A HAR file is an HTTP Archive file that contains a record of all the network requests that are made when a page is loaded. It contains information about the request and response headers, cookies, content, timings, and more. You can use HAR files to mock network requests in your tests.
To record a HAR file we use the routeFromHAR
method. This method takes in the path to the HAR file and an optional object of options.
The options object can contain the URL so that only requests with the URL matching the specified glob pattern will be served from the HAR File. If not specified, all requests will be served from the HAR file.
The update
option updates the given HAR file with the actual network information instead of serving from the file. In order to record the HAR file, you need to set update
to true.
await page.routeFromHAR('./hars/fruits.har', {
url: '*/**/api/v1/fruits',
update: true,
});
Let's take a look at how we would write our test using a HAR file. We start by recording the HAR file by setting the url
option to our API and the update
option to true and then go to our apps page to record the API call.
test('records or updates the HAR file', async ({ page }) => {
// Get the response from the HAR file
await page.routeFromHAR('./hars/fruits.har', {
url: '*/**/api/v1/fruits',
update: true,
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});
When you run the test you will see that the HAR file has been recorded in the hars
folder. You can open the HAR file and see the request and response information. Under the content section of the fruits.har
file you will see the name of a '.txt' file with a hashed name. This file contains the JSON response from your API call and is located inside the hars
folder.
"content": {
"size": -1,
"mimeType": "text/plain; charset=utf-8",
"_file": "071271e20ae0915b74df7103cbde3151afa4c4df.txt"
},
When you open the .txt
file you will see the full result of your API response. You can now use this HAR file in your test to mock the API call meaning you are testing against the real API data without having to make the API call each time.
To run our test against the HAR file we just have to set 'update' to false or remove it completely.
test('gets the json from HAR and checks a fruit is visible', async ({ page }) => {
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.routeFromHAR('./hars/fruits.har', {
url: '*/**/api/v1/fruits',
update: false,
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the Playwright fruit is visible
await expect(page.getByText('Strawberry')).toBeVisible();
});
Modifying the HAR file
You can also modify the response from the HAR file and then run your tests against the modified response. This is useful if you want to add some new data to your response before it has been implemented on the API. This file should be committed to your source control. Anytime you run this test with update: true
it will update your HAR file with the request from the API and override any manual changes.
[
{
"name": "Playwright",
"id": 100
},
// ... other fruits
]
We can now run our test against the modified HAR file and assert that the Playwright fruit is visible on the page.
test('gets the json from HAR and checks the new fruit has been added', async ({ page }) => {
// Replay API requests from HAR.
// Either use a matching response from the HAR,
// or abort the request if nothing matches.
await page.routeFromHAR('./hars/fruits.har', {
url: '*/**/api/v1/fruits',
update: false,
});
// Go to the page
await page.goto('https://demo.playwright.dev/api-mocking');
// Assert that the Playwright fruit is visible
await expect(page.getByText('Playwright', { exact: true })).toBeVisible();
});
When we inspect the trace of our test we can see our new fruit called Playwright added to the top of the list.
When we click on the next step of our test and open the network tab we can see that the 'fruits' file has been fulfilled meaning we are using the HAR file to mock the API call.
We can expand the network call and scroll down to inspect the response body. Here we can see our Playwright fruit has been added.
Conclusion
With Playwright you can intercept Browser HTTP requests and run your tests against the mock data with the route
and fulfill
methods. You can also intercept the API call and modify the response by passing in the response and your modified data to the route.fulfill
method. You can use the routeFromHAR
method to record the API call and response and then use the HAR file to run your tests against instead of hitting the API each time. You can also modify the HAR file and run your tests against the modified data.
Top comments (5)
I am at a loss for how to do this. I've tried everything to get the HAR to record, but it just doesn't work.
I tried just dumpoing the json output of
user.json
to a file calledhars/user.har
. I tried with and without update, but nothing is happening. The har file isn't even being used.Hi @debs_obrien ,I use Playwright FOR JAVA to test the WEB TIKTOK LIVE PAGE, which will result in the absence of barrage on the WEB TIKTOK LIVE PAGE,Even if I log in to my TikTok account, the page only prompts me for my account to enter the live broadcast room,It seems that the automated testing software has been detected,My code has simulated UserAgent
// Path executTable = Paths.get("C:\\Users\\wu\\AppData\\Local\\ms-playwright\\chromium-1080\\chrome-win\\chrome.exe");
Path userDataDir = Paths.get("C:\\Users\\wu\\AppData\\Local\\Google\\Chrome\\User Data\\playwright");
BrowserContext browserContext = playwright.chromium().launchPersistentContext(userDataDir,
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(false)
.setIsMobile(false)
.setSlowMo(500)
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36")
.setDeviceScaleFactor(2)
// .setExecutablePath(executTable)
);
Page page = null;
List<Page> pages = browserContext.pages();
if(pages!=null && pages.size()>0){
page = pages.get(0);
}
if(page==null){
System.out.println("page is null");
return;
}
page.addInitScript("Object.defineProperties(navigator, {webdriver:{get:()=>false}});");
page.navigate(url);
while (true) {
try {
page.content();
if (stop) {
browserContext.close();
stop = false;
isRunning = false;
break;
}
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I would like to know the solution for this,Thank you.
Hi @debs_obrien , I have created a framework with Playwright with JS [Cucumber] ....Now I need generate jira execution ..I tried different ways to generate it but it didn't work.Can you help on this.?
Is it possible to intercept a graph api call ?
Never tried it. Feel free to ask that question on our discord server and someone for sure will answer