Advanced Appian Locust Usage

Loading Test Settings from Config

These three lines look for a config.json file at the location from which the script is run (not where the locustfile is).

from appian_locust.utilities import loadDriverUtils

utls = loadDriverUtils()
utls.load_config()

This takes the content of the config.json file and places it into a variable as utls.c. This allows us to access configurations required for logging in inside the class that extends HttpUser:

config = utls.c
auth = config['auth']
host = "https://" + config['host_address']

A minimal config.json looks like:

{
    "host_address": "site-name.appiancloud.com",
    "auth": [
        "user.name",
        "password"
    ]
}

Multiple User Types with Different Credentials

You can create different types of users with different behaviors, credentials, and weights by defining multiple HttpUser classes. This is useful for simulating realistic user distributions where different user types have different access patterns.

A complete example can ge found here - example_multi_user_locustfile.py.

class RegularUserTaskSet(AppianTaskSet):
    @task
    def browse_records(self):
        # Regular user workflow
        self.appian.visitor.visit_record_type(record_type="Employees")

class ManagerUserTaskSet(AppianTaskSet):
    @task
    def manager_operations(self):
        # Manager-specific workflow with elevated permissions
        self.appian.visitor.visit_record_type(record_type="Departments")

class RegularUserActor(HttpUser):
    """
    Regular users that interact with standard interfaces.
    Weight will be relative to all defined weights, 4 in this example.
    In this configuration we expect 3 regular users for every manager user.
    """
    tasks = [RegularUserTaskSet]
    host = f'https://{utls.c["host_address"]}'
    auth = utls.c["auth"][0]  # First auth entry
    wait_time = between(0.5, 1.0)
    weight = 3

class ManagerUserActor(HttpUser):
    """
    Manager users with elevated privileges and different workflows.
    Weight will be relative to all defined weights, 4 in this example.
    In this configuration we expect 3 regular users for every manager user.
    """
    tasks = [ManagerUserTaskSet]
    host = f'https://{utls.c["host_address"]}'
    auth = utls.c["auth"][1]  # Second auth entry
    wait_time = between(1.0, 2.0)
    weight = 1

You can define multiple user credentials in a single config.json file:

{
    "host_address": "site-name.appiancloud.com",
    "auth": [
        ["regular.user", "password123"],
        ["manager.user", "managerpass456"]
    ]
}

Running

When running this locustfile, it is important to note that the user count specified at runtime are evenly and sequentially spread across the HttpUsers. If you only specify one user to run when running locust, it will only choose one user actor to start:

locust -f examples/example_multi_user_locustfile.py --headless -r 10 -t 3600 --users 1
...
All users hatched: RegularUserActor: 1, ManagerUserActor: 0 (0 already running)

Make sure to run the locustfile with at least as many users as required by how you have the weights configured for each HttpUser.

For the included sample, the weights are 3 and 1 respectively, meaning you’ll have to spawn 4 users to get the ManagerUserActor to start up

locust -f examples/example_multi_user_locustfile.py  --headless -r 10 -t 3600 --users 4
...
All users hatched: RegularUserActor: 3, ManagerUserActor: 1 (0 already running)

Procedurally Generated Credentials

For large-scale testing, you may find it convenient to procedurally generate user credentials instead of manually maintaining long lists. This is particularly useful when you need hundreds or thousands of test users with predictable naming patterns.

First, add these keys to your config.json:

{
    "host_address": "site-name.appiancloud.com",
    "procedural_credentials_prefix": "testuser",
    "procedural_credentials_password": "testpassword123",
    "procedural_credentials_count": 100
}

Then use the credential generation functions in your test script:

from locust import HttpUser, task
from appian_locust import AppianTaskSet
from appian_locust.utilities import loadDriverUtils
from appian_locust.utilities.credentials import procedurally_generate_credentials, setup_distributed_creds

utls = loadDriverUtils()
utls.load_config()

class YourTaskSet(AppianTaskSet):
    @task
    def your_workflow(self):
        # Your test workflow here
        self.appian.visitor.visit_record_type(record_type="Employees")

class UserActor(HttpUser):
    tasks = [YourTaskSet]
    host = f'https://{utls.c["host_address"]}'

    # Generate credentials procedurally
    procedurally_generate_credentials(utls.c)

    # Set the generated credentials on the HttpUser
    credentials = utls.c["credentials"]

    # Alternatively set up distributed credentials for load testing (uncomment if running distributed)
    # this will ensure that credentials are evenly split across all locust instances.
    # credentials = setup_distributed_creds(utls.c)

How it works:

  • procedural_credentials_prefix: Base string for usernames (e.g., “testuser”)

  • procedural_credentials_count: Number of users to generate (e.g., 100 creates testuser1, testuser2, …, testuser100)

  • procedural_credentials_password: Password used for all generated users

  • procedurally_generate_credentials(): Creates the credential pairs and adds them to the config

  • setup_distributed_creds(): Distributes credentials across multiple load drivers in round-robin fashion when running in distributed mode

This approach is ideal for testing scenarios where you have many similar users with sequential naming patterns, and it automatically handles credential distribution across multiple load generators.

Request Duration Assertions

You can add timing assertions to ensure that specific operations complete within expected timeframes. This is useful for performance validation of a specific request, however see the warning below:

Warning

We strongly recommend analyzing test results in aggregate after the test has completed, instead of making assertions like this during the actual test run. Individual request timing assertions are less reliable than analyzing percentiles (p50, p95, p99) across the entire test run, which provide better insights into overall system performance.

Add this context manager class to your test file:

import time
from locust import events

class time_assertion(object):
    def __init__(self, max_time, label="Operation"):
        self.max_time = max_time
        self.label = label

    def __enter__(self):
        self.start_time = time.perf_counter()
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.end_time = time.perf_counter()
        execution_time = self.end_time - self.start_time

        if execution_time > self.max_time:
            # Log to Locust's request events for tracking
            events.request.fire(
                request_type="Assertion",
                name=f"Performance threshold exceeded: {self.label}",
                response_time=execution_time * 1000,  # Convert to milliseconds
                response_length=0,
                response=None,
                context=None,
                exception=f"{self.label} - execution time exceeded the limit of {self.max_time}s"
            )

            # Also print to console for immediate visibility
            print(f"⚠️  {self.label} - execution time {execution_time:.4f}s exceeded the limit of {self.max_time}s")

Use it to wrap operations that should complete within a specific time:

@task
def monitored_workflow(self):
    uiform = self.appian.visitor.visit_record_type(record_type="Employees")

    # Monitor performance but don't fail the test
    with time_assertion(1.0, "Record Navigation"):
        uiform = self.appian.visitor.visit_record_type(record_type="Orders")

    with time_assertion(0.5, "Button Interaction"):
        uiform.click_button("Create New", locust_request_label="[Records.Create] New Order")