Skip to content

Hybrid load architecture

Protocol load testing and browser testing answer different questions. Running them separately gives you two data points with no causal link — you cannot tell whether the LCP degradation you saw in your Playwright check actually happened during the peak load window. The hybrid load pattern solves this by running both inside the same MaxoPerf run, at the same time.

The hybrid architecture uses two executors in the same Taurus YAML:

  1. Many protocol VUs — JMeter, k6, or Taurus HTTP — to stress the backend at realistic scale. These generate the API load that your server needs to handle.
  2. A few browser VUs — Taurus selenium executor — to sample the user experience during the load. These check: what does a user actually see while the API is under pressure?

Both run simultaneously. The protocol VUs generate load; the browser VUs measure UX. MaxoPerf collects results from both and displays them in the same run-detail view.

VarioTest is the MaxoPerf feature for coordinating multiple tests in a single run. Create one test for the protocol load and one for the browser check, then combine them in a VarioTest:

  1. Protocol test — your existing JMeter or k6 test targeting the API endpoints. Set concurrency to your target peak (e.g. 200 VUs, 15 min hold).
  2. Browser test — a Taurus selenium test with 3–5 browser VUs running the user journey. Set the same hold duration (15 min).
  3. VarioTest — compose the two tests. MaxoPerf starts both simultaneously and collects results in a single run.

The VarioTest Overview tab shows throughput and latency from the protocol test alongside the browser iteration timing — giving you the causal link: “at 14:32, when API p95 climbed to 800 ms, the browser LCP measurement was 4.1 s.”

Pattern 2 — Two simultaneous MaxoPerf runs

Section titled “Pattern 2 — Two simultaneous MaxoPerf runs”

If VarioTest is not set up yet, start both tests manually at the same time and correlate by timestamp:

  1. Start the protocol load run in MaxoPerf.
  2. Wait for ramp-up to complete (watch the Overview tab — look for throughput to plateau).
  3. Start the browser test run in MaxoPerf.
  4. When both finish, open both run-detail pages and compare timestamps on the latency charts.

This is less automated than VarioTest but works immediately with no additional configuration.

Example Taurus YAML — browser check only (companion to protocol load)

Section titled “Example Taurus YAML — browser check only (companion to protocol load)”

This YAML runs the browser check half of the hybrid. Run it as a separate MaxoPerf test alongside your protocol load test:

execution:
- executor: selenium
concurrency: 5 # 5 browser VUs — enough for UX sampling, not load generation
ramp-up: 1m
hold-for: 15m # match the protocol load hold duration
scenario: hybrid-browser-check
scenarios:
hybrid-browser-check:
script: scripts/hybrid_check.py
browser: chrome
headless: true
modules:
selenium:
chromedriver:
version: latest
scripts/hybrid_check.py
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def test_hybrid_ux_check(driver):
"""
UX check run during protocol load. Captures TTFB and load time.
Assertion failures appear as errors in MaxoPerf results.
"""
driver.get("https://app.example.com/dashboard")
# Wait for main content (reflects both server and render time)
WebDriverWait(driver, 20).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "[data-testid='dashboard-content']"))
)
# Capture timing
timing = driver.execute_script("""
const nav = performance.getEntriesByType('navigation')[0];
return {
ttfb: nav.responseStart - nav.requestStart,
loadComplete: nav.loadEventEnd - nav.startTime
};
""")
# Hard gate: fail the browser check if load time exceeds SLO
assert timing['ttfb'] < 800, f"TTFB {timing['ttfb']:.0f} ms > 800 ms SLO under load"
assert timing['loadComplete'] < 4000, (
f"Load {timing['loadComplete']:.0f} ms > 4 s SLO under load"
)
print(f"TTFB: {timing['ttfb']:.0f} ms | Load: {timing['loadComplete']:.0f} ms")

import Screenshot from ‘@components/Screenshot.astro’;

In the Overview tab:

  • Protocol throughput — the high-RPS line from JMeter or k6 VUs. This represents backend load.
  • Browser iteration rate — the low, steady line from Selenium VUs. Each iteration is one completed browser flow.
  • Browser error rate — assertion failures from the browser check. A spike here during the protocol load plateau means the front end degraded past your SLO.
  • Correlation point — look for the moment when protocol p95 latency rises and check whether browser load times rise at the same timestamp. Causation is now explicit rather than inferred.
Protocol VUsBrowser VUsUse case
50–2002–3Development / staging check
200–5003–5Pre-release peak simulation
500–10005–10Major launch readiness
1000+5–10Use protocol VUs for scale; browser count stays low

The browser VU count does not need to scale with protocol VUs. You need enough browser samples to detect degradation — typically 3–10 VUs running continuous iterations over the hold period gives reliable timing data.

Do:

  • Keep browser VUs at 5–10 even when protocol VUs reach 500+ — more browser VUs does not improve the measurement.
  • Assert on web vitals thresholds in the browser script so degradation shows as an error in the MaxoPerf result, not just a number you have to read manually.
  • Run the hybrid against staging with production-like data — browser VUs are sensitive to data volume (empty databases give unrealistically fast LCP).

Don’t:

  • Scale browser VUs proportionally with protocol VUs — the two answer different questions and need different counts.
  • Skip the browser check during peak-load simulation — that is exactly when front-end degradation is most likely.
  • Use the hybrid pattern for pure throughput / breakpoint tests — add browser VUs only when UX measurement is part of the goal.