In this example we are going to buy a product from the Demoblaze Shop and in doing so, learn some more about how to do things in Locust.

The source for this example is here

from locust import HttpUser, SequentialTaskSet, task, between, events
import logging

import json, random, string

class MakePurchase(SequentialTaskSet):
    
    def on_start(self):
        self.purchase_id = get_uuid()

    @task
    def home(self):
        self.client.get("/", name ="01 /")

    @task
    def get_config_json(self):
        response = self.client.get("/config.json", name="02 /config.json")
        response_json = json.loads(response.text)
        self.api_host = response_json["API_URL"]

    @task
    def get_item(self):
        response = self.client.get(self.api_host + "/entries", name="03 /entries")
        response_json = json.loads(response.text)
        self.id = response_json["Items"][0]["id"]

    @task
    def view_product(self):
        self.client.cookies["user"] = get_uuid()
        response = self.client.get("/prod.html?idp_=" + str(self.id), name="04 /prod.html?idp_")

    @task
    def view(self):
        payload = '{"id":"' + str(self.id) + '"}'
        response = self.client.post(self.api_host + "/view", payload , headers={"Content-Type": "application/json"}, name="05 /view")

    @task
    def add_to_cart(self):
        payload = '{"id":"' + self.purchase_id + '","cookie":"user=' + self.user_cookie + '","prod_id":' + str(self.id) + ',"flag":false}'
        response = self.client.post(self.api_host + "/addtocart", payload, headers={"Content-Type": "application/json"},  name="06 /addtocart")

    @task
    def view_cart(self):
        response = self.client.get("/cart.html", name="07 /cart.html")

    @task
    def post_cart(self):
        payload = '{"cookie":"user=' + self.user_cookie + '","flag":false}'
        response = self.client.post(self.api_host + "/viewcart", payload, headers={"Content-Type": "application/json"},  name="08 /viewcart")

    @task
    def delete_item(self):
        payload = '{"cookie":"user=' + self.user_cookie + '"}'
        with self.client.post(self.api_host + "/deletecart", payload, headers={"Content-Type": "application/json"},  name="09 /deletecart", catch_response=True) as response:
            if response.content != b"Delete complete":
                response.failure("delete incomplete")

class DemoBlazePurchaser(HttpUser):
    wait_time = between(2, 5)
    tasks = [MakePurchase]

def get_uuid():
    #make a random string
    r_s = ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))
    #return it in a 'uuid' format
    uuid = r_s[:8] + "-" + r_s[8:12] + "-" + r_s[12:16] + "-" + r_s[16:20] + "-" + r_s[20:32]
    return uuid

Tasks, tasksets and sequential tasksets

Just as samplers can be grouped together with controllers in JMeter, so tasks can be grouped with tasksets and sequential tasksets.

Tasksets are used where the execution order isn’t important and sequential tasksets where it does matter. In this example, order matters so we are adding a sequential taskset.

class MakePurchase(SequentialTaskSet):

Tasksets can also be nested in other tasksets and weight attributes can be set to determine the relative number of times each task is called. More details are in the Locust documentation.

Start up/setup

JMeter has setUp and tearDown thread groups to carry out any initialisation activity (as well as pre and post processors), to do this in Locust, add an on_start function. In the example, we will need to provide a unique identifier as a purchase id.

class MakePurchase(SequentialTaskSet):
def on_start(self):
    #make a unique purchase id for later use
    self.purchase_id = get_uuid()

Making custom functions

The unique identifier for purchase id would normally be done by JavaScript running in the browser and to recreate this in JMeter, we may either use a built in function such as __UUID or make groovy script and add it as a pre-processor.

With Locust, we can make a function and then call it when we need it. In this case, we create a function get_uuid

def get_uuid():
    #make a random string
    r_s = ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))
    #return it in a 'uuid' format
    uuid = r_s[:8] + "-" + r_s[8:12] + "-" + r_s[12:16] + "-" + r_s[16:20] + "-" + r_s[20:32]
    return uuid

Naming requests

As with JMeter, where the name field of HTTP Request determines what will appear in the name field, you can also set the name for a request. Add a name parameter to the request

   @task
    def home(self):
        self.client.get("/", name ="01 /")

Dealing with embedded resources

When requesting a page, you may also want to include requests to embedded resources. This doesn’t come as a standard feature in Locust, but there is a plugin in locust plugins.

Like Locust, Locust plugins can be installed using pip

pip install locust-plugins

However, if you would like to learn how to write the code to include embedded resources, an explanation is provided here

Managing cookies and headers

Cookies are managed for you by default, which is like having the cookie manager in JMeter always included.

Both headers and cookies are stored as dictionaries in the session (referenced as self.client) and can be changed for all requests or any specific request.

Let’s look at an example where a cookie has to be created with a unique value by the client and sent in a request.

#create random string using the function we made for purchase ids and add to cookies dictionary
self.client.cookies["user"] = get_uuid()

A similar approach can be taken for headers, however, in this case, the headers have been modified directly in a request:

response = self.client.post(self.api_host + "/viewcart", payload, headers={"Content-Type": "application/json"},  name="08 /viewcart")

To learn more about how cookies and headers are managed and manipulated, see the requests page

Assertions

Assertions are made by setting catch_response to True when making a request and then evaluating the response. For example:

with self.client.post(self.api_host + "/deletecart", catch_response=True) as response:
if response.content != b"Delete complete":
            	response.failure("delete incomplete")

When the response is marked as a failure, it is logged and presented in the failures tab

Failures Tab

To learn about more features, see here