Unique login for each virtual User

Hello Team/Community,

I was trying to have unique login token for each user (virtual user) and then make a HTTP.GET request to an URL with request header as loginToken . How can i achieve that using k6? I tried sharedArray but facing challenges. Is there any related article or logic for same please?

I have json file where i have stored username and password
I have a js file where i want to get the access token for each user
And then pass those accessToken as request header for http.get request call

Hi @Harshsahay

Welcome to the community forum :wave:

For your case, I would recommend taking advantage of the k6 test lifecycle. You’ll also find some examples in k6-learn/Setup-and-Teardown-functions.md.

You can have each VU do the login and retrieve its token during the setup() phase, which is execute once per VU. There is an example in the blog How to Load Test OAuth secured APIs with k6? that can serve as starting point. Under the last section of the blog “Test Script Template with OAuth Authentication” you’ll see that we can authenticate the user in the setup() and use the token from there on in the VU code.

If you already have a list of users and passwords in a JSON file, you can indeed use the SharedArray to load those, and have each VU can retrieve its unique data. Then during the setup use the user data to retrieve the token.

We have a similar example for that part in k6-learn/Using-execution-context-variables. Mostly you’ll need to access each array position using VU id.

import { vu } from 'k6/execution';
...
  const username = data[vu.idInTest - 1];

I hope this helps.

Cheers!

hello ,
Thanks for your quick reply. I didn’t see it as i was on vacation. Well this helped me
But on top of this i had one more scenario to discuss.

So i have a api where we make http request with request body as username and password

Then the response body for that http request has a accessToken and refreshToken which we then want to pass to actual URL which we want to do load test

So
1 - we make http request to login token with username and password from csv
2- now we pass this accessToken to each virtual user and then do load test for the url.

The solution u provided will work on this scenario too?

I tried using shared Array to get username and password in login.js file and return accessToken from that js file to my main.js file. But every virtual user was having same access Token rather than multiple. Whereas if i run console.log in login.js file , each iteration shows different accessToken. It may be my logic which is not working . I can play around that. But in k6 is there any help i can get from ?

Hi @Harshsahay

The example should work, though you will need to do the login either in the VU code, so each VU has their own token, or create a function that the VU code invokes.

See: HTTP Authentication and OAuth Authentication for some examples.

How to correlate dynamic data will also be useful, as you are getting the token for each VU after the login request.

You can find a similar example in k6-learn.

If it’s still no working for you, kindly share the sanitized script you are working with, and I’ll test it locally to provide a more tailored advice.

Cheers!

Hello @eyeveebe Thanks for the help above

So i followed all those steps:

1 - I used sharedArray to import data from json file where i had my username and password
2 - I used setup() where i defined my http request which has username and password as ID and password

this were my test users :slight_smile:

{

    "users": [
  
      { "username": "testuser1907202104@wtr-test.net", "password": "test1234" },
  
      { "username": "foo.aws1665706462247@email.com", "password": "test1234" }
  
    ]
  
  }

Now i import this file in my js file using

const data = new SharedArray('some data name', function () {

  return JSON.parse(open('./checkdata.json')).users;

});

I defined :

export const options = {
    scenarios: {
      login: {
        executor: 'per-vu-iterations',
        vus: data.length,
        maxDuration: '1h30m',
      },
    },
  };
export function setup() {

  const importData = data[vu.idInTest - 1];
  const postUrl='https://apie-eu-west-1-qa.ecom.*******.com/authentication-qa/v4/authentication/token'
 
  const payload= JSON.stringify({
      username    : importData.username,
      password    :importData.password,
      clientId    :'IOS_APP',
      });
  const params= {
              headers:{
                  'Content-Type':'application/json',
              },
          };

          const res=http.post(postUrl,payload,params);
          const body = res.json();
         const token = body.tokenResponse.accessToken;
          return token;

}

SO i am expecting this command to export token for me which i will use in another HTTP call
Also the request am sending in setup() , i am expecting unique login for each virtual user based on number of users in json file

Now ,

export default function (value) {
console.log(value);
}
I was trying to print the accessToken which i return from setup() but this is giving me an error

ERRO[0003] TypeError: Cannot read property 'username' of undefined
	at setup (file:///Users//loaddemo/setupTearParam.js:28:31(15))
	at native  hint="script exception"

Now if i use the same logic directly inside default function , i am getting the expected refresh token , but then it is not good and looks complicated.

So can u help me on this.
Also let me know if there is a way to connect with you on same for discussion. thanks in advance

Also to mention inside function setup() , if i pass username and password value directly rather than sharedArray , i am getting the response but not with sharedArray

Also You mentioned to use setup and Tear phase but will setup Phase help me to have unique access Token for each Virtual user? I read the document and it seems like it will be one time process and same accessToken can be shared with all virtual Users but my requirement is if i am having 2 username then 2 virtual user should have unique accessToken rather than same for all virtual user

Hi @Harshsahay

I created an example that I hope can help. Based on our public test API that accepts bearer tokens.

import http from 'k6/http';
import exec from 'k6/execution';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';

const sharedData = new SharedArray("Shared Logins", function () {
    let data = papaparse.parse(open('test-users.csv'), { header: true }).data;
    return data;
});

let vuToken;
let request_headers;

export const options = {
    vus: 2,
    iterations: 10,
};

/**
 * Gets user token from username and password
 * @function
 * @param  {string} username - Username
 * @param  {string} password - Password
 */
export function getToken(username, password) {
    let data = {
        username: username,
        password: password,
    };
    const response = http.post('https://test-api.k6.io/auth/token/login/', JSON.stringify(data), {
        headers: { 'Content-Type': 'application/json' },
    });
    return response.json('access');
}

export default function () {

    if (vuToken === undefined) {
        vuToken = getToken(sharedData[exec.vu.idInTest - 1].username, sharedData[exec.vu.idInTest - 1].password)
        request_headers = {
            'content-type': "application/json",
            'Authorization': `Bearer ${vuToken}`
        };
    }

    // create a crocodrile
    const croc = {
        name: 'My Croc',
        sex: 'F',
        date_of_birth: '2019-01-01',
    };
    console.log("request_headers: ", request_headers);
    let response = http.post('https://test-api.k6.io/my/crocodiles/', JSON.stringify(croc), { headers: request_headers });
    check(response, {
        'is status 201': (r) => r.status === 201,
    });
    // get private crocrodile list
    response = http.get('https://test-api.k6.io/my/crocodiles/', { headers: request_headers });
    console.log('My Crocodiles: ', response.json());
}

This with a file test-users.csv which contains a couple of users:

username,password
user1@example.com,superCroc2021
user2@example.com,superCroc2019

Would result in:

running (00m04.4s), 0/2 VUs, 10 complete and 0 interrupted iterations
default ✓ [======================================] 2 VUs  00m04.4s/10m0s  10/10 shared iters

     ✓ is status 201

     checks.........................: 100.00% ✓ 10       ✗ 0  
     data_received..................: 26 kB   5.9 kB/s
     data_sent......................: 9.6 kB  2.2 kB/s
     http_req_blocked...............: avg=19.19ms  min=4µs      med=8.5µs    max=212.24ms p(90)=14.9µs   p(95)=199.48ms
     http_req_connecting............: avg=9.31ms   min=0s       med=0s       max=102.52ms p(90)=0s       p(95)=97.31ms 
     http_req_duration..............: avg=374.8ms  min=115.44ms med=126.95ms max=2.92s    p(90)=150.16ms p(95)=2.67s   
       { expected_response:true }...: avg=374.8ms  min=115.44ms med=126.95ms max=2.92s    p(90)=150.16ms p(95)=2.67s   
     http_req_failed................: 0.00%   ✓ 0        ✗ 22 
     http_req_receiving.............: avg=147.18µs min=65µs     med=146µs    max=282µs    p(90)=211.6µs  p(95)=256.79µs
     http_req_sending...............: avg=48.13µs  min=26µs     med=45.5µs   max=96µs     p(90)=71.7µs   p(95)=74.84µs 
     http_req_tls_handshaking.......: avg=9.77ms   min=0s       med=0s       max=108.68ms p(90)=0s       p(95)=101.02ms
     http_req_waiting...............: avg=374.61ms min=115.21ms med=126.79ms max=2.92s    p(90)=149.93ms p(95)=2.67s   
     http_reqs......................: 22      4.99944/s
     iteration_duration.............: avg=868.36ms min=240.94ms med=249.92ms max=3.39s    p(90)=3.29s    p(95)=3.34s   
     iterations.....................: 10      2.272473/s
     vus............................: 2       min=2      max=2
     vus_max........................: 2       min=2      max=2

Each VU will create their token on the first iteration and reuse it. That should work if the tokens do not expire during the test.

You cannot create a token in the setup() function. As per k6 lifecycle, the setup is called only once. Thus cannot contain code that should be executed per VU.

I hope this helps. Thanks for your patience.

Cheers!

Hey @eyeveebee ,
How I have list of 1000 users and when I am retrieving the unique user with 25VU, I see sometime user repetition. How can i avoid that.
I have a use case where 100 - 500 unique users can login at the same moment and have their unique session for api call, I don’t want the duplicate session for same user at the same time.

I tried this solution but if i increase the user from 10 to 20 or 30 it is not able to find the other unique user after some iteration.

const uniqueUsersSet = new Set();

export default async function () {
	let login, password;
	do {
		login = sharedData[exec.vu.idInTest - 1].login;
		password = sharedData[exec.vu.idInTest - 1].password;
	} while (uniqueUsersSet.has(login));
	uniqueUsersSet.add(login);

	// eslint-disable-next-line no-undef
	console.log(`retrieved user ${login} : ${password}`);
}

I think Set is also not storing the unique users values globally to test scope independent of VU and iterations

Below seems to works

const {login, password} = sharedData[exec.scenario.iterationInTest];
1 Like

@eyeveebee
I have 2500 users in my sharedData.
When I am running below scenario, I don’t see any error while user reached 150 and for steady 150 users till 3rd stage.

{
		executor: 'ramping-vus',
		startVUs: 0,
		stages: [
			{duration: '1m', target: 75},
			{duration: '3m', target: 150},
			{duration: '3m', target: 150},
			{duration: '3m', target: 0},
		]
}

but when it enters to last stage it started throwing an error on below line

const {login, password} = sharedData[exec.scenario.iterationInTest];

running (07m03.9s), 150/150 VUs, 2330 complete and 0 interrupted iterations
load_test   [  71% ] 150/150 VUs  07m03.9s/10m00.0s

running (07m04.9s), 150/150 VUs, 2333 complete and 0 interrupted iterations
load_test   [  71% ] 150/150 VUs  07m04.9s/10m00.0s

running (07m05.9s), 149/150 VUs, 2351 complete and 0 interrupted iterations
load_test   [  71% ] 149/150 VUs  07m05.9s/10m00.0s

time="2024-02-05T11:54:54Z" level=error msg="Uncaught (in promise) TypeError: Value is not object coercible\n\tat Z (../main.mjs:81:38(7))\n" executor=ramping-vus scenario=load_test

few more logs

running (8m27.2s), 049/130 VUs, 2440 complete and 1 interrupted iterations
load_test   [  94% ] 049/130 VUs  8m27.2s/9m00.0s

running (8m28.2s), 049/130 VUs, 2445 complete and 1 interrupted iterations
load_test   [  94% ] 049/130 VUs  8m28.2s/9m00.0s

running (8m29.2s), 048/130 VUs, 2448 complete and 1 interrupted iterations
load_test   [  94% ] 048/130 VUs  8m29.2s/9m00.0s

running (8m30.2s), 048/130 VUs, 2450 complete and 1 interrupted iterations
load_test   [  94% ] 048/130 VUs  8m30.2s/9m00.0s

running (8m31.2s), 047/130 VUs, 2451 complete and 1 interrupted iterations
load_test   [  95% ] 047/130 VUs  8m31.2s/9m00.0s

running (8m32.2s), 047/130 VUs, 2452 complete and 1 interrupted iterations
load_test   [  95% ] 047/130 VUs  8m32.2s/9m00.0s
time="2024-02-05T14:22:30Z" level=error msg="Uncaught (in promise) TypeError: Value is not object coercible\n\tat Z (../main.mjs:81:38(7))\n" executor=ramping-vus scenario=load_test
time="2024-02-05T14:22:30Z" level=error msg="Uncaught (in promise) TypeError: Value is not object coercible\n\tat Z (../main.mjs:81:38(7))\n" executor=ramping-vus scenario=load_test
time="2024-02-05T14:22:30Z" level=error msg="Uncaught (in promise) TypeError: Value is not object coercible\n\tat Z (../main.mjs:81:38(7))\n" executor=ramping-vus scenario=load_test
time="2024-02-05T14:22:30Z" level=error msg="Uncaught (in promise) TypeError: Value is not object coercible\n\tat Z (../main.mjs:81:38(7))\n" executor=ramping-vus scenario=load_test