Making requests to the backend with Playwright, an example in Django
Learn how to build state in functional tests with Playwright request context
One of the most daunting tasks when writing functional tests is the need to build state before asserting.
Consider the following situation: I want to test that in a view a user can see a list of its own comments.
I might not have readily available a number of comments in the testing environment database, and in order to properly test this scenario we absolutely need to create them.
There are a number of ways to populate a database under test, for example:
- use object factories
- use your functional testing tool of choice to send requests to the backend
The first approach, object factories, might not be the quickest, as we need to build up all the code machinery to build models and their related objects. Sometimes we need to build objects faster, from the outside, without writing additional code.
In this quick post we explore the second approach.
Setting up pytest fixtures
The example described in this article assumes you are using Playwright with the pytest plugin.
The pytest plugin for Playwright offers the page
and context
fixture out of the box, which are the building utility blocks for our functional tests.
Making POST requests with Playwright, an example in Django
As described in Testing Django with Cypress, in Cypress we can completely bypass the UI when logging in.
To do so, we make a POST
request with Cypress to the authentication endpoint to get the session cookie.
In Django, you can send requests to your views as much as you would do with a JavaScript client or with the Django test client, see this example.
With Playwright, we can do the same with a so-called request context.
Here's an example of a test which makes a POST
request to the backend to build state for the test.
This approach of sending requests can work whether your application exposes or not a REST API. In Django for example, update and create view can be called from within a Django template with AJAX, as long as we provide the CSRF token.
For the scope of this test we imagine that the user:
- visits the website
- goes to the section "My Comments"
- sees a form to add a new comment in the section "My Comments"
In our functional test we go over the same steps:
from playwright.sync_api import Page, BrowserContext
def test_user_can_see_own_comments(page: Page, context: BrowserContext):
host = "http://host-under-test.dev"
page.goto(host)
page.click("text=My Comments")
# Now the test is on "http://host-under-test.dev/comments/"
csrf_token = page.locator('[name="csrfmiddlewaretoken"]').input_value()
request = context.request
params = {
"ignore_https_errors": True,
"headers": {"Referer": host, "X-CSRFToken": csrf_token},
"fail_on_status_code": True,
}
request.post(
page.url,
form={
"comment": "A Comment from Playwright",
},
**params
)
## Continue with your assertions
The relevant parts are the following. We grab the CSRF token from the form:
csrf_token = page.locator('[name="csrfmiddlewaretoken"]').input_value()
We get the request
from the browser context:
request = context.request
Finally, we make the request to Django with Playwright:
request.post(
page.url,
form={
"comment": "A Comment from Playwright",
},
**params
)
As the parameters for the request, apart from the page url and the form data (the form
parameters serializer the data as application/x-www-form-urlencoded
), we send the following:
params = {
"ignore_https_errors": True,
"headers": {"Referer": host, "X-CSRFToken": csrf_token},
"fail_on_status_code": True,
}
ignore_https_errors
is useful in case you are operating on the local development environment with a "fake" certificate such as those issued with tools like trustme.fail_on_status_code
makes the request and the test fail in case of any error code other than >= 200.headers
is important to transmit the CSRF token asX-CSRFToken
to Django
One important thing to keep in mind is that any request made by context.request
will transmit all the cookies associated with the context. For example sessionid
in Django, meaning that the request will be authenticated, if the view requires authentication.
For info on how to persist authentication in Playwright see Persistent authentication.
Takeaways
- in your functional tests, build up state through direct requests to the backend if possible. See
cy.request()
in Cypress andcontext.request
in Playwright
Further resources
Thanks for reading!