Selenium: Testautomatisierung-Tool oder Bot zur Whisky-Datenanalyse

Selenium ist ein bekanntes Framework zum automatisierten Testen von Web-Anwendungen durch den Browser. Selenium automatisiert aber auch gut Prozesse, die mit dem Browser bedient werden. In diesem Artikel stelle ich Selenium anhand eines Browser Klick-Bots vor, der die Begehrtheit von Whiskies berechnet. 🥃

English presentation of this article

Selenium und Whisky

Beruflich habe ich Selenium, wie die meisten, schon zur Automatisierung von Tests benutzt. Selenium ist ein Tool zur Browser-Steuerung. Deshalb wird es meistens für Ende zu Ende Tests eingesetzt, welche die Web-Anwendung vom Browser ausgehend testen. 

Beruflich automatisierte ich mit Selenium auch schon Prozesse, welche durch den Browser bedient werden. Als ich bei meinem Hobby "Zahlen, Daten, Fakten zu Single Malt Whiskies" (nur Trinken kann ja jeder 😉) auf die Idee kam die Whiskybase auszuwerten, fiel mir direkt Selenium als geeignetes Werkzeug ein. Die Whiskybase ist eine von Whisky-Fans getriebene Webseite bzw. Online-Datenbank mit jeder Menge Infos zu Whisky-Abfüllungen: https://www.whiskybase.com/

Mein Bot sammelt die Information wie viele Whiskybase-Benutzer einen Whisky in ihrer Sammlung haben und wie viele den Whisky auf ihrer Wunschliste haben. Das Verhältnis von Häufigkeit des Whiskies auf der Wunschliste zu Häufigkeit des Whiskies in der Sammlung ist ein Maß für die Begehrtheit des Whiskies. So viel zu meiner Whisky-Theorie...

Weitere Infos zu Selenium findet ihr auf Wikipedia und auf der Selenium Webseite:

Aufsetzen eines Selenium Projektes 

Maven Konfiguration

Mein Selenium-Projekt setze ich schnell mit Spring Boot auf (siehe microservices-mit-spring-boot-erstellen.html). Ein einfaches Maven Projekt mit Browser-Treiber passend zum installierten Browser würde aber ausreichen. Nachdem ihr mit Spring Boot oder eurer IDE ein neues Maven-Projekt erstellt habt, benötigt ihr noch die Selenium-Bibliothekt. Hinterlegt dazu einfach folgende Dependency in der Maven-Konfiguration:
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
</dependency>
Falls ihr auf Spring Boot verzichtet, müsst ihr noch das Versions-Tag in der Dependency setzen.

Browser Treiber

Danach benötigt ihr noch den Treiber passend zu eurem Browser. Ich verwende Google Chrome und habe den Treiber hier heruntergeladen: https://chromedriver.chromium.org/downloads
Beim Herunterladen achtet darauf, dass die Versionen des Treibers und des installierten Browsers übereinstimmen. Den entpackten Treiber habe ich im src/main/resources/drivers/windows Verzeichnis meiner Spring Anwendung abgelegt - der Speicherort im Dateisystem ist aber eigentlich egal.

Selenium verwenden

Der hier gezeigte Code ist in der Programmiersprache Kotlin geschrieben. Da Kotlin auf der JVM basiert, verwenden Kotlin und Java dieselbe Selenium Bibliothek. Deshalb funktioniert alles hier gezeigte analog in Java. Für Kotlin-Einsteiger habe ich diesen Artikel geschrieben: kotlin-spring-api.html

Browser öffnen und schließen

@Service
class WhiskyCrawler {
private val log = LogManager.getLogger()
private val browser: ChromeDriver

init {
System.setProperty("webdriver.chrome.driver",
"./src/main/resources/drivers/windows/chromedriver.exe")
browser = ChromeDriver()
}

@PreDestroy
fun closeBrowser() {
browser.close()
}

fun collectDataFor(whiskyName: String) {
...
  • Die Annotation @Service erzeugt eine Spring Bean. Die main-Methode unserer Spring-Anwendung lädt die WhiskyCrawler Bean und ruft die Methode collectDataFor auf. Die Implementierung von collectDataFor zeige ich weiter unten.
  • Der Kotlin init-Block wird direkt nach der Initialisierung durch den Default-Konstruktor ausgeführt. Im init-Block stelle ich die Verbindung zwischen meiner Kotlin-Anwendung und dem Browser-Treiber her, indem ich die System Property "webdriver.chrome.driver" mit dem Pfad zur heruntergeladenen Datei setze. Danach instanziiere ich das Attribut browser mit der Klasse ChromeDriver aus der Selenium-Bibliothek. Dann öffnet sich der Selenium gesteuerte Browser automatisch, so dass wir ihn in der collectDataFor-Methode benutzen können.
  • Innerhalb des init-Blocks könnten wir jetzt noch diverse Einstellungen am Selenium gesteuerten Browser vornehmen, z. B. das Setzen eines Proxies. In meiner einfachen Demo ist das nicht nötig. Hier setze ich nur die System-Property, damit Selenium den Chrome Treiber im Dateisystem findet.
  • Wenn die Spring Bean WhiskyCrawler beim Beenden des Programms zerstört wird, wird zum Abschluss durch die Spring-Annotation @PreDestroy die Methode closeBrowser ausgeführt. Diese schließt den Selenium gesteuerten Browser durch die Methode close.

Webseite aufrufen und Daten aus html mit XPath lesen

Die zuvor gezeigte Methode collectDataFor ruft die folgende Methode searchWhiskies auf:
private fun searchWhiskies(whiskyName: String): Map<String, List<String>> {
browser.get("https://whiskybase.com/search?q=%s".format(whiskyName))
val map = mutableMapOf<String, MutableList<String>>()
for(whisky in browser.findElements(
By.xpath("//div[@class='row']//a[@class='clickable']")))
map.putIfAbsent(whisky.text, mutableListOf(whisky.getAttribute(
"href")))?.add(whisky.getAttribute("href"))
return map
}
  • browser.get öffnet mittels HTTP GET im Selenium gesteuerten Browser die als Parameter übergebene URL.
  • browser.findElements sucht hier mittels XPath (By.xpath) nach HTML Elementen und gibt dann alle zum XPath passenden Elemente als Liste von WebElement Objekten zurück. In der zuvor geöffneten Whiskybase Webseite sind das Links zur Detailseite der einzelnen Whiskies, siehe gelbe Markierungen im nächsten Screenshot.
Suchergebnis für Alrik, einen deutschen, rauchigen Single Malt aus dem Harz 😍
  • Der hier verwendete XPath-Ausdruck besteht aus 2 Teilen. Im ersten Teil //div[@class='row'] werden alle div-Tags auf der kompletten Seite ausgewählt, die im Attribut class den Wert row haben. Sie sehen so aus: <div class="row" ...>.
    Im zweiten Teil werden analog
    a-Tags innerhalb der im ersten Teil selektierten div-Tags gesucht. Weitere Infos zur Syntax von XPath findet ihr hier: https://www.w3schools.com/xml/xpath_syntax.asp
  • Den Namen des gefunden Whiskies packe ich als Key in eine Map und die URL zur Detailseite des Whiskies als Value. Da es gleichnamige Whisky Abfüllungen geben kann (z.B. bei Standard Abfüllungen aus verschiedenen Jahren: "Ardbeg Uigeadail"), ist der Value eine Liste von URLs (bzw. List<String>). Zum Auslesen der URL ("href") bietet das Interface WebElement die Methode getAttribute an.

Knöpfe klicken und Exception-Handling

try {
browser.findElement(By.xpath("//button[@name='allow']"))?.click()
} catch (e: Exception) {
// Clicking allow button was not required.
}
  • Auf Instanzen des Interfaces WebElement kann die Methode click aufgerufen werden, die einen Mausklick auf dem jeweiligen Element ausführt. Mit browser.findElements habe ich einen Knopf (Button) in der Webseite gesucht. Wenn er gefunden wurde, klicke ich ihn mit der Methode click an.
  • Bei Selenium kommt es häufig zu Exceptions, z.B. weil der Knopf im Browser doch nicht angezeigt wird, es beim Laden der Seite einen Timeout gibt usw. Damit mein Selenium basierter Bot weiter läuft und die anderen Whiskies auswertet, fange ich hier jede beliebige Exception und ignoriere sie. Je nach dem was euer Programm tun soll, benötigt ihr natürlich eine andere Fehlerbehandlung.

Formulare ausfüllen

Der Whisky-Bot muss zwar keine Formulare ausfüllen, da das Ausfüllen von Eingabefelder beim Testen aber sehr häufig gemacht wird, stelle ich es kurz vor. Dazu habe ich das Öffnen der ersten Seite im Browser vereinfacht.
private fun searchWhiskies(whiskyName: String): Map<String, List<String>> {
browser.get("https://whiskybase.com")
val searchField = browser.findElement(By.id("search-input"))
searchField.sendKeys(whiskyName)
searchField.sendKeys(Keys.ENTER)
  • Den zu suchende Whisky schreibe ich mit der Methode sendKeys in das Text-Eingabefeld für die Whisky-Suche. Das Text-Eingabefeld holte ich zuvor mit der bekannten findElement Methode.
  • Neben einfachen Strings können auch Spezial-Tasten wie Enter mit der sendKeys Methode in den Browser geschickt werden.

Fazit

Im restlichen Code benötige ich keine weiteren Selenium-Features. Ich rufe nur die Whisky-Detailseite auf, um dort mittels XPath Daten auszulesen. Mit diesen Daten berechne ich die Begehrtheit des Whiskies. Wenn ihr euch für meine Whisky-Datenanalyse im Detail interessiert, schaut euch gerne den kompletten Code in GitHub an:

Selenium bietet noch viel mehr, als ich in diesem kleinen Einstiegs-Artikel gezeigt habe! Probiert es am besten aus, indem ihr euch die Methoden der ChromeDriver Klasse genauer anschaut.

Whiskybase Detailseite mit vielen Infos die per XPath lesbar sind


Kommentare

Beliebte Posts aus diesem Blog

OpenID Connect mit Spring Boot 3

Authentifizierung in Web-Anwendungen mit Spring Security 6

Validierung per Annotation in Java und Spring