How to configure a single authentication for multiple scenarios

Hi,

I’m using the K6 scenarios to send multiple API requests that goes to 3 different pages. I want to do a load test for all 3 pages at once.

So I configured a scenario as below. and I set the authentication with in the function of the executed scenario. With this approach the authentication call is getting executed for all 3 calls separately. ( 3 times)

How can I configure a single authentication call that could be used for all the 3 scenarios? (I want to make the authentication with dynamic users, but once a token is generated that token can be shared for all 3 scenarios)

import http from 'k6/http';
import {check} from 'k6';
import generateAccessToken from '../helpers/generateAccessToken.js';
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import {SharedArray} from "k6/data";
import { Trend } from 'k6/metrics';
const waiting_trend = new Trend('waiting_time(ms)');
const receiving_trend = new Trend('receiving_time(ms)');
const sending_trend = new Trend('sending_time(ms)');

const url = __ENV.GATEWAY_URL ? __ENV.GATEWAY_URL : 'https://gateway.stg.com';
const programPK = __ENV.PROGRAM_PK ? __ENV.PROGRAM_PK : 1;
const datafile = __ENV.DATA_FILE ? __ENV.DATA_FILE : './../data/users.csv';
const TEST_2_RUN = __ENV.TEST_2_RUN ? __ENV.TEST_2_RUN : 'smoke';
let Execution = TEST_2_RUN;
let ExecutionOptions_Scenarios;
let ExecutionOptions_Thresholds;
const ExecutionType = {
    load:   'load',
    smoke:  'smoke'
}

const sharedData = new SharedArray('Logins', function () {
    return papaparse.parse(open(datafile), {header: true}).data;
});

const thresholdsValues = JSON.parse(open(__ENV.MY_CONFIG_FILE  ? __ENV.MY_CONFIG_FILE : './../config/common_thresholds_config.json'));

switch(Execution) {
    case ExecutionType.smoke:
        ExecutionOptions_Scenarios = {
            Profile: {
                executor: 'shared-iterations',
                exec: 'S01_Common_Page',
                vus: 5,
                iterations: 10,
                maxDuration: '30s',
                env: { PAGE: 'profile'}
            },
            Recent_Activity: {
                executor: 'shared-iterations',
                exec: 'S01_Common_Page',
                vus: 5,
                iterations: 10,
                maxDuration: '30s',
                env: { PAGE: 'recent_activity'}
            },
            Follow: {
                executor: 'shared-iterations',
                exec: 'S01_Common_Page',
                vus: 5,
                iterations: 10,
                maxDuration: '30s',
                env: { PAGE: 'follow'}
            }
        };
        ExecutionOptions_Thresholds = thresholdsValues.Program.smoke_thresholds;
        break;
    case ExecutionType.load:
        ExecutionOptions_Scenarios = {
            Profile: {
                executor: 'ramping-vus',
                exec: 'S01_Common_Page',
                stages: [
                    { target: 50, duration: '2m' },
                    { target: 50, duration: '10m' },
                    { target: 0, duration: '1m' },
                ],
                env: { PAGE: 'profile'}
            },
            Recent_Activity: {
                executor: 'ramping-vus',
                exec: 'S01_Common_Page',
                stages: [
                    { target: 50, duration: '2m' },
                    { target: 50, duration: '10m' },
                    { target: 0, duration: '1m' },
                ],
                env: { PAGE: 'recent_activity'}
            },
            Follow: {
                executor: 'ramping-vus',
                exec: 'S01_Common_Page',
                stages: [
                    { target: 50, duration: '2m' },
                    { target: 50, duration: '10m' },
                    { target: 0, duration: '1m' },
                ],
                env: { PAGE: 'follow'}
            }
        };
        ExecutionOptions_Thresholds = thresholdsValues.Program.load_thresholds;
        break;
}

export let options = {
    scenarios: ExecutionOptions_Scenarios,
    thresholds: ExecutionOptions_Thresholds,
    hosts: thresholdsValues.DNS_Resolve,
};


// Define a Common Scenario
export function S01_Common_Page () {
    let randomUser = sharedData[Math.floor(Math.random() * sharedData.length)]

    const membersList = Array.from({length: 20}, () => sharedData[Math.floor(Math.random() * sharedData.length)].USERNAME );

    // Profile Queries
    const memberProfile_query = memberProfileQuery(randomUser.USERNAME, programPK);
    const newsfeed_profile_query = newsFeedProfileQuery(10, randomUser.USERNAME);
    // const members_query = membersQuery(JSON.stringify(membersList), programPK);

    // Recent_Activity Queries
    const memberDetails_query = memberDetailsQuery(randomUser.USERNAME, programPK);
    const memberReports_query = memberReportsQuery(randomUser.USERNAME, programPK);
    const memberGroups_query = memberGroupsQuery(randomUser.USERNAME, programPK);
    const newsfeed_query = newsFeedQuery(7);
    const members_query = membersQuery(JSON.stringify(membersList), programPK);
    const activeProgramTheme_query = activeProgramThemeQuery();
    const isNewsfeedAdmin_query = isNewsfeedAdminQuery();
    const newsfeedConfigurationForProgram_query = newsfeedConfigurationForProgramQuery();
    const viewer_query = viewerQuery();
    const menus_query = menusQuery();
    const newsfeedConfig_query = newsfeedConfigQuery('en-US');
    const activeProgramThemeLogo_query = activeProgramThemeLogoQuery();
    const GetProgramPrimaryHierarchyInfo_query = GetProgramPrimaryHierarchyInfoQuery(programPK);

    // Follow Queries
    const memberFollow_query = memberFollowQuery(randomUser.USERNAME, programPK,100);
    const membersToFollow_query = membersToFollowQuery(randomUser.USERNAME, programPK,10);

    const authToken = __ENV.AUTH_TOKEN ? __ENV.AUTH_TOKEN : generateAccessToken(randomUser.USERNAME, randomUser.PASSWORD, programPK);

    if( authToken ){
        if(__ENV.PAGE == 'profile'){
            requestExecutor(newsfeed_profile_query, 'newsfeedProfileEvents', authToken);
            requestExecutor(memberProfile_query, 'memberProfile', authToken);
            requestExecutor(members_query, 'members', authToken);
        }if(__ENV.PAGE == 'recent_activity'){
            requestExecutor(activeProgramTheme_query, 'activeProgramTheme',authToken);
            requestExecutor(isNewsfeedAdmin_query, 'isNewsfeedAdmin',authToken);
            requestExecutor(newsfeedConfigurationForProgram_query, 'newsfeedConfigurationForProgram',authToken);
            requestExecutor(viewer_query, 'viewer',authToken);
            requestExecutor(menus_query, 'menu',authToken);
            requestExecutor(newsfeedConfig_query, 'newsfeedConfig',authToken);
            requestExecutor(newsfeed_query, 'newsfeedEvents_Recent',authToken);
            requestExecutor(memberDetails_query, 'memberDetails',authToken);
            requestExecutor(memberReports_query, 'memberReports',authToken);
            requestExecutor(memberGroups_query, 'memberGroups',authToken);
            requestExecutor(members_query, 'members',authToken);
            requestExecutor(activeProgramThemeLogo_query, 'activeProgramThemeLogo',authToken);
            requestExecutor(GetProgramPrimaryHierarchyInfo_query, 'GetProgramPrimaryHierarchyInfo',authToken);
        }if(__ENV.PAGE == 'follow'){
            requestExecutor(memberFollow_query, 'memberFollow', authToken);
            requestExecutor(membersToFollow_query, 'membersToFollow', authToken);
            requestExecutor(memberFollow_query, 'memberFollow', authToken);
        }
    }
}

export function requestExecutor(query, tag, authToken) {

    const headers = {
        'Content-Type': 'application/json',
        Authorization: 'Bearer ' + authToken
    };
    const res = http.post(url, JSON.stringify({query: query}), {headers: headers, tags: {query_tag: tag}});
    // Additional breakdown on http_req_duration
    waiting_trend.add(res.timings.waiting);
    sending_trend.add(res.timings.sending);
    receiving_trend.add(res.timings.receiving);

    check(res, {
        'Status code 200 OK ': (r)=> r.body && r.status === 200,
        'Check the service is returning data': (r)=> r.body && res.headers['Content-Type'].includes('application/json') && !JSON.parse(r.body).hasOwnProperty('errors') && JSON.parse(r.body).hasOwnProperty('data'),
    });
}


Hi @sashi1,

Hhmm this is a tricky one, and I can’t think of a simple way of accomplishing what you want.

You essentially need some way of passing data across scenarios, which k6 currently doesn’t support. So you either need to authenticate once, and pass the token to each scenario, or share some global state where you can have each scenario read a token that might’ve been generated by another scenario.

The only way I can think of doing this is by using some external storage to store and fetch tokens from. Redis might be a good fit for this.

So the workflow would be:

1. Check if a token for the combination of username:password exists.

2a. If yes, retrieve and use it.

2b. If not, authenticate and store it.

This is far from ideal, though, and hopefully someone else has a better idea.