Skip to content

Hybrid load architecture

Protocol load testing and browser testing answer different questions. If you run them separately, you get two data points with no link between them. You cannot tell whether the LCP degradation in your Playwright check happened during the peak load window. The hybrid load pattern runs both in 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) stress the backend at realistic scale. They generate the API load your server has to handle.
  2. A few browser VUs (Taurus selenium executor) sample the user experience during the load. They answer one question: what does a user see while the API is under pressure?

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

VarioTest is the MaxoPerf feature that coordinates several 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 against 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. Use the same hold duration (15 min).
  3. VarioTest: combine the two tests. MaxoPerf starts both at the same time and collects results in a single run.

The VarioTest Overview tab shows protocol throughput and latency next to browser iteration timing. That links cause and effect: “at 14:32, when API p95 climbed to 800 ms, the browser LCP measurement was 4.1 s.”

If you have not set up VarioTest yet, start both tests by hand at the same time and line them up by timestamp:

  1. Start the protocol load run in MaxoPerf.
  2. Wait for the ramp-up to finish (on the Overview tab, throughput levels off).
  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 it works right away with no extra 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 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 is the 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. Find the moment protocol p95 latency rises and check whether browser load times rise at the same timestamp. You see the cause directly instead of guessing it.
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 grow with protocol VUs. You need enough browser samples to detect degradation. Typically 3–10 VUs iterating through the hold period give reliable timing data.

Do:

  • Keep browser VUs at 5–10 even when protocol VUs reach 500+. More browser VUs do not improve the measurement.
  • Assert on web vitals thresholds in the browser script so degradation shows as an error in the MaxoPerf result, not as a number you have to read by hand.
  • Run the hybrid against staging with production-like data. Browser VUs react to data volume (an empty database gives an unrealistically fast LCP).

Don’t:

  • Scale browser VUs in proportion to protocol VUs. The two answer different questions and need different counts.
  • Skip the browser check during peak-load simulation. That is 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.