How to get authenticated browser instance before test

Hello, dear k6 community!
I’m recently joined on the board and I glad to use this framework for daily performancre testing.

At the moment I have one question, that I can’t resolve on my own. I would grateful to some advice from developers or more experienced user than I am.

Question is next:

I created simple scenario like this:

import { chromium } from 'k6/x/browser';
import { sleep } from 'k6';

export const options = {
  scenarios: {
    go_to_statistics_page: {
      tags: { name: 'statistics'},
      executor: 'constant-vus',
      exec: 'go_to_statistics_page',
      vus: 1,
      iterations: '1',
      maxDuration: '30s',
      startTime: '0s',
      gracefulStop: '10s'
    },

    go_to_incidents_archive_page: {
      tags: { name: 'incidents_archive_page'},
      executor: 'constant-vus',
      exec: 'go_to_incidents_archive_page',
      vus: 1,
      maxDuration: '30s',
      iterations: '1',
      startTime: '80s',
      gracefulStop: '40s'
    },

and more similar tests in this scenario

examples of tests function are represented below:

export function go_to_statistics_page() {

 const browser = chromium.launch({ args: ['no-sandbox', 'remote-debugging-port=9223'], headless: 
  true, timeout: '5m', });
  const context = browser.newContext({ ignoreHTTPSErrors: true, })
  const page = context.newPage();

  page.goto('https://' + __ENV.SENSOR_IP + '/#/login');

  page.waitForSelector("#e-login-page_login");
  page.type("#e-login-page_login", "Administrator");

  page.waitForSelector("#e-login-page_password");
  page.type("#e-login-page_password", "P@ssw0rd");

  page.waitForSelector("button#e-login-page_login-button");
  page.locator("button#e-login-page_login-button").click();

  page,goto('https://' + __ENV.SENSOR_IP + 
  '/#/main/r/dashboard;type=preset;interval=5;autoupdate=true')
  
  page.waitForSelector("//span[contains(text(), ' Incident severity levels '),  {visible: true, 'timeout': 30000})]
 
  console.log("Statistics Page is opened")

    page.close();
    browser.close();
};

export function go_to_incidents_archive_page() {

  const browser = chromium.launch({ args: ['no-sandbox', 'remote-debugging-port=9223'], headless: 
  true, timeout: '5m', });
  const context = browser.newContext({ ignoreHTTPSErrors: true, })
  const page = context.newPage();

  page.goto('https://' + __ENV.SENSOR_IP + '/#/login');

  page.waitForSelector("#e-login-page_login");
  page.type("#e-login-page_login", "Administrator");

  page.waitForSelector("#e-login-page_password");
  page.type("#e-login-page_password", "P@ssw0rd");

  page.waitForSelector("button#e-login-page_login-button");
  page.locator("button#e-login-page_login-button").click();

  page.goto('https://' + __ENV.SENSOR_IP + '/#/main/r/incidents/archive')
  page.waitForSelector("//div[@class='ag-center-cols-viewport' and @ref='eCenterViewport']", 
  {'visible': true, 'timeout': 30000});
    
   console.log("Incidents Page is opened")

    page.close();
    browser.close();
};

As you can see tests have repetitive actions of creating new browser instance and login that i would like to reduce and use them only once before all tests.

What I tried to resolve it:

  1. It’s apparently to insert in the script setup() function and make login inside of it and then return browser instance to tests.
export function setup() {
  
  let login = "Administrator";
  let password = "P@ssw0rd";
  
  const browser = chromium.launch({ args: ['no-sandbox', 'remote-debugging-port=9223'], headless: 
  true, timeout: '5m', });
  const context = browser.newContext({ ignoreHTTPSErrors: true, })
  const page = context.newPage();
  
  page.goto('https://' + __ENV.SENSOR_IP + '/#/login').then(() => {
    page.waitForSelector('#e-login-page_login');
    page.type("#e-login-page_login", login);
    page.waitForSelector("#e-login-page_password");
    page.type("#e-login-page_password", password);
    return page.click('button#e-login-page_login-button');
  }).then(() => {
    page.screenshot({ path: 'screenshots/login' });
  }).finally(() => {
    page.close();
    browser.close();
  });
  
};

But when I try to reuse instance of browser from setuo() in tests I find that setupData is empty: {}

export function go_to_statistics_page(setupData) {
console.log(setupData)
}
  1. Also I tried to use predefined persistent context, that was created from another script.
    I found that there is issue on Github: Implement BrowserType.launchPersistentContext(userDataDir[, options]), that’s why

I created persistent context with another (not k6) JS-script:

import playwright from 'playwright';
import { test, expect } from '@playwright/test';

import os from 'os';
import fs from 'fs';
import path from 'path';

(async () => {

    const userDataDir = path.join(os.tmpdir(), 'browser-context', String('any-test'));
    const context = await playwright['chromium'].launchPersistentContext(userDataDir, { args: ['--remote-debugging-port=9222'], headless: true, ignoreHTTPSErrors: true });
    const page = await context.newPage();
    await page.goto('http://SENSOR_IP/#/login');
    await page.locator("id=e-login-page_login").fill('Administrator');
    await page.locator("id=e-login-page_password").fill('P@ssw0rd');
    await page.locator("id=e-login-page_login-button").click();
    await context.close();

})();

And then I tried to reuse it in k6 chromium launch :

import { chromium } from 'k6/x/browser';

export default function go_to_incidents_archive_page() {

  const browser = chromium.launch({ args: ['no-sandbox', 'remote-debugging-port=9223', "user-data- 
  dir=/tmp/browser-context/any-test/"], headless: 
  true, timeout: '5m', });
  const page = browser.newPage({ ignoreHTTPSErrors: true, });
  page.goto('https://' + __ENV.SENSOR_IP + '/#/main/r/incidents/archive');

But in this case it seems browser ignores ‘user-data-dir’ and doesn’t use provided profile, because it still appears on login page and does’t go to required URL

Maybe someone has already encountered such a problem and knows a way to solve it.

Sorry if my question is too long for reading, I tried to explain the problem in detail.

Thank you for advance!

Additionally my k6 environment is:

k6 v0.43.1 ((devel), go1.19.9, linux/amd64)
Extensions:
GitHub - grafana/xk6-browser: k6 extension that adds support for browser automation and end-to-end web testing via the Chrome Devtools Protocol v0.9.0, k6/x/browser [js]

1 Like

Hi @vkovalev :wave:

You won’t be able to pass a reference to the browser instance initialized in setup to the VUs function.

If you want to avoid having to launch a browser instance every time, currently the best option is to use the BrowserType.Connect() method. You’ll have to handle the browser lifecycle independently, but the browser instance will not be initialized in each iteration, instead k6 browser would connect to it.

Unfortunately right now it’s not possible to inherit the browser instance session/user data, as k6 browser will create a new browserContext on every new page created. This is something that we plan to work on in the short term.
I think the best option is to perform the login in the setup phase, retrieve the session cookie by using JS code and page.evaluate() mehtod (as currently it’s not supported to retrieve HTTP only cookies :slightly_frowning_face: , see here) and pass the session cookie to the VUs body function where you can reuse it in every browser context by using the addCookies() method.

If the cookie is HTTP only, another option would be to login and retrieve the cookie through the HTTP client instead, or through other means that you can automate.

I reckon there are quite a few pain points in this process that we are actively trying to improve in the short term. Stay tuned for next releases :bowing_man:

1 Like