Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web Test Automation: Intercepting/Mocking network calls with Playwright #111

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,8 @@
}
}
}
},
"cli": {
"analytics": "89ea0e7e-4b4f-49c2-902c-21bc47d5ec71"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Web Test Automation: Intercepting/Mocking Network Calls with Playwright

Check warning on line 1 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L1

[Google.Headings] 'Web Test Automation: Intercepting/Mocking Network Calls with Playwright' should use sentence-style capitalization.
Raw output
{"message": "[Google.Headings] 'Web Test Automation: Intercepting/Mocking Network Calls with Playwright' should use sentence-style capitalization.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 1, "column": 3}}}, "severity": "WARNING"}

Check warning on line 1 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L1

[Google.Colons] ': I' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': I' should be in lowercase.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 1, "column": 22}}}, "severity": "WARNING"}

Nowadays, QAs are much more into test automation to improve the quality and efficiency of the testing. Due to the technical limitations, the test cases are categorized as `Not Possible To Automate`.

Check warning on line 3 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L3

[write-good.Passive] 'are categorized' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'are categorized' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 3, "column": 153}}}, "severity": "WARNING"}

![](assets/Interceptor.png)

Authors: Dilshan Fernando

Check warning on line 7 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L7

[Google.Colons] ': D' should be in lowercase.
Raw output
{"message": "[Google.Colons] ': D' should be in lowercase.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 7, "column": 8}}}, "severity": "WARNING"}
Date: unpublished
Category: qa

tags: playwright

---
This blog provides a solid solution where tester use a proxy tool to intercept an API request/response to perform validations on a web app. Playwright provides APIs to monitor and intercept browser network traffic, both HTTP and HTTPS. Any requests a page does, including XHRs and fetch requests, can be tracked, modified, and handled.

Check warning on line 14 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L14

[write-good.TooWordy] 'monitor' is too wordy.
Raw output
{"message": "[write-good.TooWordy] 'monitor' is too wordy.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 14, "column": 169}}}, "severity": "WARNING"}

Check warning on line 14 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L14

[write-good.Passive] 'be tracked' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be tracked' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 14, "column": 302}}}, "severity": "WARNING"}
As the next step, look at the benefits and possibilities of intercepting and mocking HTTP requests/responses in your Playwright test.

## What are the benefits of this?

1. During manual web testing, testers use proxy tools such as [Proxyman](https://proxyman.io/) and [Charles](https://www.charlesproxy.com/) to capture the traffic between your app and the SSL Web Server. Breakpoint Tool helps you to intercept Requests/Responses data on the fly without changing any client code. If the tester can automate these manual steps, then everyone knows how it would be beneficial for the final quality of the app.

2. No need to maintain mock servers or depend on client test data 100%. While using the client data tester can,
- Control the quality of test data
- Increase data volume/varieties
- Readiness and availability of data
3. At last, the automation coverage can be increased by automating manual test cases which depends on the proxy tools.

Check warning on line 25 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L25

[write-good.Passive] 'be increased' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be increased' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 25, "column": 41}}}, "severity": "WARNING"}

---

## Next look at the implementation

Playwright allows you to intercept network requests by using the route method. You can use this method to change or log the network traffic, or even block certain requests.

Playwright provide 5 methods,
- abort -> Aborts the route's request.

Check warning on line 34 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L34

[Google.WordList] Use 'stop', 'exit', 'cancel', or 'end' instead of 'abort'.
Raw output
{"message": "[Google.WordList] Use 'stop', 'exit', 'cancel', or 'end' instead of 'abort'.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 34, "column": 3}}}, "severity": "WARNING"}
- continue -> Continue route's request with optional overrides.
- fallback -> When several routes match the given pattern, they run in the order opposite to their registration. That way the last registered route can always override all the previous ones.
- fetch -> Performs the request and fetches the result without fulfilling it so that the response can be modified and then fulfilled.

Check warning on line 37 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L37

[write-good.Passive] 'be modified' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be modified' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 37, "column": 103}}}, "severity": "WARNING"}
- fulfill -> Fulfills route's request with a given response.

### How to integrate this approach into your test framework?

Next look at the `netwrokInterceptor.ts`

```typescript
import { Page } from "@playwright/test";
import * as fs from 'fs';
import * as path from 'path';

export class NetworkInterceptor {
constructor(private page: Page) { }
/**
*
* @param urlToIntercept Response URL which need to Mock
* @param options [filePath] relative path for the Json response body
* @param options [statusCode] Mock response status code
*/
async interceptResponse(urlToIntercept: string, options?: { filePath?: string, statusCode?: number }) {
var newResponseBody = readJsonFile(options.filePath);

await this.page.route(urlToIntercept, async (route) => {
console.log('Intercepted response:', route.request().url());
// Make the original request
const response = await route.fetch();
// if mock response code is unavialble, then use the original response code
const mockResponsecode = (typeof options.statusCode !== 'undefined') ? options.statusCode : Number(response.status)
// Replace the response with the modified data
route.fulfill({
status: mockResponsecode,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newResponseBody),
});
});
}
/**
*
* @param urlToIntercept
* @param options [filePath] relative path for the Json request body
*/
async interceptRequest(urlToIntercept: string, options?: { filePath?: string}) {
var newRequestBody = readJsonFile(options.filePath);

await this.page.route(urlToIntercept, async (route) => {
console.log('Intercepted request:', route.request().url());
// Replace the request with the modified data
route.continue({
postData: newRequestBody
});
});
}
}

/**
*
* @param filePath relative location of the json file
* @returns JSON Object
*/
function readJsonFile(filePath: string) {
const rawData = fs.readFileSync(path.resolve(__dirname, filePath), 'utf-8');
return JSON.parse(rawData);
}
```
Let's go through the implementation at a high level.

Check warning on line 104 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L104

[Google.We] Try to avoid using first-person plural like 'Let's'.
Raw output
{"message": "[Google.We] Try to avoid using first-person plural like 'Let's'.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 104, "column": 1}}}, "severity": "WARNING"}

The method `InterceptResponse` is a generic method that can be used or modified (based on the use case) to intercept any response you want.

Check warning on line 106 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L106

[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 106, "column": 61}}}, "severity": "WARNING"}
The method `InterceptRequest` is a generic method that can be used or modified (based on the use case) to intercept any request you want.

Check warning on line 107 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L107

[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 107, "column": 60}}}, "severity": "WARNING"}

> 💡
> You only need to add a new parameter to the option based on what you need. This way you can modify the same method without breaking the existing code.

Check warning on line 110 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L110

[write-good.Weasel] 'only' is a weasel word!
Raw output
{"message": "[write-good.Weasel] 'only' is a weasel word!", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 110, "column": 7}}}, "severity": "WARNING"}

Check warning on line 110 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L110

[write-good.TooWordy] 'modify' is too wordy.
Raw output
{"message": "[write-good.TooWordy] 'modify' is too wordy.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 110, "column": 95}}}, "severity": "WARNING"}

### How to use this new method in Test class?

The tricky part is, how to invoke this method in your test. The most common mistakes are,

```typescript
await identityPage.loginButton.click()
await page.waitForResponse(urlToIntercept)
```
OR
```typescript
await page.waitForResponse(urlToIntercept)
await identityPage.loginButton.click()
```
In the first approach, as soon as the login button is clicked request and response occurred, but the test execution is waiting for a response.(line# 2)

Check warning on line 125 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L125

[write-good.Passive] 'is clicked' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'is clicked' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 125, "column": 52}}}, "severity": "WARNING"}

In the second approach, the request has not happened because the click event hasn't happened yet.

To resolve this you must use `await Promise.all([]);` to wait for both interactions to resolve.

```typescript
const filePath = '../data/json-files/internal-server-error.json'
const urlToIntercept = env.BASE_URL + '/api/transaction-manager/client-api/v2/transactions?size=5&orderBy=bookingDate&direction=DESC';
networkInterceptor.interceptResponse(urlToIntercept, { filePath, statusCode: 500 })

await Promise.all([
page.waitForResponse(urlToIntercept),
identityPage.loginButton.click(),
]);
```


> ✏️
> You need to add the server error JSON response to the project and replace the value for `filepath`

## Conclusion

The [page.route](https://playwright.dev/docs/api/class-page#page-route) method of Playwright allows you to intercept HTTP requests and return a mocked response. Because you are fully in control of the response, this enables you to create edge cases to cover all the possible scenarios quickly without introducing a lot of overhead.

Check warning on line 148 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L148

[write-good.Weasel] 'quickly' is a weasel word!
Raw output
{"message": "[write-good.Weasel] 'quickly' is a weasel word!", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 148, "column": 286}}}, "severity": "WARNING"}
The Playwright API is flexible enough to be used in different ways. You can just create a mocked response, return an error, or you can make the original request and alter the response.

Check warning on line 149 in content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md

View workflow job for this annotation

GitHub Actions / vale

[vale] content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md#L149

[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.
Raw output
{"message": "[write-good.Passive] 'be used' may be passive voice. Use active voice if you can.", "location": {"path": "content/posts/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright/post.md", "range": {"start": {"line": 149, "column": 42}}}, "severity": "WARNING"}

## References
Official documentation for [Network](https://playwright.dev/docs/network) interception.


3 changes: 2 additions & 1 deletion routes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
/2023/12/13/angular-micro-frontends
/principles/innersource
/principles/software-engineering
/unpublished/web-test-automation-intercepting-mocking-network-calls-with-playwright
/category/tech-life
/category/devops
/category/backend
/category/career
/category/frontend
/category/sdlc
/404
/404
Loading