skip to Main Content

I have a SPA web app, which is built by vue. the page is protected by a login page(a vue router actually),

after login, a token is stored in sessionStorage, then jump to home page(another vue router.)

I want to use lighthouse to audit the home page, but can’t make the login with puppeteer. my code:

'use strict';

const puppeteer = require('puppeteer-core')
const lighthouse = require('lighthouse')
const http = require('http')
const PORT = 8041;

const opts = {
    hostname: 'x.x.x.x',
    port: 9876,
    path: '/lighthouse/scores',
    method: 'POST',
    timeout: 5000,
    headers : {
        'Accept' : 'application/json',
        'Content-Type' : 'application/json'
    }
}

var session;
// 登录
async function login(broswer, origin){
    const loginPage =  (await broswer.pages())[0];
    await loginPage.goto(origin);
    await loginPage.waitForSelector('#pane-pwd > form > div:nth-child(1) > div > div > input', {visible: true});
    const userInput =  await loginPage.$('#pane-pwd > form > div:nth-child(1) > div > div > input');
    await userInput.type('admin');
    const pswdInput =  await loginPage.$('#pane-pwd > form > div:nth-child(2) > div > div > input');
    await pswdInput.type('123456');
    const loginButton = await loginPage.$('#app > div > div.main > div.tabs > div.bt-wrap > button');
    await loginButton.click();

    await loginPage.waitForSelector('.home');

    const states = await loginPage.evaluate(() => {
        //alert(sessionStorage.getItem('default'));
        return JSON.stringify(sessionStorage);
    })
    session = JSON.parse(states);

    return session;
}

// 上传lighthouse结果
function uploadReport(scores){
    console.log('--------------------- uploading lighthouse report --------------------- ');
    const req = http.request(opts, res => {
        console.log(`Update : statusCode: ${res.statusCode}`)
        if(res.statusCode != '204'){
            console.error(res.statusMessage)
        }else{

        }
    });
    req.on('error', error => {
        console.error(error)
    });
    req.write(scores);
    req.end();
}

async function main(){
    const broswer = await puppeteer.launch({
        args : [`--remote-debugging-port=${PORT}`],
        headless : false,
        defaultViewport : null,
        executablePath : '/usr/bin/google-chrome-stable'
    });

    const session = await login(broswer, 'http://x.x.x.x/');

    console.log(session);

    broswer.once('targetchanged', async target => {
        console.log('------- target changed -------');
        const page  = await target.page();
        await page.waitForNavigation();
        page.evaluate((session) => {
            console.log('------- copy session state -------')
            sessionStorage.setItem('___un__solvable___secrets__', session['___un__solvable___secrets__']);
            sessionStorage.setItem('default', session['default']);
        }, session);
    });


    const result = await lighthouse('http://x.x.x.x/#/admin/home', {port: PORT, disableStorageReset : true, disableDeviceEmulation: true, emulatedFormFactor : 'desktop'})
    const scores = {
        project : 'P364',
        url : '/',
        performance : result.lhr.categories.performance.score,
        accessibility : result.lhr.categories.accessibility.score,
        bestPractice : result.lhr.categories['best-practices'].score,
        pwa : result.lhr.categories.pwa.score,
        seo : result.lhr.categories.seo.score,
    }

    console.log(scores);

    uploadReport(JSON.stringify(scores));

    //await broswer.close();

}

if(require.main === module){
    main();
}else{
    module.exports = {
        login
    };
}

I want to use puppeteer to login first, in a seprate tab, then extact token from sessionStorage, and at some point when lighthouse begin to load the url(another tab), inject the token to sessionStorage. but failed with :execution context was destroyed, in the targetchange listener, please help.

besides, I want to know shoud I insist to audit the home page, as it’s a vue SPA? as most assets already loaded in login page.

2

Answers


  1. I’ve recently started working on performance tests. I have exact use case. One workaround is to make a change in the file – lighthouse > lighthouse-core > gather > connections > CriConnections > cri.js.

    Here, in function ‘connect’ under class ‘CriConnection’ comment new tab creation.

    connect() {
    // return this._runJsonCommand('new')
    //   .then(response => this._connectToSocket(/** @type {LH.DevToolsJsonTarget} */(response)))
    //   .catch(_ => {
        // Compat: headless didn't support `/json/new` before m59. (#970, crbug.com/699392)
        // If no support, we fallback and reuse an existing open tab
        
        log.warn('CriConnection', 'Cannot create new tab; reusing open tab.');
        return this._runJsonCommand('list').then(tabs => {
          if (!Array.isArray(tabs) || tabs.length === 0) {
            return Promise.reject(new Error('Cannot create new tab, and no tabs already open.'));
          }
          const firstTab = tabs[0];
          // first, we activate it to a foreground tab, then we connect
          return this._runJsonCommand(`activate/${firstTab.id}`)
              .then(() => this._connectToSocket(firstTab));
        });
      // });}
    

    Although, please note, this might not be a best practice. But this is all I have so far. I’m actively looking for a better solution. Will update, if I find any !

    Login or Signup to reply.
  2. You can use a custom gatherer to inject a token to session storage:
    `

        async function createSession(token) {
      if (sessionStorage) {
        sessionStorage.setItem("jwt_token", token);
      }
    }
    
    class SessionGatherer extends Gatherer {
      constructor() {
        super();
      }
    
      async beforePass(options) {
        // if (!credentials[0] == "Login page") {
        const TOKEN = await new TokenService().fetchToken();
        const driver = options.driver;
        return driver.evaluateScriptOnNewDocument(
          `(${createSession.toString()})('${TOKEN}')`
        );
      }
    }
    

    Custom gatherer needs to be added to the lighthouse config:

       const lighthouse_config = {
      extends: "lighthouse:default",
      settings: {
        formFactor: "desktop",
        screenEmulation: {
          mobile: false,
          width: 1024,
          height: 768,
          deviceScaleFactor: 1,
          disabled: false,
        },
      },
      passes: [
        {
          passName: "defaultPass",
          gatherers: [`session-gatherer`],
        },
      ]}
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search