How a Grafana k6 script delivers a solid answer to how many concurrent users a Magento store actually tolerates before response times tip over
A functional E2E test confirms that a checkout completes correctly for a single, isolated user, but says nothing about what happens once two hundred users run through the same checkout at the same time. That gap is exactly what load testing with k6, an open-source, script-based load testing tool, closes by simulating realistic, concurrent user load and delivering precise metrics on response time, error rate, and throughput, well before a real sale rush exposes those limits painfully in production.
Table of Contents
- 1. Why functional tests alone don't verify scalability
- 2. Fundamentals: virtual users, iterations, and stages
- 3. A realistic checkout scenario as a script
- 4. Defining thresholds objectively
- 5. Integration into the CI pipeline
- 6. Typical load targets in the Magento frontend
- 7. Interpreting results correctly
- 8. Distributed load generation for very large user counts
- 9. k6 test types at a glance
- 10. Summary
- 11. FAQ
1. Why functional tests alone don't verify scalability
A classic E2E test with Playwright or Cypress only confirms that a single, isolated test run completes correctly, but says nothing about how the same application behaves once hundreds of users simultaneously hit the same server, the same database, and the same shared resources. A Magento checkout that finishes in 800 milliseconds in a single test run can climb to several seconds or fail entirely under concurrent load through database locks, exhausted connection pools, or overloaded PHP-FPM worker processes, behavior that isolated functional tests simply can't surface.
Load tests close exactly that gap by deliberately simulating many concurrent virtual users and systematically measuring at what load level response times become unacceptable or error rates climb. For a Magento store this insight is especially valuable ahead of predictable load spikes, say a Black Friday sale or a major marketing campaign, since it lets capacity limits be known and addressed in advance, instead of discovering them painfully live under real customer traffic.
k6 has established itself in this space as a widely used, developer-friendly tool, since test scripts are written in plain JavaScript, fit seamlessly into existing development and CI workflows, and don't require heavyweight, separate infrastructure like older load testing tools.
2. Fundamentals: virtual users, iterations, and stages
A k6 script defines a flow that every virtual user (VU) repeatedly executes, while the configuration determines how many virtual users are active concurrently and how that number changes over time. Stages let load ramp up gradually, hold at a plateau, and taper off again, producing a more realistic load profile than an abruptly constant load from the start.
Every iteration of a virtual user typically runs through a sequence of HTTP requests reflecting a real user path, say opening a category page, adding a product to the cart, and submitting an order, plus short, realistic think times between steps, so the simulated load doesn't end up artificially denser than real user behavior.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
stages: [
{ duration: '2m', target: 50 }, // ramp up to 50 VUs
{ duration: '5m', target: 50 }, // hold plateau
{ duration: '2m', target: 200 }, // simulate a spike
{ duration: '3m', target: 200 },
{ duration: '2m', target: 0 }, // ramp down
],
};
export default function () {
const res = http.get('https://shop.example.com/catalog/shoes.html');
check(res, {
'status is 200': (r) => r.status === 200,
'response under 1500ms': (r) => r.timings.duration < 1500,
});
sleep(Math.random() * 3 + 1);
}
3. A realistic checkout scenario as a script
The checkout process is the most business-critical load target for most Magento stores, since it combines database writes, session management, and often external payment provider calls, exactly the components most likely to become a bottleneck under concurrent load. A load test script for checkout therefore typically models the full chain of filling the cart, choosing a shipping method, choosing a payment method, and placing the order, instead of repeating just a single, isolated request.
It's important to correctly carry session cookies and CSRF tokens between the individual requests of a virtual user, since Magento requires a valid, consistent session for many checkout steps, and a script without correct session propagation just produces error responses that don't reflect actual load capacity at all.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
checkout: {
executor: 'ramping-vus',
startVUs: 0,
stages: [
{ duration: '3m', target: 100 },
{ duration: '5m', target: 100 },
{ duration: '2m', target: 0 },
],
},
},
thresholds: {
http_req_duration: ['p(95)<2000'],
},
};
export default function () {
const jar = http.cookieJar();
const cartPage = http.get('https://shop.example.com/checkout/cart/');
check(cartPage, { 'cart loaded': (r) => r.status === 200 });
sleep(1);
const shippingRes = http.post(
'https://shop.example.com/rest/V1/carts/mine/shipping-information',
JSON.stringify({ addressInformation: { shipping_method_code: 'flatrate' } }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(shippingRes, { 'shipping set': (r) => r.status === 200 });
sleep(1);
const orderRes = http.post(
'https://shop.example.com/rest/V1/carts/mine/payment-information',
JSON.stringify({ paymentMethod: { method: 'checkmo' } }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(orderRes, { 'order placed': (r) => r.status === 200 });
sleep(2);
}
4. Defining thresholds objectively
Without clearly defined thresholds, judging a load test result stays subjective and contestable, since it's unclear at what point a measured response time counts as "too slow". k6 solves this through thresholds, objective success criteria set directly in the test configuration, say that 95 percent of all requests must stay under two seconds or that the error rate must remain below one percent.
If a test run violates a defined threshold, k6 exits the process with a non-zero exit code, letting load tests be wired into a CI pipeline automatically, exactly like functional tests: a build only counts as passed once both the functional tests and the defined load thresholds have been met, instead of treating load tests as purely manual, interpreted supplementary information.
5. Integration into the CI pipeline
A full load test with hundreds of virtual users running for several minutes rarely belongs in every single pull-request build, since it consumes time and compute capacity unsuited for fast developer feedback. In practice, a tiered model establishes itself instead: a small, fast smoke load test with a handful of virtual users runs on every merge into the main branch, while a more comprehensive load test with realistic user counts runs automatically on a daily basis or before every production deployment.
Actually running it in the CI pipeline just takes a single call to the k6 binary with the respective script, whose results can additionally be forwarded as structured JSON or InfluxDB output to a monitoring dashboard, letting load trends be tracked across multiple releases instead of viewing each test result in isolation.
# .gitlab-ci.yml excerpt
load_test_smoke:
stage: test
image: grafana/k6:latest
script:
- k6 run --vus 10 --duration 30s tests/load/checkout-smoke.js
only:
- merge_requests
load_test_full:
stage: test
image: grafana/k6:latest
script:
- k6 run --out json=results.json tests/load/checkout-full.js
artifacts:
paths:
- results.json
only:
- schedules
6. Typical load targets in the Magento frontend
Besides checkout, category pages with their layered navigation and product search rank among the most load-demanding areas of a Magento store, since both depend on expensive, dynamic database queries or Elasticsearch requests, while static content like CMS pages usually gets served entirely through the full page cache or a CDN and is thus considerably less critical under load.
For a Hyva frontend it also holds that most of the server-rendered HTML is already fully present on the initial page load, so load tests should focus primarily on server response time rather than client-side JavaScript rendering, an important difference from frontend architectures relying on extensive client-side hydration.
7. Interpreting results correctly
A common interpretation mistake is looking only at the average response time, since a good average can easily hide individual but genuinely noticeable outliers for real users. Percentile values like p95 or p99 are more informative, showing how bad the response time actually gets for the slowest five or one percent of all requests, since exactly those slowest requests shape the actually perceived user experience the most.
Equally important is watching the error rate over the course of the test: a sudden, marked rise in failed requests starting at a specific number of concurrent virtual users often marks the point where a concrete resource, say the database connection pool or the maximum number of PHP-FPM workers, gets exhausted, giving a concrete starting point for targeted capacity planning instead of vague guesswork.
8. Distributed load generation for very large user counts
A single k6 process on a single machine eventually hits physical limits, usually well before the application under test itself gets overloaded, since generating thousands of concurrent HTTP connections itself consumes considerable CPU and network resources on the load generator side. For load tests with several thousand virtual users, a single test machine therefore often no longer suffices without the measured results getting skewed by the load generator's own capacity limits.
k6 solves this through distributed execution, where multiple k6 instances on different machines run the same script in parallel and direct their load jointly against the same target application, either self-orchestrated across multiple container instances or via the commercial k6 Cloud service, which handles this distribution automatically. For most Magento stores, a manageable number of parallel load generator instances already suffices to credibly recreate realistic sale scenarios involving several thousand concurrent users, without the test infrastructure itself becoming the bottleneck.
9. k6 test types at a glance
The table below compares common k6 test configurations for different purposes.
| Test type | Typical configuration | Purpose |
|---|---|---|
| Smoke load test | 5 to 10 VUs, 30 seconds | Fast CI check on every merge |
| Standard load test | 50 to 100 VUs, several minutes | Verify realistic everyday load |
| Stress test | Ramping up to the failure point | Determine the capacity limit |
| Spike test | Sudden jump to a high VU count | Simulate a sale rush |
Mironsoft
E2E test strategy, CI integration, and stable test suites
Test suites that actually find bugs instead of just blinking red?
We review existing E2E test suites for flakiness, missing test isolation, and inefficient CI runtimes, then build a test strategy that genuinely creates confidence instead of just checking a box.
Test Audit
Systematically uncovering flaky tests, testing pyramid gaps, and coverage blind spots.
CI Optimization
Building parallel execution, retry strategies, and fast feedback loops.
Cypress/Playwright Setup
Setting up robust E2E suites for Magento frontends from the ground up.
10. Summary
Load Testing with k6: The Essentials at a Glance
Core idea
k6 simulates many concurrent virtual users and objectively measures when response times tip over.
Primary target
The Magento checkout, since it combines database writes and session management.
Objectivity
Thresholds define fixed, automatically checkable success criteria instead of subjective judgment.
CI integration
Tiered model of a fast smoke load test per merge and a more comprehensive test daily or before deployments.