Making requests to the backend with Playwright, an example in Django

Learn how to build state in functional tests with Playwright request context

Making requests to the backend with Playwright, an example in Django

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 as X-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 and context.request in Playwright

Further resources

Thanks for reading!

Valentino Gagliardi

Hi! I'm Valentino! I'm a freelance consultant with a wealth of experience in the IT industry. I spent the last years as a frontend consultant, providing advice and help, coaching and training on JavaScript, testing, and software development. Let's get in touch!