<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Testing guides on Grafana Labs</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/</link><description>Recent content in Testing guides on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/k6/v2.3.x/testing-guides/index.xml" rel="self" type="application/rss+xml"/><item><title>Load test types</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/test-types/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/test-types/</guid><content><![CDATA[&lt;h1 id=&#34;load-test-types&#34;&gt;Load test types&lt;/h1&gt;
&lt;p&gt;Many things can go wrong when a system is under load.
The system must run numerous operations simultaneously and respond to different requests from a variable number of users.
To prepare for these performance risks, teams use load testing.&lt;/p&gt;
&lt;p&gt;But a good load-testing strategy requires more than just executing a single script.
Different patterns of traffic create different risk profiles for the application.
For comprehensive preparation, teams must test the system against different &lt;em&gt;test types&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/chart-load-test-types-overview.png&#34;
  alt=&#34;Overview of load test shapes&#34; width=&#34;1920&#34;
     height=&#34;540&#34;/&gt;&lt;/p&gt;
&lt;h2 id=&#34;different-tests-for-different-goals&#34;&gt;Different tests for different goals&lt;/h2&gt;
&lt;p&gt;Start with smoke tests, then progress to higher loads and longer durations.&lt;/p&gt;
&lt;p&gt;The main types are as follows. Each type has its own article outlining its essential concepts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/smoke-testing/&#34;&gt;&lt;strong&gt;Smoke tests&lt;/strong&gt;&lt;/a&gt; validate that your script works and that the system performs adequately under minimal load.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/load-testing/&#34;&gt;&lt;strong&gt;Average-load test&lt;/strong&gt;&lt;/a&gt; assess how your system performs under expected normal conditions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/stress-testing/&#34;&gt;&lt;strong&gt;Stress tests&lt;/strong&gt;&lt;/a&gt; assess how a system performs at its limits when load exceeds the expected average.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/soak-testing/&#34;&gt;&lt;strong&gt;Soak tests&lt;/strong&gt;&lt;/a&gt; assess the reliability and performance of your system over extended periods.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/spike-testing/&#34;&gt;&lt;strong&gt;Spike tests&lt;/strong&gt;&lt;/a&gt; validate the behavior and survival of your system in cases of sudden, short, and massive increases in activity.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/breakpoint-testing/&#34;&gt;&lt;strong&gt;Breakpoint tests&lt;/strong&gt;&lt;/a&gt; gradually increase load to identify the capacity limits of the system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;In k6 scripts, configure the load configuration using 
    &lt;a href=&#34;/docs/k6/v2.3.x/get-started/running-k6/#using-options&#34;&gt;&lt;code&gt;options&lt;/code&gt;&lt;/a&gt; or 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/&#34;&gt;&lt;code&gt;scenarios&lt;/code&gt;&lt;/a&gt;. This separates workload configuration from iteration logic.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;test-type-cheat-sheet&#34;&gt;Test-type cheat sheet&lt;/h2&gt;
&lt;p&gt;The following table provides some broad comparisons.&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Type&lt;/th&gt;
              &lt;th&gt;VUs/Throughput&lt;/th&gt;
              &lt;th&gt;Duration&lt;/th&gt;
              &lt;th&gt;When?&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/smoke-testing/&#34;&gt;Smoke&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;Low&lt;/td&gt;
              &lt;td&gt;Short (seconds or minutes)&lt;/td&gt;
              &lt;td&gt;When the relevant system or application code changes. It checks functional logic, baseline metrics, and deviations&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/load-testing/&#34;&gt;Load&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;Average production&lt;/td&gt;
              &lt;td&gt;Mid (5-60 minutes)&lt;/td&gt;
              &lt;td&gt;Often to check system maintains performance with average use&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/stress-testing/&#34;&gt;Stress&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;High (above average)&lt;/td&gt;
              &lt;td&gt;Mid (5-60 minutes)&lt;/td&gt;
              &lt;td&gt;When system may receive above-average loads to check how it manages&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/soak-testing/&#34;&gt;Soak&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;Average&lt;/td&gt;
              &lt;td&gt;Long (hours)&lt;/td&gt;
              &lt;td&gt;After changes to check system under prolonged continuous use&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/spike-testing/&#34;&gt;Spike&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;Very high&lt;/td&gt;
              &lt;td&gt;Short (a few minutes)&lt;/td&gt;
              &lt;td&gt;When the system prepares for seasonal events or receives frequent traffic peaks&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/breakpoint-testing/&#34;&gt;Breakpoint&lt;/a&gt;&lt;/td&gt;
              &lt;td&gt;Increases until break&lt;/td&gt;
              &lt;td&gt;As long as necessary&lt;/td&gt;
              &lt;td&gt;A few times to find the upper limits of the system&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;general-recommendations&#34;&gt;General recommendations&lt;/h2&gt;
&lt;p&gt;When you write and run different test types in k6, consider the following.&lt;/p&gt;
&lt;h3 id=&#34;start-with-a-smoke-test&#34;&gt;Start with a smoke test&lt;/h3&gt;
&lt;p&gt;Start with a 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/smoke-testing/&#34;&gt;smoke test&lt;/a&gt;.
Before beginning larger tests, validate that your scripts work as expected and that your system performs well with a few users.&lt;/p&gt;
&lt;p&gt;After you know that the script works and the system responds correctly to minimal load,
you can move on to average-load tests.
From there, you can progress to more complex load patterns.&lt;/p&gt;
&lt;h3 id=&#34;the-specifics-depend-on-your-use-case&#34;&gt;The specifics depend on your use case&lt;/h3&gt;
&lt;p&gt;Systems have different architectures and different user bases. As a result, the correct load testing strategy is highly dependent on the risk profile for your organization. Avoid thinking in absolutes.&lt;/p&gt;
&lt;p&gt;For example, k6 can model load by either number of VUs or by number of iterations per second (
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/concepts/open-vs-closed/&#34;&gt;open vs. closed&lt;/a&gt;).
When you design your test, consider which pattern makes sense for the type.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s more, &lt;strong&gt;no single test type eliminates all risk.&lt;/strong&gt;
To assess different failure modes of your system, incorporate multiple test types.
The risk profile of your system determines what test types to emphasize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Some systems are more at risk of longer use, in which case soaks should be prioritized.&lt;/li&gt;
&lt;li&gt;Others are more at risk of intensive use, in which case stress tests should take precedence.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In any case, &lt;strong&gt;no single test can uncover all issues&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s more, the categories themselves are relative to use cases. A stress test for one application is an average-load test for another. Indeed, no consensus even exists about the names of these test types (each of the following topics provides alternative names).&lt;/p&gt;
&lt;h3 id=&#34;aim-for-simple-designs-and-reproducible-results&#34;&gt;Aim for simple designs and reproducible results&lt;/h3&gt;
&lt;p&gt;While the specifics are greatly context-dependent, what&amp;rsquo;s constant is that you want to make results that you can compare and interpret.&lt;/p&gt;
&lt;p&gt;Stick to simple load patterns. For all test types, directions is enough: ramp-up, plateau, ramp-down.&lt;/p&gt;
&lt;p&gt;Avoid &amp;ldquo;rollercoaster&amp;rdquo; series where load increases and decreases multiple times. These will waste resources and make it hard to isolate issues.&lt;/p&gt;
]]></content><description>&lt;h1 id="load-test-types">Load test types&lt;/h1>
&lt;p>Many things can go wrong when a system is under load.
The system must run numerous operations simultaneously and respond to different requests from a variable number of users.
To prepare for these performance risks, teams use load testing.&lt;/p></description></item><item><title>Automated performance testing</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/automated-performance-testing/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/automated-performance-testing/</guid><content><![CDATA[&lt;h1 id=&#34;automated-performance-testing&#34;&gt;Automated performance testing&lt;/h1&gt;
&lt;p&gt;Performance testing automation is about establishing &lt;strong&gt;a repeatable and consistent process that checks reliability issues&lt;/strong&gt; at different stages of the development and release cycle. For instance, you could run performance tests from CI/CD pipelines and nightly jobs, or manually trigger load tests and monitor their impact in real-time.&lt;/p&gt;
&lt;p&gt;In performance testing, automation does not remove the need to run tests manually. It&amp;rsquo;s about planning performance tests as part of your Software Development Life Cycle (SDLC) for &lt;strong&gt;continuous performance testing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This guide provides general recommendations to help you plan and define a strategy for running automated performance tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Which tests to automate?&lt;/li&gt;
&lt;li&gt;Which environment to test?&lt;/li&gt;
&lt;li&gt;What frequency and how to run tests?&lt;/li&gt;
&lt;li&gt;How to analyze performance results?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please note that this guide assumes you are familiar with k6 and already have performance tests. If you are new to performance testing or k6, we recommend looking at our 
    &lt;a href=&#34;/docs/k6/v2.3.x/get-started/resources/#learning&#34;&gt;get-started resources&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we dive in, let&amp;rsquo;s consider the &amp;ldquo;why&amp;rdquo; behind automation and how it unlocks the full benefits of your performance testing efforts.&lt;/p&gt;




&lt;div class=&#34;d-sm-flex flex-direction-row-reverse bg-gray-1 br-12 p-2 my-1&#34;&gt;
  &lt;img class=&#34;maxw-240 w-100p mb-1 lazyload&#34; data-src=&#34;/media/docs/learning-journey/map.svg&#34; width=&#34;237&#34; height=&#34;154&#34; alt=&#34;Grafana Learning Journeys&#34;&gt;
  &lt;div&gt;
    &lt;div class=&#34;h4 pt-0 pb-half fw-500&#34;&gt;Start your learning experience with Grafana Learning Paths&lt;/div&gt;
    &lt;p class=&#34;pr-1 pb-half&#34;&gt;Grafana Learning Paths provide a clear, structured path that leads you from beginner concepts to advanced use cases. Learn about this Grafana feature on &lt;a href=&#34;/docs/learning-paths/establish-k6-baseline/&#34;&gt;Establish a performance baseline with k6&lt;/a&gt;.&lt;/p&gt;
    &lt;div class=&#34;mx-auto&#34;&gt;
      &lt;a class=&#34;btn btn--primary btn--large arrow fw-600 br-8 w-175&#34; href=&#34;https://grafana.com/docs/learning-paths/establish-k6-baseline/&#34;&gt;Start learning&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h2 id=&#34;why-automate-performance-tests&#34;&gt;Why automate performance tests&lt;/h2&gt;
&lt;p&gt;Whether it&amp;rsquo;s a website loading in under a second, API responses in milliseconds, or instantaneous fault responses, performance is critical as it directly impacts the end-user experience. However, an organizational challenge is that performance may often not receive the recognition of a feature or requirement.&lt;/p&gt;
&lt;p&gt;Performance is still intangible in many organizations, which react only when bad things happen. Automation changes this approach - &lt;strong&gt;from reactive to proactive&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In performance testing, it&amp;rsquo;s crucial to establish routines to be consistent in our practices. Automation is necessary to create a performance testing habit, and boost some of its &lt;a href=&#34;https://k6.io/why-your-organization-should-perform-load-testing/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;benefits&lt;/a&gt;, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Improve testing coverage, confidence, and maintenance&lt;/strong&gt;: Automation creates a constant and iterative process for various types of testing. This continuous effort in performance testing leads to expanded test coverage, enhanced test maintenance, and increased confidence in testing outcomes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detect issues earlier&lt;/strong&gt;: Automating performance tests as part of the software delivery process can ensure applications meet reliability goals while catching issues earlier in the SDLC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaborate across teams&lt;/strong&gt;: Automation prompts teams to outline a strategy and plan across the SDLC and departments. It fosters engineering leaders to advocate for reliability and implement shared practices.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Without automation, the lack of a shared framework often leads to isolated and sporadic activities. Automation helps drive continuous performance and reliability testing, introducing a &lt;strong&gt;more efficient and effective testing process&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&#34;more-than-cicd&#34;&gt;More than CI/CD&lt;/h3&gt;
&lt;p&gt;Automation often refers to running tests with pass/fail conditions as part of the release process within CI/CD pipelines. However, not all performance tests are suited for CI/CD workflows, nor are they solely about providing a Pass/Fail (green/red) status and acting as a release gatekeeper.&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/integrations/#continuous-integration-and-continuous-delivery&#34;&gt;Automation into CI/CD pipelines&lt;/a&gt; is an option, but it&amp;rsquo;s not the only method to schedule the execution of performance tests. When creating a performance testing plan, it&amp;rsquo;s important to remember that there are different ways to run performance tests in a frequent basis:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cron and cron job runners.&lt;/li&gt;
&lt;li&gt;Cloud testing tools, such as &lt;a href=&#34;/docs/grafana-cloud/testing/k6/author-run/schedule-a-test/&#34;&gt;scheduling in Grafana Cloud k6&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Test management tools with automation capabilities.&lt;/li&gt;
&lt;li&gt;Trigger manual tests. Include this as a step in your release checklist process.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Running tests only from CI/CD tools on software changes limits the objectives of our performance tests. A complete performance testing strategy can include CI/CD tests, cron-based tests, or even manual tests to address various testing purposes:&lt;/p&gt;
&lt;h2 id=&#34;determine-the-purpose-of-the-tests&#34;&gt;Determine the purpose of the tests&lt;/h2&gt;
&lt;p&gt;The first step in the process is reviewing your existing or planned tests and understanding each test&amp;rsquo;s purpose. Can the test serve additional purposes if executed regularly? Some common goals are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comparing current performance against an existing performance baseline.&lt;/li&gt;
&lt;li&gt;Understanding variances over time in key performance metrics. Observing flat or changing trends.&lt;/li&gt;
&lt;li&gt;Detecting regressions of new releases.&lt;/li&gt;
&lt;li&gt;Testing Service Level Objectives (SLOs) on a regular basis.&lt;/li&gt;
&lt;li&gt;Testing critical areas during the release process.&lt;/li&gt;
&lt;li&gt;Setting quality gates in the CI/CD pipelines.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When considering a consistent and ongoing purpose for each test, you discover which tests to automate, any lacking functionality, and missing tests in your test suite. It also guides you in determining the best time to run each test and how.&lt;/p&gt;
&lt;h2 id=&#34;choose-which-tests-to-automate&#34;&gt;Choose which tests to automate&lt;/h2&gt;
&lt;p&gt;Performance tests can generally be divided into two aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test scenario (test case): What is the test verifying?&lt;/li&gt;
&lt;li&gt;Test workload (test load): How much traffic and which traffic pattern?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your test suite should incorporate a diverse range of tests that can verify critical areas of your system using distinct 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/&#34;&gt;load test types&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Any existing test that you wish to run on a frequent basis is a candidate for automation. Fundamentally, automation is about running tests frequently and consistently, whether that&amp;rsquo;s daily, weekly, or annually.&lt;/p&gt;
&lt;p&gt;When designing your performance test suite for automation, consider two key points: start simple and modularize your tests.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start simple and iterate&lt;/strong&gt;: Your test suite, and consequently test coverage, will expand as the team learns and encounters reliability issues to investigate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modularize your test suite&lt;/strong&gt;: In k6, you can separate the scenario and workload logic and reuse them across different tests. That simplifies the process of creating tests with various traffic patterns for different purposes. Modularization also allows reusing common logic across multiple tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When planning test coverage or automation, consider starting with tests that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Verify the core functionality crucial to the product and business.&lt;/li&gt;
&lt;li&gt;Evaluate the performance in scenarios with high traffic.&lt;/li&gt;
&lt;li&gt;Track key performance metrics to observe their trends and compare against their baselines.&lt;/li&gt;
&lt;li&gt;Validate reliability goals or SLOs with Pass/Fail criteria.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;model-the-scenarios-and-workload&#34;&gt;Model the scenarios and workload&lt;/h2&gt;
&lt;p&gt;Once one or multiple tests have been selected, you should determine the various types of traffic that need to be tested.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s illustrate an example with two simple tests: one test to assess the performance of a GET endpoint and one test to verify a checkout process.&lt;/p&gt;
&lt;p&gt;The next step is to identify the traffic the system under test (SUT) handles for these tests. In this case, we could utilize our analytics and monitoring tools to find the typical traffic patterns for the GET endpoint and checkout flow.&lt;/p&gt;
&lt;p&gt;Depending on the type of traffic, we can create different kinds of tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/smoke-testing/&#34;&gt;Smoke&lt;/a&gt;: Test for script errors and verify SUT with minimal traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/load-testing/&#34;&gt;Average-Load&lt;/a&gt;: Test for regular/normal traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/stress-testing/&#34;&gt;Stress&lt;/a&gt;: Test for maximum expected traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/spike-testing/&#34;&gt;Spike&lt;/a&gt;: Test for a surge of traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/soak-testing/&#34;&gt;Soak&lt;/a&gt;: Test for a prolonged period of traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In our example, we decided on the following workload for the two scenarios:&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Test scenario&lt;/th&gt;
              &lt;th&gt;Smoke&lt;/th&gt;
              &lt;th&gt;Average&lt;/th&gt;
              &lt;th&gt;Stress&lt;/th&gt;
              &lt;th&gt;Spike&lt;/th&gt;
              &lt;th&gt;Soak&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;1 iteration&lt;/td&gt;
              &lt;td&gt;100 reqs/s - 3m&lt;/td&gt;
              &lt;td&gt;1500 reqs/s - 5m&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Checkout process&lt;/td&gt;
              &lt;td&gt;3 iterations&lt;/td&gt;
              &lt;td&gt;50 VUs - 5m&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;200 VUs - 1m&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;blockquote&gt;
&lt;p&gt;We recommend always creating average-load tests for baseline comparisons and smoke tests to validate test script errors before executing larger tests.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;In our example, we have tests that use the same test scenario with distinct workloads. This pattern is extremely common. In this case, the ability to reuse the scenario logic across tests simplifies both test creation and maintenance. A common pattern for organizing tests is prefixing them with the type of workload:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;smoke-get-api.js&lt;/code&gt;:   imports the common scenario and set 1 iteration.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;load-get-api.js&lt;/code&gt;:     imports the common scenario and set 100 reqs/s during 3m.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stress-get-api.js&lt;/code&gt;: imports the common scenario and set 1500 reqs/s during 3m.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;To learn more about configuring workloads in k6, refer to 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/#scenario-executors&#34;&gt;Scenarios&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&#34;decide-the-testing-frequency-for-each-environment&#34;&gt;Decide the testing frequency for each environment&lt;/h2&gt;
&lt;p&gt;The next step is to decide which environment to test and its frequency. Each organization has different environments, and their purpose might also vary from one organization to another.&lt;/p&gt;
&lt;p&gt;Here are some common environments found at organizations, and general guidelines of what kind of testing to use them for.&lt;/p&gt;
&lt;h3 id=&#34;development-environment&#34;&gt;Development environment&lt;/h3&gt;
&lt;p&gt;This environment, whether the personal machine or its dedicated environment, might not include all the components of the system. It is commonly used for preliminary testing before deploying the application to a more comprehensive environment.&lt;/p&gt;
&lt;p&gt;This environment is great for verifying the basic functionality of our tests by running smoke tests.&lt;/p&gt;
&lt;p&gt;In this type of environment, debugging and building our performance tests is more common than any type of automation. However, if your project structure permits, you can also schedule the execution of smoke tests on project changes.&lt;/p&gt;
&lt;h3 id=&#34;qa-environment&#34;&gt;QA environment&lt;/h3&gt;
&lt;p&gt;This environment often deploys the entire application but with minimal infrastructure resources. It&amp;rsquo;s like a low-scale staging environment that all teams can use to test functional aspects and find regressions for new features.&lt;/p&gt;
&lt;p&gt;Given the infrastructure does not closely match the production environment, this type of QA environment is unsuitable for assessing the performance and scalability of the application.&lt;/p&gt;
&lt;p&gt;However, validating the functional aspects of our testing with smoke tests can help to catch errors earlier in this environment. Additionally, it verifies that the same script can run in larger load tests later.&lt;/p&gt;
&lt;p&gt;Run all the available smoke tests: end-to-end, integration, and unit test types. Schedule these tests as part of the suite of automated tests executed in the CI flow.&lt;/p&gt;
&lt;h3 id=&#34;pre-release-and-ephemeral-environments&#34;&gt;Pre-release and ephemeral environments&lt;/h3&gt;
&lt;p&gt;These environments are available to test upcoming releases, with each organization using them differently as part of their unique release process.&lt;/p&gt;
&lt;p&gt;As a general rule on pre-release environments, we should run our larger tests with quality gates, Pass/Fail criteria that validate SLOs or reliability goals. In k6, you can do that by using 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/&#34;&gt;Thresholds&lt;/a&gt; in &lt;code&gt;options&lt;/code&gt; as follows:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export const options = {
  thresholds: {
    // http errors should be less than 1%
    &amp;#39;http_req_failed&amp;#39;: [&amp;#39;rate&amp;lt;0.01&amp;#39;],
    // 90% of requests should be below 600ms
    &amp;#39;http_req_duration&amp;#39;: [&amp;#39;p(90)&amp;lt;600&amp;#39;],
    // 95% of requests tagged as static content should be below 200ms
    &amp;#39;http_req_duration{type:staticContent}&amp;#39;: [&amp;#39;p(99)&amp;lt;250&amp;#39;],
    // the error rate of my custom metric should be below 5%
    &amp;#39;my_custom_metric&amp;#39;: [&amp;#39;rate&amp;lt;0.05&amp;#39;],
  },
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;However, it can be challenging to effectively assess all reliability goals. Frequently, you&amp;rsquo;ll encounter &amp;ldquo;false positives&amp;rdquo; and &amp;ldquo;true negatives&amp;rdquo; when testing with distinct types of load.&lt;/p&gt;
&lt;p&gt;For larger tests, verifying the release based &amp;ldquo;only&amp;rdquo; on a Pass/Fail status can create a false sense of security in your performance testing and release process.&lt;/p&gt;
&lt;p&gt;We recommend keeping the pre-release environment available for a few hours or days to thoroughly test the entire system. Our recommendations include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allocating a period of one to several days for validating the release.&lt;/li&gt;
&lt;li&gt;Executing all the existing average-load, stress, and spike tests.&lt;/li&gt;
&lt;li&gt;Executing each test at least twice consecutively.&lt;/li&gt;
&lt;li&gt;Scheduling all tests to run periodically, for instance, every 4 hours or 8 hours.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;stagingpre-production&#34;&gt;Staging/pre-production&lt;/h3&gt;
&lt;p&gt;In some cases, the staging environment acts like the &amp;ldquo;Pre-release&amp;rdquo; environment. If so, follow the strategy mentioned in the previous section.&lt;/p&gt;
&lt;p&gt;The staging environment is always available and consistently updated with the latest changes. It&amp;rsquo;s generally suitable for assessing performance changes like performance trends, regressions, or improvements.&lt;/p&gt;
&lt;p&gt;In this case, we should choose the tests that assess key performance indicators and schedule them for consistent execution to collect metrics over a period. Start by selecting a few tests and scheduling their runs two to three times per week.&lt;/p&gt;
&lt;p&gt;Like in the pre-release environment, we suggest executing each test at least twice consecutively, allowing us to ignore unreliable tests.&lt;/p&gt;
&lt;p&gt;As we aim to find performance changes, consider scaling the workload of the test according to the staging infrastructure, which often does not match the scale of the production environment.&lt;/p&gt;
&lt;h3 id=&#34;production&#34;&gt;Production&lt;/h3&gt;
&lt;p&gt;Typically, the previous testing environments do not perfectly mirror the production environment, with differences in test data, infrastructure resources, and scalability policies.&lt;/p&gt;
&lt;p&gt;Testing in production provides real-world insights that cannot be achieved in other environments. However, production testing requires a careful approach to handling and storing test data in production and avoiding impacting real users.&lt;/p&gt;
&lt;p&gt;A low-risk common practice is to utilize smoke tests for synthetic testing, also called &lt;a href=&#34;/docs/grafana-cloud/testing/synthetic-monitoring/create-checks/checks/k6/&#34;&gt;synthetic monitoring&lt;/a&gt;. Testing production with minimal load is safe. Schedule smoke tests every five minutes, establishing Pass/Fail test conditions and an effective alerting mechanism. For instance, if six consecutive test runs fail, send an alert.&lt;/p&gt;
&lt;p&gt;If release strategies like Blue/Green or Canary deployments are in place, run load tests against the Green or new version to validate the release. It&amp;rsquo;s an ideal moment to see how SLOs behave in production.&lt;/p&gt;
&lt;p&gt;Also, consider scheduling nightly tests or when the system handles less traffic. The goal is not to stress the system, but to consistently gather performance results to compare changes and analyze performance trends. For instance, schedule tests with half of the average traffic level on a weekly basis.&lt;/p&gt;
&lt;h3 id=&#34;example-plan&#34;&gt;Example plan&lt;/h3&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Test&lt;/th&gt;
              &lt;th&gt;Deployment Env.&lt;/th&gt;
              &lt;th&gt;Type&lt;/th&gt;
              &lt;th&gt;Workload&lt;/th&gt;
              &lt;th&gt;Automation&lt;/th&gt;
              &lt;th&gt;Frequency&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;Checkout process&lt;/td&gt;
              &lt;td&gt;QA&lt;/td&gt;
              &lt;td&gt;Smoke&lt;/td&gt;
              &lt;td&gt;1 iteration&lt;/td&gt;
              &lt;td&gt;CI flow&lt;/td&gt;
              &lt;td&gt;Branch changes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Checkout process&lt;/td&gt;
              &lt;td&gt;Pre-release&lt;/td&gt;
              &lt;td&gt;Average&lt;/td&gt;
              &lt;td&gt;50 VUs - 5m&lt;/td&gt;
              &lt;td&gt;Scheduled during QA/Pre-release period&lt;/td&gt;
              &lt;td&gt;3 times per day during pre-release period&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Checkout process&lt;/td&gt;
              &lt;td&gt;Pre-release&lt;/td&gt;
              &lt;td&gt;Spike&lt;/td&gt;
              &lt;td&gt;200 VUs - 1m&lt;/td&gt;
              &lt;td&gt;Scheduled during QA/Pre-release period&lt;/td&gt;
              &lt;td&gt;3 times per day during pre-release period&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Checkout process&lt;/td&gt;
              &lt;td&gt;Staging&lt;/td&gt;
              &lt;td&gt;Average&lt;/td&gt;
              &lt;td&gt;50 VUs - 5m&lt;/td&gt;
              &lt;td&gt;Schedule&lt;/td&gt;
              &lt;td&gt;2 times per week&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;QA&lt;/td&gt;
              &lt;td&gt;Smoke&lt;/td&gt;
              &lt;td&gt;1 iteration&lt;/td&gt;
              &lt;td&gt;CI flow&lt;/td&gt;
              &lt;td&gt;Branch changes&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;Pre-release&lt;/td&gt;
              &lt;td&gt;Average&lt;/td&gt;
              &lt;td&gt;100 reqs/s - 3m&lt;/td&gt;
              &lt;td&gt;Scheduled during QA/Pre-release period&lt;/td&gt;
              &lt;td&gt;3 times per day during pre-release period&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;Pre-release&lt;/td&gt;
              &lt;td&gt;Stress&lt;/td&gt;
              &lt;td&gt;1500 reqs/s - 5m&lt;/td&gt;
              &lt;td&gt;Scheduled during QA/Pre-release period&lt;/td&gt;
              &lt;td&gt;3 times per day during pre-release period&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;Staging&lt;/td&gt;
              &lt;td&gt;Average&lt;/td&gt;
              &lt;td&gt;100 reqs/s - 3m&lt;/td&gt;
              &lt;td&gt;Schedule&lt;/td&gt;
              &lt;td&gt;2 times per week&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;GET endpoint&lt;/td&gt;
              &lt;td&gt;Production&lt;/td&gt;
              &lt;td&gt;50% Avg.&lt;/td&gt;
              &lt;td&gt;50 reqs/s - 3m&lt;/td&gt;
              &lt;td&gt;Schedule on minimal traffic&lt;/td&gt;
              &lt;td&gt;Weekly&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;plan-the-result-analysis-process&#34;&gt;Plan the result analysis process&lt;/h2&gt;
&lt;p&gt;Following the previous steps, you should now have an initial performance testing plan. Now, let&amp;rsquo;s see how we can analyze and interpret performance results.&lt;/p&gt;
&lt;p&gt;The first step is learning what options you have for outputting performance results. If you&amp;rsquo;re using k6, there are a few 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/&#34;&gt;options you can choose from&lt;/a&gt;. You can review those options and the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/metrics/&#34;&gt;k6 metrics&lt;/a&gt; to decide on a long-term solution to analyze the results of your test automation plan.&lt;/p&gt;
&lt;p&gt;Here are some questions to consider when creating your result analysis process.&lt;/p&gt;
&lt;h3 id=&#34;how-to-store-your-performance-results&#34;&gt;How to store your performance results&lt;/h3&gt;
&lt;p&gt;In k6, you can get the 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/end-of-test/&#34;&gt;aggregated results&lt;/a&gt; at the end of a test, or 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/real-time/&#34;&gt;time series metrics in real-time&lt;/a&gt;. Both options allow you to customize the output.&lt;/p&gt;
&lt;p&gt;The process we recommend is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select a storage backend.&lt;/li&gt;
&lt;li&gt;Understand how it stores your test data and its particular capabilities.&lt;/li&gt;
&lt;li&gt;Learn how to query and visualize results and any limitations.&lt;/li&gt;
&lt;li&gt;Establish a policy for deleting old test results, and make sure to retain key performance results for future comparisons like baseline performance data.&lt;/li&gt;
&lt;li&gt;Test the solution and decide on a long-term storage choice to avoid frequent changes to this critical component.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;which-key-performance-metrics-will-be-your-focus&#34;&gt;Which key performance metrics will be your focus&lt;/h3&gt;
&lt;p&gt;Think about the goal of each particular test, and make sure you track the metrics that depend on your test goals.&lt;/p&gt;
&lt;p&gt;k6 provides 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/metrics/reference/&#34;&gt;built-in metrics&lt;/a&gt; that aggregate all the interactions against the SUT. You can also utilize 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/tags-and-groups/#user-defined-tags&#34;&gt;tags&lt;/a&gt; and custom metrics to categorize and filter results for one interaction or particular types.&lt;/p&gt;
&lt;p&gt;Consider defining your own performance criteria and their visualization. For instance, set different colors for what is good, acceptable, slightly concerning, or wrong so you can quickly visualize if a particular set of performance results are ok.&lt;/p&gt;
&lt;p&gt;Think about performance changes. Is there any particular metric to compare changes or track its trend over time? Most of the test visualization choices focus on the results of individual test runs. Consider implementing a way to visualize the result of a critical performance metric over time so you can identify any changes and trends.&lt;/p&gt;
&lt;h3 id=&#34;how-often-will-you-analyze-results&#34;&gt;How often will you analyze results&lt;/h3&gt;
&lt;p&gt;Consider creating dashboards and custom notifications that can quickly provide an overview of the latest results of any automated tests. These dashboards are the first line to indicate issues requiring investigation.&lt;/p&gt;
&lt;p&gt;Additionally, we recommend setting up alerts for important issues. Think about priority and non-priority levels and follow-up actions. Consider these &lt;a href=&#34;/docs/grafana/latest/alerting/&#34;&gt;tips to design alerts&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;correlate-testing-and-observability-data&#34;&gt;Correlate testing and observability data&lt;/h3&gt;
&lt;p&gt;Last but not least, set up proper instrumentation of the SUT and understand the monitoring and observability in place for system and application data.&lt;/p&gt;
&lt;p&gt;Performance testing results can highlight poor performance, such as slow responses. However, it does not show what happens internally on the SUT, such as a slow SQL query or CPU and memory saturation.&lt;/p&gt;
&lt;p&gt;To bridge the gap, work out a way to correlate testing results with how you instrument your infrastructure and application code. For instance, connecting or building custom dashboards with test results or using 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/jslib/http-instrumentation-tempo/&#34;&gt;trace data from your tests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Continuous testing helps detect issues and performance degradations, whether from test results or system data. Proper observability will help to find the root cause.&lt;/p&gt;
&lt;h3 id=&#34;example-plan-1&#34;&gt;Example plan&lt;/h3&gt;
&lt;p&gt;As we finalize our planning, we can organize our test plan considering how often tests are run, the options to analyze their results, and follow-up actions. For example:&lt;/p&gt;
&lt;section class=&#34;expand-table-wrapper&#34;&gt;&lt;div class=&#34;button-div&#34;&gt;
      &lt;button class=&#34;expand-table-btn&#34;&gt;Expand table&lt;/button&gt;
    &lt;/div&gt;&lt;div class=&#34;responsive-table-wrapper&#34;&gt;
    &lt;table&gt;
      &lt;thead&gt;
          &lt;tr&gt;
              &lt;th&gt;Deployment Env.&lt;/th&gt;
              &lt;th&gt;Test type&lt;/th&gt;
              &lt;th&gt;Frequency&lt;/th&gt;
              &lt;th&gt;Alerts&lt;/th&gt;
              &lt;th&gt;Test overview&lt;/th&gt;
              &lt;th&gt;Action&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;QA&lt;/td&gt;
              &lt;td&gt;Smoke tests&lt;/td&gt;
              &lt;td&gt;CI on Branch changes&lt;/td&gt;
              &lt;td&gt;CI&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;Fix broken tests&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Pre-release&lt;/td&gt;
              &lt;td&gt;All performance tests except smoke tests&lt;/td&gt;
              &lt;td&gt;2 or 3 times daily during the QA/pre-release period&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;Validate the release after assessing performance results&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Staging&lt;/td&gt;
              &lt;td&gt;Baseline performance tests&lt;/td&gt;
              &lt;td&gt;Schedule 2 times per week&lt;/td&gt;
              &lt;td&gt;Only for critical issues&lt;/td&gt;
              &lt;td&gt;Custom dashboard&lt;/td&gt;
              &lt;td&gt;Oversee test results&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Production&lt;/td&gt;
              &lt;td&gt;Baseline performance tests&lt;/td&gt;
              &lt;td&gt;Weekly schedule&lt;/td&gt;
              &lt;td&gt;Priority and Non-priority issues&lt;/td&gt;
              &lt;td&gt;Custom dashboards and notifications&lt;/td&gt;
              &lt;td&gt;Oversee test results&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;Production&lt;/td&gt;
              &lt;td&gt;Synthetic tests&lt;/td&gt;
              &lt;td&gt;Hourly schedule&lt;/td&gt;
              &lt;td&gt;Custom alerts&lt;/td&gt;
              &lt;td&gt;&lt;/td&gt;
              &lt;td&gt;Respond to alerts&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;h2 id=&#34;considerations&#34;&gt;Considerations&lt;/h2&gt;
&lt;h3 id=&#34;start-simple-and-then-iterate&#34;&gt;Start simple and then iterate&lt;/h3&gt;
&lt;p&gt;When starting your performance test plan or automation, it is common to think about having a few dozen scenarios to test. Start small to avoid planning paralysis.&lt;/p&gt;
&lt;p&gt;We recommend beginning with a few distinct tests across testing environments.&lt;/p&gt;
&lt;p&gt;Over time, you can add more tests, and your performance test suite will gradually increase its test coverage.&lt;/p&gt;
&lt;p&gt;Focus on proving your test automation plan and solution across the software release process. A successful implementation will pave the way for collaborating with other teams and promoting the value of continuous performance testing.&lt;/p&gt;
&lt;h3 id=&#34;test-consistency-is-critical&#34;&gt;Test consistency is critical&lt;/h3&gt;
&lt;p&gt;One of the primary objectives of continuous performance testing is assessing changes in the key metrics that define reliability and performance goals. To achieve this, we need to compare the value of these metrics between test runs over a period.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s critical to compare test run results of the same test. Otherwise, you&amp;rsquo;re comparing apples with oranges. Compare identical test runs, the same workload, running the same scenario with the same test data against the same environment.&lt;/p&gt;
&lt;p&gt;Make sure not to introduce variance between test runs. If changes are necessary, rename or create a new test and start comparing test results from scratch.&lt;/p&gt;
&lt;p&gt;Additionally, we recommend scheduling the execution of the same test twice and almost consecutively. This collects one extra test run result for better comparison and allows us to ignore a potentially unreliable test.&lt;/p&gt;
&lt;h3 id=&#34;establish-how-to-stop-automated-and-manual-tests&#34;&gt;Establish how to stop automated and manual tests&lt;/h3&gt;
&lt;p&gt;Certain performance tests, especially those involving heavy-load tests, might cause outages. Automating the execution of risky tests without supervision may not be desirable, but that doesn&amp;rsquo;t mean you should avoid them.&lt;/p&gt;
&lt;p&gt;These tests require controlled test execution and real-time analysis of test results, allowing them to be stopped before the system becomes unresponsive.&lt;/p&gt;
&lt;p&gt;Similarly, you might want to stop a test when the system begins to produce a flood of errors. When a system becomes completely overloaded, continuing the test execution often doesn&amp;rsquo;t provide more meaningful insights and merely consumes resources.&lt;/p&gt;
&lt;p&gt;To stop a k6 test, learn how to use the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/#abort-a-test-when-a-threshold-is-crossed&#34;&gt;&lt;code&gt;abortOnFail&lt;/code&gt; threshold option&lt;/a&gt; or integrate with the k6 CLI or Grafana Cloud k6.&lt;/p&gt;
&lt;h3 id=&#34;complement-automation-with-a-repeatable-qa-process&#34;&gt;Complement automation with a repeatable QA process&lt;/h3&gt;
&lt;p&gt;We mentioned this at the beginning of the guide: automation in performance testing is about establishing a repeatable and consistent testing process.&lt;/p&gt;
&lt;p&gt;You should also plan the frequency of tests that are manually triggered and require supervision of the system during their execution. To ensure these different cases are consistently tested, set reminders and document them as part of the QA process and release checklists. Common examples are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Running soak tests quarterly.&lt;/li&gt;
&lt;li&gt;Running heavy-stress tests 2 months before an important seasonal event.&lt;/li&gt;
&lt;li&gt;Running heavy-load tests for major releases in a pre-release environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;quality-gates-in-cicd-may-result-in-false-assurance&#34;&gt;Quality gates in CI/CD may result in false assurance&lt;/h3&gt;
&lt;p&gt;Quality gates in performance tests are often defined as 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/&#34;&gt;Pass/Fail criteria&lt;/a&gt; that verify the release meets its reliability goals.&lt;/p&gt;
&lt;p&gt;However, setting up reliable quality gates is challenging when testing thousands or millions of interactions. The test script, the SLO for that particular environment, and the Pass/Fail criteria could easily be wrong.&lt;/p&gt;
&lt;p&gt;Assume reliability checks may have false negatives (and vice versa); ensure performance tests don&amp;rsquo;t block releases wrongly.&lt;/p&gt;
&lt;p&gt;Unless your verification process is mature, do not rely entirely on Pass/Fail results to guarantee the reliability of releases. If unsure, start utilizing Pass/Fail results to warn about possible issues for deeper investigation, and continuously tweak the criteria until becoming confident.&lt;/p&gt;
&lt;p&gt;Moreover, note that the load test duration often takes between 3 to 15 minutes or more; thus, introducing performance testing into CI/CD significantly increases the time of the release process. This is another reason we advise not to run larger tests in pipelines meant for automatic deployment. Instead, plan one or more days for performance testing pre-releases in a dedicated environment.&lt;/p&gt;
]]></content><description>&lt;h1 id="automated-performance-testing">Automated performance testing&lt;/h1>
&lt;p>Performance testing automation is about establishing &lt;strong>a repeatable and consistent process that checks reliability issues&lt;/strong> at different stages of the development and release cycle. For instance, you could run performance tests from CI/CD pipelines and nightly jobs, or manually trigger load tests and monitor their impact in real-time.&lt;/p></description></item><item><title>API load testing</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/api-load-testing/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/api-load-testing/</guid><content><![CDATA[&lt;h1 id=&#34;api-load-testing&#34;&gt;API load testing&lt;/h1&gt;
&lt;p&gt;An API load test generally starts with small loads on isolated components.
As your testing matures, your strategy can consider how to test the API more completely.
In this process, you&amp;rsquo;ll test your API with more requests, longer durations, and on a wider test scope—from isolated components to complete end-to-end workflows.&lt;/p&gt;
&lt;p&gt;When you design your API tests, first consider &lt;em&gt;why&lt;/em&gt; you want to test the API at all:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What flows or components do you want to test?&lt;/li&gt;
&lt;li&gt;How will you run the test?&lt;/li&gt;
&lt;li&gt;What criteria determine acceptable performance?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you can answer these questions, your API testing strategy will likely follow something like this procedure:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Script the test.&lt;/strong&gt; Write user flows, parameterize test data, and group URLs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert performance and correctness.&lt;/strong&gt; Use Checks to assert system responses and use Thresholds to ensure that the system performs within your SLOs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model and generate load.&lt;/strong&gt; Choose the executors to correctly model the workload that&amp;rsquo;s appropriate to your test goals. Make sure the load generators are located where they should be.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterate over your test suite.&lt;/strong&gt; Over time, you&amp;rsquo;ll be able to reuse script logic (e.g., a user log-in flow or a throughput configuration). You&amp;rsquo;ll also be able to run tests with a wider scope or as a part of your automated testing suite.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The following sections provide specific explanations and examples of the steps in this process.&lt;/p&gt;
&lt;h2 id=&#34;identify-the-components-to-test&#34;&gt;Identify the components to test&lt;/h2&gt;
&lt;p&gt;Before you start testing, identify the components you want to test.
Do you want to test a single endpoint or an entire flow?&lt;/p&gt;
&lt;p&gt;The following script uses the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-http/&#34;&gt;k6 HTTP module&lt;/a&gt; to test a single endpoint.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;

export default function () {
  const payload = JSON.stringify({
    name: &amp;#39;lorem&amp;#39;,
    surname: &amp;#39;ipsum&amp;#39;,
  });
  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, { headers });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This is a minimal test, with one call to one component.
Generally, your test suite will progress from scripts like this to more complex and complete workflows.
In this process, your test suite will advance through the &lt;a href=&#34;https://martinfowler.com/articles/practical-test-pyramid.html&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;testing pyramid&lt;/a&gt; as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Testing an isolated API&lt;/strong&gt;. Hammering an API endpoint like &lt;a href=&#34;https://httpd.apache.org/docs/2.4/programs/ab.html&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;ab&lt;/a&gt; to test the baseline performance, breaking point, or availability. If a component doesn’t meet performance requirements, it is a bottleneck. Generally, the load is set in requests per second.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing integrated APIs&lt;/strong&gt;. Testing one or multiple APIs that interact with other internal or external APIs. Your focus might be on testing one system or various.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Testing end-to-end API flows&lt;/strong&gt;. Simulating realistic interactions between APIs to test the system as a whole. The focus is often on frequent and critical user scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your load test suite should include a wide range of tests.
But, when you start, start small and simple,
testing individual APIs and uncomplicated integration tests.&lt;/p&gt;
&lt;h2 id=&#34;determined-the-reason-for-the-test&#34;&gt;Determined the reason for the test&lt;/h2&gt;
&lt;p&gt;Before you configure test load, you should know what traffic patterns you want to test the API for.
A load test typically aims to do one of two things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Validate reliability under expected traffic&lt;/li&gt;
&lt;li&gt;Discover problems and system limits under unusual traffic.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, your team might create one set of tests for frequent user flows on average traffic, and another set to find breaking points in the API.
Even if the test logic stays the same, its load might change.&lt;/p&gt;
&lt;p&gt;The test goal determines the test type, which in turn determines the test load.
Consider the following test types, which correspond to different goals load profiles:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/smoke-testing/&#34;&gt;Smoke test&lt;/a&gt;. Verify the system functions with minimal load.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/load-testing/&#34;&gt;“Average” load test&lt;/a&gt;. Discover how the system functions with typical traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/stress-testing/&#34;&gt;Stress test&lt;/a&gt;. Discover how the system functions with the load of peak traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/spike-testing/&#34;&gt;Spike test&lt;/a&gt;. Discover how the system functions with sudden and massive increases in traffic.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/breakpoint-testing/&#34;&gt;Breakpoint test&lt;/a&gt;. Progressively ramp traffic to discover system breaking points.&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/soak-testing/&#34;&gt;Soak test&lt;/a&gt;. Discover whether or when the system degrades under loads of longer duration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The test types that you choose inform how you plan and structure your test.
But each application, organization, and testing project differs.
Our recommendation is always:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Start simple and test frequently. Iterate and grow the test suite&amp;rdquo;.&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Once you&amp;rsquo;ve decided on the load profile, you can schedule it with k6 options.&lt;/p&gt;
&lt;h2 id=&#34;model-the-workload&#34;&gt;Model the workload&lt;/h2&gt;
&lt;p&gt;To configure the workload, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/&#34;&gt;test options&lt;/a&gt;.
The test load configures the traffic generated by the test. k6 provides two broad ways to model load:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Through &lt;em&gt;virtual users&lt;/em&gt; (VUs), to simulate concurrent users&lt;/li&gt;
&lt;li&gt;Through &lt;em&gt;requests per second&lt;/em&gt;, to simulate raw, real-world throughput&lt;/li&gt;
&lt;/ul&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Generally, your load tests should add 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6/sleep/&#34;&gt;sleep time&lt;/a&gt;.
Sleep time helps control the load generator and better simulates the traffic patterns of human users.&lt;/p&gt;
&lt;p&gt;However, when it comes to API load tests, these recommendations about sleep come with a few qualifications.
If testing an isolated component, you might care only about performance under a pre-determined throughput.
But, even in this case, sleep can help you avoid overworking the load generator, and including a few randomized milliseconds of sleep can avoid accidental concurrency.&lt;/p&gt;
&lt;p&gt;When testing the API against normal, human-run workflows, add sleep as in a normal test.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h3 id=&#34;virtual-users&#34;&gt;Virtual users&lt;/h3&gt;
&lt;p&gt;When you model load according to VUs, the basic load options are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#vus&#34;&gt;&lt;code&gt;vus&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#duration&#34;&gt;&lt;code&gt;duration&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#iterations&#34;&gt;&lt;code&gt;iterations&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can define these options in the test script. In the following test, 50 concurrent users continuously run the &lt;code&gt;default&lt;/code&gt; flow for 30 seconds.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;

export const options = {
  vus: 50,
  duration: &amp;#39;30s&amp;#39;,
};

export default function () {
  const payload = JSON.stringify({
    name: &amp;#39;lorem&amp;#39;,
    surname: &amp;#39;ipsum&amp;#39;,
  });
  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, { headers });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;request-rate&#34;&gt;Request rate&lt;/h3&gt;
&lt;p&gt;When analyzing API endpoint performance, the load is generally reported by request rate—either requests per second or per minute.&lt;/p&gt;
&lt;p&gt;To configure workloads according to a target request rate, use the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/executors/constant-arrival-rate/&#34;&gt;constant arrival rate executor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;constant-arrival-rate&lt;/code&gt; sets a constant rate of iterations that execute the script function.
Each iteration can generate one or multiple requests.&lt;/p&gt;
&lt;p&gt;To reach a request-rate target (&lt;code&gt;RequestsRate&lt;/code&gt;), follow this approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the rate frequency to the time unit of the target. Per second or per minute.&lt;/li&gt;
&lt;li&gt;Get the number of requests per iteration (&lt;code&gt;RequestsPerIteration&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Set the iteration rate to the requests per second target divided by the number of requests per iteration.
&lt;code&gt;rate&lt;/code&gt; = &lt;code&gt;RequestsRate ÷ RequestsPerIteration&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To reach target of 50 reqs/s with the previous example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set the &lt;code&gt;timeUnit&lt;/code&gt; options to &lt;code&gt;1s&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The number of requests per iteration is 1.&lt;/li&gt;
&lt;li&gt;Set the &lt;code&gt;rate&lt;/code&gt; option to 50/1 (so it equals 50).&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;

export const options = {
  scenarios: {
    my_scenario1: {
      executor: &amp;#39;constant-arrival-rate&amp;#39;,
      duration: &amp;#39;30s&amp;#39;, // total duration
      preAllocatedVUs: 50, // to allocate runtime resources     preAll

      rate: 50, // number of constant iterations given `timeUnit`
      timeUnit: &amp;#39;1s&amp;#39;,
    },
  },
};

export default function () {
  const payload = JSON.stringify({
    name: &amp;#39;lorem&amp;#39;,
    surname: &amp;#39;ipsum&amp;#39;,
  });
  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, { headers });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This test outputs the total number of HTTP requests and RPS on the &lt;code&gt;http_reqs&lt;/code&gt; metric:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;# the reported value is close to the 50 RPS target
 http_reqs......................: 1501   49.84156/s

# the iteration rate is the same as rps, because each iteration runs only one request
iterations.....................: 1501   49.84156/s&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;For a more extensive example, refer to this post about &lt;a href=&#34;https://k6.io/blog/how-to-generate-a-constant-request-rate-with-the-new-scenarios-api/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;generating a constant request rate&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;constant-arrival-rate&lt;/code&gt; executor, load is constant through the test.
To ramp the request rate up or down, use the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/executors/ramping-arrival-rate/&#34;&gt;&lt;code&gt;ramping-arrival-rate&lt;/code&gt;&lt;/a&gt; executor instead.&lt;/p&gt;
&lt;p&gt;For all ways to model the load in k6, refer to 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/&#34;&gt;Scenarios&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;verify-functionality-with-checks&#34;&gt;Verify functionality with Checks&lt;/h2&gt;
&lt;p&gt;Traditionally, performance tests care most about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Latency&lt;/em&gt;, how fast the system responds&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Availability&lt;/em&gt;, how often the system returns errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;http_req_duration&lt;/code&gt; metric reports the latency,
and &lt;code&gt;http_req_failed&lt;/code&gt; reports the error rate for HTTP requests.
The previous test run provided the following results:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;http_req_duration..............: avg=106.14ms min=102.54ms med=104.66ms max=198.93ms p(90)=113.78ms p(95)=114.58ms
    { expected_response:true }...: avg=106.14ms min=102.54ms med=104.66ms max=198.93ms p(90)=113.78ms p(95)=114.58ms
http_req_failed................: 0.00% ✓ 0    ✗ 1501&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Your test analysis might need to go beyond what&amp;rsquo;s available with default metrics.
For more meaningful results analysis, you might also want to validate functionalities and report errors.&lt;/p&gt;
&lt;p&gt;Some application failures happen only under certain load conditions, such as high traffic.
These errors are hard to find.
To find the cause of failures more quickly, instrument your APIs and verify that requests get the expected responses.
To verify application logic in k6, you can use &lt;em&gt;Checks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/checks/&#34;&gt;Checks&lt;/a&gt; validate conditions during the test execution.
For example, you can use checks verify and track API responses.
With checks, you can confirm expected API responses, such as the HTTP status or any returned data.&lt;/p&gt;
&lt;p&gt;Our script now verifies the HTTP response status, headers, and payload.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

export const options = {
  scenarios: {
    my_scenario1: {
      executor: &amp;#39;constant-arrival-rate&amp;#39;,
      duration: &amp;#39;30s&amp;#39;, // total duration
      preAllocatedVUs: 50, // to allocate runtime resources

      rate: 50, // number of constant iterations given `timeUnit`
      timeUnit: &amp;#39;1s&amp;#39;,
    },
  },
};

export default function () {
  const payload = JSON.stringify({
    name: &amp;#39;lorem&amp;#39;,
    surname: &amp;#39;ipsum&amp;#39;,
  });
  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  const res = http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, { headers });

  check(res, {
    &amp;#39;Post status is 200&amp;#39;: (r) =&amp;gt; res.status === 200,
    &amp;#39;Post Content-Type header&amp;#39;: (r) =&amp;gt; res.headers[&amp;#39;Content-Type&amp;#39;] === &amp;#39;application/json&amp;#39;,
    &amp;#39;Post response name&amp;#39;: (r) =&amp;gt; res.status === 200 &amp;amp;&amp;amp; res.json().name === &amp;#39;lorem&amp;#39;,
  });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In this snippet, all checks succeeded:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;my_scenario1 ✓ [======================================] 00/50 VUs  30s  50.00 iters/s
     ✓ Post status is 200
     ✓ Post Content-Type header
     ✓ Post response name&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;After the load increased to 300 requests per second, the results returned 8811 successful requests and 7 failures:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;my_scenario1 ✓ [======================================] 000/300 VUs  30s  300.00 iters/s
     ✗ Post status is 200
      ↳  99% — ✓ 8811 / ✗ 7
     ✗ Post Content-Type header
      ↳  99% — ✓ 8811 / ✗ 7
     ✗ Post response name
      ↳  99% — ✓ 8811 / ✗ 7&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;By default, a failed check doesn&amp;rsquo;t fail or abort the test.
In this regard, a check differs from how assertions work for other types of testing.
A load test can run thousands or millions of script iterations, each with dozens of assertions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Some rate of failure is acceptable&lt;/strong&gt;, as determined by your SLO&amp;rsquo;s &amp;ldquo;number of nines&amp;rdquo; or your organization&amp;rsquo;s error budget.&lt;/p&gt;
&lt;h2 id=&#34;test-your-reliability-goals-with-thresholds&#34;&gt;Test your reliability goals with Thresholds&lt;/h2&gt;
&lt;p&gt;Every test should have a goal.
Engineering organizations set their reliability goals using &lt;a href=&#34;https://en.wikipedia.org/wiki/Service-level_objective&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Service Level Objectives&lt;/a&gt; (SLOs) to validate availability, performance, or any performance requirements.&lt;/p&gt;
&lt;p&gt;SLOs may be defined at distinct scopes, such as on the level of an infrastructure component, of an API, or of the entire application.
Some example SLOs could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;That 99% of APIs returning product information respond in less than 600ms.&lt;/li&gt;
&lt;li&gt;That 99.99% of failed log-in requests respond in less than 1000ms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Design your load tests with pass/fail criteria to validate SLOs&lt;/strong&gt;, reliability goals, or other important metrics.
To ensure your system achieves its SLOs, test them frequently, both in pre-production and production environments.&lt;/p&gt;
&lt;p&gt;In k6, you can use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/&#34;&gt;Thresholds&lt;/a&gt; to set the test pass/fail criteria.
This script codifies two SLOs in the &lt;code&gt;thresholds&lt;/code&gt; object, one about error rate (availability) and one about request duration (latency).&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export const options = {
  thresholds: {
    http_req_failed: [&amp;#39;rate&amp;lt;0.01&amp;#39;], // http errors should be less than 1%
    http_req_duration: [&amp;#39;p(95)&amp;lt;200&amp;#39;], // 95% of requests should be below 200ms
  },
  scenarios: {
    my_scenario1: {
      executor: &amp;#39;constant-arrival-rate&amp;#39;,
      duration: &amp;#39;30s&amp;#39;, // total duration
      preAllocatedVUs: 50, // to allocate runtime resources

      rate: 50, // number of constant iterations given `timeUnit`
      timeUnit: &amp;#39;1s&amp;#39;,
    },
  },
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;When the test fails, the k6 CLI returns a non-zero exit code—a necessary condition for test automation.
As an example of a failed test, here&amp;rsquo;s the output for a test with a threshold that 95 percent of requests finish in under 50ms, &lt;code&gt;http_req_duration:[&amp;quot;p(95)&amp;lt;50&amp;quot;]&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;  █ THRESHOLDS

    http_req_duration
    ✗ &amp;#39;p(95)&amp;lt;200&amp;#39; p(95)=348.21ms

    http_req_failed
    ✓ &amp;#39;rate&amp;lt;0.01&amp;#39; rate=0.05%


  █ TOTAL RESULTS

    checks_total.......................: 90      13.122179/s
    checks_succeeded...................: 100.00% 90 out of 90
    checks_failed......................: 0.00%   0 out of 90

    ✓ Post status is 200
    ✓ Post Content-Type header
    ✓ Post response name

    HTTP
    http_req_duration..................: avg=140.36ms   min=119.08ms med=140.96ms max=154.63ms p(90)=146.88ms p(95)=148.21ms
      { expected_response:true }.......: avg=140.36ms   min=119.08ms med=140.96ms max=154.63ms p(90)=146.88ms p(95)=148.21ms
    http_req_failed....................: 0.00%  0 out of 45
    http_reqs..........................: 45     6.56109/s

    EXECUTION
    iteration_duration.................: avg=152.38ms   min=119.37ms med=141.27ms max=684.62ms p(90)=147.11ms p(95)=148.39ms
    iterations.........................: 45     6.56109/s
    vus................................: 1      min=1       max=1
    vus_max............................: 1      min=1       max=1

    NETWORK
    data_received......................: 519 kB 76 kB/s
    data_sent..........................: 4.9 kB 718 B/s

running (0m30.1s), 00/50 VUs, 1501 complete and 0 interrupted iterations
my_scenario1 ✓ [======================================] 00/50 VUs  30s  50.00 iters/s
ERRO[0030] some thresholds have failed&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;scripting-considerations&#34;&gt;Scripting considerations&lt;/h2&gt;
&lt;p&gt;If you have scripted tests before, implementing k6 scripts should seem familiar.
k6 tests are written in JavaScript, and the design of the k6 API has similarities with other testing frameworks.&lt;/p&gt;
&lt;p&gt;But, unlike other tests, load tests run their scripts hundreds, thousands, or millions of times.
The presence of load creates a few specific concerns.
When you load test APIs with k6, consider the following aspects of your script design.&lt;/p&gt;
&lt;h3 id=&#34;data-parameterization&#34;&gt;Data parameterization&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Data parameterization&lt;/em&gt; happens when you replace hard-coded test data with dynamic values.
Parameterization makes it easier to manage a load test with varied users and API calls.
A common case for parameterization happens when you want to use different &lt;code&gt;userID&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; values for each virtual user or iteration.&lt;/p&gt;
&lt;p&gt;For example, consider a JSON file with a list of user info such as:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JSON&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;#34;users&amp;#34;: [
    { &amp;#34;username&amp;#34;: &amp;#34;lorem&amp;#34;, &amp;#34;surname&amp;#34;: &amp;#34;ipsum&amp;#34; },
    { &amp;#34;username&amp;#34;: &amp;#34;dolorem&amp;#34;, &amp;#34;surname&amp;#34;: &amp;#34;ipsum&amp;#34; }
  ]
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can parameterize the users with the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-data/sharedarray/&#34;&gt;&lt;code&gt;SharedArray&lt;/code&gt;&lt;/a&gt; object as follows:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;
import { SharedArray } from &amp;#39;k6/data&amp;#39;;

const users = new SharedArray(&amp;#39;users.json&amp;#39;, function () {
  return JSON.parse(open(&amp;#39;./users.json&amp;#39;)).users;
});

export const options = {};

export default function () {
  // now, user data is not the same for all the iterations
  const user = users[Math.floor(Math.random() * users.length)];
  const payload = JSON.stringify({
    name: user.username,
    surname: user.surname,
  });

  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  const res = http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, {
    headers,
  });

  check(res, {
    &amp;#39;Post status is 200&amp;#39;: (r) =&amp;gt; res.status === 200,
    &amp;#39;Post Content-Type header&amp;#39;: (r) =&amp;gt; res.headers[&amp;#39;Content-Type&amp;#39;] === &amp;#39;application/json&amp;#39;,
    &amp;#39;Post response name&amp;#39;: (r) =&amp;gt; res.status === 200 &amp;amp;&amp;amp; res.json().name === user.username,
  });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To read more about data parameterization, check out the 
    &lt;a href=&#34;/docs/k6/v2.3.x/examples/data-parameterization/&#34;&gt;parameterization examples&lt;/a&gt; and 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/execution-context-variables/&#34;&gt;Execution context variables&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;error-handling-and-acceptance-of-failures&#34;&gt;Error handling and acceptance of failures&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Remember to implement error handling in the test logic&lt;/strong&gt;.
Under sufficiently heavy load, the SUT fails and starts to respond with errors.
Though a test might be designed to induce failures, sometimes we focus on only the best-case scenario and forget the importance of accounting for errors.&lt;/p&gt;
&lt;p&gt;The test script must handle API errors to avoid runtime exceptions and to ensure that it tests how the SUT behaves under saturation according to the test goals.
For example, we could extend our script to do some operation that depends on the result of the previous request:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;
import { SharedArray } from &amp;#39;k6/data&amp;#39;;

const users = new SharedArray(&amp;#39;users.json&amp;#39;, function () {
  return JSON.parse(open(&amp;#39;./users.json&amp;#39;)).users;
});

export const options = {};

export default function () {
  const user = users[Math.floor(Math.random() * users.length)];
  const payload = JSON.stringify({
    name: user.username,
    surname: user.surname,
  });
  const headers = { &amp;#39;Content-Type&amp;#39;: &amp;#39;application/json&amp;#39; };
  const res = http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, payload, {
    headers,
  });

  check(res, {
    &amp;#39;Post status is 200&amp;#39;: (r) =&amp;gt; res.status === 200,
    &amp;#39;Post Content-Type header&amp;#39;: (r) =&amp;gt; res.headers[&amp;#39;Content-Type&amp;#39;] === &amp;#39;application/json&amp;#39;,
    &amp;#39;Post response name&amp;#39;: (r) =&amp;gt; res.status === 200 &amp;amp;&amp;amp; res.json().name === user.username,
  });

  if (res.status === 200) {
    // enters only successful responses
    // otherwise, it triggers an exception
    const delPayload = JSON.stringify({ name: res.json().name });
    http.patch(&amp;#39;https://quickpizza.grafana.com/api/patch&amp;#39;, delPayload, { headers });
  }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;test-reuse-and-modularization&#34;&gt;Test reuse and modularization&lt;/h3&gt;
&lt;p&gt;Load testing can be vast in scope, and it may involve different types of tests.
Generally, teams start with simple or critical load tests and continue adding tests for new use cases, user flows, traffic patterns, features, systems, etc.&lt;/p&gt;
&lt;p&gt;In this process, load testing suites grow over time.
To minimize repetitive work, try to &lt;strong&gt;reuse test scripts early&lt;/strong&gt; and to &lt;strong&gt;modularize test functions and logic.&lt;/strong&gt;
If you script common scenarios in reusable modules, it&amp;rsquo;s easier to create different types of load tests.
The process of creating a new load test goes like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a new test file.&lt;/li&gt;
&lt;li&gt;Configure the specific load and other options.&lt;/li&gt;
&lt;li&gt;Import the scenario.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As your testing matures, consider creating tests that 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/advanced-examples/#combine-scenarios&#34;&gt;combine multiple scenarios&lt;/a&gt; to simulate more diverse traffic.&lt;/p&gt;
&lt;h3 id=&#34;dynamic-urls-for-one-endpoint&#34;&gt;Dynamic URLs for one endpoint&lt;/h3&gt;
&lt;p&gt;By default, when you access the same API endpoint with different URLs―for example, &lt;code&gt;http://example.com/posts/${id}&lt;/code&gt;―k6 reports the endpoint results separately.
This may create an unnecessary amount of metrics.&lt;/p&gt;
&lt;p&gt;To group the results of the endpoint, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/http-requests/#url-grouping&#34;&gt;URL grouping&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;load-generator-locations&#34;&gt;Load generator locations&lt;/h2&gt;
&lt;p&gt;When you plan the test, consider the locations of your &lt;em&gt;load generators&lt;/em&gt;, the machines that run the test.
Sometimes, running the test from a specific location is a test requirement.
Other times, you might just choose the location based on convenience or practicality.
Either way, when you set the location of the load generator, keep the following in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Required locations.&lt;/strong&gt; To compare performance or ensure accurate results, some load tests need to measure the latency from specific locations. These tests launch the load generators from locations that match their user&amp;rsquo;s region.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optional locations.&lt;/strong&gt; Other tests try to measure against a performance baseline—how the system performance changes from a particular performance status or time. To avoid skewed latency results, ensure that the location of the load generator is constant across test runs, and avoid running the tests from locations that are too close to the SUT.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;internal-apis&#34;&gt;Internal APIs&lt;/h3&gt;
&lt;p&gt;End-to-end API tests try to replicate real-world user flows, which access public APIs from external systems.
Other APIs are internal and unreachable from outside.
The need to run internal tests is common when testing API integrations and isolated endpoints.&lt;/p&gt;
&lt;p&gt;If the API is in an internal or restricted environment, you can use k6 to test it in a few different ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Run the test from your private network using the k6 run command or the &lt;a href=&#34;https://github.com/grafana/k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Kubernetes operator&lt;/a&gt;. Optionally, store the test results in 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/real-time/cloud/&#34;&gt;k6 Cloud&lt;/a&gt; or other 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/real-time/&#34;&gt;external services&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For cloud tests:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;/docs/grafana-cloud/testing/k6/reference/cloud-ips/&#34;&gt;Open your firewall&lt;/a&gt; for cloud test traffic.&lt;/li&gt;
&lt;li&gt;Run the cloud test from your &lt;a href=&#34;/docs/grafana-cloud/testing/k6/author-run/private-load-zone-v2/&#34;&gt;Kubernetes clusters&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;supplementary-tools&#34;&gt;Supplementary tools&lt;/h2&gt;
&lt;p&gt;You might want to use k6 in conjunction with other API tools.&lt;/p&gt;
&lt;h3 id=&#34;integrate-with-api-tools&#34;&gt;Integrate with API tools&lt;/h3&gt;
&lt;p&gt;The tooling around REST APIs is vast, but there&amp;rsquo;s not much focus on performance testing.
k6 provides a few converters to
help you incorporate the wider API tooling ecosystem into your load tests:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/apideck-libraries/postman-to-k6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Postman-to-k6 converter&lt;/a&gt;: to create a k6 test from a Postman collection.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;postman-to-k6 collection.json -o k6-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://k6.io/blog/load-testing-your-api-with-swagger-openapi-and-k6/#api-load-testing-with-swaggeropenapi-specification&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;OpenAPI k6 generator&lt;/a&gt;: to create a k6 test from an Open API (formerly Swagger) definition.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;openapi-generator-cli generate -i my-api-spec.json -g k6&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These tools generate a k6 test that you can edit and run as usual:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;k6 run k6-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Depending on the test type, the converters could help you quickly create your first tests or help onboard new users to k6.
Even so, we recommend you get familiar with the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/&#34;&gt;k6 Javascript API&lt;/a&gt; and script your own tests.&lt;/p&gt;
&lt;h3 id=&#34;using-proxy-recorders&#34;&gt;Using proxy recorders&lt;/h3&gt;
&lt;p&gt;Another option is to auto-generate a k6 test from a recorded session. These scripts might help you start building more complex end-to-end and integration tests.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&#34;https://github.com/grafana/har-to-k6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;har-to-k6 converter&lt;/a&gt; creates the k6 test from a recorded session in HAR format which collects HTTP traffic.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;har-to-k6 archive.tar -o k6-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The generated k6 test can be edited and run as usual:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;k6 run k6-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To export a recorded session to HAR format, use a proxy recorder such as &lt;a href=&#34;https://www.telerik.com/fiddler/fiddler-everywhere&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Fiddler proxy&lt;/a&gt; or &lt;a href=&#34;https://gitlab.com/gitlab-org/security-products/har-recorder/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitLab HAR recorder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As with the previous converters, the recorder can help prototype tests.
Again, we recommend learning to write your test scripts.&lt;/p&gt;
&lt;h2 id=&#34;beyond-http-apis&#34;&gt;Beyond HTTP APIs&lt;/h2&gt;
&lt;p&gt;Due to the popularity of the web and REST APIs, this guide has used the term focused on HTTP APIs. But APIs are not restricted to the HTTP protocol.&lt;/p&gt;
&lt;p&gt;By default, k6 supports testing the following protocols:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-http/&#34;&gt;HTTP/1.1, HTTP/2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-ws/&#34;&gt;WebSockets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-experimental/redis/&#34;&gt;Redis (experimental)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-net-grpc/&#34;&gt;gRPC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import grpc from &amp;#39;k6/net/grpc&amp;#39;;
import { check, sleep } from &amp;#39;k6&amp;#39;;

const client = new grpc.Client();
client.load(null, &amp;#39;quickpizza.proto&amp;#39;);

export default () =&amp;gt; {
  client.connect(&amp;#39;grpc-quickpizza.grafana.com:443&amp;#39;);

  const data = { ingredients: [&amp;#39;Tomatoes&amp;#39;, &amp;#39;Cheese&amp;#39;], dough: &amp;#39;Thin&amp;#39; };
  const response = client.invoke(&amp;#39;quickpizza.GRPC/RatePizza&amp;#39;, data);

  check(response, {
    &amp;#39;status is OK&amp;#39;: (r) =&amp;gt; r &amp;amp;&amp;amp; r.status === grpc.StatusOK,
  });

  client.close();
  sleep(1);
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;But modern software is not built only based on these protocols. Modern infrastructure and applications rely on other API protocols to provide new capabilities or improve their performance, throughput, and reliability.&lt;/p&gt;
&lt;p&gt;To test the performance and capacity of these systems, the testing tool should be able to generate protocol-specific requests against their APIs.&lt;/p&gt;
&lt;p&gt;If k6 doesn&amp;rsquo;t support a protocol you need, you can use (or create) 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/&#34;&gt;extensions&lt;/a&gt;.
The list of extensions is long:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avro&lt;/li&gt;
&lt;li&gt;ZeroMQ&lt;/li&gt;
&lt;li&gt;Ethereum&lt;/li&gt;
&lt;li&gt;STOMP&lt;/li&gt;
&lt;li&gt;MLLP&lt;/li&gt;
&lt;li&gt;NATS&lt;/li&gt;
&lt;li&gt;and 
    &lt;a href=&#34;/docs/k6/v2.3.x/extensions/explore/&#34;&gt;more&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="api-load-testing">API load testing&lt;/h1>
&lt;p>An API load test generally starts with small loads on isolated components.
As your testing matures, your strategy can consider how to test the API more completely.
In this process, you&amp;rsquo;ll test your API with more requests, longer durations, and on a wider test scope—from isolated components to complete end-to-end workflows.&lt;/p></description></item><item><title>Calculate concurrent users for load tests</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/calculate-concurrent-users/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/calculate-concurrent-users/</guid><content><![CDATA[&lt;h1 id=&#34;calculate-concurrent-users-for-load-tests&#34;&gt;Calculate concurrent users for load tests&lt;/h1&gt;
&lt;p&gt;When planning load tests, determining the right number of concurrent users is crucial for creating realistic test scenarios. This guide explains how to use Google Analytics data to calculate concurrent user numbers and design effective load tests.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;You can use any analytics tool that provides the same metrics to help you calculate the concurrent users you need for load testing.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;understanding-concurrent-users&#34;&gt;Understanding concurrent users&lt;/h2&gt;
&lt;p&gt;Google Analytics tracks visitors (&amp;ldquo;Users&amp;rdquo;) and their activity periods (&amp;ldquo;Sessions&amp;rdquo;) on your site. During a session, users generate traffic through page loads or AJAX requests that your servers must handle. To create realistic load tests, you need to determine how many users are actively generating traffic at any given time.&lt;/p&gt;
&lt;p&gt;The recommended formula for calculating your baseline of hourly concurrent users is:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;An hour in seconds = 3600
Concurrent users  = Hourly sessions * Average session duration (in seconds) / 3600&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;get-the-data-from-google-analytics&#34;&gt;Get the data from Google Analytics&lt;/h2&gt;
&lt;p&gt;To find the required metrics in Google Analytics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Log in to your Google Analytics account.&lt;/li&gt;
&lt;li&gt;Navigate to the &lt;strong&gt;Reporting&lt;/strong&gt; tab.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Audience&lt;/strong&gt; on the sidebar menu.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Overview&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Set your desired time period.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Hourly&lt;/strong&gt; view in the line graph settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You should be able to view the session count and average session duration on the overview dashboard.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;These instructions might vary depending on which version of Google Analytics you&amp;rsquo;re using. If you&amp;rsquo;re using Google Analytics 4, you can find these metrics by selecting &lt;strong&gt;Explore&lt;/strong&gt; and creating a &lt;strong&gt;Free form&lt;/strong&gt; report.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;designing-load-tests&#34;&gt;Designing load tests&lt;/h2&gt;
&lt;p&gt;With the data in hand, it&amp;rsquo;s important to mention why you want to use the &amp;ldquo;Hourly&amp;rdquo; view.&lt;/p&gt;
&lt;p&gt;Using hourly metrics rather than daily or monthly averages is crucial for accurate load testing. Daily averages can mask significant traffic variations that occur throughout the day. For example, an e-commerce site might see most of its traffic during business hours, with peaks during lunch breaks. Using daily averages would underestimate these crucial peak periods.&lt;/p&gt;
&lt;p&gt;Additionally, hourly metrics help identify patterns in user behavior. A news site might see traffic spikes during morning commute hours and evening hours, while a restaurant booking service might peak during evening hours. Understanding these patterns through hourly data helps you design more realistic load tests that match your actual usage patterns.&lt;/p&gt;
&lt;h3 id=&#34;calculate-peak-traffic&#34;&gt;Calculate peak traffic&lt;/h3&gt;
&lt;p&gt;Instead of using average traffic levels, base your load tests on peak traffic periods:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Identify peak hours in Google Analytics.&lt;/li&gt;
&lt;li&gt;Calculate concurrent users during these peaks.&lt;/li&gt;
&lt;li&gt;Add a margin to ensure you can handle higher traffic levels.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, if your site averages 0.08 concurrent sessions monthly but peaks at 7.2 concurrent sessions during high-traffic hours, you should test for at least this peak level.&lt;/p&gt;
&lt;h3 id=&#34;example-calculation&#34;&gt;Example calculation&lt;/h3&gt;
&lt;p&gt;As an example, for a website that has the following metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2,591 monthly sessions&lt;/li&gt;
&lt;li&gt;82 seconds per session&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Applying the concurrent user formula, you can find the average concurrent users:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;2,591 monthly sessions x 82 seconds per session / 3600 = 59.0172

// To convert monthly sessions to hourly sessions, you can divide the value by 720
59.0172 / 720 (30 days in November x 24h per day = 720) = 0.08 average concurrent users in November&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;But, if the same website has peak hours from 3 PM to 4 PM with different metrics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;990 sessions&lt;/li&gt;
&lt;li&gt;92 seconds&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, applying the concurrent users formula leads to:&lt;/p&gt;

&lt;div class=&#34;code-snippet code-snippet__mini&#34;&gt;&lt;div class=&#34;lang-toolbar__mini&#34;&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet code-snippet__border&#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-none&#34;&gt;990 sessions x 92 seconds per session / 3600 = 25.3 average concurrent users&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This example shows how peak traffic can reach a considerably higher value than the monthly average, in this case from 0.8 to 25.3 concurrent users, highlighting the importance of testing beyond average traffic levels.&lt;/p&gt;
&lt;h2 id=&#34;additional-testing-considerations&#34;&gt;Additional testing considerations&lt;/h2&gt;
&lt;p&gt;There are multiple scenarios where a different kind of test might give you a better insights on your system reliability. Consider implementing these testing strategies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/spike-testing/&#34;&gt;Spike tests&lt;/a&gt; before major events.&lt;/li&gt;
&lt;li&gt;Regular load tests at peak traffic levels.&lt;/li&gt;
&lt;li&gt;Tests that exceed your highest historical traffic by a safety margin.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These practices help ensure your system remains stable during unexpected traffic increases. Refer to 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/&#34;&gt;Testing guides&lt;/a&gt; for more details about all of the different performance testing strategies.&lt;/p&gt;
]]></content><description>&lt;h1 id="calculate-concurrent-users-for-load-tests">Calculate concurrent users for load tests&lt;/h1>
&lt;p>When planning load tests, determining the right number of concurrent users is crucial for creating realistic test scenarios. This guide explains how to use Google Analytics data to calculate concurrent user numbers and design effective load tests.&lt;/p></description></item><item><title>Load testing websites</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/load-testing-websites/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/load-testing-websites/</guid><content><![CDATA[&lt;h1 id=&#34;load-testing-websites&#34;&gt;Load testing websites&lt;/h1&gt;
&lt;p&gt;This doc explains some key concepts about load testing websites, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The difference between backend and frontend performance testing&lt;/li&gt;
&lt;li&gt;When to choose between protocol-based, browser-based, or hybrid scripts&lt;/li&gt;
&lt;li&gt;Recommended practices about how to load test websites&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All load tests try to simulate real user traffic to prevent failures, improve reliability, and release new code with confidence.
But your approach to load testing must adapt to the type of application you want to test.&lt;/p&gt;
&lt;p&gt;In this guide, learn about strategies to test websites, including specific recommendations for complex scenario scripting and test execution.&lt;/p&gt;
&lt;h2 id=&#34;load-testing-approaches&#34;&gt;Load testing approaches&lt;/h2&gt;
&lt;p&gt;When you approach the load test, first consider the following perspectives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Backend vs. frontend performance&lt;/li&gt;
&lt;li&gt;Protocol-based, browser-based, or hybrid load testing&lt;/li&gt;
&lt;li&gt;Component testing vs. end-to-end testing&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;backend-vs-frontend-performance&#34;&gt;Backend vs. frontend performance&lt;/h2&gt;
&lt;p&gt;Performance has a significant influence on the user experience of a website.&lt;/p&gt;
&lt;p&gt;For example, when users want to visualize or interact with some information, they expect the website to react quickly.&lt;/p&gt;
&lt;p&gt;To measure performance, testers often look at response times, which are affected by two main factors:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frontend performance&lt;/li&gt;
&lt;li&gt;Backend performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;frontend-performance&#34;&gt;Frontend performance&lt;/h3&gt;
&lt;p&gt;Frontend performance testing verifies application performance on the interface level, measuring round-trip metrics that consider how and when page elements appear on the screen. It&amp;rsquo;s concerned with the end-user experience of an application, usually involving a browser.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/frontend-rendering.png&#34;
  alt=&#34;Snapshots over time of webpage elements rendering on a browser&#34; width=&#34;1280&#34;
     height=&#34;370&#34;/&gt;&lt;/p&gt;
&lt;p&gt;Frontend performance testing excels at identifying issues on a micro level but does not expose issues in the underlying architecture of a system.&lt;/p&gt;
&lt;p&gt;Because it primarily measures a single user&amp;rsquo;s experience of the system, frontend performance testing tends to be easier to run on a small scale.
Frontend performance testing has metrics that are distinct from backend performance testing. Frontend performance tests for things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Whether the pages of the application are optimized to render quickly on a user&amp;rsquo;s screen&lt;/li&gt;
&lt;li&gt;How long it takes a user to interact with the UI elements of the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some concerns when doing this type of performance testing are its dependency on fully integrated environments and the cost of scaling. You can test frontend performance only once the application code and infrastructure have been integrated with a user interface. Tools to automate frontend testing are also inherently more resource-intensive, so they can be costly to run at scale and are not suitable for high load tests.&lt;/p&gt;
&lt;h3 id=&#34;backend-performance&#34;&gt;Backend performance&lt;/h3&gt;
&lt;p&gt;Backend performance testing targets the underlying application servers to see how they behave under production-like conditions. When it comes to websites, while frontend performance involves how assets included in the page are rendered, backend performance focuses on how those assets are processed by the application servers, served to users, and downloaded by browsers.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/backend-performance.png&#34;
  alt=&#34;Backend components&#34; width=&#34;1280&#34;
     height=&#34;500&#34;/&gt;&lt;/p&gt;
&lt;p&gt;Backend testing is broader in scope than frontend performance testing. API testing can be used to target specific components or integrated components, meaning that application teams have more flexibility and higher chances of finding performance issues earlier. Backend testing is less resource-intensive than frontend performance testing and is thus more suitable for generating high load.&lt;/p&gt;
&lt;h3 id=&#34;which-one-should-you-test&#34;&gt;Which one should you test?&lt;/h3&gt;
&lt;p&gt;It depends! Ideally, both.&lt;/p&gt;
&lt;p&gt;Frontend testing tools are executed on the client side and are limited in scope: they do not provide enough information about backend components for fine-tuning beyond the user interface.&lt;/p&gt;
&lt;p&gt;This limitation can lead to false confidence in overall application performance when the amount of traffic against an application increases. While the frontend component of response time remains more or less constant, the backend component of response time increases exponentially with the number of concurrent users:&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/frontend-backend-load-testing.png&#34;
  alt=&#34;Backend performance often contributes significantly to overall user experience&#34; width=&#34;1613&#34;
     height=&#34;724&#34;/&gt;&lt;/p&gt;
&lt;p&gt;Testing &lt;em&gt;only&lt;/em&gt; frontend performance ignores a large part of the application, one more susceptible to increased failures and performance bottlenecks at higher levels of load.&lt;/p&gt;
&lt;p&gt;Testing &lt;em&gt;only&lt;/em&gt; backend performance, on the other hand, ignores &amp;ldquo;the first mile&amp;rdquo; of user experience and breadth. Backend testing involves messaging at the protocol level rather than interacting with page elements the way a real user would. It verifies the foundation of an application rather than the highest layer of it that a user ultimately sees.&lt;/p&gt;
&lt;p&gt;Testing both frontend and backend performance leads to the best overall performance and user experience for your application. Ignoring one or the other exposes you to performance bottlenecks that significantly decrease a user&amp;rsquo;s satisfaction with your application.&lt;/p&gt;
&lt;p&gt;However, if your testing is smaller in scope, you can choose to focus on either frontend or backend with the goal of eventually building a test suite that encompasses both.&lt;/p&gt;
&lt;h2 id=&#34;component-testing-vs-end-to-end-testing&#34;&gt;Component testing vs. end-to-end testing&lt;/h2&gt;
&lt;p&gt;When you&amp;rsquo;re testing a web app, you may wonder whether how you structure your test scripts. Consider the following two methods.&lt;/p&gt;
&lt;h3 id=&#34;component-testing&#34;&gt;Component testing&lt;/h3&gt;
&lt;p&gt;One way you could load test a web app is to load test its components. Maybe you know there are issues with a specific functionality due to previous production issues, or maybe you&amp;rsquo;d like to target components that are business-critical to reduce risk exposure.&lt;/p&gt;
&lt;p&gt;In these cases, the type of test you write may:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;be protocol-based&lt;/li&gt;
&lt;li&gt;call only those API endpoints that are relevant&lt;/li&gt;
&lt;li&gt;have smaller or no sleep/think time&lt;/li&gt;
&lt;li&gt;be focused on eventually stress testing a component or service or finding its breaking point&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Testing in this way is more flexible. With protocol testing, you can specify the endpoints or servers you&amp;rsquo;d like to hit, and narrow your test&amp;rsquo;s target that way. You can test specific functionalities while skipping others that come chronologically before it in a standard user flow. You can more finely control the type of traffic that is generated. If you&amp;rsquo;re recreating a specific mix of requests, you can script those requests and reproduce them in a repeatable manner.&lt;/p&gt;
&lt;p&gt;Doing component testing for load may not always require that your script behave like an end user. In fact, it may be necessary to artificially inflate traffic to more quickly reproduce issues, or to deflate traffic to reduce the noise in the logs. By the nature of this type of testing, scripts don&amp;rsquo;t contain the full flow of a request that you would expect to see in production, so realism is not a priority.&lt;/p&gt;
&lt;h3 id=&#34;end-to-end-testing&#34;&gt;End-to-end testing&lt;/h3&gt;
&lt;p&gt;You could also do end-to-end testing against a web app. End-to-end testing seeks to replicate real user behaviour and track its effects across the entire stack. When doing end-to-end testing, you might:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;do protocol-level, browser-level, or hybrid load testing&lt;/li&gt;
&lt;li&gt;replicate the actions in a typical user flow&lt;/li&gt;
&lt;li&gt;think about the performance of the entire workflow as well as how long a request took to be processed by each component&lt;/li&gt;
&lt;li&gt;write scripts that access the application the same way users would, such as by browsing to the homepage before finding their way to other parts of the website&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This type of load testing is broader in scope than component testing, but shallower in terms of depth. With end-to-end testing, you get a better idea of the full user experience of the application as a whole. However, it can also be more complex to troubleshoot, as you have more components to monitor and more places to look for issues that are found.&lt;/p&gt;
&lt;p&gt;End-to-end testing is based on real user behavior, so it&amp;rsquo;s often important that end-to-end test scripts also be realistic.&lt;/p&gt;
&lt;h2 id=&#34;protocol-based-browser-based-or-hybrid-load-testing&#34;&gt;Protocol-based, browser-based, or hybrid load testing&lt;/h2&gt;
&lt;p&gt;The decision of whether to test the frontend, backend, or both will also affect the type of load testing you should carry out and the kind of scripts you should write.&lt;/p&gt;
&lt;h3 id=&#34;protocol-based-load-testing&#34;&gt;Protocol-based load testing&lt;/h3&gt;
&lt;p&gt;Protocol-based load testing verifies the backend performance of an application by simulating the requests underlying user actions. For websites, this commonly involves HTTP requests that bypass the user interface of your application and are sent directly to a server or application component.&lt;/p&gt;
&lt;p&gt;For example, a protocol-based load testing script might request all the resources on a webpage from the application servers, but those resources are merely downloaded. The response times reported by a purely protocol-based script do not include frontend metrics such as the time taken for images to render on a browser. The load is generated by simulating many requests sent to application servers.&lt;/p&gt;
&lt;p&gt;While protocol-based load testing may seem to lend itself better to component testing, you can also do end-to-end website testing with protocol-level scripts.&lt;/p&gt;
&lt;h4 id=&#34;sample-protocol-based-test-script&#34;&gt;Sample protocol-based test script&lt;/h4&gt;
&lt;p&gt;The following is an example of a protocol-based load testing script in k6 that fetches the homepage, along with resources embedded into the page.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;
import { sleep, check } from &amp;#39;k6&amp;#39;;

export function Homepage() {
  const params = {
    &amp;#39;sec-ch-ua&amp;#39;: &amp;#39;&amp;#34;Chromium&amp;#34;;v=&amp;#34;94&amp;#34;, &amp;#34;Google Chrome&amp;#34;;v=&amp;#34;94&amp;#34;, &amp;#34;;Not A Brand&amp;#34;;v=&amp;#34;99&amp;#34;&amp;#39;,
    &amp;#39;accept-encoding&amp;#39;: &amp;#39;gzip, deflate, br&amp;#39;,
    &amp;#39;accept-language&amp;#39;: &amp;#39;en-GB,en;q=0.9&amp;#39;,
  };

  // 01. Go to the homepage
  let responses = http.batch([
    [&amp;#39;GET&amp;#39;, &amp;#39;https://mywebsite.com/&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://mywebsite.com/style.min.css&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://website.com/header.png&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://website.com/polyfill.min.js&amp;#39;, params],
  ]);
  check(responses, {
    &amp;#39;Homepage loaded&amp;#39;: (r) =&amp;gt; JSON.stringify(r).includes(&amp;#39;Welcome to my site&amp;#39;),
  });

  sleep(4);

  // 02. View products
  responses = http.batch([
    [&amp;#39;GET&amp;#39;, &amp;#39;https://mywebsite.com/products&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://mywebsite.com/style.css&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://website.com/product1.jpg&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://website.com/product2.jpg&amp;#39;, params],
    [&amp;#39;GET&amp;#39;, &amp;#39;https://website.com/displaylist.js&amp;#39;, params],
  ]);
  check(responses, {
    &amp;#39;Products loaded&amp;#39;: (r) =&amp;gt; JSON.stringify(r).includes(&amp;#39;Add to Cart&amp;#39;),
  });

  sleep(1);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/test-authoring/create-tests-from-recordings/using-the-browser-recorder/&#34;&gt;Recording browser traffic&lt;/a&gt; helps you prototype to test websites on the protocol level.&lt;/p&gt;
&lt;h3 id=&#34;browser-based-load-testing&#34;&gt;Browser-based load testing&lt;/h3&gt;
&lt;p&gt;Browser-based load testing verifies the frontend performance of an application by simulating real users using a browser to access your website.&lt;/p&gt;
&lt;p&gt;For example, a browser-based load testing script might include instructions to navigate to a page, click on a button, and type out text on a form. Those user actions then trigger underlying requests on the protocol layer, but only user actions are scripted in browser-based testing.&lt;/p&gt;
&lt;p&gt;Unlike protocol-based load testing, browser-based load testing scripts generate load by starting multiple instances of browsers and interacting with your application the way real users would. Testing at the browser level can also be the only option for testing Single-Page Applications where a lot of the application logic is executed by client-side scripts.&lt;/p&gt;
&lt;p&gt;Scripting on the browser level usually requires the use of different tools from the ones used to test at the protocol level.
However, k6 now has a module called 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6-browser/&#34;&gt;k6 browser&lt;/a&gt; that allows the creation of browser-based test scripts alongside protocol-based ones.&lt;/p&gt;
&lt;h4 id=&#34;sample-browser-based-test-script&#34;&gt;Sample browser-based test script&lt;/h4&gt;
&lt;p&gt;The following is an example of a browser-based load testing script in k6 using the browser module on a dummy website. Instead of making an HTTP request, the script views the homepage, then looks for and clicks on a link to the product page.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;
&lt;!-- eslint-skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { browser } from &amp;#39;k6/browser&amp;#39;;

export default async function () {
  const page = await browser.newPage();

  // 01. Go to the homepage
  try {
    await page.goto(&amp;#39;https://mywebsite.com&amp;#39;);

    await page.waitForSelector(&amp;#39;p[class=&amp;#34;woocommerce-result-count&amp;#34;]&amp;#34;]&amp;#39;);
    await page.screenshot({ path: &amp;#39;screenshots/01_homepage.png&amp;#39; });

    await page.waitForTimeout(4000);

    // 02. View products
    const element = page.locator(
      &amp;#39;a[class=&amp;#34;woocommerce-LoopProduct-link woocommerce-loop-product__link&amp;#34;]&amp;#39;
    );
    await element.click();
    await page.waitForSelector(&amp;#39;button[name=&amp;#34;add-to-cart&amp;#34;]&amp;#39;);
    await page.screenshot({ path: &amp;#39;screenshots/02_view-product.png&amp;#39; });

    await page.waitForTimeout(1000);
  } finally {
    await page.close();
  }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h4 id=&#34;tips-for-writing-browser-level-scripts&#34;&gt;Tips for writing browser-level scripts&lt;/h4&gt;
&lt;p&gt;The following steps can help you get started with a browser-level test script.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Script user actions, not requests.&lt;/strong&gt; Determine what a user might do for a particular task, and script interactions with elements on the browser level. For example, script what buttons the user clicks on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Identify unique selectors.&lt;/strong&gt; Once you have identified which page elements a user interacts with, use the Element Inspector for DevTools in your browser to find a unique, static, and simple way to identify each element. The script needs selectors to find the right element to interact with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use elements to verify responses.&lt;/strong&gt; After every action, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-browser/locator/&#34;&gt;locators&lt;/a&gt; to search for elements on the page that you would expect to find. This verification helps ensure that the script has reached the expected page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Take screenshots for every action while debugging.&lt;/strong&gt; One of the advantages of browser-based testing is the ability to take screenshots. After every user interaction the script simulates, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-browser/page/&#34;&gt;page.screenshot&lt;/a&gt; to save a visual image of what the script encountered for later troubleshooting.&lt;/p&gt;
&lt;h3 id=&#34;hybrid-load-testing&#34;&gt;Hybrid load testing&lt;/h3&gt;
&lt;p&gt;Hybrid load testing is a combination of protocol-based and browser-based load testing. While you can use two tools or two scripts to execute different types of load tests (one protocol-based and one browser-based), having both types of tests executed by the same script and the same tool is ideal. Aggregating results between different tools can be difficult at best and inconsistent at worst.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/hybrid-testing.png&#34;
  alt=&#34;Hybrid of frontend and backend performance testing&#34; width=&#34;1280&#34;
     height=&#34;720&#34;/&gt;&lt;/p&gt;
&lt;p&gt;A best practice in hybrid load testing is to generate most of the load using the protocol-level test, and then to run a smaller number of the browser-level testing scripts. This approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reduces the number of load generators needed, since protocol-level testing requires fewer machines to generate the same load&lt;/li&gt;
&lt;li&gt;measures backend and frontend performance in the same test execution&lt;/li&gt;
&lt;li&gt;provides a single source of aggregated output at the end&lt;/li&gt;
&lt;li&gt;reduces complexity in the creation and maintenance of scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;scripting-considerations&#34;&gt;Scripting considerations&lt;/h2&gt;
&lt;p&gt;When you script a test for a website, consider these recommendations.&lt;/p&gt;
&lt;h3 id=&#34;consider-factors-that-affect-script-realism&#34;&gt;Consider factors that affect script realism&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Record your user journey.&lt;/strong&gt; Using the browser recorder can facilitate initial test script creation by capturing all the embedded resources on webpages. Check out the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/test-authoring/create-tests-from-recordings/&#34;&gt;Session Recording guide&lt;/a&gt; to learn more about how to auto-generate your load test from a user session.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Correlate data.&lt;/strong&gt; Recordings often don&amp;rsquo;t take into account dynamic values that are generated anew each time a request is made. Go through the recorded requests and determine whether you need to 
    &lt;a href=&#34;/docs/k6/v2.3.x/examples/correlation-and-dynamic-data/&#34;&gt;extract values from previous responses&lt;/a&gt; and use parameters in subsequent requests. This practice ensures your VUs behave more like real users would.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Include or exclude static resources.&lt;/strong&gt; Determine whether you should include or exclude static resources on pages such as images, JavaScript, etc. Consider including them if you want to measure overall user experience. Consider excluding them if you are using a Content Delivery Network (CDN) that is under a separate Service Level Agreement (SLA).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exclude third-party requests.&lt;/strong&gt; Don&amp;rsquo;t load test servers that you don&amp;rsquo;t own. Many applications make calls to third-party providers for authentication, social sharing, and marketing analytics. Disable these requests unless you have permission to include them in your tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use concurrent requests.&lt;/strong&gt; To mimic the way modern browsers download some requests in parallel, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-http/batch/&#34;&gt;batching&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Determine cache and cookie behaviour.&lt;/strong&gt; k6 automatically resets cookies between iterations, but you can also 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#no-cookies-reset&#34;&gt;change this behavior&lt;/a&gt; if maintaining cookies would be more realistic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use dynamic think time and pacing.&lt;/strong&gt; Consider adding varying 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6/sleep/&#34;&gt;delays&lt;/a&gt; so you don&amp;rsquo;t artificially stagger a script with completely uniform delays.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use test data.&lt;/strong&gt; Real users typically don&amp;rsquo;t search for or submit the same data repeatedly. Consider adding a 
    &lt;a href=&#34;/docs/k6/v2.3.x/examples/data-parameterization/&#34;&gt;test data file&lt;/a&gt; for the script to iterate through.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Model test parameters and load profile after production.&lt;/strong&gt; In k6, you can use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/how-to/&#34;&gt;test options&lt;/a&gt; to determine the exact shape and profile of your load test script. Select the appropriate 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/executors/&#34;&gt;executors&lt;/a&gt; for the job.&lt;/p&gt;
&lt;h3 id=&#34;create-a-reusable-framework&#34;&gt;Create a reusable framework&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use tags and groups.&lt;/strong&gt; Organizing requests by 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/tags-and-groups/&#34;&gt;tagging and grouping them&lt;/a&gt; helps you consolidate like metrics and makes your test scripts more understandable by others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use scenarios.&lt;/strong&gt; When combining protocol-based and browser-based tests, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/&#34;&gt;scenarios&lt;/a&gt; to independently control their test parameters and 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/scenarios/executors/&#34;&gt;executors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Modularize scripts.&lt;/strong&gt; Use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/modules/&#34;&gt;modules&lt;/a&gt; to separate and organize functions for protocol-level testing and browser-level testing, and then use a test runner script to execute them. This approach means different scripts can be versioned and changed without affecting each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Integrate your tests into your CI pipeline.&lt;/strong&gt; Adopting a &amp;ldquo;tests as code&amp;rdquo; approach enables you to tie your load tests more closely into your project&amp;rsquo;s existing CI/CD processes, helping you get the most value out of every test.&lt;/p&gt;
&lt;h3 id=&#34;test-with-thresholds-in-mind&#34;&gt;Test with thresholds in mind&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Create thresholds for both types of testing.&lt;/strong&gt; Some browser-level and protocol-level metrics cannot be combined because they don&amp;rsquo;t measure the same thing. Set 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/&#34;&gt;thresholds&lt;/a&gt; for relevant metrics from both the browser-level script and the protocol-level one.&lt;/p&gt;
&lt;h3 id=&#34;when-possible-use-hybrid-load-testing&#34;&gt;When possible, use hybrid load testing&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Use protocol-based scripts to generate majority of the load.&lt;/strong&gt; When writing the test scenario, use the protocol-based requests to simulate most of the traffic and use fewer VUs for the browser-based requests. Relying on the protocol-level traffic helps keep resource utilization on load generators down.&lt;/p&gt;
&lt;h2 id=&#34;execution-considerations&#34;&gt;Execution considerations&lt;/h2&gt;
&lt;p&gt;When you run tests, consider the test environment and the load generator location.&lt;/p&gt;
&lt;h3 id=&#34;run-your-tests-in-the-appropriate-environment&#34;&gt;Run your tests in the appropriate environment&lt;/h3&gt;
&lt;p&gt;Testing in pre-production environments and in production environments both add value.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing in pre-production environments&lt;/strong&gt; (staging, test, system integration testing, user acceptance testing, and production replica environments) enables you to identify performance defects early, which can save a lot of time and effort (and reputation) later on. Running in test environments also means you can often afford to be more aggressive with your tests. However, it&amp;rsquo;s also more critical to get your load profiles right, and the test results you get may not necessarily apply to production.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Testing in production&lt;/strong&gt; yields the most accurate results, but it&amp;rsquo;s also more risky. Often, testing production is the only feasible alternative. You can reduce the risk of impact to real customers while testing in production by using lower levels of load when running load tests during peak hours, schedule tests for off-peak hours, choosing load test types that are less risky, using techniques like &lt;a href=&#34;/docs/grafana-cloud/testing/synthetic-monitoring/create-checks/checks/k6/&#34;&gt;synthetic monitoring&lt;/a&gt; that generate less traffic, using real user monitoring tools to get snapshots of user performance at load, and ensuring that your observability stack is working at peak efficiency.&lt;/p&gt;
&lt;h3 id=&#34;run-tests-where-your-customers-are&#34;&gt;Run tests where your customers are&lt;/h3&gt;
&lt;p&gt;The location of the load generator(s), where the traffic is coming &lt;em&gt;from&lt;/em&gt;, can also have an impact on your test results. The question is: where are your end users located?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On-premises load testing&lt;/strong&gt; can be ideal when testing early on in the development a website, or when there are machines that can be repurposed as load generators. However, testing entirely from within a corporate network may also yield false positives, in that response times reported are significantly lower than if the same application servers were accessed from across the country.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Load testing on the cloud&lt;/strong&gt; is an essential part of the testing strategy for many public-facing websites. Using load generators on the cloud gives you access to test in different states and geographical countries, creating a mix of load generators proportional to your users&amp;rsquo; locations. Cloud load generators are easier to provision and cheaper to maintain in the long run than on-premise ones. Load testing on the cloud can help you include the effects of network latency in your tests and yield results that are more realistic.&lt;/p&gt;
&lt;h2 id=&#34;recommendations&#34;&gt;Recommendations&lt;/h2&gt;
&lt;p&gt;Here are some recommendations to help you plan, script, and execute your load tests for websites.&lt;/p&gt;
&lt;p&gt;If you want to test the last-mile user experience of your website:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;focus on &lt;strong&gt;frontend performance&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;write &lt;strong&gt;browser-based test scripts&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;and consider doing more realistic &lt;strong&gt;end-to-end&lt;/strong&gt; tests of the user flow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you want to test the underlying infrastructure of your website:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;focus on &lt;strong&gt;backend performance&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;write &lt;strong&gt;protocol-based test scripts&lt;/strong&gt;,&lt;/li&gt;
&lt;li&gt;and consider starting with &lt;strong&gt;component testing&lt;/strong&gt; and then gradually increasing the scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your website is meant for internal or has limited access:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;on-premise load generators&lt;/strong&gt; located within the network that most of your users access the website from.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your website is external and public-facing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;strong&gt;cloud load generators&lt;/strong&gt; in the load zones where your users reside.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test in &lt;strong&gt;pre-production environments&lt;/strong&gt; when you can, but also consider &lt;strong&gt;testing in production&lt;/strong&gt; in a limited capacity.&lt;/p&gt;
&lt;p&gt;Load testing websites can be complex due to the number of viable testing approaches available, the scope for performance testing, and the potential effects of releasing undertested code. By following the recommendations we&amp;rsquo;ve put forward here, you can tailor your testing more closely to your objectives.&lt;/p&gt;
&lt;h2 id=&#34;read-more&#34;&gt;Read more&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6-browser/&#34;&gt;Browser testing with k6 browser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/&#34;&gt;Load test types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/test-authoring/create-tests-from-recordings/&#34;&gt;Session recording guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://k6.io/blog/monthly-visits-concurrent-users&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Determining concurrent users in your load tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/examples/correlation-and-dynamic-data/&#34;&gt;Data correlation in your test script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="load-testing-websites">Load testing websites&lt;/h1>
&lt;p>This doc explains some key concepts about load testing websites, including:&lt;/p>
&lt;ul>
&lt;li>The difference between backend and frontend performance testing&lt;/li>
&lt;li>When to choose between protocol-based, browser-based, or hybrid scripts&lt;/li>
&lt;li>Recommended practices about how to load test websites&lt;/li>
&lt;/ul>
&lt;p>All load tests try to simulate real user traffic to prevent failures, improve reliability, and release new code with confidence.
But your approach to load testing must adapt to the type of application you want to test.&lt;/p></description></item><item><title>Performance testing gRPC services</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/performance-testing-grpc-services/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/performance-testing-grpc-services/</guid><content><![CDATA[&lt;h1 id=&#34;performance-testing-grpc-services&#34;&gt;Performance testing gRPC services&lt;/h1&gt;
&lt;p&gt;In this guide, you&amp;rsquo;ll learn the basics of gRPC and how to write a gRPC performance test using k6.&lt;/p&gt;
&lt;h2 id=&#34;before-you-begin&#34;&gt;Before you begin&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/install-k6/&#34;&gt;Install k6 v0.29&lt;/a&gt; or higher.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;what-is-grpc&#34;&gt;What is gRPC&lt;/h2&gt;
&lt;p&gt;gRPC is a lightweight open source RPC framework. It was originally developed by Google, with 1.0 being released in August 2016.&lt;/p&gt;
&lt;p&gt;In comparison to JSON, which is transmitted as human-readable text, gRPC is binary, making it both faster to transmit and more compact. For chatty, distributed systems, these improvements accumulate quickly, making the difference not only noticeable in benchmarks but also by the end-user.&lt;/p&gt;
&lt;h3 id=&#34;api-types&#34;&gt;API types&lt;/h3&gt;
&lt;p&gt;gRPC supports four different types of RPCs: unary, server streaming, client streaming, and bi-directional streaming. In reality, the messages are multiplexed using the same connection, but in the spirit of keeping things simple and approachable, this isn&amp;rsquo;t illustrated in the gRPC service model diagrams below.&lt;/p&gt;
&lt;h4 id=&#34;unary&#34;&gt;Unary&lt;/h4&gt;
&lt;p&gt;Unary calls work the same way as a regular function call: a single request is sent to the server which in turn replies with a single response.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/diagram-grpc-unary.png&#34;
  alt=&#34;Unary call&#34; width=&#34;800&#34;
     height=&#34;400&#34;/&gt;&lt;/p&gt;
&lt;h4 id=&#34;server-streaming&#34;&gt;Server streaming&lt;/h4&gt;
&lt;p&gt;In server streaming mode, the client sends a single request to the server, which in turn replies with multiple responses.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/diagram-grpc-server.png&#34;
  alt=&#34;Server streaming&#34; width=&#34;800&#34;
     height=&#34;400&#34;/&gt;&lt;/p&gt;
&lt;h4 id=&#34;client-streaming&#34;&gt;Client streaming&lt;/h4&gt;
&lt;p&gt;The client streaming mode is the opposite of the server streaming mode. The client sends multiple requests to the server, which in turn replies with a single response.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/diagram-grpc-client.png&#34;
  alt=&#34;Client streaming&#34; width=&#34;800&#34;
     height=&#34;400&#34;/&gt;&lt;/p&gt;
&lt;h4 id=&#34;bi-directional-streaming&#34;&gt;Bi-directional streaming&lt;/h4&gt;
&lt;p&gt;In bi-directional streaming mode, both the client and the server may send multiple messages.&lt;/p&gt;
&lt;p&gt;&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/diagram-grpc-bidirectional.png&#34;
  alt=&#34;Bi-directional streaming&#34; width=&#34;800&#34;
     height=&#34;400&#34;/&gt;&lt;/p&gt;
&lt;h3 id=&#34;the-proto-definition&#34;&gt;The &lt;code&gt;.proto&lt;/code&gt; definition&lt;/h3&gt;
&lt;p&gt;The messages and services used for gRPC are described in .proto files, containing &lt;a href=&#34;https://en.wikipedia.org/wiki/Protocol_Buffers&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Protocol buffers&lt;/a&gt;, or protobuf, definitions.&lt;/p&gt;
&lt;p&gt;The definition file is then used to generate code which can be used by both senders and receivers as a contract for communicating through these messages and services. As the binary format used by gRPC lacks any self-describing properties, this is the only way for senders and receivers to know how to interpret the messages.&lt;/p&gt;
&lt;p&gt;For this guide, you can use the &lt;code&gt;quickpizza.proto&lt;/code&gt; definition available for download on the &lt;a href=&#34;https://raw.githubusercontent.com/grafana/quickpizza/refs/heads/main/proto/quickpizza.proto&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;QuickPizza demo application GitHub repository&lt;/a&gt;. For details on how to build your own gRPC proto definition, refer to &lt;a href=&#34;https://grpc.io/docs/what-is-grpc/core-concepts/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;the official gRPC docs&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;protobuf&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-protobuf&#34;&gt;syntax = &amp;#34;proto3&amp;#34;;
option go_package = &amp;#34;pkg/grpc/quickpizza&amp;#34;;
package quickpizza;

service GRPC {
    rpc Status(StatusRequest) returns (StatusResponse) {}
    rpc RatePizza(PizzaRatingRequest) returns (PizzaRatingResponse) {}
}

message StatusRequest {
}

message StatusResponse {
    bool ready = 1;
}

message PizzaRatingRequest {
    repeated string ingredients = 1;
    string dough = 2;
}

message PizzaRatingResponse {
    int32 stars_rating = 1;
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;write-a-grpc-performance-test-with-k6&#34;&gt;Write a gRPC performance test with k6&lt;/h2&gt;
&lt;p&gt;Starting with k6 v0.29.0, you can use a built-in module for gRPC communication. You can find more details for all the methods available on the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-net-grpc/&#34;&gt;k6/net/grpc module documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;create-the-test&#34;&gt;Create the test&lt;/h3&gt;
&lt;p&gt;The gRPC module is a separate module, available from your test script as &lt;code&gt;k6/net/grpc&lt;/code&gt;. Before you can use it, you first have to create an instance of the client. Instantiating the client, as well as the &lt;code&gt;load()&lt;/code&gt; function, is only available during test initialization, that is, directly in the global scope.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import grpc from &amp;#39;k6/net/grpc&amp;#39;;
import { check, sleep } from &amp;#39;k6&amp;#39;;

const client = new grpc.Client();&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Next, load a &lt;code&gt;.proto&lt;/code&gt; definition applicable to the system under test. For the purpose of this article, you can use &lt;a href=&#34;grpc-quickpizza.grafana.com:443&#34;&gt;QuickPizza&lt;/a&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The QuickPizza gRPC service URL grpc-quickpizza.grafana.com:443 returns a 464 HTTP status code if you try to access it in your browser. However, you can still use it in your k6 test scripts, and the gRPC functionality works correctly.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;load()&lt;/code&gt; function takes two arguments, the first one being an array of paths to search for proto files, and the second being the name of the file to load.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import grpc from &amp;#39;k6/net/grpc&amp;#39;;
import { check, sleep } from &amp;#39;k6&amp;#39;;

// Download quickpizza.proto for grpc-quickpizza.grafana.com, located at:
// https://raw.githubusercontent.com/grafana/quickpizza/refs/heads/main/proto/quickpizza.proto
// and put it in the same folder as this script.
const client = new grpc.Client();
client.load([&amp;#39;definitions&amp;#39;], &amp;#39;quickpizza.proto&amp;#39;);&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;After that&amp;rsquo;s done, add the rest of the test that calls the gRPC service.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import grpc from &amp;#39;k6/net/grpc&amp;#39;;
import { check, sleep } from &amp;#39;k6&amp;#39;;

// Download quickpizza.proto for grpc-quickpizza.grafana.com, located at:
// https://raw.githubusercontent.com/grafana/quickpizza/refs/heads/main/proto/quickpizza.proto
// and put it in the same folder as this script.
const client = new grpc.Client();
client.load(null, &amp;#39;quickpizza.proto&amp;#39;);

export default () =&amp;gt; {
  client.connect(&amp;#39;grpc-quickpizza.grafana.com:443&amp;#39;, {
    // plaintext: false
  });

  const data = { ingredients: [&amp;#39;Cheese&amp;#39;], dough: &amp;#39;Thick&amp;#39; };
  const response = client.invoke(&amp;#39;quickpizza.GRPC/RatePizza&amp;#39;, data);

  check(response, {
    &amp;#39;status is OK&amp;#39;: (r) =&amp;gt; r &amp;amp;&amp;amp; r.status === grpc.StatusOK,
  });

  console.log(JSON.stringify(response.message));

  client.close();
  sleep(1);
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;This test script does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;First, it uses the &lt;code&gt;.connect()&lt;/code&gt; function to connect to the system under test. By default, the client will set &lt;code&gt;plaintext&lt;/code&gt; to false, only allowing you to use encrypted connections. If you, for any reason, need to connect to a server that lacks SSL/TLS, just flip this setting to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The script then continues by creating the object you want to send to the remote procedure you&amp;rsquo;re invoking. In the case of &lt;code&gt;RatePizza&lt;/code&gt;, the object includes the ingredients for the pizza, and the type of dough.&lt;/li&gt;
&lt;li&gt;Next, it invokes the remote procedure, using the syntax &lt;code&gt;&amp;lt;package&amp;gt;.&amp;lt;service&amp;gt;/&amp;lt;procedure&amp;gt;&lt;/code&gt;, as described in the proto file. This call is made synchronously, with a default timeout of 60000 ms (60 seconds). To change the timeout, add the key &lt;code&gt;timeout&lt;/code&gt; to the config object of &lt;code&gt;.connect()&lt;/code&gt; with the duration as the value, for instance &lt;code&gt;&#39;2s&#39;&lt;/code&gt; for 2 seconds.&lt;/li&gt;
&lt;li&gt;After k6 receives a response from the server, the script checks to make sure the procedure executed successfully. The gRPC module includes constants for this comparison which are listed here. Comparing the response status with &lt;code&gt;grpc.StatusOK&lt;/code&gt;, which is &lt;code&gt;200 OK&lt;/code&gt; just like for HTTP/1.1 communication, ensures the call was completed successfully.&lt;/li&gt;
&lt;li&gt;The script then logs the message in the response, closes the client connection, and sleeps for a second.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;run-the-test&#34;&gt;Run the test&lt;/h3&gt;
&lt;p&gt;You can execute the test just like any other test by using the &lt;code&gt;k6 run&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;$ k6 run grpc-example.js

         /\      Grafana   /‾‾/
    /\  /  \     |\  __   /  /
   /  \/    \    | |/ /  /   ‾‾\
  /          \   |   (  |  (‾)  |
 / __________ \  |_|\_\  \_____/

     execution: local
        script: grpc-example.js
        output: -

     scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
              * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)

INFO[0000] {&amp;#34;starsRating&amp;#34;:3}                             source=console

  █ TOTAL RESULTS

    checks_total.......................: 1       0.86/s
    checks_succeeded...................: 100.00% 1 out of 1
    checks_failed......................: 0.00%   0 out of 1

    ✓ status is OK

    GRPC
    grpc_req_duration..................: avg=33.75ms min=33.75ms med=33.75ms max=33.75ms p(90)=33.75ms p(95)=33.75ms

    EXECUTION
    iteration_duration.................: avg=1.16s   min=1.16s   med=1.16s   max=1.16s   p(90)=1.16s   p(95)=1.16s
    iterations.........................: 1       0.86/s
    vus................................: 1       min=1      max=1
    vus_max............................: 1       min=1      max=1

    NETWORK
    data_received......................: 4.1 kB  3.6 kB/s
    data_sent..........................: 762 B   656 B/s


running (00m01.2s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m01.2s/10m0s  1/1 iters, 1 per VU&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;From the output, you can check that the script is working and that the server indeed responds with a rating for the type of pizza supplied in the request body. You can also see that the &lt;code&gt;check&lt;/code&gt; was successful, meaning the server responded with &lt;code&gt;200 OK&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&#34;summary&#34;&gt;Summary&lt;/h2&gt;
&lt;p&gt;In this article, you&amp;rsquo;ve gone through some of the fundamentals of gRPC and how it works. You also had a look at the k6 gRPC client and created a working test script demonstrating its functionality.&lt;/p&gt;
&lt;h2 id=&#34;additional-resources&#34;&gt;Additional resources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-net-grpc/&#34;&gt;k6 gRPC Module API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;grpc-quickpizza.grafana.com:443&#34;&gt;k6 QuickPizza: A demo service for gRPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://grpc.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;The official website of the gRPC project&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/grpc/grpc/tree/master/examples&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Examples from the gRPC repository on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="performance-testing-grpc-services">Performance testing gRPC services&lt;/h1>
&lt;p>In this guide, you&amp;rsquo;ll learn the basics of gRPC and how to write a gRPC performance test using k6.&lt;/p>
&lt;h2 id="before-you-begin">Before you begin&lt;/h2>
&lt;ul>
&lt;li>
&lt;a href="/docs/k6/v2.3.x/set-up/install-k6/">Install k6 v0.29&lt;/a> or higher.&lt;/li>
&lt;/ul>
&lt;h2 id="what-is-grpc">What is gRPC&lt;/h2>
&lt;p>gRPC is a lightweight open source RPC framework. It was originally developed by Google, with 1.0 being released in August 2016.&lt;/p></description></item><item><title>Running distributed tests</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/running-distributed-tests/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/running-distributed-tests/</guid><content><![CDATA[&lt;h1 id=&#34;running-distributed-tests&#34;&gt;Running distributed tests&lt;/h1&gt;
&lt;p&gt;It has already been established that k6 can 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/running-large-tests/&#34;&gt;run large load tests&lt;/a&gt; from a single instance, but what about &lt;em&gt;multiple instances running a single test&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Several reasons why you may wish to run a distributed test include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#system-under-test&#34;&gt;system under test&lt;/a&gt; (SUT) should be accessed from multiple IP addresses.&lt;/li&gt;
&lt;li&gt;A fully optimized node cannot produce the load required by your extremely large test.&lt;/li&gt;
&lt;li&gt;Kubernetes is already your preferred operations environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For scenarios such as these, we&amp;rsquo;ve created the &lt;a href=&#34;https://github.com/grafana/k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6-operator&lt;/a&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Refer to &lt;a href=&#34;/docs/k6/latest/set-up/set-up-distributed-k6/usage/k6-operator-to-gck6/&#34;&gt;Use the k6 Operator with Grafana Cloud k6&lt;/a&gt; on how to integrate the k6 Operator with Grafana Cloud, including creating &lt;a href=&#34;/docs/grafana-cloud/testing/k6/author-run/private-load-zone-v2/&#34;&gt;Private Load Zones&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;introducing-k6-operator&#34;&gt;Introducing k6-operator&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/grafana/k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6-operator&lt;/a&gt; is an implementation of the 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#operator-pattern&#34;&gt;operator pattern&lt;/a&gt; in Kubernetes, defining 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#custom-resource&#34;&gt;custom resources&lt;/a&gt; in Kubernetes.
The intent is to automate tasks that a &lt;em&gt;human operator&lt;/em&gt; would normally do; tasks like provisioning new application components, changing configurations, or resolving run-time issues.&lt;/p&gt;
&lt;p&gt;The k6-operator defines the custom &lt;code&gt;TestRun&lt;/code&gt; resource type and listens for changes to, or creation of, &lt;code&gt;TestRun&lt;/code&gt; objects.
Each &lt;code&gt;TestRun&lt;/code&gt; object references a k6 test script, configures the environment, and specifies the number of instances, as &lt;code&gt;parallelism&lt;/code&gt;, for a test run.
Once a change is detected, the operator will react by modifying the cluster state, spinning up k6 test jobs as needed.&lt;/p&gt;
&lt;h2 id=&#34;get-started-with-k6-operator&#34;&gt;Get started with k6-operator&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s walk through the process for getting started with the k6-operator.
The only requirement being access to a Kubernetes cluster and having the appropriate access and tooling.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;This process can be performed on a local Kubernetes cluster running within &lt;a href=&#34;https://docs.docker.com/get-docker/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Docker&lt;/a&gt;!
Using &lt;a href=&#34;https://kind.sigs.k8s.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;kind&lt;/a&gt; or &lt;a href=&#34;https://k3d.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k3d&lt;/a&gt; are awesome options to experiment with the process.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#introducing-k6-operator&#34;&gt;Introducing k6-operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#get-started-with-k6-operator&#34;&gt;Get started with k6-operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#1-install-the-operator&#34;&gt;1. Install the operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#2-create-a-test-script&#34;&gt;2. Create a test script&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#3-add-test-scripts&#34;&gt;3. Add test scripts&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#add-as-a-configmap&#34;&gt;Add as a ConfigMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#add-inside-a-persistentvolume&#34;&gt;Add inside a PersistentVolume&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#4-create-a-custom-resource&#34;&gt;4. Create a custom resource&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;#script-in-a-configmap&#34;&gt;Script in a ConfigMap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#script-in-a-persistentvolume&#34;&gt;Script in a PersistentVolume&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#configure-the-environment&#34;&gt;Configure the environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#change-command-line-arguments&#34;&gt;Change command-line arguments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#5-run-your-test&#34;&gt;5. Run your test&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#6-when-things-go-wrong&#34;&gt;6. When things go wrong&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;#see-also&#34;&gt;See also&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;1-install-the-operator&#34;&gt;1. Install the operator&lt;/h2&gt;
&lt;p&gt;The first step to running distributed tests in Kubernetes is to install the operator if not already installed in the cluster.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Besides privileged access to a Kubernetes cluster, installation will require that the system performing the installation has the following tools installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://kubernetes.io/docs/tasks/tools/#kubectl&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Kubectl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Ensure that your &lt;code&gt;kubectl&lt;/code&gt; tool is set for the appropriate Kubernetes cluster.&lt;/p&gt;
&lt;p&gt;Then, from your command-line, execute the following:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;curl https://raw.githubusercontent.com/grafana/k6-operator/main/bundle.yaml | kubectl apply -f -&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Refer to &lt;a href=&#34;/docs/k6/latest/set-up/set-up-distributed-k6/install-k6-operator/&#34;&gt;Install k6 Operator&lt;/a&gt; for other installation methods.&lt;/p&gt;
&lt;p&gt;By default, the operator will be installed into a new namespace, &lt;code&gt;k6-operator-system&lt;/code&gt;.
You can verify the successful installation by listing available resources within the namespace:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl get pod -n k6-operator-system&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;After a few moments, your resulting status should become &lt;code&gt;Running&lt;/code&gt; as shown below:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;NAME                                              READY   STATUS    RESTARTS   AGE
k6-operator-controller-manager-7664957cf7-llw54   2/2     Running   0          160m&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You are now ready to start create and execute test scripts!&lt;/p&gt;
&lt;h2 id=&#34;2-create-a-test-script&#34;&gt;2. Create a test script&lt;/h2&gt;
&lt;p&gt;Creating k6 test scripts for Kubernetes is no different from creating the script for the command-line.
If you haven’t already created test cases for your system, then we suggest having a read through one of our guides for creating tests for websites and APIs/microservices:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/load-testing-websites/&#34;&gt;Website testing guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/api-load-testing/&#34;&gt;API testing guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, it is advised to start small and expand on your scripts over iterations.
So let&amp;rsquo;s start simple and create a &lt;code&gt;test.js&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import http from &amp;#39;k6/http&amp;#39;;
import { sleep } from &amp;#39;k6&amp;#39;;

export const options = {
  vus: 10,
  duration: &amp;#39;10s&amp;#39;,
};

export default function () {
  http.get(&amp;#39;https://test.k6.io/&amp;#39;);
  sleep(1);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;While creating scripts, 
    &lt;a href=&#34;/docs/k6/v2.3.x/get-started/running-k6/#running-local-tests&#34;&gt;run them locally&lt;/a&gt; before publishing to your cluster.
This can give you immediate feedback if you have errors in your script.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Let&amp;rsquo;s go ahead and verify our script is valid by performing a brief test:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;k6 run test.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;We should see a successful execution and resulting output summary.&lt;/p&gt;
&lt;h2 id=&#34;3-add-test-scripts&#34;&gt;3. Add test scripts&lt;/h2&gt;
&lt;p&gt;In order for your test scripts to be run, they must be published into your cluster.
Two main methods for this are using &lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/configmap/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;ConfigMap&lt;/a&gt; or &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PersistentVolume&lt;/a&gt; resources.&lt;/p&gt;
&lt;h3 id=&#34;add-as-a-configmap&#34;&gt;Add as a ConfigMap&lt;/h3&gt;
&lt;p&gt;Using a &lt;code&gt;ConfigMap&lt;/code&gt; is a quick and straightforward mechanism for adding your test scripts to Kubernetes.
The &lt;code&gt;kubectl&lt;/code&gt; tool provides a convenient method to create a new &lt;code&gt;ConfigMap&lt;/code&gt; from a local script.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create our ConfigMap as &lt;code&gt;my-test&lt;/code&gt; with the content of our &lt;code&gt;test.js&lt;/code&gt; script we created in the previous step:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl create configmap my-test --from-file test.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;Limitations exist on how large your test script can be when deployed within a &lt;code&gt;ConfigMap&lt;/code&gt;.
Kubernetes imposes a size limit of 1,048,576 bytes (1 MiB) for the data, therefore if your test scripts exceed this limit, you&amp;rsquo;ll need to mount a &lt;code&gt;PersistentVolume&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Check the &lt;a href=&#34;https://kubernetes.io/docs/concepts/configuration/configmap/#motivation&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;motivations&lt;/a&gt; for when you should use a &lt;code&gt;ConfigMap&lt;/code&gt; versus a &lt;code&gt;PersistentVolume&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;You should see confirmation with &lt;code&gt;configmap/my-test created&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;add-inside-a-persistentvolume&#34;&gt;Add inside a PersistentVolume&lt;/h3&gt;
&lt;p&gt;Setting up a &lt;code&gt;PersistentVolume&lt;/code&gt; is beyond the scope of this guide, but enables access to a shared filesystem from your Kubernetes cluster via &lt;code&gt;PersistentVolumeClaim&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When using this option, organize your test scripts in the applicable filesystem just as you would locally.
This mechanism is ideal when breaking up monolithic scripts into reusable 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/modules/&#34;&gt;modules&lt;/a&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Organizing your test scripts was part of the discussion during &lt;a href=&#34;https://www.youtube.com/watch?v=zDtEzp_JUOE&amp;amp;utm=k6-guides&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;episode #76&lt;/a&gt; of k6 Office Hours.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;



&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;When using a &lt;code&gt;PersistentVolume&lt;/code&gt;, the operator will expect all test scripts to be contained within a directory named &lt;code&gt;/test/&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;To learn more about creating &lt;code&gt;PersistentVolume&lt;/code&gt; and &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; resources, review the &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Kubernetes documentation&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;4-create-a-custom-resource&#34;&gt;4. Create a custom resource&lt;/h2&gt;
&lt;p&gt;During &lt;a href=&#34;#1-install-the-operator&#34;&gt;installation&lt;/a&gt;, the &lt;code&gt;TestRun&lt;/code&gt; &lt;a href=&#34;https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Custom Resource definition&lt;/a&gt; was added to the Kubernetes API.
The data we provide in the custom resource &lt;code&gt;TestRun&lt;/code&gt; object should contain all the information necessary for the k6-operator to start a distributed load test.&lt;/p&gt;
&lt;p&gt;Specifically, the main elements defined within the &lt;code&gt;TestRun&lt;/code&gt; object relate to the name and location of the test script to run, and the amount of 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#parallelism&#34;&gt;parallelism&lt;/a&gt; to utilize.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The &lt;code&gt;TestRun&lt;/code&gt; custom resource provides many configuration options to control the initialization and execution of tests.
For the full listing of possible options, please refer to the project &lt;a href=&#34;https://github.com/grafana/k6-operator/blob/main/config/crd/bases/k6.io_testruns.yaml&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;source&lt;/a&gt; and &lt;a href=&#34;https://github.com/grafana/k6-operator/blob/main/README.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;README&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;The following examples will show some common variations for the custom resource:&lt;/p&gt;
&lt;h3 id=&#34;script-in-a-configmap&#34;&gt;Script in a ConfigMap&lt;/h3&gt;
&lt;p&gt;When the test script to be executed is contained within a &lt;code&gt;ConfigMap&lt;/code&gt; resource, we specify the script details within the &lt;code&gt;configMap&lt;/code&gt; block of 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#yaml&#34;&gt;YAML&lt;/a&gt;.
The &lt;code&gt;name&lt;/code&gt; is the name of the ConfigMap and the &lt;code&gt;file&lt;/code&gt; is the key-value for the entry.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s create the file &lt;code&gt;run-k6-from-configmap.yaml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: run-k6-from-configmap
spec:
  parallelism: 4
  script:
    configMap:
      name: my-test
      file: test.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Recall when the script was &lt;a href=&#34;#add-as-a-configmap&#34;&gt;added as a ConfigMap&lt;/a&gt; for our configuration values.
We created the ConfigMap named &lt;code&gt;my-test&lt;/code&gt;.
The test script content was added to the map using the filename as the key-value, therefore the &lt;code&gt;file&lt;/code&gt; value is &lt;code&gt;test.js&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The amount of &lt;code&gt;parallelism&lt;/code&gt; is up to you; how many pods do you want to split the test amongst?
The operator will split the workload between the pods using 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#execution-segment&#34;&gt;execution segments&lt;/a&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;It is important that the &lt;code&gt;ConfigMap&lt;/code&gt; and &lt;code&gt;CustomResource&lt;/code&gt; are created in the same &lt;a href=&#34;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Namespace&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h3 id=&#34;script-in-a-persistentvolume&#34;&gt;Script in a PersistentVolume&lt;/h3&gt;
&lt;p&gt;If the test script to be executed is contained within a &lt;code&gt;PersistentVolume&lt;/code&gt;, creation of a &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PersistentVolumeClaim&lt;/a&gt; will be required.
We won&amp;rsquo;t go into the details of PersistentVolumes and PersistentVolumeClaims, but to learn more, you should review the &lt;a href=&#34;https://kubernetes.io/docs/concepts/storage/persistent-volumes/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Kubernetes documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Assume we&amp;rsquo;ve created a &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; named &lt;code&gt;my-volume-claim&lt;/code&gt; against a &lt;code&gt;PersistentVolume&lt;/code&gt; containing the test script &lt;code&gt;/test/test.js&lt;/code&gt;, we can create the file &lt;code&gt;run-k6-from-volume.yaml&lt;/code&gt; with the following content:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: run-k6-from-volume
spec:
  parallelism: 4
  script:
    volumeClaim:
      name: my-volume-claim
      # File is relative to /test/ directory within volume
      file: test.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-caution&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Caution&lt;/p&gt;&lt;p&gt;It is important that the &lt;code&gt;PersistentVolumeClaim&lt;/code&gt; and &lt;code&gt;CustomResource&lt;/code&gt; are created in the same &lt;a href=&#34;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Namespace&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h3 id=&#34;configure-the-environment&#34;&gt;Configure the environment&lt;/h3&gt;
&lt;p&gt;Not everything should be included directly in your scripts.
Well written scripts will allow for variability to support multiple scenarios and to avoid hard-coding values that tend to change.
These could be anything from passwords to target urls, in addition to system options.&lt;/p&gt;
&lt;p&gt;We can pass this data as 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#environment-variables&#34;&gt;environment variables&lt;/a&gt; for use with each pod executing your script.
This can be defined explicitly within the &lt;code&gt;TestRun&lt;/code&gt; resource, or by referencing a &lt;code&gt;ConfigMap&lt;/code&gt; or &lt;code&gt;Secret&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: run-k6-with-vars
spec:
  parallelism: 4
  script:
    configMap:
      name: my-test
      file: test.js
  runner:
    env:
      - name: MY_CUSTOM_VARIABLE
        value: &amp;#39;this is my variable value&amp;#39;
    envFrom:
      - configMapRef:
          name: my-config-vars
      - secretRef:
          name: my-secrets-vars&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;The above YAML introduces the &lt;code&gt;runner&lt;/code&gt; section. This section applies to each pod that will be running a portion of your test, based upon the desired &lt;code&gt;parallelism&lt;/code&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;p&gt;Now, with the referenced resources, our test scripts can 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/environment-variables/&#34;&gt;use environment variables&lt;/a&gt; as in the following:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export function setup() {
  console.log(`Variable is set as: ${__ENV.MY_CUSTOM_VARIABLE}`);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;change-command-line-arguments&#34;&gt;Change command-line arguments&lt;/h3&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/&#34;&gt;k6 options&lt;/a&gt; can be specified in many ways, one being the command-line.
Specifying options via command-line can still be accomplished when using the operator as shown with the following example:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: run-k6-with-args
spec:
  parallelism: 4
  script:
    configMap:
      name: my-test
      file: test.js
  arguments: --tag testid=run-k6-with-args --log-format json&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With the above arguments, we&amp;rsquo;re adding a 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/tags-and-groups/#test-wide-tags&#34;&gt;test-wide custom tag&lt;/a&gt; to metrics and changing the output format of logs to 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#json&#34;&gt;JSON&lt;/a&gt;.&lt;/p&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Be sure to visit the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/&#34;&gt;options reference&lt;/a&gt; for a listing of available options.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;5-run-your-test&#34;&gt;5. Run your test&lt;/h2&gt;
&lt;p&gt;Tests are executed by applying the custom resource &lt;code&gt;TestRun&lt;/code&gt; to a cluster where the operator is running.
The test configuration is applied as in the following:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl apply -f /path/to/your/k6-resource.yaml&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;After completing a test run, you need to clean up the test jobs created.
This is done by running the following command:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;shell&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-shell&#34;&gt;kubectl delete -f /path/to/your/k6-resource.yaml&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;If you make use of 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/real-time/&#34;&gt;real-time results output&lt;/a&gt;, e.g. 
    &lt;a href=&#34;/docs/k6/v2.3.x/results-output/real-time/prometheus-remote-write/&#34;&gt;Prometheus Remote Write&lt;/a&gt;, use the &lt;code&gt;cleanup&lt;/code&gt; option to automatically remove resources upon test completion as with the following example:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;YAML&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-yaml&#34;&gt;apiVersion: k6.io/v1alpha1
kind: TestRun
metadata:
  name: run-k6-with-realtime
spec:
  parallelism: 4
  # Removes all resources upon completion
  cleanup: post
  script:
    configMap:
      name: my-test
      file: test.js
  arguments: -o experimental-prometheus-rw&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;6-when-things-go-wrong&#34;&gt;6. When things go wrong&lt;/h2&gt;
&lt;p&gt;Sadly nothing works perfectly all the time, so knowing where you can go for help is important.&lt;/p&gt;
&lt;p&gt;Be sure to check the &lt;a href=&#34;/docs/k6/latest/set-up/set-up-distributed-k6/troubleshooting/&#34;&gt;troubleshooting guide&lt;/a&gt; as it contains many common pitfalls.&lt;/p&gt;
&lt;p&gt;You can also search the &lt;a href=&#34;https://community.grafana.com/c/grafana-k6/k6-operator/73&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6-operator category in the community forum&lt;/a&gt;.
k6 has a growing and helpful community of engineers working with k6-operator, so there&amp;rsquo;s a good chance your issue has already been discussed and overcome.
It&amp;rsquo;s also in these forums where you&amp;rsquo;ll be able to get help from members of the k6 development team.&lt;/p&gt;
&lt;h2 id=&#34;see-also&#34;&gt;See also&lt;/h2&gt;
&lt;p&gt;Here are some additional resources to help on your learning journey:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.youtube.com/watch?v=5d5zxsGz8L4&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Distributed load testing using Kubernetes with k6 (k6 Office Hours #72)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/javaducky/demo-k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Demo for k6-operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://k6.io/blog/running-distributed-tests-on-k8s/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Blog: Running distributed k6 tests on Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="running-distributed-tests">Running distributed tests&lt;/h1>
&lt;p>It has already been established that k6 can
&lt;a href="/docs/k6/v2.3.x/testing-guides/running-large-tests/">run large load tests&lt;/a> from a single instance, but what about &lt;em>multiple instances running a single test&lt;/em>?&lt;/p></description></item><item><title>Running large tests</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/running-large-tests/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/running-large-tests/</guid><content><![CDATA[&lt;h1 id=&#34;running-large-tests&#34;&gt;Running large tests&lt;/h1&gt;
&lt;p&gt;k6 can generate a lot of load from a single machine. With proper monitoring and script optimization, you might be able to run a rather large load test without needing &lt;a href=&#34;#distributed-execution&#34;&gt;distributed execution&lt;/a&gt;. This document explains how to launch such a test, and some of the aspects you should be aware of.&lt;/p&gt;
&lt;p&gt;Maximizing the load a machine generates is a multi-faceted process, which includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Changing operating system settings to increase the default network and user limits.&lt;/li&gt;
&lt;li&gt;Monitoring the load-generator machine to ensure adequate resource usage.&lt;/li&gt;
&lt;li&gt;Designing efficient tests, with attention to scripting, k6 options, and file uploads.&lt;/li&gt;
&lt;li&gt;Monitoring the test run to detect errors logged by k6, which could indicate limitations of the load generator machine or the 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#system-under-test&#34;&gt;system under test&lt;/a&gt; (SUT).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A single k6 process efficiently uses all CPU cores on a load generator machine. Depending on the available resources, and with the guidelines described in this document, a single instance of k6 can run 30,000-40,000 simultaneous users (VUs).
In some cases, this number of VUs can generate up to 300,000 HTTP 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#requests-per-second&#34;&gt;requests per second&lt;/a&gt; (RPS).&lt;/p&gt;
&lt;p&gt;Unless you need more than 100,000-300,000 requests per second (6-12M requests per minute), a single instance of k6 is likely sufficient for your needs.
Read on to learn about how to get the most load from a single machine.&lt;/p&gt;
&lt;h2 id=&#34;os-fine-tuning&#34;&gt;OS fine-tuning&lt;/h2&gt;
&lt;p&gt;Modern operating systems are configured with a fairly low limit of the number of concurrent network connections an application can create. This is a safe default since most programs don&amp;rsquo;t need to open thousands of concurrent TCP connections like k6. However, if we want to use the full network capacity and achieve maximum performance, we need to change some of these default settings.&lt;/p&gt;
&lt;p&gt;On a GNU/Linux machine, run the following commands as the &lt;code&gt;root&lt;/code&gt; user:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;sysctl -w net.ipv4.ip_local_port_range=&amp;#34;1024 65535&amp;#34;
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_timestamps=1
ulimit -n 250000&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;These commands enable reusing network connections, increase the limit of network connections, and range of local ports.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;sysctl&lt;/code&gt; commands apply immediately for the entire system, and will reset to their default values if you restart the network service or reboot the machine. The &lt;code&gt;ulimit&lt;/code&gt; command applies only for the current shell session, and you&amp;rsquo;ll need to run it again for it to be set in another shell instance.&lt;/p&gt;
&lt;p&gt;For detailed information about these settings, how to make them permanent, and instructions for macOS, check out our 
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/fine-tune-os/&#34;&gt;&amp;ldquo;Fine tuning OS&amp;rdquo; article&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;hardware-considerations&#34;&gt;Hardware considerations&lt;/h2&gt;
&lt;p&gt;As load increases, you need to pay attention to hardware constraints.&lt;/p&gt;
&lt;DescriptionList&gt;
&lt;h3 id=&#34;network&#34;&gt;Network&lt;/h3&gt;
&lt;p&gt;Network throughput of the machine is an important consideration when running large tests. Many AWS EC2 machines come with connections of 1Gbit/s, which may limit the load k6 can generate.&lt;/p&gt;
&lt;p&gt;When running the test, use a tool like &lt;code&gt;iftop&lt;/code&gt; in the terminal to view the amount of network traffic generated in real time.
If the traffic is constant at 1Gbit/s, your test is probably limited by the network card. Consider upgrading to a different EC2 instance.&lt;/p&gt;
&lt;h3 id=&#34;cpu&#34;&gt;CPU&lt;/h3&gt;
&lt;p&gt;k6 is heavily multi-threaded, and will effectively utilize all available CPU cores.&lt;/p&gt;
&lt;p&gt;The amount of CPU you need depends on your test script and associated files.
Regardless of the test file, you can assume that large tests require a significant amount of CPU power.
We recommend that you size the machine to have at least 20% idle cycles (up to 80% used by k6, 20% idle).
If k6 uses 100% of the CPU to generate load, the test will experience throttling limits, and this may
cause the result metrics to have a much larger response time than in reality.&lt;/p&gt;
&lt;h3 id=&#34;memory&#34;&gt;Memory&lt;/h3&gt;
&lt;p&gt;k6 can use a lot of memory, though &lt;a href=&#34;https://k6.io/blog/comparing-best-open-source-load-testing-tools#memory-usage&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;more efficiently than some other load testing tools&lt;/a&gt;.
Memory consumption heavily depends on your test scenarios. To estimate the memory requirement of your test,
run the test on your development machine with 100VUs and multiply the consumed memory by the target number of VUs.&lt;/p&gt;
&lt;p&gt;Simple tests use ~1-5MB per VU. (1000VUs = 1-5GB).
Tests that use file uploads, or load large JS modules, can consume tens of megabytes per VU.
Note that each VU has a copy of all JS modules your test uses.
To share memory between VUs, consider using 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-data/sharedarray/&#34;&gt;SharedArray&lt;/a&gt;, or an external data store, such as 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-experimental/redis/&#34;&gt;Redis&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re using &lt;a href=&#34;https://en.wikipedia.org/wiki/Memory_paging&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;swap space&lt;/a&gt;, consider disabling it. If the system runs out of physical memory, the process of swapping memory to much slower secondary storage will have erratic effects on performance and system stability. Which likely will invalidate any test results as the load generator had different performance in different parts of the test. Instead, plan ahead for the memory usage you expect your tests to achieve, and ensure that you have enough physical RAM for the usage to not exceed 90%.&lt;/p&gt;
&lt;h2 id=&#34;monitoring-the-load-generator&#34;&gt;Monitoring the load generator&lt;/h2&gt;
&lt;p&gt;The previous section described how hardware limits the load your machine can generate.
Keeping this in mind, you should monitor resources while the test runs—especially when running tests for the first time.&lt;/p&gt;
&lt;p&gt;The easiest way to monitor the load generator is to open multiple terminals on the machine: one to run k6, and others to monitor the CPU, memory, and network.
We recommend the following tools:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU and memory: &lt;a href=&#34;https://en.wikipedia.org/wiki/Htop&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;htop&lt;/a&gt; or &lt;a href=&#34;https://nmon.sourceforge.net/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;nmon&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Network: &lt;a href=&#34;https://en.wikipedia.org/wiki/Iftop&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;iftop&lt;/a&gt; or &lt;a href=&#34;https://nmon.sourceforge.net/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;nmon&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you prefer graphical applications, use the system monitoring tool from your OS (e.g. GNOME System
Monitor or Plasma System Monitor), or standalone tools like &lt;a href=&#34;https://github.com/KrispyCamel4u/SysMonTask&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;SysMonTask&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a screenshot of 3 terminal sessions showing k6, iftop and htop.
&lt;img
  class=&#34;lazyload d-inline-block&#34;
  data-src=&#34;/media/docs/k6-oss/large-scale-testing-3-terminals.png&#34;
  alt=&#34;k6 iftop htop&#34; width=&#34;859&#34;
     height=&#34;1178&#34;/&gt;&lt;/p&gt;
&lt;h3 id=&#34;monitoring-guidelines&#34;&gt;Monitoring guidelines&lt;/h3&gt;
&lt;p&gt;As the test runs, these are good indicators to monitor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CPU utilization stays within 80%.&lt;/strong&gt;
If all CPU cores are 100% utilized during the test run, you might notice performance degradation in your test results.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Network utilization is at an acceptable level.&lt;/strong&gt;
Depending on your test, you might expect to fully saturate the network bandwidth, or this might be a signal that your test is bound by the available bandwidth. In other scenarios, you might want to minimize network usage to keep costs down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Memory utilization doesn&amp;rsquo;t exceed 90%.&lt;/strong&gt;
If you&amp;rsquo;re close to exhausting available physical RAM, the system might start swapping memory to disk, which will affect performance and system stability (note that &lt;a href=&#34;#memory&#34;&gt;we recommend disabling swap space altogether&lt;/a&gt;). In extreme cases, running out of memory on Linux will cause the system to end the k6 process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;error-handling-should-be-resilient&#34;&gt;Error handling should be resilient&lt;/h2&gt;
&lt;p&gt;When running 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/test-types/stress-testing/&#34;&gt;large stress tests&lt;/a&gt;, your script shouldn&amp;rsquo;t assume anything about the HTTP response.
An oversight of some scripts is to test with only the 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/glossary/#happy-path&#34; title=&#34;The default behavior that happens when the system returns no errors&#34;&gt;happy path&lt;/a&gt; in mind.&lt;/p&gt;
&lt;p&gt;For example, in k6 scripts, we often see something like this &lt;em&gt;happy path&lt;/em&gt; check:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

const res = http.get(&amp;#39;https://test.k6.io&amp;#39;);
const checkRes = check(res, {
  &amp;#39;Homepage body size is 11026 bytes&amp;#39;: (r) =&amp;gt; r.body.length === 11026,
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Code like this runs fine when the SUT is not overloaded and returns proper responses.
When the system starts to fail, this check won&amp;rsquo;t work as expected.&lt;/p&gt;
&lt;p&gt;The issue is that the check assumes that the response always has a body.
But if the server is failing, &lt;code&gt;r.body&lt;/code&gt; might not exist.
In such cases, the check itself won&amp;rsquo;t work as expected, and an error such as the following will be returned:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;ERRO[0625] TypeError: Cannot read property &amp;#39;length&amp;#39; of undefined&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;To fix this issue, make your checks resilient to any response type.&lt;/p&gt;
&lt;p&gt;The following change will handle the exception.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

const res = http.get(&amp;#39;https://test.k6.io&amp;#39;);
const checkRes = check(res, {
  &amp;#39;Homepage body size is 11026 bytes&amp;#39;: (r) =&amp;gt; r.body &amp;amp;&amp;amp; r.body.length === 11026,
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Refer to subsequent sections for a list of &lt;a href=&#34;#common-errors&#34;&gt;common errors&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;k6-options-to-reduce-resource-use&#34;&gt;k6 options to reduce resource use&lt;/h2&gt;
&lt;p&gt;The following k6 settings can reduce the performance costs of running large tests.&lt;/p&gt;
&lt;h3 id=&#34;save-memory-with-discardresponsebodies&#34;&gt;Save memory with &lt;code&gt;discardResponseBodies&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;By default, k6 loads the response body of the request into memory. This causes much higher memory consumption and is often unnecessary.&lt;/p&gt;
&lt;p&gt;To instruct k6 to not process all response bodies, set &lt;code&gt;discardResponseBodies&lt;/code&gt; in the options object like this:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;export const options = {
  discardResponseBodies: true,
};&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;If you need the response body for some requests, override the option with 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6-http/params/&#34;&gt;Params.responseType&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;when-streaming-use---no-thresholds-and---summary-modedisabled&#34;&gt;When streaming, use &lt;code&gt;--no-thresholds&lt;/code&gt; and &lt;code&gt;--summary-mode=disabled&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re running a local test and streaming results to the cloud (&lt;code&gt;k6 cloud run script.js --local-execution&lt;/code&gt;), you might want to disable the terminal summary and local threshold calculation, because the cloud service will display the summary and calculate the thresholds.&lt;/p&gt;
&lt;p&gt;Without these options, the operations will be duplicated by both the local machine and the cloud servers.
This will save some memory and CPU cycles.&lt;/p&gt;
&lt;p&gt;Here are all the mentioned flags, all in one:&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;k6 cloud run scripts/website.js \
  --local-execution \
  --vus=20000 \
  --duration=10m \
  --no-thresholds \
  --summary-mode=disabled&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;script-optimizations&#34;&gt;Script optimizations&lt;/h2&gt;
&lt;p&gt;To squeeze more performance out of the hardware, consider optimizing the code of the test script itself.
Some of these optimizations involve limiting how you use certain k6 features.
Other optimizations are general to all JavaScript programming.&lt;/p&gt;
&lt;h3 id=&#34;limit-resource-intensive-k6-operations&#34;&gt;Limit resource-intensive k6 operations&lt;/h3&gt;
&lt;p&gt;Some features of the k6 API entail more computation to execute.
To maximize load generation, you might need to limit how your script uses the following.&lt;/p&gt;
&lt;DescriptionList&gt;
&lt;dl&gt;
&lt;dt&gt;Checks and groups&lt;/dt&gt;
&lt;dd&gt;k6 records the result of every individual check and group separately. If you are using many checks and groups, you may consider removing them to boost performance.&lt;/dd&gt;
&lt;dt&gt;Custom metrics&lt;/dt&gt;
&lt;dd&gt;Similar to checks, values for custom metrics (Trend, Counter, Gauge and Rate) are recorded separately. Consider minimizing the usage of custom metrics.&lt;/dd&gt;
&lt;dt&gt;Thresholds with abortOnFail&lt;/dt&gt;
&lt;dd&gt;If you have configured 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/#aborting-a-test-when-a-threshold-is-crossed&#34;&gt;abortOnFail thresholds&lt;/a&gt;, k6 needs to evaluate the result constantly to verify that the threshold wasn&amp;rsquo;t crossed. Consider removing this setting.&lt;/dd&gt;
&lt;dt&gt;URL grouping&lt;/dt&gt;
&lt;dd&gt;k6 v0.41.0 introduced a change to support time-series metrics. A side effect of this is that every unique URL creates a new time-series object, which may consume more RAM than expected.&lt;/dd&gt;
&lt;dd&gt;To solve this, use the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/http-requests/#url-grouping&#34;&gt;URL grouping&lt;/a&gt; feature.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/DescriptionList&gt;
&lt;h3 id=&#34;javascript-optimizations&#34;&gt;JavaScript optimizations&lt;/h3&gt;
&lt;p&gt;Finally, if the preceding suggestions are insufficient, you might be able to do some general JavaScript optimizations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avoid deeply nested &lt;code&gt;for&lt;/code&gt; loops.&lt;/li&gt;
&lt;li&gt;Avoid references to large objects in memory as much as possible.&lt;/li&gt;
&lt;li&gt;Keep external JS dependencies to a minimum.&lt;/li&gt;
&lt;li&gt;Perform tree shaking of the k6 script if you have a build pipeline, and so on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Refer to this article about &lt;a href=&#34;https://javascript.info/garbage-collection&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;garbage collection&lt;/a&gt; in the V8 runtime. While the JavaScript VM that k6 uses is very different and runs on Go, the general principles apply. Note that memory leaks are still possible in k6 scripts, and, if not fixed, they might exhaust RAM much more quickly.&lt;/p&gt;
&lt;h2 id=&#34;file-upload-considerations&#34;&gt;File upload considerations&lt;/h2&gt;
&lt;p&gt;File uploads can affect both resource usage and cloud costs.&lt;/p&gt;
&lt;DescriptionList&gt;
&lt;dl&gt;
&lt;dt&gt;Network throughput&lt;/dt&gt;
&lt;dd&gt;The network throughput of the load generator machine and of the SUT are likely bottlenecks for file uploads.&lt;/dd&gt;
&lt;dt&gt;Memory&lt;/dt&gt;
&lt;dd&gt;k6 needs a significant amount of memory when uploading files. Every VU is independent and has its own memory, and files will be copied in all VUs that upload them. You can follow &lt;a href=&#34;https://github.com/grafana/k6/issues/1931&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;issue #1931&lt;/a&gt; that aims to improve this situation.&lt;/dd&gt;
&lt;dt&gt;Data transfer costs&lt;/dt&gt;
&lt;dd&gt;k6 can upload a large amount of data in a very short period of time. Make sure you understand the data transfer costs before commencing a large scale test.&lt;/dd&gt;
&lt;dd&gt;&lt;a href=&#34;https://www.cloudmanagementinsider.com/data-transfer-costs-everything-you-need-to-know/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Outbound Data Transfer is expensive in AWS EC2&lt;/a&gt;. The price ranges between $0.08 to $0.20 per GB depending on the region.
If you use the cheapest region, the cost is about $0.08 per GB. Uploading 1TB, therefore, costs about $80. A long-running test can cost several hundreds of dollars in data transfer alone.&lt;/dd&gt;
&lt;dd&gt;If your infrastructure is already hosted on AWS, consider running your load generator machine within the same AWS region and availability zone. In some cases, this traffic will be much cheaper or even free. For additional data cost-saving tips, check this &lt;a href=&#34;https://www.stormit.cloud/blog/aws-data-transfer-pricing-how-to-reduce-costs/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;article on how to reduce data transfer costs on AWS&lt;/a&gt;. Our examples are made with AWS in mind. However, the same suggestions also apply to other cloud providers.&lt;/dd&gt;
&lt;dt&gt;Virtual server costs&lt;/dt&gt;
&lt;dd&gt;The AWS EC2 instances are relatively cheap. Even the largest instance we have used in this benchmark (m5.24xlarge) costs only $4.6 per hour.&lt;/dd&gt;
&lt;dd&gt;Make sure you turn off the load generator servers once you are done with your testing. A forgotten EC2 server might cost thousands of dollars per month.
&lt;strong&gt;Tip:&lt;/strong&gt; it&amp;rsquo;s often possible to launch &amp;ldquo;spot instances&amp;rdquo; of the same hardware for 10-20% of the cost.&lt;/dd&gt;
&lt;/dl&gt;
&lt;/DescriptionList&gt;
&lt;h2 id=&#34;common-errors&#34;&gt;Common errors&lt;/h2&gt;
&lt;p&gt;If you run into errors during the execution, it helps to know whether they were caused by the load generator or by the failing SUT.&lt;/p&gt;
&lt;h3 id=&#34;read-connection-reset-by-peer&#34;&gt;read: connection reset by peer&lt;/h3&gt;
&lt;p&gt;This is caused by the target system resetting the TCP connection. It happens when the Load Balancer or the server itself isn&amp;rsquo;t able to handle the traffic.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;WARN[0013] Request Failed       error=&amp;#34;Get http://test.k6.io: read tcp 172.31.72.209:35288-&amp;gt;63.32.205.136:80: read: connection reset by peer&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;context-deadline-exceeded&#34;&gt;context deadline exceeded&lt;/h3&gt;
&lt;p&gt;This happens when k6 can send a request, but the target system doesn&amp;rsquo;t respond in time. The default timeout in k6 is 60 seconds. If your system doesn&amp;rsquo;t produce the response in this time frame, this error will appear.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;WARN[0064] Request Failed    error=&amp;#34;Get http://test.k6.io: context deadline exceeded&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;dial-tcp-52182422280-io-timeout&#34;&gt;dial tcp 52.18.24.222:80: i/o timeout&lt;/h3&gt;
&lt;p&gt;This error is similar to &lt;code&gt;context deadline exceeded&lt;/code&gt;, but in this case, k6 wasn&amp;rsquo;t even able to make an HTTP request. The target system can&amp;rsquo;t establish a TCP connection.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;WARN[0057] Request Failed     error=&amp;#34;Get http://test.k6.io/: dial tcp 52.18.24.222:80: i/o timeout&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;socket-too-many-open-files&#34;&gt;socket: too many open files&lt;/h3&gt;
&lt;p&gt;This error means that the load generator can&amp;rsquo;t open TCP sockets because it reached the limit of open file descriptors.
Make sure that your limit is set sufficiently high.
Refer to the 
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/fine-tune-os/#viewing-limits-configuration&#34;&gt;&amp;ldquo;Fine tuning OS&amp;rdquo; article&lt;/a&gt;.&lt;/p&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;WARN[0034] Request Failed     error=&amp;#34;Get http://test.k6.io/: dial tcp 99.81.83.131:80: socket: too many open files&amp;#34;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&#34;admonition admonition-note&#34;&gt;&lt;blockquote&gt;&lt;p class=&#34;title text-uppercase&#34;&gt;Note&lt;/p&gt;&lt;p&gt;Decide what level of errors is acceptable. At large scale, some errors are always present.
If you make 50M requests with 100 failures, this is generally a good result (0.00002% errors).&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;benchmarking-k6&#34;&gt;Benchmarking k6&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ve executed a few large tests on different EC2 machines to see how much load k6 can generate. Our general observation is that k6 scales proportionally to the hardware. A machine with 2x more resources can generate roughly 2x more traffic. The limit to this scalability is the number of open connections. A single Linux machine can open up to 65,535 sockets per IP address (refer to &lt;a href=&#34;https://www.rfc-editor.org/rfc/rfc6056#section-2&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;RFC 6056&lt;/a&gt; for details). This means that a maximum of 65k requests can be sent simultaneously on a single machine. This is a theoretical limit that will depend on the &lt;a href=&#34;https://en.wikipedia.org/wiki/Ephemeral_port&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;configured ephemeral port range&lt;/a&gt;, whether HTTP/2 is in use, which uses request multiplexing and can achieve higher throughput, and other factors.&lt;/p&gt;
&lt;p&gt;Primarily, though, the RPS limit depends on the response time of the SUT. If responses are delivered in 100ms, the theoretical RPS limit is 650,000 for HTTP/1 requests to a single remote address.&lt;/p&gt;
&lt;p&gt;We maintain a &lt;a href=&#34;https://github.com/grafana/k6-benchmarks&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;repository&lt;/a&gt; with some scripts used to benchmark k6 and create reports. These tests are run for every new k6 version, and you can see the results in the &lt;a href=&#34;https://github.com/grafana/k6-benchmarks/tree/master/results&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;results/&lt;/code&gt; directory&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;distributed-execution&#34;&gt;Distributed execution&lt;/h2&gt;
&lt;p&gt;In load testing, &lt;em&gt;distributed execution&lt;/em&gt; happens when a load is distributed across multiple machines.&lt;/p&gt;
&lt;p&gt;Users often look for the distributed execution mode to run large-scale tests. Although a single k6 instance can generate enormous load, distributed execution is necessary to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simulate load from multiple locations simultaneously.&lt;/li&gt;
&lt;li&gt;Scale the load of your test beyond what a single machine can handle.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In k6, you can split the load of a test across multiple k6 instances using the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#execution-segment&#34;&gt;&lt;code&gt;execution-segment&lt;/code&gt;&lt;/a&gt; option. For example:&lt;/p&gt;



  

  

  






  

  

  



  &lt;div class=&#34;code&#34; x-data=&#34;app_code([&amp;#34;bash&amp;#34;,&amp;#34;bash&amp;#34;,&amp;#34;bash&amp;#34;], false)&#34; x-init=&#34;init()&#34; data-codetoggle=&#34;true&#34;&gt;
    &lt;div class=&#34;toggle-toolbar &#34;&gt;
      &lt;div&gt;&lt;button class=&#34;toggle-toolbar__item&#34; :class=&#34;{ &#39;toggle-toolbar__item-active&#39;: active === &#39;bash&#39; }&#34; @click=&#34;$store.code.language = &#39;bash&#39;&#34;&gt;
              &lt;span&gt;Bash&lt;/span&gt;
            &lt;/button&gt;&lt;button class=&#34;toggle-toolbar__item&#34; :class=&#34;{ &#39;toggle-toolbar__item-active&#39;: active === &#39;bash&#39; }&#34; @click=&#34;$store.code.language = &#39;bash&#39;&#34;&gt;
              &lt;span&gt;Bash&lt;/span&gt;
            &lt;/button&gt;&lt;button class=&#34;toggle-toolbar__item&#34; :class=&#34;{ &#39;toggle-toolbar__item-active&#39;: active === &#39;bash&#39; }&#34; @click=&#34;$store.code.language = &#39;bash&#39;&#34;&gt;
              &lt;span&gt;Bash&lt;/span&gt;
            &lt;/button&gt;&lt;/div&gt;
      &lt;div class=&#34;d-flex&#34;&gt;&lt;span class=&#34;code-clipboard&#34; x-ref=&#34;tooltip&#34;&gt;
          &lt;button @click=&#34;copy()&#34;&gt;
            &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
            &lt;span&gt;Copy&lt;/span&gt;
          &lt;/button&gt;
        &lt;/span&gt;
      &lt;/div&gt;
      &lt;div class=&#34;toggle-toolbar__border&#34;&gt;&lt;/div&gt;
    &lt;/div&gt;
    
    &lt;div class=&#34;code-rendered&#34; &gt;
      
&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;## split the load of my-script.js across two machines
k6 run --execution-segment &amp;#34;0:1/2&amp;#34;   --execution-segment-sequence &amp;#34;0,1/2,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;1/2:1&amp;#34;   --execution-segment-sequence &amp;#34;0,1/2,1&amp;#34; my-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;## split the load of my-script.js across three machines
k6 run --execution-segment &amp;#34;0:1/3&amp;#34;   --execution-segment-sequence &amp;#34;0,1/3,2/3,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;1/3:2/3&amp;#34; --execution-segment-sequence &amp;#34;0,1/3,2/3,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;2/3:1&amp;#34;   --execution-segment-sequence &amp;#34;0,1/3,2/3,1&amp;#34; my-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;Bash&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-bash&#34;&gt;## split the load of my-script.js across four machines
k6 run --execution-segment &amp;#34;0:1/4&amp;#34;     --execution-segment-sequence &amp;#34;0,1/4,2/4,3/4,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;1/4:2/4&amp;#34;   --execution-segment-sequence &amp;#34;0,1/4,2/4,3/4,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;2/4:3/4&amp;#34;   --execution-segment-sequence &amp;#34;0,1/4,2/4,3/4,1&amp;#34; my-script.js
k6 run --execution-segment &amp;#34;3/4:1&amp;#34;     --execution-segment-sequence &amp;#34;0,1/4,2/4,3/4,1&amp;#34; my-script.js&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;

    &lt;/div&gt;
  &lt;/div&gt;


&lt;p&gt;However, at this moment, the distributed execution mode of k6 is not entirely functional. The current limitations are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k6 does not provide the functionality of a &amp;ldquo;primary&amp;rdquo; instance to coordinate the distributed execution of the test. Alternatively, you can use the 
    &lt;a href=&#34;/docs/k6/v2.3.x/reference/k6-rest-api/&#34;&gt;k6 REST API&lt;/a&gt; and 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#paused&#34;&gt;&lt;code&gt;--paused&lt;/code&gt;&lt;/a&gt; to synchronize the multiple k6 instances&amp;rsquo; execution.&lt;/li&gt;
&lt;li&gt;Each k6 instance evaluates 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/thresholds/&#34;&gt;Thresholds&lt;/a&gt; independently - excluding the results of the other k6 instances. If you want to disable the threshold execution, use 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#no-thresholds&#34;&gt;&lt;code&gt;--no-thresholds&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;k6 reports the metrics individually for each instance. Depending on how you store the load test results, you&amp;rsquo;ll have to aggregate some metrics to calculate them correctly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With the limitations mentioned above, we built a &lt;a href=&#34;https://github.com/grafana/k6-operator&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Kubernetes operator&lt;/a&gt; to distribute the load of a k6 test across a &lt;strong&gt;Kubernetes cluster&lt;/strong&gt;. For further instructions, check out &lt;a href=&#34;https://k6.io/blog/running-distributed-tests-on-k8s/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;the tutorial for running distributed k6 tests on Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The k6 goal is to support a native open-source solution for distributed execution. If you want to follow the progress, subscribe to the &lt;a href=&#34;https://github.com/grafana/k6/issues/140&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;distributed execution issue&lt;/a&gt; on GitHub.&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id=&#34;large-scale-tests-in-the-cloud&#34;&gt;Large-scale tests in the cloud&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;/products/cloud/k6/&#34;&gt;Grafana Cloud k6&lt;/a&gt;, our commercial offering, provides an instant solution for running large-scale tests, among other &lt;a href=&#34;/docs/grafana-cloud/testing/k6/&#34;&gt;benefits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you aren&amp;rsquo;t sure whether OSS or Cloud is a better fit for your project, we recommend reading this &lt;a href=&#34;/media/docs/k6/what-to-consider-when-building-or-buying-a-load-testing-solution-white-paper-2021.pdf&#34;&gt;white paper&lt;/a&gt; to learn more about the risks and features to consider when building a scalable solution.&lt;/p&gt;
&lt;h2 id=&#34;read-more&#34;&gt;Read more&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/set-up/fine-tune-os/&#34;&gt;Fine tuning OS&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/javascript-compatibility-mode/&#34;&gt;JavaScript Compatibility Mode&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://k6.io/blog/comparing-best-open-source-load-testing-tools&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;A biased comparison of the best open source load testing tools&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;/media/docs/k6/what-to-consider-when-building-or-buying-a-load-testing-solution-white-paper-2021.pdf&#34;&gt;White paper: what to consider when building or buying a load testing solution&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="running-large-tests">Running large tests&lt;/h1>
&lt;p>k6 can generate a lot of load from a single machine. With proper monitoring and script optimization, you might be able to run a rather large load test without needing &lt;a href="#distributed-execution">distributed execution&lt;/a>. This document explains how to launch such a test, and some of the aspects you should be aware of.&lt;/p></description></item><item><title>Synthetic monitoring</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/synthetic-monitoring/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/synthetic-monitoring/</guid><content><![CDATA[&lt;h1 id=&#34;synthetic-monitoring&#34;&gt;Synthetic monitoring&lt;/h1&gt;
&lt;p&gt;You can use k6 as synthetic monitoring tool by scheduling k6 smoke tests for continuous production monitoring.&lt;/p&gt;
&lt;p&gt;For a more comprehensive solution, you can use &lt;a href=&#34;/docs/grafana-cloud/testing/synthetic-monitoring/create-checks/checks/k6/&#34;&gt;Grafana Cloud Synthetic Monitoring&lt;/a&gt; to proactively monitor your application and services in production. You can run k6 smoke tests at frequent intervals, receive alerts on potential issues, and manage incidents directly from &lt;a href=&#34;/products/cloud/&#34;&gt;Grafana Cloud&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="synthetic-monitoring">Synthetic monitoring&lt;/h1>
&lt;p>You can use k6 as synthetic monitoring tool by scheduling k6 smoke tests for continuous production monitoring.&lt;/p>
&lt;p>For a more comprehensive solution, you can use &lt;a href="/docs/grafana-cloud/testing/synthetic-monitoring/create-checks/checks/k6/">Grafana Cloud Synthetic Monitoring&lt;/a> to proactively monitor your application and services in production. You can run k6 smoke tests at frequent intervals, receive alerts on potential issues, and manage incidents directly from &lt;a href="/products/cloud/">Grafana Cloud&lt;/a>.&lt;/p></description></item><item><title>Use Chai with k6</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/use-chai-with-k6/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/use-chai-with-k6/</guid><content><![CDATA[&lt;h1 id=&#34;use-chai-with-k6&#34;&gt;Use Chai with k6&lt;/h1&gt;
&lt;p&gt;As a codebase grows, engineering teams might adopt frameworks or libraries that can help them write more readable and maintainable code. Similarly, as teams write new k6 test scripts, or expand existing tests, you might start looking for ways to keep them readable and organized, so that they&amp;rsquo;re easier to update in the future.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.chaijs.com/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Chai&lt;/a&gt; is a BDD/TDD assertion library that&amp;rsquo;s commonly used with testing frameworks. You can use Chai with k6 by using the k6chaijs library. With k6chaijs, you get:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BDD, or Behavior-Driven Development, style of assertions for more expressive language&lt;/li&gt;
&lt;li&gt;Chainable assertions&lt;/li&gt;
&lt;li&gt;More powerful assertions functions such as: &lt;code&gt;deep&lt;/code&gt;, &lt;code&gt;nested&lt;/code&gt;, &lt;code&gt;ordered&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;Automatic assertion messages&lt;/li&gt;
&lt;li&gt;
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/jslib/k6chaijs/error-handling/&#34;&gt;Exception handling&lt;/a&gt; for better test stability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can use k6chaijs as an alternative to the 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6/check/&#34;&gt;check&lt;/a&gt; and 
    &lt;a href=&#34;/docs/k6/v2.3.x/javascript-api/k6/group/&#34;&gt;group&lt;/a&gt; functions.&lt;/p&gt;
&lt;h2 id=&#34;installation&#34;&gt;Installation&lt;/h2&gt;
&lt;p&gt;The k6chaijs library is hosted on &lt;a href=&#34;https://jslib.k6.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;jslib&lt;/a&gt; and can be imported directly in your k6 script.&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe, expect } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Alternatively, you can use a copy of this file stored locally. The source code is available on &lt;a href=&#34;https://github.com/grafana/k6-jslib-k6chaijs&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;benefits-of-k6chaijs&#34;&gt;Benefits of k6chaijs&lt;/h2&gt;
&lt;p&gt;Here are some of the ways you can use k6chaijs to write better tests.&lt;/p&gt;
&lt;h3 id=&#34;expressive-language&#34;&gt;Expressive language&lt;/h3&gt;
&lt;p&gt;Using the &lt;code&gt;expect&lt;/code&gt; and &lt;code&gt;describe&lt;/code&gt; methods can make code easier to read and maintain. For example, writing a code snippet to check if a string length is equal to five can look like the following:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;if (&amp;#39;Hello&amp;#39;.length !== 5) {
  throw new Error(`Expected &amp;#39;Hello&amp;#39; to have a length of 5 but got ${&amp;#39;Hello&amp;#39;.length}`);
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With Chai, that can be rewritten as follows:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe, expect } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;

describe(&amp;#39;should match expected length&amp;#39;, () =&amp;gt; {
  expect(&amp;#39;Hello&amp;#39;).to.have.lengthOf(5);
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;automatic-messages&#34;&gt;Automatic messages&lt;/h3&gt;
&lt;p&gt;Chai can also help make your code more concise by providing automatic error messages. For example, the following code triggers an error when executed:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe, expect } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;

describe(&amp;#39;should match expected length&amp;#39;, () =&amp;gt; {
  expect(&amp;#39;Goodbye&amp;#39;).to.have.lengthOf(6);
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;And when you run that code, you automatically get an error message without writing any additional code that says:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;// 🔥 AssertionError: expected &amp;#39;Goodbye&amp;#39; to have a length of 6 but got 7&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;structure-tests&#34;&gt;Structure tests&lt;/h3&gt;
&lt;p&gt;Through encapsulation, your tests can define sections for your application. This makes it easier to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Locate tests.&lt;/li&gt;
&lt;li&gt;Spot missing edge cases.&lt;/li&gt;
&lt;li&gt;Keep everything tidy.&lt;/li&gt;
&lt;li&gt;Provide a logical format for adding additional tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, you can encapsulate your tests for two different services in your test script as the following:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;

describe(&amp;#39;service A&amp;#39;, () =&amp;gt; {
  describe(&amp;#39;should do x from service A&amp;#39;, () =&amp;gt; {
    /*...*/
  });
  describe(&amp;#39;should do y from service A&amp;#39;, () =&amp;gt; {
    /*...*/
  });
});

describe(&amp;#39;service B&amp;#39;, () =&amp;gt; {
  describe(&amp;#39;should do x from service B&amp;#39;, () =&amp;gt; {
    /*...*/
  });
  describe(&amp;#39;should do y from service B&amp;#39;, () =&amp;gt; {
    /*...*/
  });
});&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;assert-api-responses&#34;&gt;Assert API responses&lt;/h3&gt;
&lt;p&gt;Assertion libraries are typically used to write mocked unit/integration tests, but you can also use it to test any API response. For example:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import expect from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

const expected = { foo: &amp;#39;Hello&amp;#39;, bar: &amp;#39;World&amp;#39; };
const response = http.get(&amp;#39;https://your.example.domain&amp;#39;);

expect(response.body).to.deep.equal(expected);&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;error-handling-with-describe&#34;&gt;Error handling with &lt;code&gt;describe&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;check&lt;/code&gt; function doesn&amp;rsquo;t protect your tests against unsafe code. If a check fails, k6 throws an exception and restarts the script execution from the beginning. For example:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { check } from &amp;#39;k6&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

export default function () {
  const response = http.get(&amp;#39;https://your.example.domain&amp;#39;); // 🙈 could return Error 503

  check(response, {
    &amp;#39;got more than 5 items&amp;#39;: (res) =&amp;gt; {
      // 🙉 `.json()` might be undefined
      return res.json().length &amp;gt; 5;
    },
  }); // 🙊 k6 will throw an exception and restarts execution from the beginning.

  // 💀 RIP
  check(response, {
    /*...*/
  });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;With k6chaijs, error handling is provided automatically. Script errors are caught by the describe block and execution can proceed to the next set of tests. For example:&lt;/p&gt;
&lt;!-- md-k6:skip --&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe, expect } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

export default function () {
  // 😇 You are safe now
  describe(&amp;#39;got more than 5 items&amp;#39;, () =&amp;gt; {
    const response = http.get(&amp;#39;https://your.example.domain&amp;#39;); // 🙈 could return Error 503

    // 🙉 `.json()` might be undefined
    expect(response.json()).to.have.lengthOf(5);
  });

  describe(&amp;#39;hooray I still get a turn!&amp;#39;, () =&amp;gt; {
    /*...*/
  });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;example&#34;&gt;Example&lt;/h2&gt;
&lt;p&gt;The following script incorporates thresholds with three test cases that use Chai:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One &lt;code&gt;describe&lt;/code&gt; function creates sections and groups tests together.&lt;/li&gt;
&lt;li&gt;Another &lt;code&gt;describe&lt;/code&gt; provides clear instructions on what we hope to achieve from each test.&lt;/li&gt;
&lt;li&gt;Chai&amp;rsquo;s BDD-style &lt;code&gt;expect&lt;/code&gt; function is used to write the tests in an expressive, readable way.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&#34;code-snippet &#34;&gt;&lt;div class=&#34;lang-toolbar&#34;&gt;
    &lt;span class=&#34;lang-toolbar__item lang-toolbar__item-active&#34;&gt;JavaScript&lt;/span&gt;
    &lt;span class=&#34;code-clipboard&#34;&gt;
      &lt;button x-data=&#34;app_code_snippet()&#34; x-init=&#34;init()&#34; @click=&#34;copy()&#34;&gt;
        &lt;img class=&#34;code-clipboard__icon&#34; src=&#34;/media/images/icons/icon-copy-small-2.svg&#34; alt=&#34;Copy code to clipboard&#34; width=&#34;14&#34; height=&#34;13&#34;&gt;
        &lt;span&gt;Copy&lt;/span&gt;
      &lt;/button&gt;
    &lt;/span&gt;
    &lt;div class=&#34;lang-toolbar__border&#34;&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-snippet &#34;&gt;
    &lt;pre data-expanded=&#34;false&#34;&gt;&lt;code class=&#34;language-javascript&#34;&gt;import { describe, expect } from &amp;#39;https://jslib.k6.io/k6chaijs/4.5.0.1/index.js&amp;#39;;
import http from &amp;#39;k6/http&amp;#39;;

export default function () {
  const headers = { Authorization: &amp;#39;token abcdef0123456789&amp;#39; };

  describe(&amp;#39;crocodiles API&amp;#39;, () =&amp;gt; {
    describe(&amp;#39;should fetch a list of public crocodiles&amp;#39;, () =&amp;gt; {
      const response = http.get(&amp;#39;https://quickpizza.grafana.com/api/ratings&amp;#39;, { headers: headers });

      expect(response.status, &amp;#39;response status&amp;#39;).to.equal(200);
      expect(response).to.have.validJsonBody();
      expect(response.json(&amp;#39;ratings&amp;#39;).length, &amp;#39;number of ratings&amp;#39;).to.be.above(1);
    });

    describe(&amp;#39;should respond with status 200, when a valid rating id is provided&amp;#39;, () =&amp;gt; {
      const expected = {
        id: 1,
        stars: 5,
        pizza_id: 1,
      };

      const response = http.get(&amp;#39;https://quickpizza.grafana.com/api/ratings/1&amp;#39;, {
        headers: headers,
      });

      expect(response.status, &amp;#39;status&amp;#39;).to.equal(200);
      expect(JSON.parse(response.body), &amp;#39;response body&amp;#39;).to.deep.equal(expected);
    });

    describe(&amp;#39;should respond with status 404, when an invalid rating id is provided&amp;#39;, () =&amp;gt; {
      const response = http.get(&amp;#39;https://quickpizza.grafana.com/api/ratings/12312123123123&amp;#39;, {
        headers: headers,
      });

      expect(response.status, &amp;#39;status&amp;#39;).to.equal(404);
      expect(JSON.parse(response.body).error, &amp;#39;error message&amp;#39;).to.contain(&amp;#39;not found&amp;#39;);
    });
  });
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;k6 users will use the &lt;code&gt;group&lt;/code&gt; function to perform this type of operation, however, for users of Javascript testing frameworks, &lt;code&gt;describe&lt;/code&gt; is the familiar term. Under the hood, k6chaijs still calls &lt;code&gt;group&lt;/code&gt;, but it&amp;rsquo;s wrapped with a additional extra logic. The same is true for the &lt;code&gt;expect&lt;/code&gt; function. That can help users who are already familiar with testing frameworks, and still provide the same output summaries you expect from the built-in k6 functions.&lt;/p&gt;
]]></content><description>&lt;h1 id="use-chai-with-k6">Use Chai with k6&lt;/h1>
&lt;p>As a codebase grows, engineering teams might adopt frameworks or libraries that can help them write more readable and maintainable code. Similarly, as teams write new k6 test scripts, or expand existing tests, you might start looking for ways to keep them readable and organized, so that they&amp;rsquo;re easier to update in the future.&lt;/p></description></item><item><title>Injecting faults with xk6-disruptor</title><link>https://grafana.com/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/</guid><content><![CDATA[&lt;h1 id=&#34;injecting-faults-with-xk6-disruptor&#34;&gt;Injecting faults with xk6-disruptor&lt;/h1&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/grafana/xk6-disruptor&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;xk6-disruptor&lt;/a&gt; is an extension that adds fault injection capabilities to k6. It implements the principles of the Chaos Engineering discipline to test the reliability of our applications under turbulent conditions such as delays and response errors.&lt;/p&gt;
&lt;p&gt;Key features include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Everything as code. Facilitate test reuse and collaboration between teams without learning a new DSL.&lt;/li&gt;
&lt;li&gt;Fast to adopt with no day-two surprises. 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/how--it-works/&#34;&gt;No need to deploy and maintain&lt;/a&gt; a fleet of agents or operators.&lt;/li&gt;
&lt;li&gt;Easy to extend and integrate with other types of k6 tests. No need to try to glue multiple tools together to get the job done.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;capabilities&#34;&gt;Capabilities&lt;/h2&gt;
&lt;p&gt;Currently, the disruptor is intended to test applications running in Kubernetes. Other platforms are not supported at this time.&lt;/p&gt;
&lt;p&gt;It provides a Javascript API to inject different 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/xk6-disruptor/faults/&#34;&gt;faults&lt;/a&gt; in HTTP and gRPC requests, such as errors and delays, into the selected Kubernetes 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/xk6-disruptor/poddisruptor/&#34;&gt;Pods&lt;/a&gt; or 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/xk6-disruptor/servicedisruptor/&#34;&gt;Services&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Other types of faults and disruptors will be introduced in the future. The &lt;a href=&#34;https://github.com/grafana/xk6-disruptor/blob/main/ROADMAP.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Roadmap&lt;/a&gt; presents the project&amp;rsquo;s goals for the coming months regarding new functionalities and enhancements.&lt;/p&gt;
&lt;h2 id=&#34;use-cases&#34;&gt;Use cases&lt;/h2&gt;
&lt;p&gt;The disruptor lets you test the resiliency of distributed applications by introducing errors in the requests served by your services.&lt;/p&gt;
&lt;p&gt;The disruptor does not try to reproduce root causes, such as failed application instances or degraded computing or network resources.
It focuses on reproducing the side effects of such failures, so you can focus on understanding the propagation of errors between internal and public services and improving the error handling in your application.&lt;/p&gt;
&lt;p&gt;This way, the disruptor makes reliability tests repeatable and predictable while limiting their blast radius.
These are essentials to test applications deployed on shared infrastructures such as pre-production and testing environments.&lt;/p&gt;
&lt;p&gt;Common use cases are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test resilient policies such as backoff, timeouts, retries, etc.&lt;/li&gt;
&lt;li&gt;Test the fallback functionality when internal failures arise.&lt;/li&gt;
&lt;li&gt;Test SLOs under common internal failures.&lt;/li&gt;
&lt;li&gt;Test application performance when experiencing delays.&lt;/li&gt;
&lt;li&gt;Add fault injection to existing performance tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;learn-more&#34;&gt;Learn more&lt;/h2&gt;
&lt;p&gt;Learn more about &lt;a href=&#34;https://k6.io/blog/democratize-chaos-testing/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Fault injection&lt;/a&gt; and &lt;a href=&#34;https://k6.io/blog/building-resilience-early-in-the-development-cycle/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Building Resilience early in the development cycle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check the 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/first-steps/&#34;&gt;first steps&lt;/a&gt; to get started with the disruptor.&lt;/p&gt;
&lt;p&gt;Follow the 
    &lt;a href=&#34;/docs/k6/v2.3.x/testing-guides/injecting-faults-with-xk6-disruptor/examples/&#34;&gt;examples of injecting faults in different scenarios&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Visit the &lt;a href=&#34;https://killercoda.com/grafana-xk6-disruptor/scenario/killercoda&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;interactive demo environment in Killercoda&lt;/a&gt; and try the disruptor in a demo application without having to do any setup.&lt;/p&gt;
&lt;h2 id=&#34;contributing&#34;&gt;Contributing&lt;/h2&gt;
&lt;p&gt;For any unexpected behavior, please search the &lt;a href=&#34;https://github.com/grafana/xk6-disruptor/issues&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;GitHub issues&lt;/a&gt; first.&lt;/p&gt;
&lt;p&gt;And if you are interested in contributing to the development of this project, check the &lt;a href=&#34;https://github.com/grafana/xk6-disruptor/blob/main/docs/01-development/01-contributing.md&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;contributing guide&lt;/a&gt;.&lt;/p&gt;
]]></content><description>&lt;h1 id="injecting-faults-with-xk6-disruptor">Injecting faults with xk6-disruptor&lt;/h1>
&lt;p>&lt;a href="https://github.com/grafana/xk6-disruptor" target="_blank" rel="noopener noreferrer">xk6-disruptor&lt;/a> is an extension that adds fault injection capabilities to k6. It implements the principles of the Chaos Engineering discipline to test the reliability of our applications under turbulent conditions such as delays and response errors.&lt;/p></description></item></channel></rss>