Blog

Leistungstests mit Selenium WebDriver: Erkennung hoher Reaktionszeiten

Luis Rodriguez Castro
October 25, 2021

Das Problem

Hier bei FloQast haben wir die vor Kurzem veröffentlichtDashboard „Alle Workflows“ wodurch Benutzer auf einfache Weise einen Überblick über den Fortschritt der einzelnen Workflows erhalten. Diese neue Seite enthält einen Filter, mit dem Sie eine Zeitleiste mit zwei Typen anzeigen können: Arbeitsablauf und Teammitglied.

image

Das Teammitglied und die Arbeitsablauf Filter haben mehrere Endpunkte, die Daten anfordern, um sie in ihren Zeitleisten anzuzeigen. Leider sind die Ladezeiten zwischen den einzelnen Filtertypen waren Wir brauchten 1—2 Minuten für eine Antwort. Die Entwickler identifizierten die Probleme schnell und begannen, an einer Lösung zu arbeiten, aber die Frage, die wir hatten, war: „Wie identifizieren wir dieses Problem früher in der Zukunft?“ Zu diesem Zeitpunkt wurde uns klar, dass wir unserem QA Automation Framework einen Leistungstest hinzufügen mussten. Nachdem wir uns darauf geeinigt hatten, lautete die nächste Frage: „Wo sollen die Ergebnisse für jeden Leistungstest gespeichert werden?“ Ich habe vorgeschlagen, die Google Sheets-API zu verwenden, um die Antwortzeiten von jedem Test in einem gemeinsamen Google Sheet zu speichern und den Leistungstest jeden Morgen automatisch mit Jenkins durchzuführen.

image

Google Tabellen-API

Zuerst habe ich eine Methode für die Google-API-Authentifizierung erstellt, die wiederverwendet werden kann: Hinweis: siehe Blog zur Google Sheets API

npm install googleapis 
or 
yarn add googleapis
import { google } from 'googleapis';
import { logger } from '../../src/lib/logger';

import { GOOGLE_SHEETS_KEYS } from '../constants';

const creds = {
    type: GOOGLE_SHEETS_KEYS.TYPE,
    project_id: GOOGLE_SHEETS_KEYS.PROJECT_ID,
    private_key_id: GOOGLE_SHEETS_KEYS.PRIVATE_KEY_ID,
    private_key: GOOGLE_SHEETS_KEYS.PRIVATE_KEY,
    client_email: GOOGLE_SHEETS_KEYS.CLIENT_EMAIL,
    client_id: GOOGLE_SHEETS_KEYS.CLIENT_ID,
    auth_uri: GOOGLE_SHEETS_KEYS.AUTH_URI,
    token_uri: GOOGLE_SHEETS_KEYS.TOKEN_URI,
    auth_provider_x509_cert_url: GOOGLE_SHEETS_KEYS.AUTH_PROVIDER_X509_CERT_URL,
    client_x509_cert_url: GOOGLE_SHEETS_KEYS.CLIENT_X509_CERT_URL
};

export const GoogleAPI = {
    spreadsheetId: GOOGLE_SHEETS_KEYS.SPREAD_SHEET_ID,
    authentication: async function () {
        logger.info('Generating Google Api Authentication');
        const auth = new google.auth.GoogleAuth({
            credentials: creds,
            scopes: 'https://www.googleapis.com/auth/spreadsheets'
        });
        const authClientObject = await auth.getClient();
        return { authClientObject, auth };
    }
};

Dieser Code:

  • importiert das googleapis-Paket.
  • importiert ein personalisiertes Logger-Modul zum Drucken in der Konsole.
  • importiert ein Konstantenmodul mit den erforderlichen Anmeldeinformationen für die Google-API-Authentifizierung.
  • exportiert das GoogleApi-Objekt zur späteren Verwendung mit dem Schlüssel Tabellenkalkulations-ID die Excel-Sheet, in der wir die Daten speichern, und eine Authentifizierung Methode.

Nachdem wir den obigen Code haben, können wir uns jetzt so etwas einfallen lassen:

import { google } from 'googleapis';
import { logger } from '../../src/lib/logger';
import { GoogleAPI } from '../../src/api/GoogleAPI';

const spreadsheetId = GoogleAPI.spreadsheetId;
/**
 * Data to send to google sheets
 * @param {array} data - multidimensional array with [data,data,data]
 */
export const insertPerformanceTestData = async (data) => {
    const { auth, authClientObject } = await GoogleAPI.authentication();
    const googleSheetsInstance = google.sheets({ version: 'v4', auth: authClientObject });
    logger.info('Adding performance test result data to sheet: Performance Test ');
    await googleSheetsInstance.spreadsheets.values.append({
        auth,
        spreadsheetId,
        range: 'Performance Test!A:K', // columns A to K where data is going to be inserted.
        valueInputOption: 'USER_ENTERED',
        resource: {
            values: data
        }
    });
};

Dieser Code:

  • importiert das googleapis-Paket.
  • importiert ein personalisiertes Logger-Modul zum Drucken in der Konsole.
  • importiert das GoogleApi-Objekt mit der Authentifizierungsmethode.
  • exportiert eine Funktion, die die Sheets-Methode verwendet, um Leistungsdaten in Spalten von A bis K einzufügen.

Die Testhelfer

Zuerst habe ich ein paar Hilfsfunktionen erstellt, die ich später verwenden kann:

import { std, mean } from 'mathjs';
import { logger } from '../../src/lib/logger';
import moment from 'moment';

export let requestIterations = process.env.REQUEST_ITERATIONS || 20;
export let pandoraPerformanceDataSize = process.env.PERFORMANCE_DATA_SIZE || 'average';
pandoraPerformanceDataSize = pandoraPerformanceDataSize.charAt(0).toUpperCase() + pandoraPerformanceDataSize.slice(1);

function msToTime(ms) {
    let seconds = (ms / 1000).toFixed(1);
    if (seconds < 60) return `${seconds} Sec`;

    let minutes = (ms / (1000 * 60)).toFixed(1);
    if (minutes < 60) return `${minutes} Min`;

    let hours = (ms / (1000 * 60 * 60)).toFixed(1);
    if (hours < 24) return `${hours} Hrs`;

    let days = (ms / (1000 * 60 * 60 * 24)).toFixed(1);
    return `${days} Days`;
}

export const performanceResults = (requestTimes) => {
    if (!requestTimes || requestTimes.length === 0) return 0; // to avoid divide by 0
    return {
        stdDeviation: msToTime(std(requestTimes)),
        mean: msToTime(mean(requestTimes)),
        max: msToTime(Math.max(...requestTimes))
    };
};

export const performRequestIterations = async ({ times = requestIterations, callback, inParallel = true }) => {
    if (inParallel) {
        let requestTimes = [];
        for (let index = 1; index <= times; index++) {
            let time = getResponseTime(callback);
            requestTimes.push(time);
        }
        return Promise.all(requestTimes);
    }
    let requestTimes = [];
    for (let i = 1; i <= times; i++) {
        let time = await getResponseTime(callback);
        requestTimes.push(time);
        logger.info(`Iteration #${i} out of ${times}`);
    }
    return requestTimes;
};

export const getResponseTime = async (callback) => {
    const startTime = Date.now();
    await callback();
    return Date.now() - startTime;
};

const performanceDataWrapper = (requestTimes) => {
    const performance = performanceResults(requestTimes);
    const requesTimesToTime = requestTimes.map((t) => msToTime(t));
    return {
        mean: performance.mean,
        max: performance.max,
        stdDeviation: performance.stdDeviation,
        date: moment().format('MMMM Do YYYY, h:mm:ss a'),
        times: requesTimesToTime
    };
};

export const assignResults = ({ requestTimes, query, results }) => {
    const { date, mean, max, stdDeviation, times } = performanceDataWrapper(requestTimes);
    const _results = [
        query.component, //A
        query.periods, //B
        query.workflow, //C
        pandoraPerformanceDataSize, //D
        date, //E
        query.periods, //F
        requestIterations.toString(), //G
        times.toString().split(',').join(', '), //H
        mean,//I
        max,//J
        stdDeviation//K
    ];
    results.push(_results);
};

Da die Antwortzeiten in Millisekunden angegeben sind, habe ich die Funktion „mstoTime“ hinzugefügt, die Millisekunden in Sekunden, Minuten, Stunden oder Tage umrechnet, basierend auf der Anzahl der Millisekunden, wie sie in der Funktion angezeigt wird. In Bezug auf die Leistung ist der Test entscheidend für die Berechnung der Standardabweichung weil es die Stabilität der Anwendung darstellt, in diesem Fall also einen Endpunkt. Aus diesem Grund wurde die Funktion „PerformanceResults“ erstellt, um die Standardabweichung einfach zu berechnen. Mittelwert (oder Durchschnitt), und die Max der Antwortzeiten der Anfragen mit dem npm-Paket math.js. Die Funktionen:

  • Antwortzeit abrufen ist eine Funktion, die einen Callback empfängt. Dabei handelt es sich um die HTTP-Anforderungsfunktion, die dafür zuständig ist, eine Anfrage an einen Endpunkt zu senden und Daten zurückzuempfangen. In diesem Fall wollen wir nur die Reaktionszeit wissen, wir wollen nicht die Daten selbst überprüfen.
  • Iterationen anfordern constant ist eine Variable, die einen Wert von Jenkins erhält, der die Anzahl der Anfragen angibt, die ausgeführt werden sollen.
  • Pandora Performance-Datengröße ist eine konstante Variable, die von Jenkins einen Wert erhält, der die Datenmenge angibt, gegen die ein Leistungstest durchgeführt werden sollte.
  • Durchführen von Anforderungsiterationen ist eine Funktion, die sich darum kümmert, die gewünschten Mengenanforderungen parallel oder nacheinander auszuführen.
  • Leistungsdaten-Wrapper ist eine Funktion, die Ergebnisse kapselt, um sie später einfach zu verwenden Ergebnisse zuordnen Funktion jeder anderen Funktion, die wir hinzugefügt haben.
  • Ergebnisse zuordnen eine Funktion, die die Ergebnisse formatiert und dieses Ergebnis in ein Array überträgt, das „als Referenz übergeben“ wird.

Den Test schreiben

Jetzt können wir den Test schreiben, der Durchführen von Anforderungsiterationen, Leistungstestdaten einfügen und Ergebnisse zuordnenMethoden, die als Hilfsfunktionen erstellt wurden.

describe('All workflow dashboard performance test Team Member Overview', () => {
    let headers, workflows;
    let results = [];
    let data = {
        periods: 'July_2021,June_2021,August_2021',
        pathType: 'overview',
    };

    beforeAll(async () => {
        headers = await generatePandoraPerformanceHeaders(); //authentication based on selected pandoraPerformanceDataSize
        workflows = await UsersAPI.getUserWorkflows(headers);
    });

    afterAll(async () => {
        await insertPerformanceTestData(results); // here we insert the data to Google Sheets after test is done.
    });

    it('team member timeline data panel cards', async () => {
        const _workflows = workflows.body
            .filter((workflow) => workflow.companyIds.length > 0)
            .map((workflows) => workflows.type)
            .toString();
        const query = {
            ...data,
            workflow: _workflows,
            headers,
            component: 'Team Member Panel Card',
            verbose: false,
        };
        const requestTimes = await performRequestIterations({
            callback: () => BiAPI.getTeamMemberTimelineOrOverview(query), //method that makes the request.
        });
        assignResults({ requestTimes, query, results });
    });
});

Die Ergebnisse

Nachdem wir Jenkins mit der obigen Testdatei eingerichtet und mit 20 Iterationen ausgeführt haben, erhalten wir dieses Ergebnis:

Gelernte Lektionen

  • Leistungstests sind wichtig, da sie die Stabilität der Anwendung widerspiegeln.
  • Berechnen Sie die Standardabweichung, den Mittelwert (oder Durchschnitt) und den Höchstwert der Antwortzeiten.
  • Automatisieren Sie den Leistungstest so, dass er automatisch zu einem bestimmten Zeitpunkt ausgeführt werden kann.
  • Speichern Sie die Ergebnisse der Leistungstests für eine spätere Analyse.

Weitere Informationen zu Leistungstests finden Sie unter: