k6

A modern load testing tool, using Go and JavaScript

"like unit testing, for performance"

k6 is a modern load testing tool, building on Load Impact's years of experience. It provides a clean, approachable JavaScript scripting API, distributed and cloud execution, and orchestration via a REST API.

Get Started    Documentation

HTTP Cookies are used by web sites and apps to store pieces of stateful information on the user's device. A servers tells the client, via a Set-Cookie HTTP header, what information it wants to be stored on the user's machine. The user's browser will store the cookie data and associate it with the hostname of the server, and for each subsequent request to that hostname it will include the stored cookie data in a Cookie header.

You can then control more specific rules for when cookie data should be sent or not, including limiting it to specific subdomains of the domain or a specific path. It's also possible to set an expiry date on the cookie, and tell the browser only to send it over encrypted (SSL/TLS) connections.

Cookies with k6

For most intents and purposes k6 will transparently manage the receiving, storage and sending of cookies as described above, so that testing of your cookie based web site or app will just work without you having to do anything special.

There are however use cases where more control over cookies is desired. In k6 you have two options, either to directly manipulate HTTP headers, or use the more ergonomic cookie API. We will go through the latter below.

Setting simple cookies

To simulate that a cookie has previously been set by a browser and is now supposed to be included in a subsequent request to the server we include the cookie in the cookies request parameter:

import http from "k6/http";

export default function() {
  http.get("https://httpbin.org/cookies", { cookies: { my_cookie: "hello world" } });
}

This will only apply the cookie for the request in question, but will not be sent for any subsequent requests. If you want to do that you have to add the cookie to a cookie jar, and by default there's a per-VU cookie jar we can interact with to set and inspect cookies:

import http from "k6/http";

export default function() {
  let jar = http.cookieJar();
  jar.set("https://httpbin.org/cookies", "my_cookie", "hello world");
  http.get("https://httpbin.org/cookies");
}

The per-VU cookie jar stores all cookies received from the server in a Set-Cookie header. You can also create "local cookie jars" that overrides the per-VU cookie jar, but more on that in a bit.

You can also specify that a cookie should be overridden if already part of the per-VU cookie jar:

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

export default function() {
  let jar = http.cookieJar();
  jar.set("https://httpbin.org/cookies", "my_cookie", "hello world");
  let cookies = { my_cookie: { value: "hello world 2", replace: true }};
  let res = http.get("https://httpbin.org/cookies", { cookies: cookies });
  check(res, {
    "cookie has correct value": (r) => r.json().cookies.my_cookie === "hello world 2",
  });
}

Accessing cookies

To see which cookies were set for a particular response we can look in the cookies property of the response object:

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

export default function() {
  let res = http.get("https://httpbin.org/cookies/set?my_cookie=hello%20world", { redirects: 0 });
  check(res, {
    "has cookie 'my_cookie'": (r) => r.cookies.my_cookie.length > 0,
    "cookie has correct value": (r) => r.cookies.my_cookie[0].value === "hello world"
  });
}

The response object's cookies property is a map where the key is the cookie name and the value is an array of reponse cookie objects (see below for description). It is an array to support multiple cookies having the same name (but different domain and/or path attributes), which is part of RFC6265.

Properties of a response cookie object

A response cookie object contains the following properties:

  • name (string): the name of the cookie
  • value (string): the value of the cookie
  • domain (string): domain deciding what hostnames this cookie should be sent to
  • path (string): limiting the cookie to only be sent if the path of the request matches this value
  • expires (number): when the cookie expires, is no longer valid and deleted from the cookie jar, expressed as number of milliseconds elapsed since 1 January 1970 00:00:00 UTC
  • max_age (number): used for the same purpose as expires but defined as a the number of seconds a cookie will be valid (they both exist because of evolving standards and varying browser support)
  • secure (boolean): if true, cookie will only be sent over an encrypted (SSL/TLS) connection
  • http_only (boolean): if true, cookie would not be exposed to JavaScript in a browser environment

Inspecting cookie in jar

To see which cookies are set, and stored in the cookie jar, for a particular URL we can use the cookieForURL() method of the cookie jar object:

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

export default function() {
  let res = http.get("https://httpbin.org/cookies/set?my_cookie=hello%20world", { redirects: 0 });
  let jar = http.cookieJar();
  let cookies = jar.cookiesForURL("http://httpbin.org/");
  check(res, {
    "has cookie 'my_cookie'": (r) => cookies.my_cookie.length > 0,
    "cookie has correct value": (r) => cookies.my_cookie[0] === "hello world"
  });
}

The cookies object returned by the jar's cookiesForURL() method is a map where the key is the cookie name and the value is an array of cookie values (strings). It is an array to support multiple cookies having the same name (but different domain and/or path attributes), which is part of RFC6265.

Setting advanced cookies with attributes

To set cookies that more tightly controls the behavior of the cookie we must add the cookie to a cookie jar. An example:

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

export default function() {
  let jar = http.cookieJar();
  jar.set("https://httpbin.org/cookies", "my_cookie", "hello world", { domain: "httpbin.org", path: "/cookies", secure: true, max_age: 600 });
  let res = http.get("https://httpbin.org/cookies");
  check(res, {
    "has status 200": (r) => r.status === 200,
    "has cookie 'my_cookie'": (r) => r.json().cookies.my_cookie !== null,
    "cookie has correct value": (r) => r.json().cookies.my_cookie == "hello world"
  });
}

Local cookie jars

Besides the per-VU cookie jar you can also create local cookie jars that can override the per-VU cookie jar on a per-request basis. An example:

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

export default function() {
  let jar = new http.CookieJar();

  // Add cookie to local jar
  jar.set("https://httpbin.org/cookies", "my_cookie", "hello world", { domain: "httpbin.org", path: "/cookies", secure: true, max_age: 600 });

  // Override per-VU jar with local jar for the following request
  let res = http.get("https://httpbin.org/cookies", { jar: jar });
  check(res, {
    "has status 200": (r) => r.status === 200,
    "has cookie 'my_cookie'": (r) => r.json().cookies.my_cookie !== null,
    "cookie has correct value": (r) => r.json().cookies.my_cookie == "hello world"
  });
}

Cookies