Efficiently Tracking Frontend Performance with xk6-browser and Slack Integration
Published: 2023-02-21 12:13:48


![k6-slack](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l90vtld931w9ivig2ct7.png)

In this article, we will discover the process of sending custom metrics, provided to us through the [xk6-browser](https://k6.io/docs/javascript-api/xk6-browser/), in order to gain a better understanding of our performance tests. Through the integration of these metrics, we will be able to create more tailored Slack messages that will help to manage our rule sets. Furthermore, this process will give us a more detailed insight into our performance tests, with the custom metrics helping to provide granular data about our performance tests and the associated rule sets. This will allow us to identify any issues that may arise, and take the necessary action to ensure that our performance tests are running as efficiently as possible.

### Understanding the Protocol Level Testing

xk6-browser provides us with the power of browser automation and end-to-end web testing, all while supporting core k6 features. It adds a set of APIs at the browser-level to interact with browsers, enabling developers to collect frontend performance metrics as part of their k6 tests. This is a powerful tool for anyone looking to develop web applications with an emphasis on performance and reliability.


> The metrics are still subject to change as k6 tries to align them to the standard web vitals.


On the other hand, xk6-browser was designed to provide rough compatibility with the Playwright API, so developers don't need to learn an entirely new API to take advantage of the browser automation and web testing capabilities that xk6-browser provides. This means that developers can easily transition to using xk6-browser, taking advantage of the powerful features it has to offer.


### Setting Up xk6-browser

In the latest development of k6, you don't need to build xk6-browser. It has already been included in the k6 package. You need to upgrade k6 to v.43.0 version to implement it like the k6 package.

```javascript
import { chromium } from "k6/experimental/browser"
```

And also you have to set env var `K6_BROWSER_ENABLED=true` that runs browser tests.

> **Experimental module, use at your own risk**

> There are still changes, please follow the releases regularly.



### Write first code of xk6-browser

Let's create a simple example with xk6-browser. This script navigates to the login page, enters login credentials, submits the form and checks if the login was successful. It also closes the browser and page after the test was completed.

```javascript
import { check } from "k6";
import { chromium } from "k6/experimental/browser";
import { sendSlackMessages } from "./slackHelper.js";

export default async function() {
    const browser = chromium.launch({
      headless: __ENV.XK6_HEADLESS ? true : false,
    });
    const context = browser.newContext();
    const page = context.newPage();
  
    try {
      // Goto front page, find login link and click it
      await page.goto('https://test.k6.io/', { waitUntil: 'networkidle' });
      await Promise.all([
        page.waitForNavigation(),
        page.locator('a[href="/my_messages.php"]').click(),
      ]);
      // Enter login credentials and login
      page.locator('input[name="login"]').type('admin');
      page.locator('input[name="password"]').type('123');
      // We expect the form submission to trigger a navigation, so to prevent a
      // race condition, setup a waiter concurrently while waiting for the click
      // to resolve.
      await Promise.all([
        page.waitForNavigation(),
        page.locator('input[type="submit"]').click(),
      ]);
      check(page, {
        'header': page.locator('h2').textContent() == 'Welcome, admin!',
      });
    } finally {
      page.close();
      browser.close();
   }
}
```


### Magic of Metrics: handleSummary()

One of the essential features of k6 is that we can get all the metrics we want. You can get it as stdout, XML, or JSON if you want. For this, we need to use the handleSummary() function. You can also use this function with xk6-browser. It provides us with all the metrics in JSON form after the test. Since we have these metrics, we can use them however we want. Especially if you are measuring the web performance of your site and want to follow it on slack, xk6-browser provides us with the metrics we want.



```javascript
export function handleSummary(data) {
  return {
    stdout: textSummary(data, { indent: "→", enableColors: true }),
  };
}
```

You will see the output below, it provides us with all the values as stdout.


![k6-metrics-output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/blh649a5puycxis4zxfg.png)

You can create many more custom metrics for handleSummary, remove what you want and add any metrics you want. For all this, you can check [the document](https://k6.io/docs/results-output/end-of-test/custom-summary/) where handleSummary is explained in detail.


### Combining Slack with k6 Metrics

We know how to custom-pull our metrics. Now we can create a custom slack message. 

> To customize Slack messages, I use the Block Kit Builder page offered by slack, where you can customize your messages as you wish. Other than that, all we need is a slack bot token. You can get bot tokens from an existing app or by creating a new app. You can examine [this page](https://api.slack.com/authentication/token-types) in detail.

You can follow the steps below to create the function to that we will send the Slack message;
* First of all, we determine the metrics we will pull and access them from the data object (handleSummary provides us with this data object).
* Then we create our custom message and add the metrics we have taken into our message.
* Finally, we send our message to the channel we want with the slack chat.postMessage method.

```javascript
import http from "k6/http";
import { check } from "k6";

// Read a token from the environment variables
const token = process.env.SLACK_TOKEN;

// Initialize
export function sendSlackMessages(data) {
  // Get the average values for each metric
  let browserDomContent =
    data.metrics.browser_dom_content_loaded.values["avg"].toFixed(2);
  let browserFirstPaint =
    data.metrics.browser_first_paint.values["avg"].toFixed(2);
  let browserFirstContentfulPaint =
    data.metrics.browser_first_contentful_paint.values["avg"].toFixed(2);
  let browserFirstMeaningfulPaint =
    data.metrics.browser_first_meaningful_paint.values["avg"].toFixed(2);
  let browserLoaded = data.metrics.browser_loaded.values["avg"].toFixed(2);
  let testRunDuration = Math.floor(
    (data.state["testRunDurationMs"] / 1000) % 60
  );

  // Create the payload for the Slack message
  const payload = {
    channel: "random",
    attachments: [
      {
        color: "#9205a2",
        blocks: [
          {
            type: "section",
            text: {
              type: "mrkdwn",
              text:
                "*<reportlinkhere.com|" + testCount + "test passed in " +
                testRunDuration +
                " sec>*",
            },
          },
          {
            type: "section",
            fields: [
              {
                type: "mrkdwn",
                text:
                  "*Browser Dom Content Load:*\n :large_green_circle: " +
                  browserDomContent +
                  " ms",
              },
              {
                type: "mrkdwn",
                text:
                  "*Browser First Paint:*\n :large_green_circle: " +
                  browserFirstPaint +
                  " ms",
              },
              {
                type: "mrkdwn",
                text:
                  "*Browser First Contentful Paint:*\n :large_green_circle: " +
                  browserFirstContentfulPaint +
                  " ms",
              },
              {
                type: "mrkdwn",
                text:
                  "*Browser First Meaningful Paint:*\n :large_green_circle: " +
                  browserFirstMeaningfulPaint +
                  " ms",
              },
              {
                type: "mrkdwn",
                text:
                  "*Browser Loaded:*\n :large_green_circle: " +
                  browserLoaded +
                  " ms",
              },
            ],
          },
          {
            type: "divider",
          },
          {
            type: "context",
            elements: [
              {
                type: "image",
                image_url:
                  "https://raw.githubusercontent.com/k6io/awesome-k6/master/assets/bert.png",
                alt_text: "k6 croc",
              },
              {
                type: "mrkdwn",
                text: "*Crocodile* has approved this message.",
              },
            ],
          },
        ],
      },
    ],
  };

  // Send a message to Slack
  const res = http.post(
    "https://slack.com/api/chat.postMessage",
    JSON.stringify(payload),
    {
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + token,
      },
    }
  );

  // Check the response
  check(res, {
    "is status 200": (r) => r.status === 200,
  });
}

```

Now that we've created our function, let's see how to use it in handleSummary.

```javascript
export function handleSummary(data) {
  sendSlackMessages(data);
  return {
    stdout: textSummary(data, { indent: "→", enableColors: true }),
  };
}
```

We are now ready to run our test. 

```bash
k6 run k6-test.js
```

Let's check slack, yes your message has dropped on our channel, we can see the message that we shot and added as a custom. Also, since it is approved by the crocodile, we can use it easily. :) 


![k6-custom-slack-message](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ysgun4zgxsjgdy1kzcma.png)



From here on, it depends on our imagination and requirements. You can edit which metrics you want to use, and you can even create an alert mechanism according to the rules of [Core Web Vitals](https://web.dev/vitals/#core-web-vitals).