aboutsummaryrefslogtreecommitdiffstats

load-pw

a simple "load testing" not-really-a-framework framework built on top of Python-Playwright.

config.py is straight-forward and has everything.

it's also designed to make it easy to add your own "scraping" or page navigation logic during the load test — copy scenarios/example.py, name the new file something relevant, go ahead and subclass BaseScenario, override the run() method, and you're good to go.

deps

  • python 3.14+ (probably works with older versions too, but I haven't tested it)
  • playwright for Python

install

pip install -r requirements.txt
playwright install

playwright install downloads the browser binaries (chromium, firefox, webkit)

usage

1. configure your target

edit config.py and set BASE_URL to whatever you're testing:

BASE_URL = "http://localhost:8080"

there are a bunch of other settings in there too — concurrent users, iterations, think time, browser type, timeouts, logging, etc. it has comments and the settings are mostly self-explanatory.

2. run the load test

python3 loadtest.py

that's it.

it'll use the example.py scenario by default, which just loads the BASE_URL and closes per user.

3. CLI overrides

you can override config values from the command line if you don't want to edit config.py every time (I don't use this so I can't guarantee it works perfectly, but it should be good enough for basic use cases):

# hit a different URL with 10 users, 5 iterations each
python3 loadtest.py --url http://myapp:3000 --users 10 --iterations 5

# run headed (with browser UI)
python3 loadtest.py --headed

# use a custom scenario
python3 loadtest.py --scenario scenarios/my_scenario.py
usage: loadtest.py [-h] [--scenario SCENARIO] [--url URL]
                   [--users USERS] [--iterations ITERATIONS]
                   [--headed]

writing your own scenario

"scenarios" are just Python classes that inherit from BaseScenario. the only thing you need to implement is the run(page) method — page is a full Playwright Page object.

minimal scenario

from scenarios.base import BaseScenario

class MyScenario(BaseScenario):
    name = "my-scenario"

    def run(self, page):
        page.goto("/")
        page.wait_for_load_state("networkidle")
        print(f"title: {page.title()}")

save that as scenarios/my_scenario.py and run:

python3 loadtest.py --scenario scenarios/my_scenario.py

scenario with some scraping & navigation

import logging
from scenarios.base import BaseScenario

logger = logging.getLogger("loadtest.scenario.scraper")

class ScraperScenario(BaseScenario):
    name = "scraper"

    def run(self, page):
        # hit the homepage
        page.goto("/")
        page.wait_for_load_state("networkidle")

        # scrape all the links
        links = page.query_selector_all("a")
        logger.info(f"found {len(links)} links")

        for link in links[:3]:
            href = link.get_attribute("href")
            text = link.text_content()
            logger.info(f"  link: {text!r} -> {href}")

        # click into a page
        page.click("a[href='/about']")
        page.wait_for_load_state("networkidle")
        logger.info(f"navigated to: {page.url}")

        # grab some content
        body = page.text_content("body")
        logger.info(f"page body length: {len(body)} chars")

    def on_response(self, response):
        if response.status >= 400:
            logger.warning(f"HTTP {response.status} <- {response.url}")

available hooks

method when it's called
setup(page) before each iteration
run(page) the main scenario logic (required)
teardown(page) after each iteration
on_request(request) on every outgoing HTTP request
on_response(response) on every incoming HTTP response

results

after a run, you'll get:

  1. verbose stdout logging — everything that happened, when, and how long it took
  2. a log file — same stuff, but in a file
  3. a results file — structured results with timing stats and per-user breakdowns

license

distributed under the 0BSD license.

see LICENSE for more information.