Skip to content

Instantly share code, notes, and snippets.

@zanonnicola
Created November 16, 2020 20:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zanonnicola/41d0d983f6b9cb4787278af53bfc659a to your computer and use it in GitHub Desktop.
Save zanonnicola/41d0d983f6b9cb4787278af53bfc659a to your computer and use it in GitHub Desktop.
K6 Load test example
import { group, sleep, check } from "k6";
import { parseHTML } from 'k6/html';
import { Trend } from "k6/metrics";
import http from "k6/http";
import { randomIntBetween, uuidv4 } from "https://jslib.k6.io/k6-utils/1.0.0/index.js";
const usersToken = JSON.parse(open("./users.json"));
const URL = "https://myapp.com";
const TEST_NAME = "First Test - simple user flow";
function createRequestsObject(staticPaths, url, params) {
return staticPaths.map((url) => {
const key = url.slice(1);
return {
[key]: {
method: "GET",
url,
params: Object.assign({}, params, { tags: { type: 'static-asset' } }),
}
}
}).reduce(((r, c) => Object.assign(r, c)), {});
}
function generateCorrelationId(runId, userId) {
return `k6-${runId}-${userId}-${uuidv4()}`;
}
function generateTestId(testName) {
return testName.toLowerCase().replace(/\s+/g, "");
}
/*
*
* START K6
*
*/
export let options = {
stages: [
{ duration: "1m", target: 50 },
{ duration: "3m", target: 50 },
{ duration: "1m", target: 0 },
],
thresholds: {
error_rate: ["rate < 0.9"],
http_req_duration: ["p(95)<2000"],
"time_to_first_byte{type:static-assets}": ["p(95)<500"],
},
ext: {
loadimpact: {
projectID: 3492606,
name: TEST_NAME,
distribution: {
"amazon:us:ashburn": { loadZone: "amazon:us:ashburn", percent: 100 }
}
}
}
};
const timeToFirstByte = new Trend("time_to_first_byte", true);
export function setup() {
// Set up code that we can pass down results to the main function
// Called only once per test
// Return any data if you want to be use it in the next steps
// Collecting static resources paths ( Doesn't matter which user, static assets will be the same)
const res = http.get(URL, {
cookies: Object.assign({}, usersToken[0].cookies),
headers: {
"X-Requested-With": "XMLHttpRequest",
"x-csrf-token": usersToken[0].cookies["XSRF-TOKEN"],
"Accept": "application/json",
}
});
// Extracting the paths from <link /> elements
// Vue uses link prefetch with all the static assets
const doc = parseHTML(res.body);
const assets = doc.find('link');
const staticAssetsPaths = [];
assets.each((idx, el) => {
const path = el.getAttribute("href");
if (
path.startsWith("/css") ||
path.startsWith("/js") ||
path.startsWith("/fonts")
) {
staticAssetsPaths.push(URL + path);
}
});
return staticAssetsPaths;
}
export default function (staticAssetsPaths) {
// You can't import any files here. Use the global scope to import modules or data.
// The below code will be run in loop for the amount of Virtual Users that we have specified.
// Select random user
const user = Math.floor(Math.random() * usersToken.length);
// Getting and setting the required cookies for auth calls
const { cookies, id } = usersToken[user];
// This will set the cookies for all the requests
const jar = http.cookieJar();
Object.entries(cookies).forEach(([key, value]) => {
jar.set(URL, key, value);
});
// Request headers
const params = () => ({
headers: {
"X-Requested-With": "XMLHttpRequest",
"x-csrf-token": cookies["XSRF-TOKEN"],
"Accept": "application/json",
"x-correlationid": generateCorrelationId(generateTestId(TEST_NAME), id) // We can easilly track and identify the requests in Kibana/NewRelic/Dynatrace for example
}
}); // We need to pass params on every requests
group("HomePage", () => {
const requests = {
"user/preference": {
method: "GET",
url: `${URL}/user/preference`,
params: params(),
},
"some/endpoint": {
method: "GET",
url: `${URL}/some/endpoint`,
params: params(),
},
"another/one": {
method: "GET",
url: `${URL}/another/one`,
params: params(),
}
};
// Will run requests in parallel (default: 6 at once. Similar to browser behaviour)
const responses = http.batch(requests);
Object.keys(requests).forEach((reqKey) => {
check(responses[reqKey], {
"Response status was 200": res => res.status === 200,
});
timeToFirstByte.add(responses[reqKey].timings.waiting, { ttfbURL: responses[reqKey].url }); // Use a custom metric
});
group("Static assets", function () {
const staticAssetsResponses = http.batch(createRequestsObject(staticAssetsPaths, URL, params)); // Helper function to get the abs URL for each asset involved in the test
Object.entries(staticAssetsResponses).forEach(([key, value]) => {
check(value, {
"Static response status was 200": res => res.status === 200,
});
timeToFirstByte.add(value.timings.waiting, { ttfbURL: value.url, type: "static-assets" });
});
});
});
// User will probably spend some time looking at the Home page
sleep(randomIntBetween(3, 10));
};
export function teardown(data) {
// Teardown code
// Called only once per test
// Data will be whatever is returned in the setup function
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment