Tanz Browser, Tanz! – Ein Puppeteer Intro

Tanz Browser, Tanz! – Ein Puppeteer Intro

Hallo liebe Entwickler*innen!

In letzter Zeit habe ich mich mit Möglichkeiten zur Automatisierung des Browserverhaltens befasst. Als ich mithilfe des LHCI Tools die Performance des E-Shops eines unserer Kunden testen und messen wollte, stieß ich plötzlich auf ein kleines Problem. Der Shop ist mit der Annahme “Mobile First” gebaut worden! Und trotzdem lädt LHCI standardmäßig nur in der Desktop-Ansicht. Was jetzt?

Ich erinnere mich, dass ich die Dokumente durchgesehen und versucht habe, herauszufinden, ob ich irgendeine obskure Einstellung zum Laden einer Website im Mobilformat übersehen habe.

Das Tool beinhaltet zwar solche Settings, aber als ich sie dann getestet habe, fand ich sie nicht sehr verständlich (jedenfalls als ich diesen Beitrag geschrieben habe). Aber das ist kein Problem! Denn egal welche Defizite LHCI hat, sie können fast immer mit einem kleinen Feature überwunden werden – der Integration von Puppeteer.

Was ist Puppeteer?

Ich meine, ihr könnt euch die Definition auf der Hauptseite ansehen. Aber am liebsten beschreibe ich sie, wie Google es hier tut:

Most things that you can do manually in the browser can be done using Puppeteer!

Die meisten Dinge, die du im Browser manuell machen kannst, kann Puppeteer auch.

Und sie übertreiben nicht!

Puppeteer ist eine API für das Scripting. Nicht mehr, nicht weniger. Ein Set von vordefinierten Tools, verpackt als Node-Library, um den Browser zu eurer Marionette zu machen.

Da stellt sich doch erstmal die Frage: was geht nicht? Also… alles außerhalb von Chrome. Aktuell könnt ihr nämlich nur mit den Marionetten spielen, wenn ihr auch Chrome nutzt. Aber eine Firefox Version wird gerade entwickelt (und wird in der Dokumentation als experimentell gekennzeichnet). Aktuell – während ich diesen Artikel schreibe – ist Chrome aber die einzig stabile Version.

Wenn ihr euch etwas mehr wünscht, wie zum Beispiel eine mobile Ansicht oder Browser-die-nicht-Chrome sind, könnt ihr zum Beispiel Selenium benutzen. Da dieses Tool älter und ausgereifter ist, besitzt es eine sehr viel breitere Kompatibilitätsspanne.

Jedenfalls – der Hauptgrund für Puppeteers Popularität ist, dass das Tool wirklich einfach bedienbar ist!

Und ich beweise es euch am Praxisbeispiel.

Was bauen wir da eigentlich?

Ich führe euch jetzt durch eine einfache Skriptversion, die ich für mein npm package wps gebaut habe. Natürlich werde ich nicht jeden Punkt und alle Features ansprechen, sondern konzentriere mich auf zwei Features, die ich als Stand-alone File gebaut habe, um euch Puppeteers Potential zu verdeutlichen. Das Skript ist simpel und soll zwei Dinge erreichen:

  • eine URL in einem angegebenen Viewport laden (in den ungefähren Größen eines Handys, Tablets und der Desktop Geräte)

  • am jeweiligen Punkt einen Screenshot von der Website machen.

Ich teile mein Vorgehen in Schritte auf und am Ende jedes Schrittes werde ich einen Link hinzufügen, sodass ihr jede Änderung, die ich gemacht habe, auf jeder Datei sehen könnt. Der Startpunkt sollte so aussehen: [master] initial commit – js file created · Duclearc/puppeteer-demo-lemundo@533cbeb

Schritt 1: Dateien und Abhängigkeiten

Um den Überblick zu behalten, sollten wir als erstes einen Ordner anlegen, bevor wir die Pakete, die als Grundlage gebraucht werden, installieren. Der Name ist eigentlich egal. Hauptsache es gibt eine JavaScript-Datei, die unser Projekt beherbergen kann. Ich nenne meine puppeteer.js, aber ihr könnt euch natürlich euren eigenen Namen ausdenken.

Nächster Punkt: Puppeteer einbinden! Wir downloaden jetzt das Tool mit npm. Also navigiert in eurer Repo und lasst npm i puppeteer darüberlaufen. So wird Puppeteer in eurem Projektordner als Abhängigkeit installiert.

Alle Schritte sind hier zusammengefasst:

Copy to Clipboard

Ebenfalls könnt ihr den Befehl  npm start zum Starten des Projekts in die package.json hinzufügen.

Das geht entweder manuell oder von eurem Terminal aus.

Copy to Clipboard

Egal wie ihr es macht, das sollte dabei herauskommen:

Copy to Clipboard

Um zu testen, ob euer Setup funktioniert, fügt einen console.log Befehl zu eurer puppeteer.js Datei hinzu und lasst npm start darüberlaufen, um zu checken, ob es geklappt hat.

Schritt 2: den Browser manipulieren

Als nächstes müssen wir herausfinden, wie wir Puppeteer zum Laufen bringen.

Da das Tool nur im Browser funktioniert, müssen wir einen starten.

Zum Glück steht uns hier Puppeteer selbst zur Seite und übernimmt den Vorgang für uns.

Fangen wir mal mit dem Import von Puppeteer an. Da ich persönlich keine Lust habe die ganze Zeit den vollen Namen auszuschreiben, nenne ich es einfach mal ppt. Dann können wir eine anonyme asynchrone Funktion erstellen, die in Kraft gesetzt wird, sobald die Datei ausgeführt wird.

Sie kann mit einem einfachen Test besiedelt werden:

  • Browser launchen

  • 3 Sekunden warten

  • Browser schließen

Copy to Clipboard

Aktiviert das Programm bzw. den geschriebenen Code mit npm start und ihr könnt beobachten, wie sich der Browser öffnet und schließt… ganz von alleine.

Dafür könnt ihr dem headless: false Attribute danken, welches für die Anzeige des Browsers verantwortlich ist. Nehmt es mal raus und lasst den Code nochmal darüberlaufen. Könnt ihr sehen, was passiert?

Nichts, oder?

Falsch.

Wie auch im Beispiel davor, wird euer Browser gerade geschlossen und geöffnet. Nur war hierheadless auf true gesetzt. Das ist der Standardwert. Und das bedeutet, dass der Browser sich ohne ein grafisches User Interface (GUI) öffnet. So könnt ihr nichts sehen. Für manche Tasks wäre das ok, aber wir wollen ja das ganze Spiel beobachten und werden also headless auf false stellen.

Da wir hier fertig sind, können wir mit unserem Ziel Nummer 1 weitermachen: eine URL in einem angegebenen Viewport laden (ungefähr in den Größen eines Handys, Tablets und Desktop Geräts).

Dafür müssen wir den Viewport (also die “Bildschirm-Größen”) eines Handys, Tablets und eines Desktop Geräts definieren. Da es natürlich Millionen von mobilen Gerätegrößen gibt, die wir benutzen können, nehme ich einfach mal ein modernes iPhone, iPad und ein MacBook und ordne sie im Array an, sodass wir sie einfacher nutzen können. Das Format muss Puppeteers Viewport Typ sein (also ein Objekt mit width and height). Ich habe sie hier gefunden: Viewport Size for Devices | Screen Sizes, Phone Dimensions and Device Resolution | YesViz.com

Copy to Clipboard

Da wir nun die Bildschirmdimensionen festgelegt haben, müssen wir sie auch in dem gerade gestarteten Browser verwenden. Es gibt verschiedene Wege dieses umzusetzen. Da wären beispielsweise das Nutzen eines Standard defaultViewport oder verschiedene page-Instanzen mit den Dimensionen zu erstellen und parallel zu steuern. Was ich aber tun werde, ist die page zuerst zu navigieren, dann das Viewport einzustellen und zu der Website zu navigieren, die wir ansteuern wollen. Das macht den Code meiner Meinung nach ein bisschen ordentlicher.

Aber halt mal…was ist dieses page Ding? Ich dachte wir hätten den Browser schon offen?

Ihr habt Recht. Und ich schlage auch vor, dass ihr damit ein paar Minuten rumspielt, um es euch zu verdeutlichen. Aber kurzgesagt: stellt euch den browser als das eigentliche Program vor. Und eine page ist ein Tab, den ihr benutzen könnt, um Zugriff auf die URL zu bekommen. Wenn ihr einen browser öffnet, hat er ja noch keine page offen. Deswegen müsst ihr in jedem Fall eine aufmachen.

Fangen wir damit an.

Wie üblich, macht Puppeteer es den Nutzer*innen sehr leicht: erstellt eine Konstante, um eure Page zu sichern und initiiert sie mit browser.newPage()

Um sicherzugehen, dass alles gut läuft, wird empfohlen, dass ihr bei den meisten Puppeteer Operations await benutzt. Als nächstes stellen wir ein, welchen Viewport wir angezeigt haben wollen. Dazu benutzen wir page.setViewport(viewports[0]) und als letztes müssen wir zu einer URL gehen page.goto(‚https://github.com/Duclearc‘). Jetzt heißt es erstmal Skript darüberlaufen lassen.

Will sollten die Webpage jetzt etwa im Viewport eines iPhones sehen.

Toll! Das Problem ist aber, dass wir mehrere Viewports laden wollen, nicht nur eins. Also lasst uns das ein bisschen geradebiegen. Wir verschachteln also den Vorgang in einer Schleife und lassen das Programm erneut laufen. Das sollte schneller gehen, als wir gucken können. Also ist es schlau console.log() hier irgendwo einzufügen, sodass wir Feedback bekommen können, was passiert. 

Wie gesagt, der Browser sollte sich öffnen und währenddessen könnt ihr ein bisschen klicken, da er durch die unterschiedlichen Viewports geht und die Seite erneut lädt. Genau das solltet ihr jetzt auf eurer Konsole beobachten können.

Wenn das bei euch so aussieht: perfekt! Jetzt bringen wir aber noch die Screenshots ins Spiel. 

Aktueller Status: https://github.com/Duclearc/puppeteer-demo-lemundo/commit/20cf082edffb9da0adbd1aba8cab233598bcea7d

Schritt 3: den Screenshot machen

Ok – wenn ihr denkt, dass Screenshots machen leicht ist, dann muss ich euch sagen … stimmt. Puppeteer übernimmt auch hier die Arbeit für uns. Alles was wir jetzt machen müssen ist, zu spezifizieren “wo” wir den Screenshot gespeichert haben wollen. Dafür brauchen wir nur einen einzigartigen “Namen” für die Datei und das “Format” des Bildes. Wir fügen diese Teile zusammen und übergeben sie im Format eines String path an die screenshot() Methode der page.  Das Wichtigste aber zuerst: wir müssen einen Ort erstellen, an dem wir die Screenshots ablegen können. Ich mache es mir leicht und erstelle einen screenshots/ Ordner als Basis des Projekts. Ihr könnt das entweder manuell machen oder das Terminal mit dem untenstehenden Befehl benutzen:

Copy to Clipboard

Da wir das jetzt geklärt haben, machen wir aber jetzt einfach eine Zeile in unsere Schleife.

Copy to Clipboard

Das war’s. Um es kurz zusammenzufassen: Puppeteer macht einen (.screenshot()) und speichert ihn auf eurem Ordner ({path: `./screenshots/…) mit dem Namen auf unserem Index – i –, da der Name einzigartig (${i}…) und in einem jpg Format ( .jpg`});) sein muss.

Das war’s wirklich. Wenn ihr jetzt npm start drüberlaufen lasst, solltet ihr 3 Dateien in euren Ordner gelegt bekommen, die jeweils einen Viewport repräsentiert.

Das war’s auf jeden Fall. All die Funktionalität, die wir wollten – fertig. Persönlich mag ich es, ein bisschen aufzuräumen, um etwas Refaktorisierung zu betreiben und alles etwas netter zu machen.  Das könnt ihr natürlich auch oder ihr könnt, wenn ihr neugierig seid, mir etwas länger zuhören.

 Aktueller Status: [master] step 3 complete · Duclearc/puppeteer-demo-lemundo@f1384ad

Schritt 4: Cleanup

Ich halte mich in diesem letzten Schritt kurz, nur, um euch zu zeigen, was mich stört, ohne zu sehr ins Detail zu gehen. Nochmal, wenn ihr mehr sehen wollt, könnt ihr auch auf GitHub gehen und das Ganze selbst auschecken.

Entfernt setTimeout und console.log()!

  • Das war nur zur Demonstration. Aber ist eigentlich für nichts gut. Deshalb weg damit!

  • erinnert euch trotzdem daran, den Browser nach der Schleife zu schließen!

  • ihr könntet auch console.log() aus der Schleife entfernen. Wenn ihr kein visuelles Feedback wollt, dann weg damit.

Eine bessere Namensstruktur für die Dateien erstellen

  • ich mag keine Dateinamen, die nicht auch informativ sind, also macht es mich wirklich irre, wenn meine Bilder nach Nummern benannt sind

  • deshalb expandiere ich die Viewports, die jetzt ein device Attribut enthalten, in dem ich den Namen hinzufügen kann, den ich möchte

  • als nächstes, gebe ich das an meine Screenshot Methode weiter und ersetze damit “i“.

  • daraus entstehen dann 3 Dateien: desktop.jpg, mobile.jpg und tablet.jpg. Viel besser.

  • Was ist, wenn ich das mehrmals am Tag mache? Dann wird ja dieses Bennenungssystem kaum funktionieren. Puppeteer wird einfach die Dateien mit den neuesten Bildern mit dem gleichen Namen überschreiben. Also werde ich eine andere Funktion erstellen und das Datum einfügen, um alles sorgfältig anzuordnen

  • Ich schiebe es auf den path der Screenshots, um es vor den Gerättyp zu stellen.

  • Wahlweise könnt ihr aber auch Regex nutzen, um an die URL zu gelangen und sie zum Dateinamen hinzuzufügen. Ich werde hier nicht tiefer darauf eingehen,  aber fügt Regex doch mal zum Code auf GitHub hinzu, wenn ihr neugierig seid.

refactor hard-coded data

  • Ich mag es, wenn mein Code ordentlich ist. Und ich glaube, dass Konfiguration und Logik – wenn möglich – getrennt werden müssen. In diesem Sinne, extrahiere ich ein paar Dinge in eine config File:

    • der Bilddatei Typ
    • der path auf dem die Screenshots gesichert werden
    • die gewünschte URL
    • die viewports
  • Ich füge all diese Punkte einer config.json Datei hinzu, sodass ich dann nur noch das Hauptskript importieren muss.  Auf diesem Weg können alle Änderungen, die ich machen will, separat von der Logik betrieben werden.

    Aktueller Status: [master] step 4 complete · Duclearc/puppeteer-demo-lemundo@efd1605

Und wie geht es jetzt weiter?

Das war’s! Den ganzen Code könnt ihr hier finden: GitHub – Duclearc/puppeteer-demo-lemundo: companion code to my ‚Intro to Puppeteer‘ article for Lemundo

Es gibt aber noch viel mehr, was ihr mit dem Skript machen könnt.. Ein paar Ideen wären zum Beispiel:

  • Puppeteer für Webpage Crawling nutzen. Schaut, ob ihr nur den Screenshot triggern könnt, wenn eine Änderung der Page passiert.

  • Ändert nicht nur den Viewport, sondern auch alle anderen Browser-Attribute, um ein Handy vollständig zu simulieren (Kopfzeilen, Touchscreen-Aktionen, etc). Tipp: benutzt Puppeteers built in devices und emulate() method, sie werden auch viel Arbeit sparen!

  • greift auf Seiten hinter BasicAuth zu und mache Screenshots von ihnen.

Warum mache ich das jetzt nicht selbst? Weil ich euch nicht den ganzen Spaß nehmen wollte, die Welt von Puppeteer selbst zu ergründen. Auch habe ich Hunger. Ich habe keine Lust zu sitzen und weiterzuschreiben 🤪

Trotzdem, hinterlasst doch einen Kommentar, wenn ihr zu einer der Funktionen ein Tutorial wollt. Wenn es genug Anfragen gibt, melde ich mich gerne wieder.

Habt ihr schon meinen ersten Los Geht’s Beitrag zu LHCI gelesen? Oder habt ihr Lust auf innovative Beiträge rund ums Internet und Coding? Schaut gerne mal auf unserer Website, was unser DT Team gerade macht, denn wir sind bekannt für angeregte Diskussionen rund um digitale Themen.

Und soll ich euch einen Geheimtipp verraten? Gerade suchen wir im Team sogar nach Verstärkung.

Also worauf wartet ihr? Offene Jobanzeigen findet ihr unter diesem Beitrag.

Happy Coding! 

Wollt ihr diesen Beitrag auf Englisch lesen? Dann geht es hier weiter.

Jetzt informieren >>

Hast du Lust ein Teil der Lemundo Family zu werden?

Informiere dich über Jobs bei Lemundo. Wir freuen uns darauf dich kennen zu lernen!
Jetzt informieren >>
Veröffentlicht am: 31. August 2022Kategorien: Shop Entwicklung

Gemeinsam
können wir
Großes
bewegen.

Philip Günther

Philip Günther

Geschäftsführer

Aktuelle Blog Beiträge

Über den Autor / die Autorin: Dan Pina

💻 + ☕️ = 🤩