Bye-Bye Internet Explorer

Microsoft will sich nun schon sehr lange vom Internet Explorer trennen, und in Kürze wird es endgültig: Mitte Juni wird ein Update erscheinen, dessen einziger Zweck es ist, den IE zu löschen.

Vielleicht sagen Sie jetzt, naja na und, mit der alten Schabracke lässt sich doch schon seit Ewigkeiten sowieso nichts mehr anfangen. Nicht ganz. Der IE ist der einzige Browser mit einem COM-Modell. Und lässt sich damit nahtlos in eine Vielzahl von Anwendungen und Programmiersprachen integrieren.

Stellen Sie sich vor, Sie hätten in Excel VBA umfangreiche finanztechnische Software kodiert, die für den Betrieb auch Daten aus dem Internet benötigt. Und um das nicht alles immer per Hand einzupflegen, haben Sie den IE automatisiert, dass er die Daten holt. Und *schwupps* mit einem Federstrich macht Microsoft jetzt ihre ganze Arbeit wertlos, tausende von Zeilen, obwohl sie bis heute klaglos den Dienst verrichten. Denn der eine oder andere Darstellungsfehler bei moderneren Seiten ist bei so etwas ja egal, insbesondere da man den IE seine Arbeit komplett im Hintergrund verrichten lassen kann.

Was kann man also tun? Das Update aufschieben? Das geht nur mit den Pro-Versionen von Windows, und auch dort nur eine gewisse Zeit. Die Software in eine VM verlagern, und diese nach jedem Start auf den letzten Sicherungspunkt zurücksetzen? Mit der Zeit würde das wohl eine ziemlich unsichere Angelegenheit, weil gar keine Updates mehr eingespielt würden.

Der saubere Schritt ist hingegen die Rekodierung für eine neue Browser-Automation. Glücklicherweise gibt es nämlich für Edge, Firefox und Chrome eine Automatisierungs-Schnittstelle, sie nennt sich Selenium. Und für Excel  und Word VBA gibt es die vorzügliche Bibliothek SeleniumBasic von Florent Breheret. Sie können die Installationsdatei dafür hier downloaden.

Im Folgenden möchte ich Ihnen die ersten Schritte mit dieser Bibliothek ein wenig erleichtern.

(zum Anfang des Beitrags geht es hier)

Mit SeleniumBasic können Sie Browser-Aufrufe in VBA- und VBS-Software automatisieren. Dazu benötigen Sie aber außerdem noch ein Treibermodul für den zu verwendenden Browser. Ich beziehe mich im Folgenden auf Google Chrome, dessen Modul finden Sie hier. Aber es gibt entsprechende Module für alle modernen Browser.

Bitte beachten Sie, dass Sie bei einem Browser-Update u.U. ein neues Treibermodul benötigen. Bei einem Update des Browsers muss man also immer nachsehen, ob es eine neuere Version des Treibers gibt, sonst funktioniert die Software plötzlich nicht mehr, und das auch noch ohne jegliche Fehlermeldung. //Update: in diesem Artikel ist beschrieben, wie sich das automatisieren lässt.

Nach der Installation von SeleniumBasic (Doppelklick auf SeleniumBasic.exe) findet sich in dessen Installationsverzeichnis
c:\Users\[Benutzer]\appdata\Local\SeleniumBasic
eine, allerdings recht knapp gehaltene, Hilfedatei „Selenium.chw“ als Referenz für alle verfügbaren Funktionen.

Weiterhin muss das eben beschriebene Treibermodul für den fernzusteuernden Browser in das gleiche Verzeichnis kopiert werden, in dem auch diese Hilfedatei liegt. Denken Sie daran zu überprüfen, ob in den Dateieigenschaften des Treibermoduls („chromedriver.exe“ für Google Chrome) bei Sicherheit “Die Datei stammt von einem anderen Computer … Zugriff blockiert” das Häkchen für Zulassen gesetzt ist, sonst wird – auch dies ohne jegliche Fehlermeldung – nämlich absolut gar nichts passieren in Selenium.

Für VBA-Anwendungen ist außerdem wichtig, dass nach der Installation von SeleniumBasic dann noch im VBA-Editor unter „Extras/Verweise“ („Tools/References“) die „Selenium Type Library“ importiert wird.

Und schon kann es losgehen. Um eine IE-COM-Anwendung portieren zu können, sind zunächst ein paar grundsätzliche Änderungen zu beachten. 1.) Die Namen der Funktionen sind ähnlich, aber vielfach doch deutlich verschieden. GetElementByTagName wird zu FindElementByTag, zum Beispiel. Generell wird Get zu Find und hinten raus ist es meist ähnlich oder gleich, aber nicht immer. Dafür gibt es jedoch viel mehr Funktionen, das spart eine Menge Code.

2.) Außerdem gibt es keine .document-Instanz mehr, man arbeitet immer direkt auf dem Inhalt des Browsers. (Das alte IE-Objekt selbst manipuliert die Instanz-Eigenschaften von IE, aber das geht nicht mehr mit Selenium).

3.) Zurückgeliefert werden für Aufrufe mit mehreren Treffern (z.B. für FindElementsByName) nicht mehr Arrays wie beim IE, sondern Listen. Der Hauptunterschied ist, dass (i.Allg., man könnte es ändern) Arrays zur Basis 0 sind, Listen hingegen zur Basis 1. Um also alten Code zu transponieren, muss man bei direktem Zugriff auf einzelne Elemente für Selenium den Index um 1 erhöhen. Auch Schleifen könnten betroffen sein, aber [ FOR EACH x IN xx ] war dafür ohnehin schon immer die bessere Wahl, und für solche Schleifen ändert sich nichts.

Im Folgenden ein wenig generischer Beispiel-Code, der als Einstieg für Standardanforderungen reichen müsste:

Dim gc As Object
Set gc = New WebDriver
gc.SetCapability "goog:chromeOptions", _
  "{""excludeSwitches"": [""enable-automation""], _
  ""args"": [""user-data-dir=c:\\users\\[Benutzer]\\AppData\\Local\\Google\\Chrome\\User Data\\""]}"
gc.Start "chrome"
SendKeys "{TAB}"

Da sind ein paar Spezialbefehle drin, die ich Ihnen kurz erläutere. 1.) Ich möchte nicht, dass oben im Browser steht „Fernsteuerung aktiv“, und dafür ist „excludeSwitches:enable-automation“.

2.) Ich möchte, dass das Default-Profil des Browsers benutzt wird, es wird sonst mühsam, für spezifische Profil-Einstellungen das jedesmal im (für jeden Aufruf neu angelegten) Automatisierungs-Profil wiederholen zu müssen. Auch andere Profile als der Default sind dort möglich, man könnte also auch ein spezielles Selenium-Profil anlegen.

3.) Der Befehl [ Sendkeys “{TAB}” ] dient dazu, dass der Focus von der Adresszeile auf das Dokument gelegt wird, das sieht sonst merkwürdig und auch ungewohnt aus beim automatisierten Wandern durch die ferngesteuerte Seite.

Und nun die wichtigsten Befehle für die Fernsteuerung, anzupassen nach Belieben für Ihre gegebene Anforderung:

gc.Window.SetPosition x, y
gc.Window.SetSize width, height

gc.Get "https://beispiel.url"

gc.FindElementById("user").SendKeys "Nutzername"
gc.FindElementById("password").SendKeys "Passwort"
gc.FindElementById("submit").Click

set elem  = gc.FindElementByTag("div") 'findet das erste div-Element
set elems = gc.FindElementsByName("beispiel") 'liefert Liste aller Elemente mit dem Namen "beispiel", elems(1) ist das erste

Großartig ist die Funktion FindElement(s)ByXPath, aber die Syntax ist schwer zu durchschauen und irgendwie nicht sonderlich konsistent. Deshalb im Folgenden eine Reihe von Beispielen dafür:

Set elem  = gc.FindElementByXPath("//span[text()=""such-text""]")
Set elem  = gc.FindElementByXPath("//button[@title='such-title']")
Set elems = gc.FindElementsByXPath("//div[@class='such-klasse']")
Set elem  = gc.FindElementByXPath("//input[@value='such-wert']")
Set elem  = gc.FindElementByXPath("//p[contains(text(), ""suchteil-text"")]")
Set elems = gc.FindElementsByXPath("//div[contains(@id, ""suchteil-id"")]")

Sehr leistungsfähig, aber ich sagte ja, die Syntax ist mühsam. Die jeweiligen einfachen und doppelten Hochkommata braucht es genau so wie es oben steht!

Ein wesentlicher Unterschied zum IE besteht außerdem darin, dass man nicht mehr direkt auf Attribute zugreifen kann. Z.B. ist beim IE der Zugriff auf elem.ID möglich, aber für Selenium benötigt es elem.Attribute(“id”), elem.Attribute(“href”), elem.Attribute(“class”), und so weiter. Für den Text gibt es aber elem.Text, da braucht man kein Attribute().

Bitte beachten Sie auch: Falls Sie möchten, dass der Browser nach Ablauf der Animation bzw. nach Ende der Funktion offen bleibt, müssen Sie die Objektvariable für den Selenium-Browser global definieren. Wird sie lokal definiert, beendet sich der Browser mit dem Ende der Funktion.

Und um einen solchen global laufenden Browser zu schließen, verwendet man:

gc.quit
Set gc = Nothing

Sie werden sehen, die Umstellung ist zwar eine Menge Arbeit, aber es lohnt sich. Alles wird danach viel schneller und stabiler. Kleiner Wermutstropfen jedoch: Das unsichtbare Ausführen (browser.Visible = False) gibt es nicht mehr mit Selenium. Man hat also das Browserfenster bei Selenium immer im Vordergrund laufen und kann/muss zusehen, was auf der Seite passiert. Es gäbe zwar einen headless-Modus, aber damit kann man praktisch nur auslesen, nicht mehr steuern, was ihn für die meisten Aufgaben nutzlos macht.

Ansonsten, Sie werden möglicherweise unkonditionelle Wartezeiten benötigen, um festzustellen, wann der Browser tatsächlich fertig geladen hat, das ist bei den modernen Technologien (AJAX, etc.) sehr schwierig und gelingt Selenium nicht immer. Um eine Sleep-Funktion in Excel VBA zu erhalten, setzen Sie folgende Zeile in den Deklarationsbereichs eines Moduls des Projekts:

Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)

[ Sleep 1000 ] wartet dann eine Sekunde. Muss man leider oftmals empirisch ermitteln für eine gegebene Webseite, was jeweils gebraucht ist.

Außerdem, die folgende Funktion ist ein implizites Warten auf Fertigstellung des Browsers (aber auch das hilft nicht immer):

Sub gcWait(gc As Object)
  Dim t As Integer
  Const maxwait = 60000, ival = 250

  t = 0 'nicht wirklich nötig, nur damit es klar ist
  Sleep ival 'immer erstmal etwas warten

  On Error Resume Next
  Do While gc.ExecuteScript("return document.readyState") <> "complete"
    t = t + ival
    If t > maxwait Then 'um Endlosschleife zu vermeiden
      Exit Do
    End If
    Sleep ival
  Loop
  On Error GoTo 0
End Sub

In vielen Fällen bleibt aber nichts anderes übrig, als darauf zu warten, bis ein bestimmtes Element auf der Webseite abrufbar geworden ist. Dieses Element sollte so gewählt sein, dass sein Vorhandensein das erfolgreiche Laden anzeigt. Hier ist ein Beispiel dafür:

cnt = 0: Do While True
  Set elem = gc.FindElementByXPath("//button[@title='Button-Title']")
  If Not elem Is Nothing Then
    elem.click
    gcWait gc
    Exit Do
  End If
  Sleep 1000
  gcWait gc
  cnt = cnt + 1
  If cnt > 60 Then 'Endlosschleife vermeiden
    MsgBox "Warten auf Button-Element fehlgeschlagen. Abbruch.", vbExclamation, "Fehler"
    Exit Sub
  End If
Loop

Dafür gibt es natürlich beliebig viele Variationen. Es könnte z.B. auch eine Liste von Elementen zu Beginn des Ladevorgangs nur mit einem Dummy-Wert vorbefüllt sein, und man muss darauf warten, dass elems.Count der Liste nicht mehr 0 (Null) ist. Und so weiter, das ist äußerst variabel je nach Webseite, man kann es nur ausprobieren. Der Browser-Debugger (F12 bzw. „Element untersuchen“) leistet dabei sehr gute Dienste.

Zum Schluss noch ein paar Anlaufpunkte für Ihre weitere Recherche:

stackoverflow: Using Google Chrome in Selenium VBA
Guru99: Using Excel VBA and Selenium
ChromeDriver: Capabilities & ChromeOptions
QAFox: Selenium Locators – contains() XPath Function

Florent Breheret, der Autor von SeleniumBasic, ist übrigens sehr hilfsbereit, er gibt oft Auskunft auf Fragen, z.B. auf stackoverflow.

Nebenbei, auch Microsoft empfiehlt für den Ersatz der Internet Explorer Fernsteuerung SeleniumBasic von Florent Breheret.

Und fast hätte ich es vergessen: Im Installationsverzeichnis von Selenium findet sich unter Examples\ eine Reihe von aufschlussreichen Beispielen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.