Browser based performance testing multiple iterations

Hi @ankur
I’m currently evaluating k6 as part of browser based performance testing, especially the k6/browser module, to simulate real user flows like launch → login → action → logout — similar to what LoadRunner TruClient does.

  1. The Problem

When running browser-based scenarios using per-vu-iterations or shared-iterations, I observe that:

The first iteration runs correctly (browser opens, login works, navigation completes).

From the second iteration onwards, sometimes:

The page loads as blank

Navigation fails silently

Session seems broken (maybe due to caching, cookies, etc.)

How to handle the situation in browser based performance testing.

Hi @priyavp,

That doesn’t sound correct.

  1. Can you send me a test script that reproduces this issue?
  2. What version of k6 are you running?

Best,

Ankur

@ankur I’m using version v0.54.0.

Can you please suggest me how we can handle the multiple iterations same as load runner vuser init- action-vuser end.

Launch and login should run as per the vuser and other action should run as per the number of iteration and logout also should run as per the vuser count.

E.g: vus: 3

Iteration:3

In this case launch login should run for 3 times and other actions(e.g enter product and search, click on product) should run for 9 times and logout should run for 3 times.

Hi @priyavp

I’m not familiar with load runner. Could you share a k6 script that exhibits the issue you are running into that you mentioned in your first post? Also, would be handy to also see the load runner script so that I can compare the two scripts.

I’m using version v0.54.0.

I would highly recommend you update to the latest version of k6 which is currently v1.1.0, unless there is a good reason to stay on that old version, which i would like to understand why.

Best,

Ankur

Please find the below script and find the response as well.

import { browser } from 'k6/browser';
import { check } from 'k6';
import { sleep } from 'k6';
import { group } from 'k6';

export const options = {
  scenarios: {
    ui: {
      executor: 'per-vu-iterations',
      vus: 1,
      iterations: 2,
      maxDuration: '5m', // Add maxDuration as per K6 best practices
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

// Global variables to store browser context per VU
let vuPage = null;
let browserContext = null; // Store browser context to maintain session
const TOTAL_ITERATIONS = 2; // Define iterations as a constant

// VU Setup - runs once per VU before iterations start (per K6 standards)
export function setup() {
  // This runs once before all VUs start - use for global setup only
  console.log('Global setup completed');
  return {}; // Return any data needed by VUs
}

// Main VU function - called for each iteration (per K6 standards)
export default async function (data) {
  console.log(`=== STARTING ITERATION ${__ITER + 1} of ${TOTAL_ITERATIONS} ===`);

  // VU-specific initialization: Launch browser and login only on first iteration (once per VU)
  if (__ITER === 0) {
    console.log('First iteration - initializing browser and login...');
    await initializeAndLogin();
  } else {
    console.log(`Subsequent iteration ${__ITER + 1} - reusing existing authenticated session...`);
  }

  // Run the main business actions for each iteration
  try {
    await performBusinessActions();
  } catch (error) {
    console.log(`*** ITERATION ${__ITER + 1} ENCOUNTERED ERROR: ${error.message} ***`);
    console.log(`Stopping current iteration ${__ITER + 1} and proceeding to next iteration...`);
    // Don't re-throw the error - this allows the next iteration to continue
  }

  // VU-specific cleanup: If this is the last iteration, perform logout (once per VU)
  if (__ITER === TOTAL_ITERATIONS - 1) {
    console.log('Last iteration - performing logout and cleanup...');
    try {
      await performLogout();
    } catch (error) {
      console.log(`*** LOGOUT ERROR: ${error.message} ***`);
      console.log('Error during logout, but continuing with VU completion...');
    }
  } else {
    console.log(`Iteration ${__ITER + 1} complete. Moving to next iteration...`);
  }

  console.log(`=== COMPLETED ITERATION ${__ITER + 1} ===`);
}

// VU Teardown - runs once per VU after all iterations complete (per K6 standards)
export function teardown(data) {
  // This runs once after all VUs complete - use for global cleanup only
  console.log('Global teardown completed');
}

async function initializeAndLogin() {
  console.log('VU Init: Starting browser context and performing login...');

  // Create browser context first to maintain session
  browserContext = await browser.newContext();
  // Create new page within the browser context
  vuPage = await browserContext.newPage();

  try {
    // Navigate to JPetStore homepage
    group('01_Homepage_Load', () => {
      console.log('Navigating to homepage...');
    });

    await vuPage.goto('https://jpetstore.aspectran.com/', { waitUntil: 'networkidle' });
    sleep(2);

    const homeContent = await vuPage.content();
    check(homeContent, {
      'Homepage loaded': (t) => t.includes('JPetStore') || t.includes('Enter the Store'),
    });
    console.log('Homepage loaded successfully');
    sleep(1);

    // Click Sign In
    group('02_Navigate_to_SignIn', () => {
      console.log('Clicking Sign In...');
    });

    await vuPage.locator('a[href*="signonForm"]').click();
    await vuPage.waitForLoadState('networkidle');

    const signinContent = await vuPage.content();
    check(signinContent, {
      'Sign in page loaded': (t) => t.includes('Username') || t.includes('Password'),
    });
    console.log('Sign in page loaded');
    sleep(1);

    // Fill username and password and login
    group('03_User_Login', () => {
      console.log('Performing user login...');
    });

    // Fill username
    console.log('Filling username...');
    await vuPage.locator('input[name="username"]').click();
    await vuPage.locator('input[name="username"]').clear();
    await vuPage.locator('input[name="username"]').fill('testuser1');
    sleep(1);

    // Fill password
    console.log('Filling password...');
    await vuPage.locator('input[name="password"]').click();
    await vuPage.locator('input[name="password"]').clear();
    await vuPage.locator('input[name="password"]').fill('perftest');
    sleep(1);

    // Click Login button
    console.log('Attempting login...');
    await vuPage.locator('#Signon > form > div > div > button').click();
    await vuPage.waitForLoadState('networkidle');
    sleep(3);

    const loginContent = await vuPage.content();
    check(loginContent, {
      'Login successful': (t) => t.includes('Welcome') || t.includes('testuser1') || t.includes('Sign Out'),
    });
    console.log('Login completed - Browser context established with authenticated session');
    sleep(1);

  } catch (error) {
    console.log(`Login failed with error: ${error.message}`);
    // Close browser context and page if login fails
    if (vuPage) {
      try {
        await vuPage.close();
      } catch (e) {
        console.log('Error closing page:', e.message);
      }
    }

    if (browserContext) {
      try {
        await browserContext.close();
      } catch (e) {
        console.log('Error closing browser context:', e.message);
      }
    }

    throw error;
  }
}

async function performBusinessActions() {
  console.log(`*** ITERATION ${__ITER + 1}: Running business actions... ***`);

  // Simple check - if page doesn't exist, skip this iteration
  if (!vuPage || vuPage.isClosed()) {
    console.log('Page is not available for business actions. Skipping this iteration.');
    return;
  }

  try {
    // Business Action 1: Navigate to Dogs category (runs per iteration)
    group('04_Browse_Dogs_Category', () => {
      console.log(`Iteration ${__ITER + 1}: Clicking on Dogs category...`);
    });

    await vuPage.locator('#SidebarContent a[href*="DOGS"], a[href*="viewCategory"][href*="DOGS"]').click();
    await vuPage.waitForLoadState('networkidle');
    sleep(2);

    const dogsContent = await vuPage.content();
    check(dogsContent, {
      'Dogs category loaded': (t) => t.includes('Dogs') || t.includes('Bulldog'),
    });
    console.log(`Iteration ${__ITER + 1}: Dogs category loaded successfully`);
    sleep(1);

    // Business Action 2: Select K9-BD product
    group('05_Select_Product', () => {
      console.log(`Iteration ${__ITER + 1}: Clicking on K9-BD product...`);
    });

    await vuPage.locator('a[href*="K9-BD-"], a[href*="productId=K9-BD"]').click();
    await vuPage.waitForLoadState('networkidle');
    sleep(2);

    const productContent = await vuPage.content();
    check(productContent, {
      'Product page loaded': (t) => t.includes('K9-BD-') || t.includes('Bulldog'),
    });
    console.log(`Iteration ${__ITER + 1}: Product page loaded successfully`);
    sleep(1);

    // Business Action 3: Add item to cart
    group('06_Add_Item_to_Cart', () => {
      console.log(`Iteration ${__ITER + 1}: Adding item to cart...`);
    });

    await vuPage.locator('#Catalog > table > tbody > tr:nth-child(2) > td:nth-child(5) > a').click();
    await vuPage.waitForLoadState('networkidle');
    sleep(2);

    const cartContent = await vuPage.content();
    check(cartContent, {
      'Item added to cart': (t) => t.includes('Shopping Cart') || t.includes('Sub Total'),
    });
    console.log(`Iteration ${__ITER + 1}: Item added to cart successfully`);
    sleep(1);

    // Business Action 4: Remove item from cart
    group('07_Remove_Item_from_Cart', () => {
      console.log(`Iteration ${__ITER + 1}: Removing item from cart...`);
    });

    await vuPage.locator('#Cart > form > table > tbody > tr:nth-child(2) > td:nth-child(8) > a').click();
    await vuPage.waitForLoadState('networkidle');
    sleep(2);

    const removeContent = await vuPage.content();
    check(removeContent, {
      'Item removed from cart': (t) => t.includes('Shopping Cart'),
    });
    console.log(`Iteration ${__ITER + 1}: Item removed from cart successfully`);
    sleep(1);

    console.log(`*** ITERATION ${__ITER + 1} COMPLETED SUCCESSFULLY ***`);

  } catch (error) {
    console.log(`*** ITERATION ${__ITER + 1} FAILED with error: ${error.message} ***`);
    console.log(`Stopping current iteration ${__ITER + 1} and allowing next iteration to proceed...`);
    // Don't throw error - this allows the next iteration to continue
    return; // Exit current iteration gracefully
  }
}

// Logout function - called after all iterations are complete
async function performLogout() {
  console.log('All iterations completed. Performing logout...');

  if (vuPage && !vuPage.isClosed()) {
    group('08_User_Logout', () => {
      console.log('Signing out...');
    });

    try {
      await vuPage.locator('#Menu > div:nth-child(1) > a:nth-child(7)').click();
      await vuPage.waitForLoadState('networkidle');
      sleep(2);

      const signoutContent = await vuPage.content();
      check(signoutContent, {
        'Signed out successfully': (t) => t.includes('Sign In') || !t.includes('testuser1'),
      });
      console.log('Signed out successfully');

    } catch (e) {
      console.log('Error during logout:', e.message);
    }
  }

  // Clean up browser resources
  try {
    if (vuPage && !vuPage.isClosed()) {
      await vuPage.close();
      console.log('Page closed');
    }
    if (browserContext) {
      await browserContext.close();
      console.log('Browser context closed');
    }
  } catch (e) {
    console.log('Error closing browser resources:', e.message);
  }
}

Response:

scenarios: (100.00%) 1 scenario, 1 max VUs, 5m30s max duration (incl. graceful stop):     
              \* ui: 2 iterations for each of 1 VUs (maxDuration: 5m0s, gracefulStop: 30s)      

 

INFO\[0000\] Global setup completed                        source=console
INFO\[0000\] === STARTING ITERATION 1 of 2 ===             source=console
INFO\[0000\] First iteration - initializing browser and login...  source=console                 
INFO\[0000\] VU Init: Starting browser context and performing login...  source=console           
INFO\[0001\] Navigating to homepage...                     source=console
INFO\[0005\] Homepage loaded successfully                  source=console
INFO\[0006\] Clicking Sign In...                           source=console
INFO\[0008\] Sign in page loaded                           source=console
INFO\[0009\] Performing user login...                      source=console
INFO\[0009\] Filling username...                           source=console                        
INFO\[0010\] Filling password...                           source=console
INFO\[0012\] Attempting login...                           source=console
INFO\[0015\] Login completed - Browser context established with authenticated session  source=console
INFO\[0016\] \*\*\* ITERATION 1: Running business actions... \*\*\*  source=console
INFO\[0016\] Iteration 1: Clicking on Dogs category...     source=console                        
INFO\[0019\] Iteration 1: Dogs category loaded successfully  source=console
INFO\[0020\] Iteration 1: Clicking on K9-BD product...     source=console
INFO\[0024\] Iteration 1: Product page loaded successfully  source=console
INFO\[0025\] Iteration 1: Adding item to cart...           source=console
INFO\[0028\] Iteration 1: Item added to cart successfully  source=console
INFO\[0029\] Iteration 1: Removing item from cart...       source=console
INFO\[0032\] Iteration 1: Item removed from cart successfully  source=console
INFO\[0033\] \*\*\* ITERATION 1 COMPLETED SUCCESSFULLY \*\*\*    source=console
INFO\[0033\] Iteration 1 complete. Moving to next iteration...  source=console                   
INFO\[0033\] === COMPLETED ITERATION 1 ===                 source=console                        
INFO\[0034\] === STARTING ITERATION 2 of 2 ===             source=console
INFO\[0034\] Subsequent iteration 2 - reusing existing authenticated session...  source=console  
INFO\[0034\] \*\*\* ITERATION 2: Running business actions... \*\*\*  source=console                    
INFO\[0034\] Page is not available for business actions. Skipping this iteration.  source=consol 
INFO\[0034\] Last iteration - performing logout and cleanup...  source=console                   
INFO\[0034\] All iterations completed. Performing logout...  source=console                      
INFO\[0034\] Browser context closed                        source=console                        
INFO\[0034\] === COMPLETED ITERATION 2 ===                 source=console                        
INFO\[0034\] Global teardown completed                     source=console

Hi @priyavp,

Sorry for the delay in getting back to you!

I finally got a chance to take a look at the test (which i very much appreciated :person_bowing: ).

After taking a look I now understand where the misunderstanding lies (and possibly some gaps in our docs). Every iteration opens a new incognito browser session, and no data is shared between each iteration. We currently do not have a way to save or cache data and share it between iterations through the browser itself (i.e. non-incognito window).

I was able to perform the test successfully when removing the conditions that were logging in on the first iteration and logging out in the last iteration.

We do have a setup and teardown functions that could be utilised if there’s a way of extracting some authentication token or cookie from a response which could then be used in subsequent iterations. Do you know how the authentication works?

Best,
Ankur