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 installdownloads 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:
- verbose stdout logging — everything that happened, when, and how long it took
- a log file — same stuff, but in a file
- a results file — structured results with timing stats and per-user breakdowns
license
distributed under the 0BSD license.
see LICENSE for more information.
