# `load-pw` a simple "load testing" *not-really-a-framework* framework built on top of [Python-Playwright](https://playwright.dev/python/). `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 ```bash 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: ```python 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 ```bash 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): ```bash # 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](https://playwright.dev/python/docs/api/class-page) object. ### minimal scenario ```python 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: ```bash python3 loadtest.py --scenario scenarios/my_scenario.py ``` ### scenario with some scraping & navigation ```python 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.