Building a Load Testing Framework using K6.io — Using Configs and Environments (Part 2)

Building a Load Testing Framework using K6.io — Using Configs and Environments (Part 2)

In Part-1 of this article series, we had a basic k6 ultra-brief intro, created a k6 project structure, and then, wrote and ran a simple test case. Now in this article, we will shape our current k6 load-testing-framework project into something more.. (adding a target website and adding multiple configs support😉)

Adding multiple configs/environments support

Oftentimes, we need to run our test suites on different environments; say while merging PRs we can validate changes on a Staging or Dev environment, and once a release is decided, then we can validate the changes on a much more stable environment such as QA or a prod-like environment.

In this scenario, we would want to run tests, which we had run during the course of our development and it would make sense to write our tests in a creative way, such that it requires minimum changes to run our tests in different environments.

Multiple config/environment support with k6

Things to consider, when running tests in multiple environments:

- URLs
- User roles and creds (test data)
- Software version or features availability

We are gonna be using config to achieve this goal. Let's first create 2 different JSON files under directory config/, which will contain our environment-specific config data, we'll call our environments DEV and QA.

sample DEV.json file

Now that we have our config files, we need a way to initialize our code with the config details. to achieve that we will create a singleton class on startup which will serve as a single source of truth.

Enter init.js.

Let's create a file called init.js inside code/common/ directory. This file will be solely important for managing the init context, which is important for operations such as reading from the config files and loading other files into memory that might be used during runtime.

// :: SINGLETON ::
// selects a config based on (whether provided from Github actions/or from the repo)

const configPath = "../../config";

class InitFactory {

	constructor() {
		this.activeConfig = {};
		this.activeConfig.NAME = __ENV.config ? "GitHub Config" : __ENV.configFile
		this.activeConfig.PATH = configPath
		this.activeConfig.CONTENT = __ENV.config ? JSON.parse(__ENV.config) : JSON.parse(open(`${configPath}/${__ENV.configFile}`))
		
		// set user-info from GitHub only if not found in passed config/configFile
		if(typeof this.activeConfig.CONTENT.DOC_AI_USER === "undefined"){
			this.activeConfig.CONTENT.user_info = {};
			this.activeConfig.CONTENT.user_info.email = __ENV.docAiUserEmail
			this.activeConfig.CONTENT.user_info.password = __ENV.docAiUserPassword
		}
	}

	// returns the config details
	getActiveConfig() {
		return this.activeConfig
	}
}

const init = new InitFactory();

Object.freeze(init);

console.log('activeConfig NAME', init.getActiveConfig().NAME)
console.log('activeConfig CONTENT', init.getActiveConfig().CONTENT)

export default init;

code/common/init.js

Now the project structure should look like:

project directory after adding config/ and /common/*.js files

Using test.js with init.js code

import init from '../common/init.js';

// get config details
const config = init.getActiveConfig().CONTENT

const baseUrl = config.baseUrl;
const userInfo = config.user_info

Using client.js with init.js code:

import http from 'k6/http';
import init from './init.js';

// get config details
const config =  init.getActiveConfig().CONTENT
const baseUrl = config.baseUrl;

// returns an authentication token
export function login( userName, password) {

	let body = {
		"userName": userName, 
		"password": password,
		"rememberMe": true
	}

	console.log(JSON.stringify(body.userName))

	let signInResponse = http.get(`${baseUrl}/basic-auth/${userName}/${password}`, {
		headers: {
			'Content-Type': 'application/json'
		},
	})

	if(signInResponse.status !== 200){
		console.log("ERROR:", signInResponse)
	}

	return signInResponse;
}

Note: this is a dummy login request so it might fail during the test run.

additionally,

client.js - serves as reusable functions/api-calls which are related to the application under test

util.js
- serves as a place to add generic helper functions

Running the code:

Now with init.js code in place, all we have to do is pass the configFile param as an environment variable so that our framework can initialize this config and run scripts based on these global values:

k6 run --vus 10 --iterations 20 -e configFile=DEV.json --out json=report.json code/scripts/test.js

it will result in the following run:

Conclusion:

in this article, we saw how to use config files to manage running the same script code with different environments and how we can leverage this to run tests on a target website.

GitHub repo:
k6-LoadTestingFramework/Part 02 at main · far11ven/k6-LoadTestingFramework
A Load Testing framework using https://k6.io (https://github.com/grafana/k6) - far11ven/k6-LoadTestingFramework