https://d226lax1qjow5r.cloudfront.net/blog/blogposts/how-to-build-a-visual-regression-test-system-using-playwright/visual-regression-test.png

Erstellen eines visuellen Regressionstestsystems mit Playwright

Zuletzt aktualisiert am August 23, 2022

Lesedauer: 10 Minuten

Angenommen, Joe arbeitet an der wiederverwendbaren Komponente "Schaltfläche", nimmt eine Änderung vor und stellt sicher, dass die Schaltfläche wie erwartet funktioniert. Weiß Joe, dass die anderen Komponenten mit dieser Änderung gut aussehen? Joe müsste wissen, welche Komponenten von der Schaltfläche abhängen, und alle Komponenten in verschiedenen Szenarien visuell überprüfen, um sicherzustellen, dass die Schaltflächen gut aussehen. Und was ist mit den verschiedenen Browsern? Die Testmatrix wird immer größer...

Helfen wir Joe, seine Zeit besser zu nutzen, indem wir den visuellen Testprozess automatisieren!

Genau wie Joe haben Sie eine App am Laufen, oder Sie haben Ihre Komponenten erstellt und möchten sie mit der Welt teilen. Bei jeder Änderung verbringen Sie (oder jemand anderes in Ihrem Team) Stunden damit, die Benutzeroberfläche zu überprüfen, um sicherzustellen, dass sie pixelgenau ist. Und selbst dann fallen manchmal Dinge durch die Maschen der menschlichen Aufmerksamkeit. Machen wir uns nichts vor - Maschinen sind besser als wir im Abgleichen von Pixeln. Sie kosten auch weniger und können es immer und immer wieder tun.

Hier beginnt unsere Geschichte. Wir haben einen eine Reihe von UI-Komponenten. Wir haben auch Dokumentation für unsere Komponenten in der wir mit ihnen "spielen" können (d.h. sie manuell testen).

Die Dokumentation wird aus den Readme-Dateien in den Ordnern der einzelnen Komponenten erstellt. Hier ist ein Beispiel.

Beachten Sie, dass die Beispiele einfache HTML-Schnipsel sind:

<vwc-icon type="heart-solid" connotation="accent"></vwc-icon>
<vwc-icon type="heart-solid" connotation="announcement"></vwc-icon>
<vwc-icon type="heart-solid" connotation="cta"></vwc-icon>
<vwc-icon type="heart-solid" connotation="success"></vwc-icon>
<vwc-icon type="heart-solid" connotation="alert"></vwc-icon>
<vwc-icon type="heart-solid" connotation="info"></vwc-icon>

Wir werden später darauf zurückkommen.

Wir haben also etwa 50 Komponenten und eine Möglichkeit, sie visuell zu sehen, während wir sie entwickeln und manuell testen. Das ist großartig! Sie wollen die Komponenten sehen können, bevor Sie sie ausliefern :).

Einige dieser Komponenten setzen sich aus mehreren atomaren Komponenten zusammen. Das heißt, eine Änderung der Komponente "Schaltfläche" kann sich auf mehrere komplexe Komponenten auswirken, die die Schaltfläche verwenden.

Wie richtet man einen Dramaturgen für ein Projekt ein?

Der erste Schritt ist das Laufen:

npm i -D playwright

Das wird eine ganze Reihe von Dingen bewirken:

  1. Fügen Sie playwright zu unserer package.json-Datei hinzu.

  2. Installieren Sie playwright vor Ort.

  3. Installieren Sie playwrightdie Browser lokal.

Außerdem müssen wir eine playwright.config Datei einrichten. Für diese Einführung (und weil es das ist, was ich persönlich mache), werden wir die typescript Version, aber Sie können auch einfaches Javascript verwenden. Wir erstellen eine playwright.config.ts Datei mit dem folgenden Inhalt:

import type {
  PlaywrightTestConfig
} from '@playwright/test';

const config: PlaywrightTestConfig = {
  testMatch: 'src/**/*.test.ts',
  projects: [{
      name: 'Chrome Stable',
      use: {
        browserName: 'chromium',
        channel: 'chrome',
      },
    },
    {
      name: 'Desktop Safari',
      use: {
        browserName: 'webkit',
      }
    },
    {
      name: 'Desktop Firefox',
      use: {
        browserName: 'firefox',
      }
    },
  ]
};

export default config;

Die obige Konfiguration bewirkt Folgendes:

  1. testMatch: Sagt Playwright, wo die Testdateien zu finden sind. Beachten Sie, dass der Pfad relativ zum Speicherort der Datei ist. Im lebendigen Repository befindet sich die Konfigurationsdatei im Ordner components, und alle Komponenten befinden sich im Ordner src Ordner. Wir haben die Konvention, die Testdateien *.test.ts - auf, so dass jede Datei, die diesem Muster folgt, in den Testlauf einbezogen wird.

  2. projects: Sagt Playwright, in welchen Konfigurationen die Tests ausgeführt werden sollen. In diesem Fall haben wir 3 Konfigurationen für jeden der wichtigsten Browser, die wir testen möchten.

Es gibt noch viel mehr zu der playwright Konfiguration. Sie können hier mehr darüber lesen.

Nun, da playwright "weiß, wo die Dateien zu finden sind und auf welchen Browsern unsere Tests laufen sollen, können wir mit der Einrichtung der Tests selbst beginnen.

Wie man Dramatiktests schreibt

Um einen Test zu erstellen, müssen wir eine Datei erstellen, die von der testMatches. Deshalb erstellen wir in unserem Repository eine Datei ui.test.ts für jede Komponente. Hier ist ein Beispiel für eine Testdatei:

test('should show the component', async ({
  page
}: {
  page: Page
}) => {
  const template = `...`;

  await loadComponents({
    page,
    components,
  });
  await loadTemplate({
    page,
    template,
  });

  const testWrapper = await page.locator('#wrapper');
  await page.locator('#modal');

  await page.waitForLoadState('networkidle');

  await page.evaluate(() => {
    const modal = (document.getElementById('modal') as Dialog);
    modal.showModal();
    return modal;
  });

  expect(await testWrapper?.screenshot()).toMatchSnapshot(
    './snapshots/dialog.png'
  );
});

Für die vollständige Datei, klicken Sie hier.

Schauen wir uns das mal an.

Dramatiker-Testblock

Das erste, was wir bemerken, nachdem alle Importe erledigt sind, ist, dass wir einen test Block haben. Die Funktion, die an den test Block gesendete Funktion nimmt ein Testobjekt an. Eine der Eigenschaften des Objekts ist die page Eigenschaft. Diese page ist die eigentliche page Darstellung in Playwright. Wir verwenden sie, um die getestete Seite zu manipulieren und zu beobachten.

Vorbereiten der Testseite

In dieser Datei gibt es einen Test, der eine Vorlage erzeugt (die template Variable). Anschließend werden die benötigten Komponenten und die Vorlage mithilfe der von uns erstellten Dienstprogramme in die Testseite geladen (mehr dazu später).

Warten, bis der HTML-Code geladen ist

Die nächsten Zeilen verwenden die page.locator Methode, um die gesuchten Elemente zu erhalten. Ein weiterer "Nebeneffekt" der Verwendung des Locators ist, dass er sich auflöst, wenn unsere Elemente auf der Seite sichtbar sind. Dadurch wird sichergestellt, dass sich unsere Elemente bereits im DOM befinden, wenn wir die Tests ausführen.

Warten auf den Abruf von Ressourcen

Eine weitere Wartezeit besteht darin, zu warten, bis der gesamte Netzwerkverkehr im Leerlauf ist. Dies ist wichtig für uns, weil wir manchmal warten wollen, bis Icons oder Bilder von einem CDN geladen werden.

DOM-Elemente auslösen Programmatische API

Schließlich ist da noch die evaluate Methode des page Objekts. Diese Methode wertet den JavaScript-Code innerhalb der Seite aus. In diesem Fall ist dies der ausgewertete Code:

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

In diesem Fall erhalten wir das modal Element und rufen seine showModal Methode auf. Auf diese Weise können wir die programmatische API von DOM-Elementen testen.

Schnappschüsse erstellen und vergleichen

Die letzte Zeile ist die "Magie" der visuellen Regression im Dramatiker:

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

Das locator aufgelöste Objekt testWrapper hat eine screenshot Methode. Wenn man diese Methode aufruft und die toMatchSnapshot aufruft, führt sie eine der folgenden Aktionen aus:

  1. Wenn es einen Schnappschuss in dem Pfad gibt, der an toMatchSnapshotangegeben wird, wird der Schnappschuss mit dem im Dateisystem vorhandenen Schnappschuss verglichen. Wenn sie nicht übereinstimmen, schlägt der Test fehl.

  2. Wenn der Pfad keinen Snapshot enthält, wird ein neuer erstellt.

Wie lädt man die Testseite während eines Tests?

Im letzten Abschnitt haben wir ein Beispiel dafür gesehen, wie Tests geschrieben werden können. Wir haben ein page Objekt verwendet, um unsere Testseite zu manipulieren, aber das wirft ein paar Fragen auf - was ist diese mysteriöse Seite? Woher kommt der HTML/CSS/JS-Inhalt?

Im Wesentlichen hat das Objekt des Dramatikers page Objekts hat eine Methode goto. Diese Methode nimmt eine URL an und lädt sie in die page. Das ist in Ordnung, wenn Sie eine funktionierende Anwendung haben. In diesem Fall müssen Sie nur noch Ihre Anwendung bereitstellen. Dies kann mit dem Entwicklungsserver Ihres Bundlers geschehen, mit einem statischen HTML-Server oder sogar durch Testen Ihrer QA-, Entwicklungs- oder Produktionsumgebung.

Ihre Tests beginnen normalerweise mit einem goto und würden in etwa so aussehen:

test('should show text hello world', async ({page}) => {
	await page.goto(myAppUrl);
	const headerText = await manipulateApp(page);
	expect(headerText).toEqual('Hello World');
});

Wir goto die App oder die Testseite, manipulieren sie in irgendeiner Weise und erwarten dann, dass eine Funktion vorhanden ist oder etwas entspricht.

Wie können wir einzelne Komponenten für den Snapshot-Vergleich bereitstellen?

In vividgibt es, wie bei anderen UI-Komponenten-Bibliotheken, keine echte Anwendung zum Testen. Wir haben unsere Dokumentation - aber das Testen dieser Dokumentation würde auch andere Dinge testen, die nichts mit unseren Komponenten zu tun haben. Tatsächlich sind die meisten der visuellen Daten in der Dokumentation nicht komponentenbezogen.

Am besten wäre es, wenn Sie für jede Komponente eine eigene Testseite erstellen würden. Zu diesem Zweck bedienen wir unser gesamtes Projekt mit http-server. Unser visueller Regressionsbefehl sieht in etwa so aus: npm run build & npx http-server -s & npx playwright test.

Das bedeutet, dass wir unsere Komponenten bauen, das Repository und damit unseren Build bereitstellen und dann die Tests starten. Wir haben auch eine leere HTML-Datei, die ausgeliefert wird, und wir verwenden sie in dem page.goto Befehl wie folgt:

await page.goto('[http://127.0.0.1:8080/scripts/visual-tests/index.html](http://127.0.0.1:8080/scripts/visual-tests/index.html)');

Diese Datei ist eine leere HTML-Datei. Wir könnten für jeden Test eine manuelle HTML-Datei erstellen - aber was macht das für einen Spaß? Sehen wir uns an, wie wir die Erzeugung unserer Testseite aus unserem Code heraus automatisieren können.

Wie kann man JS-Dateien und Stile in unsere Seite mit Playwright einfügen?

Wir haben jetzt eine HTML-Seite zum goto. Sie ist immer noch nutzlos, weil wir in der Lage sein müssen, den Code unserer Komponenten zu injizieren.

Dazu können wir uns die Dienstprogrammfunktion ansehen, die wir im Beispiel der Testdatei gesehen haben - loadComponents.

loadComponents ist eine Funktion, die ein Array mit Komponentennamen akzeptiert und die Playwright-Methode addScriptTag Methode, um die Komponenten hinzuzufügen:

export async function loadComponents({
    page,
    components,
    styleUrls = defaultStyles,
}: {
    page: Page,
    components: string[],
    styleUrls ? : string[]
}) {
    await page.goto('http://127.0.0.1:8080/scripts/visual-tests/index.html');

    (async function() {
        for (const component of components) {
            await page.addScriptTag({
                url: `http://127.0.0.1:8080/dist/libs/components/${component}/index.js`,
                type: 'module',
            });
        }
    })();

    const styleTags$ = styleUrls.map(url => page.addStyleTag({
        url
    }));
    await Promise.all(styleTags$);
}

Die Magie geschieht innerhalb der asynchronen for-Schleife in der Mitte der Funktion: Sie geht über jede Komponente im Array und verwendet addScriptTag um sie auf unsere Demo-Seite zu laden.

Dies gilt auch für Stil-Tags eine Zeile tiefer. Die Funktion wird aufgelöst, wenn sich alle Style-Tags im DOM befinden.

Auf diese Weise nehmen wir bei jedem Test dieselbe leere Seite und laden die Komponenten, die wir testen wollen. Wenn wir zum Beispiel Formularelemente testen wollen, setzen wir ein Array mit text-field, text-area, select, button und anderen Formularelementen. Die Funktion loadComponents Funktion wird sie für uns in die Testseite einfügen.

Unser Netzwerkverkehr wird wie folgt aussehen, wenn wir unser Array so eingestellt haben: [icon, button, focus, dialog, text-field,layout]:

The JavaScript files loaded in the test page as shown in the network tab of chrome dev toolsA list of JS files from the network tab

Beachten Sie, dass wir auch Vorlagen geladen haben:

The CSS files loaded in the test page as shown in the network tab of chrome dev toolsA list of css files from the network tab

Hurra - JS und CSS sind geladen!

Wie zeigt man die Komponenten auf der HTML-Seite an?

Wir haben jetzt eine HTML-Seite, in die die benötigten Skripte und Stile eingefügt wurden. Unsere verbleibende Aufgabe ist es, das HTML einzubinden, das unsere Komponenten tatsächlich verwenden wird.

Nehmen wir an, wir wollen eine Schaltfläche testen. Wir erstellen eine Zeichenfolge, die wie folgt aussieht:

<vwc-button label=”Click Me”></vwc-button>

Wir können der Schaltfläche weitere Elemente hinzufügen, wie z. B. die Bedeutung oder das Aussehen, oder sogar eine andere Komponente wählen (ich meine, wenn wir den Dialog testen wollen, wäre es ziemlich nutzlos, ein Snippet einer Schaltfläche zu erstellen).

Nun soll dieses HTML auf unserer Testseite angezeigt werden. Dazu werden wir einen Trick mit Playwrights page.addScriptTag. In der Hilfsfunktion loadTemplateakzeptieren wir die Seite und den Template-String. Dann fügen wir ein Skript in die Seite ein, das die HTML-Vorlage in die Seite einfügt:

export async function loadTemplate({
    page,
    template
}: {
    page: Page,
    template: string
}) {
    const wrappedTemplate = `<div id="wrapper">${template}</div>`;
    await page.addScriptTag({
        content: `
            document.body.innerHTML = \`${wrappedTemplate}\`;
        `,
    });
}

Die Funktion umhüllt zunächst unsere Vorlage mit einer div Zeichenkette und fügt dann ein Skript in die Seite ein, das das innerHTML des Bodys durch unsere eingewickelte HTML-Zeichenkette ersetzt.

Wenn wir also eine HTML-Zeichenkette wie diese haben:

<vwc-dialog id="modal"
		icon="info" 
		headline="Headline"
		text="This is the content that I want to show, and I will show it!!!"
>				
</vwc-dialog>

Im Browser erhalten wir das folgende Ergebnis:

A web browser with a modal dialog open. To the side the chrome dev tools are open showing the HTML we expectedThe test page and the devtools

Unser Modal wird angezeigt. Auf der rechten Seite des Modals sehen Sie den HTML-Code, den wir in die Seite eingefügt haben.

Screenshot-Erstellung und -Vergleich in Playwright

Jetzt, wo unsere Testseite fertig ist, können wir unseren Schnappschuss machen. Das haben wir bereits in der Übersicht getan, aber hier ist eine Erinnerung:

const testWrapper = await page.locator('#wrapper');
await page.locator('#modal');

await page.waitForLoadState('networkidle');

await page.evaluate(() => {
  const modal = (document.getElementById('modal') as Dialog);
  modal.showModal();
  return modal;
});

expect(await testWrapper?.screenshot()).toMatchSnapshot(
  './snapshots/dialog.png'
);

Schritt 1: Warten, bis die Seite fertig ist

Wir warten, bis der Wrapper und das Modal auf der Seite sind, indem wir die page.locator Methode und auf das networkidle Ereignis, um sicherzustellen, dass alle unsere Assets geladen sind.

Schritt 2: Manipulieren Sie die Seite

Sobald die Seite fertig ist, evaluieren wir das Skript, das die showModal API für das modale Element verwendet.

Schritt 3: Machen Sie einen Screenshot und überprüfen Sie ihn

Wenn nun alles richtig funktioniert, gelangen wir zu unserer expect Zeile, die den Schnappschuss nimmt und ihn mit einem früheren Schnappschuss vergleicht.

Die testWrapper.screenshot Methode ist Teil der playwright.locator API. Sie nimmt ein Bildschirmfoto des Elements auf und ermöglicht uns die Verwendung der toMatchSnapshot Erwartungs-API. In diesem Fall wird es mit einem Dateipfad (der match path) - dort wird der Screenshot gespeichert.

Wenn wir den Test zum ersten Mal ausführen, wird ein Screenshot in der Datei match path. Alle von uns vorgenommenen Änderungen werden mit dem ursprünglichen Ergebnis verglichen, so dass sichergestellt ist, dass die Änderung des Aussehens unserer Komponente den Test nicht beeinträchtigt und wir keine unerwünschten visuellen Änderungen vornehmen.

Schritt 4: Aktualisieren der Screenshots

In diesem Fall wollen wir das Aussehen ändern, playwright hat ein update-snapshots Flagge, die den Snapshot aktualisiert. Hierfür haben wir eine update Aufgabe in unserem Repository, aber in Playwright funktioniert es folgendermaßen:

npx playwright test --update-snapshots

Zusammenfassung

Wir haben jetzt ein voll funktionsfähiges visuelles Regressionssystem für UI-Komponenten. Der Aufbau dieser Art von Tests für UI-Komponenten ist ein bisschen komplizierter als das Testen einer Anwendung.

Wenn Sie eine Anwendung testen, müssen Sie nur die Anwendung über einen externen Server laden und dann mit goto auf die Adresse. Beim Testen von Komponenten benötigen Sie denselben Server, aber dieses Mal wird er auf eine leere Seite verweisen, die wir während des Tests selbst erstellen müssen.

Die beiden Nutzenfunktionen, loadComponents und loadTemplate die der Dramatiker verwendet addScriptTag und addStyleTagverwenden, sind die geheime Soße, die es uns ermöglicht, jede Komponente isoliert zu testen, indem wir einfach unsere HTML-Snippets erstellen.

Wir verwenden einen sehr ähnlichen Code, um einen Test für jede Komponente in unserer Bibliothek oder Seite/Benutzergeschichte in unserer Anwendung zu erstellen. Auf diese Weise erstellen wir einen Schnappschuss für jede Komponente/Seite/Benutzergeschichte und stellen sicher, dass der Benutzer das gleiche Ergebnis für den gleichen Vorgang erhält. Das alles geschieht automatisch in den Browsern, die wir in unserer Konfiguration festgelegt haben (Chrome, Firefox und Safari). Bessere Qualität - Zeitersparnis.

Wir haben jetzt einen funktionierenden lokalen automatisierten visuellen Regressionstestmechanismus. Der Grund dafür ist, dass diese Tests lokal ausgeführt werden können, so dass der Entwickler schnelles Feedback erhält und die Fehler beheben kann, noch bevor er seine Änderungen veröffentlicht.

Aber es gibt noch viel mehr, was wir verbessern können:

  • Wie können wir die Testerstellung automatisieren?

  • Wie können wir uns vor Flockigkeit schützen (Instabilität ist ein bekanntes Problem bei e2e-Tests)?

  • Wie können wir unsere Tests in GitHub-Aktionen integrieren und optimieren?

  • Und was ist mit dem Entwicklungs- und Debugging-Modus für die visuellen Tests?

Auf diese Themen werde ich im nächsten Artikel eingehen. In der Zwischenzeit können Sie einen Blick werfen auf unser Vivid-Repository, unsere Playwright-Konfiguration und Dokumentation der UI-Tests um sie in Aktion zu sehen.

Teilen Sie:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan KraVonage Software Architekt

Yonatan war an einigen großartigen Projekten in der Akademie und in der Industrie beteiligt - von C/C++ über Matlab bis hin zu PHP und Javascript. Früher war er CTO bei Webiks und Softwarearchitekt bei WalkMe. Derzeit ist er Softwarearchitekt bei Vonage und egghead-Dozent.