<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>End of test on Grafana Labs</title><link>https://grafana.com/docs/k6/v2.3.x/results-output/end-of-test/</link><description>Recent content in End of test on Grafana Labs</description><generator>Hugo -- gohugo.io</generator><language>en</language><atom:link href="/docs/k6/v2.3.x/results-output/end-of-test/index.xml" rel="self" type="application/rss+xml"/><item><title>Custom summary</title><link>https://grafana.com/docs/k6/v2.3.x/results-output/end-of-test/custom-summary/</link><pubDate>Mon, 21 Sep 2026 15:16:28 +0000</pubDate><guid>https://grafana.com/docs/k6/v2.3.x/results-output/end-of-test/custom-summary/</guid><content><![CDATA[&lt;h1 id=&#34;custom-summary&#34;&gt;Custom summary&lt;/h1&gt;
&lt;p&gt;With &lt;code&gt;handleSummary()&lt;/code&gt;, you can completely customize your end-of-test summary.
In this document, read about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How &lt;code&gt;handleSummary()&lt;/code&gt; works&lt;/li&gt;
&lt;li&gt;How to customize the content and output location of your summary&lt;/li&gt;
&lt;li&gt;The data structure of the summary object&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;However, we plan to support the feature for k6 Cloud tests, too.
&lt;a href=&#34;https://github.com/grafana/k6-cloud-feature-requests/issues/24&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Track progress in this issue&lt;/a&gt;.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;about-handlesummary&#34;&gt;About &lt;code&gt;handleSummary()&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;After your test runs, k6 aggregates your metrics into a JavaScript object.
The &lt;code&gt;handleSummary()&lt;/code&gt; function takes this object as an argument (called &lt;code&gt;data&lt;/code&gt; in all examples here).&lt;/p&gt;
&lt;p&gt;You can use &lt;code&gt;handleSummary()&lt;/code&gt; to create a custom summary or return the default summary object.
To get an idea of what the data looks like,
run this script and open the output file, &lt;code&gt;summary.json&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;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 () {
  http.get(&amp;#39;https://test.k6.io&amp;#39;);
}

export function handleSummary(data) {
  return {
    &amp;#39;summary.json&amp;#39;: JSON.stringify(data), //the default data object
  };
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Fundamentally, &lt;code&gt;handleSummary()&lt;/code&gt; is just a function that can access a data object.
As such, you can transform the summary data into any text format: JSON, HTML, console, XML, and so on.
You can pipe your custom summary to &lt;a href=&#34;https://en.wikipedia.org/wiki/Standard_streams&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;standard output or standard error&lt;/a&gt;, write it to a file, or send it to a remote server.&lt;/p&gt;
&lt;p&gt;k6 calls &lt;code&gt;handleSummary()&lt;/code&gt; at the end of the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/test-lifecycle/&#34;&gt;test lifecycle&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;use-handlesummary&#34;&gt;Use handleSummary()&lt;/h2&gt;
&lt;p&gt;The following sections go over the &lt;code&gt;handleSummary()&lt;/code&gt; syntax and provide some examples.&lt;/p&gt;
&lt;p&gt;To look up the structure of the summary object, refer to the reference section.&lt;/p&gt;
&lt;h3 id=&#34;syntax&#34;&gt;Syntax&lt;/h3&gt;
&lt;p&gt;k6 expects &lt;code&gt;handleSummary()&lt;/code&gt; to return a &lt;code&gt;{key1: value1, key2: value2, ...}&lt;/code&gt; map that represents the summary metrics.&lt;/p&gt;
&lt;p&gt;The keys must be strings.
They determine where k6 displays or saves the content:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;stdout&lt;/code&gt; for standard output&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stderr&lt;/code&gt; for standard error,&lt;/li&gt;
&lt;li&gt;any relative or absolute path to a file on the system (this operation overwrites existing files)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The value of a key can have a type of either &lt;code&gt;string&lt;/code&gt; or &lt;a href=&#34;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;&lt;code&gt;ArrayBuffer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can return multiple summary outputs in a script.
As an example, this &lt;code&gt;return&lt;/code&gt; statement sends a report to standard output and writes the &lt;code&gt;data&lt;/code&gt; object to a JSON file.&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;  return {
    &amp;#39;stdout&amp;#39;: textSummary(data, { indent: &amp;#39; &amp;#39;, enableColors: true }), // Show the text summary to stdout...
    &amp;#39;other/path/to/summary.json&amp;#39;: JSON.stringify(data), // and a JSON with all the details...
  };&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;example-extract-data-properties&#34;&gt;Example: Extract data properties&lt;/h3&gt;
&lt;p&gt;This minimal &lt;code&gt;handleSummary()&lt;/code&gt; extracts the &lt;code&gt;median&lt;/code&gt; value for the &lt;code&gt;iteration_duration&lt;/code&gt; metric and prints it to standard output:&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 () {
  http.get(&amp;#39;https://test.k6.io&amp;#39;);
}

export function handleSummary(data) {
  const med_latency = data.metrics.iteration_duration.values.med;
  const latency_message = `The median latency was ${med_latency}\n`;

  return {
    stdout: latency_message,
  };
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;example-modify-default-output&#34;&gt;Example: Modify default output&lt;/h3&gt;
&lt;p&gt;If &lt;code&gt;handleSummary()&lt;/code&gt; is exported, k6 &lt;em&gt;does not&lt;/em&gt; print the default summary.
However, if you want to keep the default output, you could import &lt;code&gt;textSummary&lt;/code&gt; from the &lt;a href=&#34;https://jslib.k6.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;K6 JS utilities library&lt;/a&gt;.
For example, you could write a custom HTML report to a file, and use the &lt;code&gt;textSummary()&lt;/code&gt; function to print the default report to the console.&lt;/p&gt;
&lt;p&gt;You can also use &lt;code&gt;textSummary()&lt;/code&gt; to make minor modifications to the default end-of-test summary.
To do so:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Modify the &lt;code&gt;data&lt;/code&gt; object however you want.&lt;/li&gt;
&lt;li&gt;In your &lt;code&gt;return&lt;/code&gt; statement, pass the modified object as an argument to the &lt;code&gt;textSummary()&lt;/code&gt; function.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The &lt;code&gt;textSummary()&lt;/code&gt; function comes with a few options:&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;Option&lt;/th&gt;
              &lt;th&gt;Description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;indent&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;How to start the summary indentation&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;&lt;code&gt;enableColors&lt;/code&gt;&lt;/td&gt;
              &lt;td&gt;Whether to print the summary in color.&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&gt;&lt;p&gt;For example, this &lt;code&gt;handleSummary()&lt;/code&gt; modifies the default summary in the following ways:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It deletes the &lt;code&gt;http_req_duration{expected_response:true}&lt;/code&gt; sub-metric.&lt;/li&gt;
&lt;li&gt;It deletes all metrics whose key starts with &lt;code&gt;iteration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It begins each line with the &lt;code&gt;→&lt;/code&gt; character.&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 http from &amp;#39;k6/http&amp;#39;;
import { textSummary } from &amp;#39;https://jslib.k6.io/k6-summary/0.0.2/index.js&amp;#39;;

export default function () {
  http.get(&amp;#39;https://test.k6.io&amp;#39;);
}

export function handleSummary(data) {
  delete data.metrics[&amp;#39;http_req_duration{expected_response:true}&amp;#39;];

  for (const key in data.metrics) {
    if (key.startsWith(&amp;#39;iteration&amp;#39;)) delete data.metrics[key];
  }

  return {
    stdout: textSummary(data, { indent: &amp;#39;→&amp;#39;, enableColors: true }),
  };
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;In the collapsible section, you can compare the default and modified reports.&lt;/p&gt;
&lt;div class=&#34;collapse&#34; x-data=&#34;app_collapse()&#34;&gt;
  &lt;button class=&#34;collapse-trigger&#34; @click=&#34;toggle()&#34;&gt;
    &lt;span class=&#34;body-large&#34;&gt;Compare the default and modified reports&lt;/span&gt;
    &lt;span class=&#34;collapse-trigger__icon&#34; :class=&#34;{ &#39;collapse-trigger__icon-open&#39; : open }&#34;&gt;
      
  &lt;svg width=&#34;27&#34; height=&#34;26&#34; viewBox=&#34;0 0 27 26&#34; fill=&#34;none&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34;&gt;
&lt;path opacity=&#34;0.2&#34; d=&#34;M1.73047 12.8359C1.73047 19.4634 7.10305 24.8359 13.7305 24.8359C20.3579 24.8359 25.7305 19.4634 25.7305 12.8359C25.7305 6.20852 20.3579 0.835937 13.7305 0.835937C7.10305 0.835937 1.73047 6.20852 1.73047 12.8359Z&#34; stroke=&#34;black&#34; stroke-width=&#34;1.5&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;/&gt;
&lt;path d=&#34;M18.2344 12.8359L9.23438 12.8359&#34; stroke=&#34;black&#34; stroke-width=&#34;1.5&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;/&gt;
&lt;path d=&#34;M13.7344 8.33594L13.7344 17.3359&#34; stroke=&#34;black&#34; stroke-width=&#34;1.5&#34; stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34;/&gt;
&lt;/svg&gt;


    &lt;/span&gt;
  &lt;/button&gt;
  &lt;div class=&#34;collapse-content&#34; x-ref=&#34;content&#34; hidden=&#34;until-found&#34;&gt;
    &lt;div class=&#34;collapse-content__inner&#34; x-ref=&#34;content-inner&#34;&gt;&lt;p&gt;For compactness, these outputs were limited with the &lt;code&gt;summaryTrendStats&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;The default report would be:&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;    HTTP
    http_req_blocked...............: med=10.39µs  count=5 p(99)=451.07ms p(99.99)=469.67ms
    http_req_connecting............: med=0s       count=5 p(99)=223.97ms p(99.99)=233.21ms
    http_req_duration..............: med=202.26ms count=5 p(99)=225.81ms p(99.99)=226.71ms
      { expected_response:true }...: med=202.26ms count=5 p(99)=225.81ms p(99.99)=226.71ms
    http_req_failed................: 0.00% ✓ 0        ✗ 5
    http_req_receiving.............: med=278.27µs count=5 p(99)=377.64µs p(99.99)=381.29µs
    http_req_sending...............: med=47.57µs  count=5 p(99)=108.42µs p(99.99)=108.72µs
    http_req_tls_handshaking.......: med=0s       count=5 p(99)=204.42ms p(99.99)=212.86ms
    http_req_waiting...............: med=201.77ms count=5 p(99)=225.6ms  p(99.99)=226.5ms
    http_reqs......................: 5     3.352646/s

    EXECUTION
    iteration_duration.............: med=204.41ms count=5 p(99)=654.78ms p(99.99)=672.43ms
    iterations.....................: 5     3.352646/s
    vus............................: 1     min=1      max=1
    vus_max........................: 1     min=1      max=1

    NETWORK
    data_received..................: 63 kB 42 kB/s
    data_sent......................: 830 B 557 B/s&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;The modified report would be:&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;    HTTP
→   http_req_blocked...........: med=10.98µs  count=5 p(99)=485.16ms p(99.99)=505.18ms
→   http_req_connecting........: med=0s       count=5 p(99)=245.05ms p(99.99)=255.15ms
→   http_req_duration..........: med=208.68ms count=5 p(99)=302.87ms p(99.99)=306.61ms
→   http_req_failed............: 0.00% ✓ 0        ✗ 5
→   http_req_receiving.........: med=206.12µs count=5 p(99)=341.05µs p(99.99)=344.13µs
→   http_req_sending...........: med=47.8µs   count=5 p(99)=166.94µs p(99.99)=170.92µs
→   http_req_tls_handshaking...: med=0s       count=5 p(99)=207.2ms  p(99.99)=215.74ms
→   http_req_waiting...........: med=208.47ms count=5 p(99)=302.49ms p(99.99)=306.23ms
→   http_reqs..................: 5     3.054928/s

    EXECUTION
→   vus........................: 1     min=1      max=1
→   vus_max....................: 1     min=1      max=1

    NETWORK
→   data_received..............: 63 kB 39 kB/s
→   data_sent..................: 830 B 507 B/s&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;h3 id=&#34;example-make-custom-file-format&#34;&gt;Example: Make custom file format&lt;/h3&gt;
&lt;p&gt;This script imports a helper function to turn the summary into a JUnit XML.
The output is a short XML file that reports whether the test thresholds failed.&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;;

// Use example functions to generate data
import { jUnit } from &amp;#39;https://jslib.k6.io/k6-summary/0.0.2/index.js&amp;#39;;
import k6example from &amp;#39;https://raw.githubusercontent.com/grafana/k6/master/examples/thresholds_readme_example.js&amp;#39;;

export default k6example;
export const options = {
  vus: 5,
  iterations: 10,
  thresholds: {
    http_req_duration: [&amp;#39;p(95)&amp;lt;200&amp;#39;], // 95% of requests should be below 200ms
  },
};

export function handleSummary(data) {
  console.log(&amp;#39;Preparing the end-of-test summary...&amp;#39;);

  return {
    &amp;#39;junit.xml&amp;#39;: jUnit(data), // Transform summary and save it as a JUnit XML...
  };
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Output for a test that crosses a threshold looks something like this:&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;xml&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-xml&#34;&gt;&amp;lt;?xml version=&amp;#34;1.0&amp;#34;?&amp;gt;
&amp;lt;testsuites tests=&amp;#34;1&amp;#34; failures=&amp;#34;1&amp;#34;&amp;gt;
&amp;lt;testsuite name=&amp;#34;k6 thresholds&amp;#34; tests=&amp;#34;1&amp;#34; failures=&amp;#34;1&amp;#34;&amp;gt;&amp;lt;testcase name=&amp;#34;http_req_duration - p(95)&amp;amp;lt;200&amp;#34;&amp;gt;&amp;lt;failure message=&amp;#34;failed&amp;#34; /&amp;gt;&amp;lt;/testcase&amp;gt;
&amp;lt;/testsuite &amp;gt;
&amp;lt;/testsuites &amp;gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h3 id=&#34;example-send-data-to-remote-server&#34;&gt;Example: Send data to remote server&lt;/h3&gt;
&lt;p&gt;You can also send the generated reports to a remote server (over any protocol that k6 supports).&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;;

// use example function to generate data
import k6example from &amp;#39;https://raw.githubusercontent.com/grafana/k6/master/examples/thresholds_readme_example.js&amp;#39;;
export const options = { vus: 5, iterations: 10 };

export function handleSummary(data) {
  console.log(&amp;#39;Preparing the end-of-test summary...&amp;#39;);

  // Send the results to some remote server or trigger a hook
  const resp = http.post(&amp;#39;https://quickpizza.grafana.com/api/post&amp;#39;, JSON.stringify(data));
  if (resp.status != 200) {
    console.error(&amp;#39;Could not send summary, got status &amp;#39; &amp;#43; resp.status);
  }
}&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 last examples use imported helper functions.
These functions might change, so keep an eye on &lt;a href=&#34;https://jslib.k6.io/&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;jslib.k6.io&lt;/a&gt; for the latest.&lt;/p&gt;
&lt;p&gt;Of course, we always welcome &lt;a href=&#34;https://github.com/grafana/jslib.k6.io&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;PRs to the jslib&lt;/a&gt;, too!&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h2 id=&#34;summary-data-reference&#34;&gt;Summary data reference&lt;/h2&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;k6 v1.5.0 introduced a new format available for the end-of-test aggregated statistics object, designed for easier integration
with external systems and analytics pipelines.&lt;/p&gt;
&lt;p&gt;You can use the new format via the &lt;code&gt;--new-machine-readable-summary&lt;/code&gt; flag or the &lt;code&gt;K6_NEW_MACHINE_READABLE_SUMMARY&lt;/code&gt;
environment variable.&lt;/p&gt;
&lt;p&gt;The JSON Schema definition for this object can be found in the &lt;a href=&#34;https://github.com/grafana/k6-summary&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;grafana/k6-summary&lt;/a&gt;
GitHub repository. You can also use these definitions to generate types for the programming language of your preference. For example, &lt;a href=&#34;https://github.com/grafana/k6/blob/f0805f0d90c3f4d3d9d8403edb09bdc32d7314b6/internal/lib/summary/machinereadable/generate.sh&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;k6 uses it&lt;/a&gt; for Go with &lt;a href=&#34;https://github.com/grafana/cog&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;cog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This new format will become the default in k6 v2.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

Summary data includes information about your test run time and all built-in and custom metrics (including checks).&lt;/p&gt;
&lt;p&gt;All metrics are in a top-level &lt;code&gt;metrics&lt;/code&gt; object.
In this object, each metric has an object whose key is the name of the metric.
For example, if your &lt;code&gt;handleSummary()&lt;/code&gt; argument is called &lt;code&gt;data&lt;/code&gt;,
the function can access the object about the &lt;code&gt;http_req_duration&lt;/code&gt; metric at &lt;code&gt;data.metrics.http_req_duration&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&#34;metric-schema&#34;&gt;Metric schema&lt;/h3&gt;
&lt;p&gt;The following table describes the schema for the metrics object.
The specific values depend on the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/metrics/&#34;&gt;metric type&lt;/a&gt;:&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;Property&lt;/th&gt;
              &lt;th&gt;Description&lt;/th&gt;
          &lt;/tr&gt;
      &lt;/thead&gt;
      &lt;tbody&gt;
          &lt;tr&gt;
              &lt;td&gt;type&lt;/td&gt;
              &lt;td&gt;String that gives the metric type&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;contains&lt;/td&gt;
              &lt;td&gt;String that describes the data&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;values&lt;/td&gt;
              &lt;td&gt;Object with the summary metric values (properties differ for each metric type)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;thresholds&lt;/td&gt;
              &lt;td&gt;Object with info about the thresholds for the metric (if applicable)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;thresholds.{name}&lt;/td&gt;
              &lt;td&gt;Name of threshold (object)&lt;/td&gt;
          &lt;/tr&gt;
          &lt;tr&gt;
              &lt;td&gt;thresholds.{name}.ok&lt;/td&gt;
              &lt;td&gt;Whether threshold was crossed (boolean)&lt;/td&gt;
          &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/div&gt;
&lt;/section&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 change the default trend metrics with the 
    &lt;a href=&#34;/docs/k6/v2.3.x/using-k6/k6-options/reference/#summary-trend-stats&#34;&gt;&lt;code&gt;summaryTrendStats&lt;/code&gt;&lt;/a&gt; option,
the keys for the values of the trend will change accordingly.&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;

&lt;h3 id=&#34;example-summary-json&#34;&gt;Example summary JSON&lt;/h3&gt;
&lt;p&gt;To see what the summary &lt;code&gt;data&lt;/code&gt; looks like in your specific test run:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Add this to your handleSummary() function:&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;return { &amp;#39;raw-data.json&amp;#39;: JSON.stringify(data)};`&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inspect the resulting &lt;code&gt;raw-data.json&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;The following is an abridged example of how it might look:&lt;/p&gt;
&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;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;root_group&amp;#34;: {
    &amp;#34;path&amp;#34;: &amp;#34;&amp;#34;,
    &amp;#34;groups&amp;#34;: [
      // Sub-groups of the root group...
    ],
    &amp;#34;checks&amp;#34;: [
      {
        &amp;#34;passes&amp;#34;: 10,
        &amp;#34;fails&amp;#34;: 0,
        &amp;#34;name&amp;#34;: &amp;#34;check name&amp;#34;,
        &amp;#34;path&amp;#34;: &amp;#34;::check name&amp;#34;
      }
      // More checks...
    ],
    &amp;#34;name&amp;#34;: &amp;#34;&amp;#34;
  },
  &amp;#34;options&amp;#34;: {
    // Some of the global options of the k6 test run,
    // Currently only summaryTimeUnit and summaryTrendStats
  },

  &amp;#34;state&amp;#34;: {
    &amp;#34;testRunDurationMs&amp;#34;: 30898.965069
    // And information about TTY checkers
  },

  &amp;#34;metrics&amp;#34;: {
    // A map with metric and sub-metric names as the keys and objects with
    // details for the metric. These objects contain the following keys:
    //  - type: describes the metric type, e.g. counter, rate, gauge, trend
    //  - contains: what is the type of data, e.g. time, default, data
    //  - values: the specific metric values, depends on the metric type
    //  - thresholds: any thresholds defined for the metric or sub-metric
    //
    &amp;#34;http_reqs&amp;#34;: {
      &amp;#34;type&amp;#34;: &amp;#34;counter&amp;#34;,
      &amp;#34;contains&amp;#34;: &amp;#34;default&amp;#34;,
      &amp;#34;values&amp;#34;: {
        &amp;#34;count&amp;#34;: 40,
        &amp;#34;rate&amp;#34;: 19.768856959496336
      }
    },
    &amp;#34;vus&amp;#34;: {
      &amp;#34;type&amp;#34;: &amp;#34;gauge&amp;#34;,
      &amp;#34;contains&amp;#34;: &amp;#34;default&amp;#34;,
      &amp;#34;values&amp;#34;: {
        &amp;#34;value&amp;#34;: 1,
        &amp;#34;min&amp;#34;: 1,
        &amp;#34;max&amp;#34;: 5
      }
    },
    &amp;#34;http_req_duration&amp;#34;: {
      &amp;#34;type&amp;#34;: &amp;#34;trend&amp;#34;,
      &amp;#34;contains&amp;#34;: &amp;#34;time&amp;#34;,
      &amp;#34;values&amp;#34;: {
        // actual keys depend depend on summaryTrendStats

        &amp;#34;avg&amp;#34;: 268.31137452500013,
        &amp;#34;max&amp;#34;: 846.198634,
        &amp;#34;p(99.99)&amp;#34;: 846.1969478817999
        // ...
      },
      &amp;#34;thresholds&amp;#34;: {
        &amp;#34;p(95)&amp;lt;500&amp;#34;: {
          &amp;#34;ok&amp;#34;: false
        }
      }
    },
    &amp;#34;http_req_duration{staticAsset:yes}&amp;#34;: {
      // sub-metric from threshold
      &amp;#34;contains&amp;#34;: &amp;#34;time&amp;#34;,
      &amp;#34;values&amp;#34;: {
        // actual keys depend on summaryTrendStats
        &amp;#34;min&amp;#34;: 135.092841,
        &amp;#34;avg&amp;#34;: 283.67766343333335,
        &amp;#34;max&amp;#34;: 846.198634,
        &amp;#34;p(99.99)&amp;#34;: 846.1973802197999
        // ...
      },
      &amp;#34;thresholds&amp;#34;: {
        &amp;#34;p(99)&amp;lt;250&amp;#34;: {
          &amp;#34;ok&amp;#34;: false
        }
      },
      &amp;#34;type&amp;#34;: &amp;#34;trend&amp;#34;
    }
    // ...
  }
}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;custom-output-examples&#34;&gt;Custom output examples&lt;/h2&gt;
&lt;p&gt;These examples are community contributions.
We thank everyone who has shared!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/microsoftazure/load-testing-with-azure-devops-and-k6-839be039b68a&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Using the JUnit output with Azure Test Plan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://dev.to/kwidera/introducing-testrail-in-you-k6-tests-eck&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Using the JUnit output with TestRail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/geekculture/k6-custom-slack-integration-metrics-are-the-magic-of-tests-527aaf613595&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;handleSummary and custom Slack integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://docs.getxray.app/display/XRAYCLOUD/Performance&amp;#43;and&amp;#43;load&amp;#43;testing&amp;#43;with&amp;#43;k6&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;Reporting to Xray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/benc-uk/k6-reporter&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;HTML reporter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
]]></content><description>&lt;h1 id="custom-summary">Custom summary&lt;/h1>
&lt;p>With &lt;code>handleSummary()&lt;/code>, you can completely customize your end-of-test summary.
In this document, read about:&lt;/p>
&lt;ul>
&lt;li>How &lt;code>handleSummary()&lt;/code> works&lt;/li>
&lt;li>How to customize the content and output location of your summary&lt;/li>
&lt;li>The data structure of the summary object&lt;/li>
&lt;/ul>
&lt;div class="admonition admonition-note">&lt;blockquote>&lt;p class="title text-uppercase">Note&lt;/p></description></item></channel></rss>