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