How to use test dependencies
This guide builds the Maxomart checkout test, one test that uses all five dependency kinds. The script calls a mocked payment API with an API key, then drives the Maxomart shop, which runs on your laptop, in a real browser and checks that the order is confirmed.
You need these entities before you start. Create each in its own product:
- Secret
PAYMENTS_API_KEY: The key the payment API expects. - Virtual service
payments:POST /chargesanswers201with{"id": "...", "status": "approved"}only forAuthorization: Bearer <the key>, and401otherwise.GET /healthanswers200withok. - A named tunnel: it forwards to the Maxomart shop on your laptop.
- Browser fleet
eu-browsers: One Chrome browser. Created, not started. - Dataset
checkout-users: A CSV with the columnsemail,first_name,last_nameandcity. - Test Maxomart checkout: A Selenium (Python) script test with 1 virtual user, 1 runner and a 45 second duration. A failure criterion fails the run when any iteration fails.
- Test Payments health: An HTTP request builder test with one
GET /healthrequest.
The shop is a small local app with an Add to cart button, a Checkout button and an Order confirmed page. Its checkout handler calls the payment API with the same key.
Keep the API key out of the script
Section titled “Keep the API key out of the script”A key pasted into a script is stored with the script and readable by everyone who can open it. A secret dependency keeps the value in the vault and injects it at run time.
- Open Secrets in the workspace and click Create secret. Name it
PAYMENTS_API_KEY, paste the key as the Value and save. The value is never shown again. - Open Maxomart checkout, go to Dependencies and click Add dependency.
- Select the Secret kind and pick
PAYMENTS_API_KEY. Leave the suffix under Env name empty. The hint reads Injected as SECRET_PAYMENTS_API_KEY. - Click Add.
The script reads SECRET_PAYMENTS_API_KEY from its environment. The console always adds the SECRET_ prefix. See
Secrets for the naming rules.
Mock the payment provider
Section titled “Mock the payment provider”The checkout test must not charge a real card, and the real provider may rate limit a load test. The
payments virtual service answers instead.
- On the Dependencies tab, click Add dependency and select the Virtual service kind.
- Pick
paymentsand typePAYMENTSunder Env var name. The hint reads Injected as MAXOPERF_VS_PAYMENTS_URL. - Leave Auto-start with the run on and click Add.
The service is stopped. That is fine: with auto-start on, the run deploys it and waits for it. See Virtual services.
Test the app on your laptop
Section titled “Test the app on your laptop”Runners cannot reach localhost, so the shop needs a public address.
-
Open Tunnels, click Create tunnel, name it and copy its client token.
-
Start the shop, then connect the tunnel to its port in a terminal:
Terminal window npx @maxoperf/tunnel http 3000 --token <your-tunnel-token> --name <your-tunnel-name> -
Wait for the tunnel to show Online in the console.
-
On the test’s Dependencies tab, click Add dependency, select Tunnel, pick the tunnel and type
SHOP. The hint reads Injected as MAXOPERF_TUNNEL_SHOP_URL. Click Add.
If you type http://localhost:3000 into a builder URL field, the builder warns “Runners can’t reach
localhost” and offers a tunnel picker beside it. See Tunnels.
Run the browser steps on real browsers
Section titled “Run the browser steps on real browsers”The script drives a real Chrome from the eu-browsers fleet instead of a browser on the runner.
- Click Add dependency and select the Browser fleet kind.
- Pick
eu-browsersand typeEUunder Env var name. The hint shows the WebDriver variable,MAXOPERF_FLEET_EU_WEBDRIVER_URL. The fleet also injects the Playwright URL, the CDP URL and the token. - Click Add.
The fleet is stopped. A bound fleet has no auto-start switch because the run always starts it. See Browser fleets for how the script connects.
Feed each virtual user its own data
Section titled “Feed each virtual user its own data”A dataset is the one kind you bind outside the Dependencies tab.
- Open the test’s Data tab.
- Pick
checkout-usersunder Bound Dataset and click Save binding. The tab shows Binding saved. - Return to Dependencies. The dataset appears as a read-only row with a spreadsheet icon, and its name links back to the Data tab.
Data is bound per virtual user. It is not a lease, so a run has no preparing step for it and nothing to start or stop. The checkout script in this example does not read the rows. To read them in a script, see Test data.
Point the script at them
Section titled “Point the script at them”Every binding reaches the script as an environment variable. Here is the whole test script. It reads five of them, calls the payment API with the key, then buys through the shop in a fleet browser. It is a Selenium script in Python:
# The Maxomart checkout example. Runs on the MaxoPerf runner; every value below is# injected by the test's dependencies (secret, virtual service, tunnel, browser fleet).import jsonimport osimport urllib.requestfrom urllib.parse import quote, urlsplit
from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.ui import WebDriverWait
def need(name): value = os.environ.get(name) if not value: raise RuntimeError(f"{name} is not set: bind the dependency to this test") return value
def fleet_url(): # The injected WebDriver URL has no credentials; the fleet id is the user, the token the password. url = urlsplit(need("MAXOPERF_FLEET_EU_WEBDRIVER_URL")) fleet_id = url.path.split("/")[2] return url._replace(netloc=f"{fleet_id}:{quote(need('MAXOPERF_FLEET_EU_TOKEN'), safe='')}@{url.netloc}").geturl()
def test_maxomart_checkout(): request = urllib.request.Request( f"{need('MAXOPERF_VS_PAYMENTS_URL')}/charges", data=json.dumps({"amount": 4200, "currency": "usd"}).encode(), headers={"Authorization": f"Bearer {need('SECRET_PAYMENTS_API_KEY')}", "Content-Type": "application/json"}, method="POST", ) with urllib.request.urlopen(request, timeout=30) as charge: assert charge.status == 201 assert json.load(charge)["status"] == "approved"
options = Options() options.add_argument("--no-sandbox") driver = webdriver.Remote(command_executor=fleet_url(), options=options) try: wait = WebDriverWait(driver, 20) driver.get(need("MAXOPERF_TUNNEL_SHOP_URL")) for label in ("Add to cart", "Checkout"): wait.until(EC.element_to_be_clickable((By.XPATH, f"//button[normalize-space()='{label}']"))).click() wait.until(lambda d: "Order confirmed" in d.page_source) finally: driver.quit()The script reaches the fleet with Selenium. The injected WebDriver URL carries no credentials, so the script adds the fleet id (the third path segment) as the user name and the token as the password. See Browser fleets.
These are the seven variables the script receives:
- Secret
PAYMENTS_API_KEY:SECRET_PAYMENTS_API_KEY. - Virtual service
payments, stemPAYMENTS:MAXOPERF_VS_PAYMENTS_URL. - Tunnel, stem
SHOP:MAXOPERF_TUNNEL_SHOP_URL. - Browser fleet
eu-browsers, stemEU, four variables:MAXOPERF_FLEET_EU_WEBDRIVER_URLMAXOPERF_FLEET_EU_PLAYWRIGHT_URLMAXOPERF_FLEET_EU_CDP_URLMAXOPERF_FLEET_EU_TOKEN
The REST call that creates each binding:
PUT /v1/tests/{testId}/secret-bindingsPUT /v1/virtual-services/bindingsPUT /v1/tests/{testId}/dependencies/tunnels/{tunnelId}PUT /v1/tests/{testId}/dependencies/browser-fleets/{fleetId}The REST bodies are in Automate.
A builder test refers to a dependency differently. Open Payments health, find the request URL and
click Use dependency beside it. Pick the payments virtual service. The URL field now holds a chip,
and its Env var name is PAYMENTS. The test’s Dependencies tab shows that row as Used in the
test model. You never typed ${MAXOPERF_RUNTIME__MAXOPERF_VS_PAYMENTS_URL}. See
Automate.
Start the run and watch it prepare
Section titled “Start the run and watch it prepare”Open Maxomart checkout, click Run test, then click Start run in the dialog.
-
The run page shows a Preparing dependencies banner naming what MaxoPerf is waiting for:
payments (deploying)andeu-browsers (starting fleet). The tunnel is already online, so it is ready at once. -
When every lease is Ready, the runners start with the seven variables set.
-
Open the run’s Dependencies tab. It lists one row per dependency:
- Virtual service
payments: Ready, Started by this run, with Open analytics. - The tunnel: Ready, no badge, with Open stats.
- Browser fleet
eu-browsers: Ready, Started by this run, with Open sessions. - The secret: no state. The row shows the variable name, never the value.
- Virtual service
-
The run ends Passed. The leases keep their last state (Ready), the virtual service and the fleet are stopped, and the tunnel is still online, because a run never stops a tunnel.
The script drives a browser on the fleet, not on the runner. The run’s Video tab therefore shows the runner’s own idle screen next to the list of WebDriver commands the script sent (navigate, find element, click). Its HAR and console are empty. The fleet’s Open sessions page lists one session per iteration, with a step count and the state of its HAR and console capture.
Find out what depends on what
Section titled “Find out what depends on what”Before you rotate the key or stop the payment service, check who else uses it.
- On Secrets, click Used by on the
PAYMENTS_API_KEYrow. - On the
paymentsvirtual service, the tunnel and theeu-browsersfleet, open the Used by section.
Each list names the tests that bind the entity, the variable each one uses, whether the binding was made
by hand (manual) or by a builder chip (inline), and any run holding it right now. The same data
comes from GET /v1/dependencies/usage. See Run-time behavior.
Next steps
Section titled “Next steps”- Secrets, virtual services, tunnels and browser fleets: one page per kind.
- Run-time behavior: lease states, limits and troubleshooting.
- Automate: the same bindings through the API, MCP tools and the skill.