PowerShell: Einführung in die Webbrowser Automation mit Selenium WebDriver
Inhaltsverzeichnis
Einleitung
Diese Anleitung soll eine Einführung in den Einsatz der Browser Automatisierung mittels Selenium WebDriver und Powershell geben und anhand von kleinen Beispielen den Einsatz der gebotenen Funktionen aufzeigen. Mit der Selenium WebDriver Technik ist das Automatisieren von unterschiedlichen Browsern möglich darunter auch die drei Schwergewichte Google Chrome, Microsoft Edge, Mozilla Firefox auf die ich mich hier primär beziehen werde. Selenium Webdriver gibt es für die Nutzung unter diversen Skript- und Programmiersprachen darunter auch als .NET Bibliothek die in diesem Beitrag Verwendung findet.
Gründe für die Nutzung einer Browserautomatisierung
Extrahieren von Daten
In der Regel ist es ja so das man Aufgaben wie das Extrahieren von Daten auf Webseiten schneller und einfacher mittels der CMDLetsInvoke-WebRequest
oder Invoke-Restmethod
extrahieren lassen. Aber es gibt immer mehr Webseiten die Ihre Daten dynamisch zusammenstellen und diese bspw. nur mit aktivem JavaScript Ihre Inhalte auch preisgeben. Wenn es also weder eine API zum Abfragen von Daten gibt, oder man keine direkten Ajax-Pfade mit den Developer-Tools der Browser ausfindig machen kann, oder sich deren Umgang schwierig bis unmöglich gestaltet, bleibt einem oft nur noch die Browser-Automatisierung bei der der Browser quasi mit Befehlen ferngesteuert wird.Lange benutzte man dazu unter Windows den Internet-Explorer, da dieser ein COM-Objekt zur Verfügung stellt mit dem sich der Browser mit etlichen Skript- und Programmiersprachen fernsteuern lässt. Da aber mittlerweile viele Webseiten einfach keine vernünftige Unterstützung mehr für den IE bieten, dieser nicht mehr alle aktuellen Techniken einsetzt und der IE in naher Zukunft sowieso abgekündigt (R.I.P) ist, sollte nun wirklich die Zeit gekommen sein das man diesbezüglich auf aktuellere Pferde setzen sollte. Das war auch einer der Gründe dafür das ich diesen Beitrag hier schreibe, es soll den Umstieg von der IE Automation hin zu Selenium etwas erleichtern.
Simulieren von realen Usern
Ein anderes Feld ist das Simulieren von realen Usern und das automatisierte Testen von Webseiten. Das kann bspw. dazu dienen ob die eigene Webseite auch unter der Last von vielen Usern noch so funktioniert wie vorgesehen, wie das UI reagiert, etc. pp. Dafür bietet sich Selenium Webdriver ebenfalls an.Voraussetzungen und benötigte Komponenten für die Nutzung in der Powershell
Selenium Webdriver besteht aus mehreren Komponenten. Einmal benötigen wir die Selenium.WebDriver .NET Bibliothek als DLL und dazu den passenden Driver (eine EXE) abhängig von dem zu automatisierenden Browser. Der Driver ist oftmals versionsspezifisch, so dass ein Driver der für Chrome 92 geschaffen wurde kein installiertes Chrome 94 fernsteuern kann.Die in diesem Beitrag verwendeten Driver sind: ChromeDriver, MSEdgeDriver, FirefoxDriver
Der Browser den man fernsteuern möchte muss zudem auf dem System installiert sein.
Um euch das Herunterladen und laden der Assemblies für den Anfang zu erleichtern habe ich all das in eine Funktion Namens Create-Browser gepackt welche je nachdem welchen Browser man nutzen möchte die benötigten DLLs bei der ersten Verwendung entweder in das Skriptverzeichnis (wenn das Skript schon gespeichert ist) oder in das TEMP-Verzeichnis (wenn z.B. in der ISE nur ausprobiert wird) herunterlädt und die Bibliothek via
Add-Type
in die Session importiert. Sind die Driver und die DLL bereits im Skript- oder TEMP-Verzeichnis wird natürlich kein erneuter Download angestoßen!Create-Browser: Grundfunktion zum Erzeugen einer Browser-Instanz
Für den Anfang müssen wir ein WebDriver-Objekt des jeweiligen Browsers erzeugen auf dem anschließend alles andere aufbaut. Das ist für alle Browser ähnlich, es gibt aber ein paar kleine Detailunterschiede. (Mit den Details will ich euch jetzt nicht behelligen, das lässt sich im folgenden CMDLet selbst nachlesen.)
Die Erzeugung des Webdriver-Services und des Driver-Objekts stelle ich euch mittels folgender Funktion zur Verfügung. Sie unterstützt die zum Zeitpunkt der Erstellung dieses Beitrags aktuellen Major Versionen der o.g. Browser. Sollte eine ältere oder neuere Version zum Einsatz kommen die nicht in der Funktion hinterlegt ist kann der Funktion die zu nutzende Driver-Version die auf Nuget unter den oben genannte Links zu den Drivern als verfügbar gelistet wird mit dem Parameter -driverversion <Versionstring> mitgegeben werden.
function Create-Browser {
param(
[Parameter(mandatory=$true)][ValidateSet('Chrome','Edge','Firefox')][string]$browser,
[Parameter(mandatory=$false)][bool]$HideCommandPrompt = $true,
[Parameter(mandatory=$false)][string]$driverversion = '',
[Parameter(mandatory=$false)][object]$options = $null
)
$driver = $null
function Load-NugetAssembly {
[CmdletBinding()]
param(
[string]$url,
[string]$name,
[string]$zipinternalpath,
[switch]$downloadonly
)
if($psscriptroot -ne ''){
$localpath = join-path $psscriptroot $name
}else{
$localpath = join-path $env:TEMP $name
}
$tmp = "$env:TEMP\$([IO.Path]::GetRandomFileName())"
$zip = $null
try{
if(!(Test-Path $localpath)){
Add-Type -A System.IO.Compression.FileSystem
write-host "Downloading and extracting required library '$name' ... " -F Green -NoNewline
(New-Object System.Net.WebClient).DownloadFile($url, $tmp)
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp)
$zip.Entries | ?{$_.Fullname -eq $zipinternalpath} | %{
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_,$localpath)
}
write-host "OK" -F Green
}
if (Get-Item $localpath -Stream zone.identifier -ea SilentlyContinue){
Unblock-File -Path $localpath
}
if(!$downloadonly.IsPresent){
Add-Type -Path $localpath -EA Stop
}
}catch{
throw "Error: $($_.Exception.Message)"
}finally{
if ($zip){$zip.Dispose()}
if(Test-Path $tmp){del $tmp -Force -EA 0}
}
}
# Load Selenium Webdriver .NET Assembly and dependencies
Load-NugetAssembly 'https://www.nuget.org/api/v2/package/Newtonsoft.Json' -name 'Newtonsoft.Json.dll' -zipinternalpath 'lib/net45/Newtonsoft.Json.dll' -EA Stop
Load-NugetAssembly 'https://www.nuget.org/api/v2/package/Selenium.WebDriver/4.23.0' -name 'WebDriver.dll' -zipinternalpath 'lib/netstandard2.0/WebDriver.dll' -EA Stop
if($psscriptroot -ne ''){
$driverpath = $psscriptroot
}else{
$driverpath = $env:TEMP
}
switch($browser){
'Chrome' {
$chrome = Get-Package -Name 'Google Chrome' -EA SilentlyContinue | select -F 1
if (!$chrome){
throw "Google Chrome Browser not installed."
return
}
Load-NugetAssembly "https://www.nuget.org/api/v2/package/Selenium.WebDriver.ChromeDriver/$driverversion" -name 'chromedriver.exe' -zipinternalpath 'driver/win32/chromedriver.exe' -downloadonly -EA Stop
# create driver service
$dService = [OpenQA.Selenium.Chrome.ChromeDriverService]::CreateDefaultService($driverpath)
# hide command prompt window
$dService.HideCommandPromptWindow = $HideCommandPrompt
# create driver object
if ($options){
$driver = New-Object OpenQA.Selenium.Chrome.ChromeDriver $dService,$options
}else{
$driver = New-Object OpenQA.Selenium.Chrome.ChromeDriver $dService
}
}
'Edge' {
$edge = Get-Package -Name 'Microsoft Edge' -EA SilentlyContinue | select -F 1
if (!$edge){
throw "Microsoft Edge Browser not installed."
return
}
Load-NugetAssembly "https://www.nuget.org/api/v2/package/Selenium.WebDriver.MSEdgeDriver.win32/$driverversion" -name 'msedgedriver.exe' -zipinternalpath 'driver/win32/msedgedriver.exe' -downloadonly -EA Stop
# create driver service
$dService = [OpenQA.Selenium.Edge.EdgeDriverService]::CreateDefaultService($driverpath)
# hide command prompt window
$dService.HideCommandPromptWindow = $HideCommandPrompt
# create driver object
if ($options){
$driver = New-Object OpenQA.Selenium.Edge.EdgeDriver $dService,$options
}else{
$driver = New-Object OpenQA.Selenium.Edge.EdgeDriver $dService
}
}
'Firefox' {
$ff = Get-Package -Name "Mozilla Firefox*" -EA SilentlyContinue | select -F 1
if (!$ff){
throw "Mozilla Firefox Browser not installed."
return
}
Load-NugetAssembly "https://www.nuget.org/api/v2/package/Selenium.WebDriver.GeckoDriver/$driverversion" -name 'geckodriver.exe' -zipinternalpath 'driver/win64/geckodriver.exe' -downloadonly -EA Stop
# create driver service
$dService = [OpenQA.Selenium.Firefox.FirefoxDriverService]::CreateDefaultService($driverpath)
# hide command prompt window
$dService.HideCommandPromptWindow = $HideCommandPrompt
# create driver object
if($options){
$driver = New-Object OpenQA.Selenium.Firefox.FirefoxDriver $dService, $options
}else{
$driver = New-Object OpenQA.Selenium.Firefox.FirefoxDriver $dService
}
}
}
return $driver
}
Erzeugen eines Browser-Objekts
Der erste Schritt ist nun das Erzeugen des jeweiligen Browser-Objekts. Man muss das obige CMDLet immer als erstes im Skript platzieren damit man sie nutzen kann. Um nun den jeweiligen Browser anzugeben besitzt die
Create-Browser]] Funktion den Parameter -browser.Eingebaut sind die folgenden Browser Google Chrome, Microsoft Edge (chromium based) und Mozilla Firefox.Valide Parameter-Werte für den Parameter lauten: {{Chrome, Edge, Firefox
.Beispiel für das Erzeugen eines Chrome-Browsers:
$browser = Create-Browser -browser Chrome
Hat das geklappt war das eigentlich schon die halbe Miete und das Browser-Fenster sollte auf dem Desktop erscheinen.
Die Funktion hat noch einen weiteren Boolean-Parameter namens -HideCommandPrompt, welcher das normalerweise erscheinende Kommandozeilenfenster des Webdrivers per Default unterdrückt, möchte man es sehen wenn man bspw. Debug-Ausgaben sehen möchte setzt man diesen Parameter auf $false mittels -HideCommandPrompt $false.
Ebenso kann eine spezielle WebDriver-Version mittel -driverversion <Versionstring> angeben werden die genutzt werden soll, sollte das nötig sein, siehe Hinweis weiter oben.
In den nun folgenden Code-Schnippseln werde ich der Verständlichkeit halber die Variable $browser weiterhin beibehalten und das Erzeugen des Browser-Objekts nicht erneut wiederholen, ich gehe also davon aus das Ihr das $browser Objekt in euren Skripten bereits vor dem Ausprobieren der folgenden Funktionen bereits erzeugt habt.
Browser-Fenster anpassen
Die Anpassung des Browser-Fensters lässt sich mit den folgenden Funktionen und Eigenschaften steuern:
x/y Position festlegen
$browser.Manage().window.position = '0,0'
Größe des Fensters in Pixeln festlegen (Breite,Höhe)
$browser.Manage().window.Size = '1024,768'
Fenster maximieren, minimieren, Fullscreen
# maximize Window
$browser.Manage().Window.Maximize()
# minimize Window
$browser.Manage().Window.Minimize()
# Fullscreen
$browser.Manage().Window.FullScreen()
Navigieren zu URLs
Das Öffnen von Webseiten bzw. URLs lässt sich nun folgendermaßen erledigen:
$browser.Url = 'https://administrator.de'
$navi = $browser.Navigate()
Man legt die URL in der Eigenschaft
URL
fest. Das zurückgegebene Objekt der Navigate()
Funktion bietet seinerseits weitere Funktionen um im Fenster zu vor und zurück zu navigieren oder die aktuelle Seite zu aktualisieren, es ist also meist eine gute Idee das Ergebnis einer Variablen zuzuweisen wenn man es später wieder benötigt.Name MemberType Definition
---- ---------- ----------
Back Method void Back(), void INavigation.Back()
Forward Method void Forward(), void INavigation.Forward()
GoToUrl Method void GoToUrl(string url), void GoToUrl(uri url), void INavigation.GoToUrl(string url), void INavigation.GoToUrl(uri url)
Refresh Method void Refresh(), void INavigation.Refresh()
$browser.Navigate().GotoURL("https://google.com")
Schließen/Beenden des Browsers
Das Beenden der Browser-Session erledigt sich mit der für sich sprechenden Funktion
$browser.Quit()
Wichtig! Das sollte man am Ende einer Session nicht vergessen auszuführen, da ansonsten der WebDriver im Hintergrund (unsichtbar) weiter läuft auch wenn die Powershell-Session beendet wird. Lässt sich die heruntergeladenen EXE des WebDriver nicht manuell löschen weil sie noch im Zugriff ist ist das ein Zeichen dafür das eine Instanz des WebDrivers noch im Hintergrund läuft oder die PS Session nicht richtig abgeschlossen wurde oder hängt.
Seitenquelltext extrahieren
$browser.PageSource
Informationen über Cookies
Cookies der aktuell aufgerufenen Seite erhält man mittels
$browser.Manage().Cookies.AllCookies
Screenshot des aktuell sichtbaren Seiteninhalts erzeugen
Von dem aktuell sichtbaren Fenster lässt sich ein Screenshot erstellen, und lässt sich entweder direkt als Datei in diversen Dateiformaten (.NET Standardformate) speichern ...
$browser.GetScreenshot().SaveAsFile("C:\Pfad\screenshot.png",'png')
[byte[]]$img = $browser.GetScreenshot().AsByteArray
$b64 = $browser.GetScreenshot().AsBase64EncodedString
Ausführen von JavaScript-Funktionen oder eigenem JavaScript-Code
Möchte man eine Funktion oder eigenen JavaScript Code im Kontext der Seite aufrufen lässt sich dies so erreichen
$browser.ExecuteScript('alert("Test");')
Suchen/Finden von bestimmten Elementen auf Webseiten
Ein wichtiges Thema ist natürlich das referenzieren von bestimmten Elementen auf Webseiten. Bevor man etwas mit einem Objekt macht muss man es erst mal anhand unterschiedlicher Kriterien finden. Dazu bietet Selenium eine große Vielfalt an Funktionen mit der sich wirklich alles effektiv finden lässt.
Hier eine kleine Übersicht jeweils für das Finden von einem oder mehreren Elementen
[OpenQA.Selenium.By]::ClassName("meineKlasse")
[OpenQA.Selenium.By]::CssSelector(".button")
[OpenQA.Selenium.By]::Id("btnSearch")
[OpenQA.Selenium.By]::Linktext("Suchen")
[OpenQA.Selenium.By]::Name("Elementname")
[OpenQA.Selenium.By]::PartitialLinkText("TeileinesLinks")
[OpenQA.Selenium.By]::TagName("div")
[OpenQA.Selenium.By]::XPath("//input")
Die statischen Funktionen sollten in der Regel für sich sprechen und man übergibt ihnen jeweils einen String von dem Element und dem Typ das man sucht. Daher fasse ich diese hier in Beispielen zusammen
$items = $browser.FindElements([OpenQA.Selenium.By]::ClassName("meineKlasse"))
$items = $browser.FindElements([OpenQA.Selenium.By]::CssSelector(".button"))
$item = $browser.FindElement([OpenQA.Selenium.By]::Id("btnSearch"))
$items = $browser.FindElements([OpenQA.Selenium.By]::TagName("img"))
Tipp: Bei der Entwicklung seiner Skripte sind die DeveloperTools der Browser (meist erreichbar mittel F12) ein fast unverzichtbares Werkzeug um die Elemente im Quellcode der Seite ausfindig zu machen. Ein pratisches Tool dabei ist das "Locate" Tool das man in den Developer Tools oben ganz links in der Ecke findet:
Man fährt anschließend mit dem Mauszeiger auf das Element auf das man sich beziehen möchte, und klickt es an. Daraufhin wird automatisch die Stelle des Elements im Quellcode markiert. Man braucht die Elemente also nicht erst langwierig von Hand im Quellcode suchen.
[OpenQA.Selenium.By]::XPath()
Nicht jeder wird wohl etwas mit dem Begriff XPath anfangen können. Wer aber schon mal mit XPath gearbeitet hat weiß die Selektionssprachensyntax zu schätzen.
Das sieht dann bspw. so aus:
$browser.FindElements([OpenQA.Selenium.By]::XPath('//div[@class = "teaser-question-list-row"]//span[@class = "teaser-question-autor"]')).Text
Wer mehr über die XPath Syntax erfahren möchte kann sich z.B. auf folgender Seite schlau machen
https://www.w3schools.com/xml/xpath_syntax.asp
Eine Serie von Befehlen zusammenhängend ausführen
Oftmals müssen eine Reihe von Aktionen zusammenhängend oder auch wiederholend durchgeführt werden. Dafür bietet Selenium ein Objekt Namens Action an, mit welchem sich eine Sequenz erstellen, und dann mit einem einzigen Befehl ausführen lässt. Ähnlich wie eine Funktion in Powershell.
Das sieht dann in der Praxis folgendermaßen aus
# zu einer Seite navigieren
$browser.Url = 'https://administrator.de'
$navi = $browser.Navigate()
# Irgendein Element suchen auf das später in den Actions verwiesen wird
$inputfield = $browser.FindElement([OpenQA.Selenium.By]::Id('global-search-field'))
# Actions Objekt mit dem WebDriver Objekt als Parameter erstellen
$actions = [OpenQA.Selenium.Interactions.Actions]::new($browser)
# Erste Aktion: Text in ein Eingabefeld setzen
$actions.SendKeys($inputfield,"Testsuche")
# Zweite Aktion: Enter Taste im Inputfeld drücken
$actions.SendKeys($inputfield,[OpenQA.Selenium.Keys]::Enter)
# Actions-Objekt bauen
$actions.Build()
# Finales Ausführen aller im Sequenzobjekt vorhandenen Aktionen
$actions.Perform()
Um noch zu zeigen was das Actions-Objekt sonst noch an möglichen Aktionen bietet, nachfolgend eine Auflistung der verfügbaren Methoden der Actions-Klasse
TypeName: OpenQA.Selenium.Interactions.Actions
Name MemberType
---- ----------
Build Method
Click Method
ClickAndHold Method
ContextClick Method
DoubleClick Method
DragAndDrop Method
DragAndDropToOffset Method
GetType Method
KeyDown Method
KeyUp Method
MoveByOffset Method
MoveToElement Method
Perform Method
Release Method
SendKeys Method
Praxis-Beispiel: Einloggen auf einer Webseite
Oftmals liegen Inhalte hinter Logins versteckt, es ist also häufig eine der ersten Aufgaben dies abzuarbeiten. Da jeder Login anders aufgebaut ist, kann es hier natürlich kein Rezept für alle existierenden Logins geben. Als Beispiel verwende hier der Einfachheit den Login in hiesigen Forum. Dies sähe dann etwa so aus (Funktionsfähig zum aktuellen Zeitpunkt beim Schreiben des Artikels):
# zur Login Seite navigieren
$browser.URL = 'https://administrator.de/login/'
$navi = $browser.Navigate()
# Username, Passwort und Login Elemente referenzieren
$user = $browser.FindElement([OpenQA.Selenium.By]::Id('global-login-field'))
$pass = $browser.FindElement([OpenQA.Selenium.By]::Id('global-login-pass'))
$btnlogin = $browser.FindElement([OpenQA.Selenium.By]::TagName('button'))
# Benutzername und Passwort in die Eingabefelder laden
$user.SendKeys('USERNAME')
$pass.SendKeys('PASSWORD')
# Login Button betätigen
$btnlogin.Click()
# Zu
$navi.GoToUrl('https://administrator.de/latest')
# Nur fürs Beispiel 2 Sekunden warten
sleep 2
# Ausloggen
$navi.GoToUrl('https://administrator.de/members/logout.php?true')
Praxis-Beispiel: Extrahieren von sich wiederholenden Elementen
Um die Nutzung der Funktionen zum Suchen von Elementen zu demonstrieren, und wie man damit sich wiederholende Elemente wie etwa. die hier im Forum gelisteten Posts extrahiert, nehme ich die Seite unter Ticker hier im Forum als Demonstrationsobjekt
# Die "Aktuell" Seite öffnen
$browser.Url = 'https://administrator.de/latest'
$navi = $browser.Navigate()
# Finde alle Elemente mit dem Klassennamen **list-element-light-wrapper**
$browser.FindElements([OpenQA.Selenium.By]::ClassName('list-element-light-wrapper')) | %{
# ... und erzeuge für jedes dieser Elemente ein Custom-Object mit drei Eigenschaften die innerhalb des aktuellen Objekts die Details extrahieren
[pscustomobject]@{
Titel = $_.FindElement([OpenQA.Selenium.By]::ClassName("teaser-question-light-title")).Text
Link = $_.getAttribute('href')
Autor = $_.FindElement([OpenQA.Selenium.By]::ClassName("list-element-user-alias")).Text
}
}
Das Ergebnis ist dann ein Array aus Objekten das sich dann sehr gut weiterverarbeiten lässt
Titel Link Autor
----- ---- -----
Word Benutzerdefinierter Druck funktioniert nicht [content:1194406300] ti-buh
Brocade RSTP 802-1w berechnet sich immer neu [content:1194372159] alikber
Upload nur 50 Prozent der möglichen Leistung [content:1194161674] supernicky
Android und Samsung Mail app - kein Empfang von CC Mails [content:1194097066] hausrocker
Microsoft Excel Namen über Batchdatei ändern [content:1193997546] Plxgot
Windows Update KB5005033 lässt sich nicht installiere... https://administrator.de/forum/windows-update-kb5005033-laesst-sich-nicht-installieren-win-10-pro-... mecie21313
Anhang
Eigenschaften welche Objekte haben die sich mit den "Find*" Funktionen referenzieren lassen
Das Ergebnis einer der oben aufgeführten Suchaktionen für Elemente auf einer Webseite ist/sind eines oder mehrere Objekte der Klasse OpenQA.Selenium.Remote.RemoteWebElement.
Übersicht welche Eigenschaften und Funktionen solche Elemente bieten:
TypeName: OpenQA.Selenium.Remote.RemoteWebElement
Name MemberType Definition
---- ---------- ----------
Clear Method void Clear(), void IWebElement.Clear()
Click Method void Click(), void IWebElement.Click()
FindElement Method OpenQA.Selenium.IWebElement FindElement(OpenQA.Selenium.By by), OpenQA.Selenium.IWebElement ISearchContext.FindElement(...
FindElementByClassName Method OpenQA.Selenium.IWebElement FindElementByClassName(string className), OpenQA.Selenium.IWebElement IFindsByClassName.Fin...
FindElementByCssSelector Method OpenQA.Selenium.IWebElement FindElementByCssSelector(string cssSelector), OpenQA.Selenium.IWebElement IFindsByCssSelect...
FindElementById Method OpenQA.Selenium.IWebElement FindElementById(string id), OpenQA.Selenium.IWebElement IFindsById.FindElementById(string id)
FindElementByLinkText Method OpenQA.Selenium.IWebElement FindElementByLinkText(string linkText), OpenQA.Selenium.IWebElement IFindsByLinkText.FindEl...
FindElementByName Method OpenQA.Selenium.IWebElement FindElementByName(string name), OpenQA.Selenium.IWebElement IFindsByName.FindElementByName(...
FindElementByPartialLinkText Method OpenQA.Selenium.IWebElement FindElementByPartialLinkText(string partialLinkText), OpenQA.Selenium.IWebElement IFindsByP...
FindElementByTagName Method OpenQA.Selenium.IWebElement FindElementByTagName(string tagName), OpenQA.Selenium.IWebElement IFindsByTagName.FindEleme...
FindElementByXPath Method OpenQA.Selenium.IWebElement FindElementByXPath(string xpath), OpenQA.Selenium.IWebElement IFindsByXPath.FindElementByXP...
FindElements Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElements(OpenQA.Selenium.By by), Sys...
FindElementsByClassName Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByClassName(string className...
FindElementsByCssSelector Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByCssSelector(string cssSele...
FindElementsById Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsById(string id), System.Coll...
FindElementsByLinkText Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByLinkText(string linkText),...
FindElementsByName Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByName(string name), System....
FindElementsByPartialLinkText Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByPartialLinkText(string par...
FindElementsByTagName Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByTagName(string tagName), S...
FindElementsByXPath Method System.Collections.ObjectModel.ReadOnlyCollection[OpenQA.Selenium.IWebElement] FindElementsByXPath(string xpath), Syste...
GetAttribute Method string GetAttribute(string attributeName), string IWebElement.GetAttribute(string attributeName)
GetCssValue Method string GetCssValue(string propertyName), string IWebElement.GetCssValue(string propertyName)
GetHashCode Method int GetHashCode()
GetProperty Method string GetProperty(string propertyName), string IWebElement.GetProperty(string propertyName)
GetScreenshot Method OpenQA.Selenium.Screenshot GetScreenshot(), OpenQA.Selenium.Screenshot ITakesScreenshot.GetScreenshot()
SendKeys Method void SendKeys(string text), void IWebElement.SendKeys(string text)
Submit Method void Submit(), void IWebElement.Submit()
ToString Method string ToString()
Coordinates Property OpenQA.Selenium.Interactions.Internal.ICoordinates Coordinates {get;}
Displayed Property bool Displayed {get;}
Enabled Property bool Enabled {get;}
Location Property System.Drawing.Point Location {get;}
LocationOnScreenOnceScrolledIntoView Property System.Drawing.Point LocationOnScreenOnceScrolledIntoView {get;}
Selected Property bool Selected {get;}
Size Property System.Drawing.Size Size {get;}
TagName Property string TagName {get;}
Text Property string Text {get;}
Jedes Element bietet also unter anderem erneut die oben aufgeführten "FindElement(s)" Funktionen um die Suche auf die Elemente zu beschränken die sich innerhalb des/der gefundenen Elemente befinden. Weitere wichtige Funktionen sind GetAttribute() mit der sich der Inhalt von Element-Attributen wie bspw. den Link aus dem "href" Attribut zu extrahieren lässt:
$browser.FindElements([OpenQA.Selenium.By]::TagName("a")).GetAttribute('href')
Weiterführende Links
- The Selenium Browser Automation Project
- Selenium - Getting Started
- WebDriver - Common strategies
- W3C WebDriver Specification
Für Powershell Neulinge ein Powershell Link-Leitfaden für Anfänger
So, das war es für's erste. Sollte jemandem noch ein Topic zum Thema ins Gedächtnis rücken welches er hier behandelt haben möchte kann er das gerne in den Kommentaren kund tun. Ich werden dann wenn die Zeit es zulässt, gerne Ergänzungen im Beitrag vornehmen.
Ich wünsche euch nun viel Spaß bei den ersten Ausflügen in die Browser-Automatisierung mit Selenium.
Gruß @colinardo
Updates
Datum | Änderung |
---|---|
09.10.2023 | Beschreibung für das Auffinden von Objekten auf den aktuellen Stand gebracht. |
09.10.2023 | Ergänzung der Funktion um den -options Parameter mit dem man benutzerdefinierte Driver-Optionen (proxy usw.) mitgeben kann |
09.08.2022 | Aktualisierung der Funktion (Anpassung Nuget Package interner Pfad) |
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 1197173647
Url: https://administrator.de/contentid/1197173647
Ausgedruckt am: 21.11.2024 um 12:11 Uhr
27 Kommentare
Neuester Kommentar
Hi colinardo,
Aktuell verwende ich Selenium um vorher ausgezeichnete Tests automatisch per Pipeline nach jedem Commit auszuführen, um beispielsweise zu testen ob bestimmte Funktionen im UI beschädigt wurden. Aber die Verwendung mit der Powershell sieht sehr interessant aus, danke.
Wirst du in Zukunft noch etwas auf die Lasttests mit Selenium eingehen? Aktuell verwende ich dafür eine Kombination aus k6 (simulieren von virtuellen Usern z.B. 1000 User) und Selenium (z.B. automatisches einloggen in die Anwendung und messen wie lange dies dauert).
VG
Aktuell verwende ich Selenium um vorher ausgezeichnete Tests automatisch per Pipeline nach jedem Commit auszuführen, um beispielsweise zu testen ob bestimmte Funktionen im UI beschädigt wurden. Aber die Verwendung mit der Powershell sieht sehr interessant aus, danke.
Wirst du in Zukunft noch etwas auf die Lasttests mit Selenium eingehen? Aktuell verwende ich dafür eine Kombination aus k6 (simulieren von virtuellen Usern z.B. 1000 User) und Selenium (z.B. automatisches einloggen in die Anwendung und messen wie lange dies dauert).
VG
Hallo Colinardo, Dein Beitrag hat mir sehr geholfen, um die Drucker- und Netzwerknamen unserer Ricoh Drucker zu ändern. Bei vierhundert Druckern das über die Weboberfläche manuell zu machen, war mir nichts.
Am Anfang hatte ich Probleme die Webdriver.dll mit diesem Skript runterzuladen bzw. finden. Dann habe ich in der PS install-module Selenium ausgeführt und dort wurde sie dann mit ins PS Modulverzeichnis von Selenium gepackt.
Vielen Dank !
Am Anfang hatte ich Probleme die Webdriver.dll mit diesem Skript runterzuladen bzw. finden. Dann habe ich in der PS install-module Selenium ausgeführt und dort wurde sie dann mit ins PS Modulverzeichnis von Selenium gepackt.
Vielen Dank !
Hallo Colinardo,
zunächst einmal vielen Dank für diesen Post. Das ist mit Abstand das Beste, was ich zu Powershell und Selenium gefunden habe. Deine Arbeit hat mich sehr vorangebracht.
Ich bearbeite gerade eine mit einem CMS generierte Website mit Selenium.
Dazu habe ich zwei Fragen:
1.)
Da habe ich zwei unmittelbar aufeinander folgende Felder (Klassen), die ich mit FindElementsByClassName kombiniert abfragen will (wenn beide einen gewissen Zustand haben, geht es weiter).
Gibt es in Selenium außer, daß man die Y-Koordinaten miteinander vergleicht, die Möglichkeit sozusagen in der Nähe eines Feldes nach dem anderen Feld zu suchen? Ich löse das z.Zt. mit zwei geschachtelten foreach-Schleifen.
2.)
Auf der Website befindet sich rechts von dem Text ein Button, mit dem man eine Unterseite aufrufen kann.
Ich finde diesen Button mit FindElementByXPath. Wenn ich dann auf diesen Button einen Click auslöse, dauert es einen Moment und dann kommt die Fehlermeldung, daß der Button 8 Pixel tiefer als im Button-Objekt steht nicht gefunden wurde. Hast Du dafür eine Erklärung. In einem anderen Skriptteil läuft dieser Click() problemlos.
Vielen Dank für Deine Hilfe
zunächst einmal vielen Dank für diesen Post. Das ist mit Abstand das Beste, was ich zu Powershell und Selenium gefunden habe. Deine Arbeit hat mich sehr vorangebracht.
Ich bearbeite gerade eine mit einem CMS generierte Website mit Selenium.
Dazu habe ich zwei Fragen:
1.)
Da habe ich zwei unmittelbar aufeinander folgende Felder (Klassen), die ich mit FindElementsByClassName kombiniert abfragen will (wenn beide einen gewissen Zustand haben, geht es weiter).
Gibt es in Selenium außer, daß man die Y-Koordinaten miteinander vergleicht, die Möglichkeit sozusagen in der Nähe eines Feldes nach dem anderen Feld zu suchen? Ich löse das z.Zt. mit zwei geschachtelten foreach-Schleifen.
2.)
Auf der Website befindet sich rechts von dem Text ein Button, mit dem man eine Unterseite aufrufen kann.
Ich finde diesen Button mit FindElementByXPath. Wenn ich dann auf diesen Button einen Click auslöse, dauert es einen Moment und dann kommt die Fehlermeldung, daß der Button 8 Pixel tiefer als im Button-Objekt steht nicht gefunden wurde. Hast Du dafür eine Erklärung. In einem anderen Skriptteil läuft dieser Click() problemlos.
Vielen Dank für Deine Hilfe
Hallo Uwe,
danke für die schnelle Antwort.
Die Lösung zu 1 habe ich verworfen; meine doppelte foreach-Schleife funktioniert genauso gut.
Zu 2 habe ich jetzt eine sehr merkwürdige Lösung gefunden.
Ich benutze vor der Suche nach dem richtigen Button ein scrollintoView().
Damit befindet sich mein Button (je nach Anzahl der Aktionsblöcke) häufig auf y-Position 316.
Mit dieser Position sucht er beim Click auf Position 325 und findet nichts.
Ich mache jetzt einen erneuten scrollintoView() direkt auf den Button (dann hat er y-Position 0)
Damit funktioniert der Klick problemlos (der auch funktioniert hat, wenn ich das
Fenster manuell etwas verschoben habe und den Button neu gesucht habe; nur mit
316 geht es nicht).
Zwischendurch habe ich beim Programmieren das Gefühl, man muß nicht alles auf
der Welt verstehen.
Vielen Dank für Deine Hilfe
Herbert
danke für die schnelle Antwort.
Die Lösung zu 1 habe ich verworfen; meine doppelte foreach-Schleife funktioniert genauso gut.
Zu 2 habe ich jetzt eine sehr merkwürdige Lösung gefunden.
Ich benutze vor der Suche nach dem richtigen Button ein scrollintoView().
Damit befindet sich mein Button (je nach Anzahl der Aktionsblöcke) häufig auf y-Position 316.
Mit dieser Position sucht er beim Click auf Position 325 und findet nichts.
Ich mache jetzt einen erneuten scrollintoView() direkt auf den Button (dann hat er y-Position 0)
Damit funktioniert der Klick problemlos (der auch funktioniert hat, wenn ich das
Fenster manuell etwas verschoben habe und den Button neu gesucht habe; nur mit
316 geht es nicht).
Zwischendurch habe ich beim Programmieren das Gefühl, man muß nicht alles auf
der Welt verstehen.
Vielen Dank für Deine Hilfe
Herbert
Ich möchte täglich die Lübecker Nachrichten herunterladen. Leider ist die Seite dynamisch aufgebaut, das heißt, der Link zum ePaper ändert sich täglich. Heute heißt er https://epaper.ln-online.de/webreader/997286.
Das einzig konstante scheint die CSS-Klasse zu sein:
Es scheint immer der Link zu sein, der mit "edition-cover__link" definiert wird. Kann man das irgendwie abbilden?
Der nächste Schritt, wenn das ePaper aufgerufen ist, ist dann vermutlich noch schwieriger. Wo ist hier die Download-URL:
Das einzig konstante scheint die CSS-Klasse zu sein:
Es scheint immer der Link zu sein, der mit "edition-cover__link" definiert wird. Kann man das irgendwie abbilden?
Der nächste Schritt, wenn das ePaper aufgerufen ist, ist dann vermutlich noch schwieriger. Wo ist hier die Download-URL:
Hallo Uwe
Danke das Du die Ehre der Damen ("grüssen") eisern verteidigst.
Für viele Online Printmedien hilft das Projekt 12ft.io weiter...
12ft.io/
Einen schattigen Sonntag wünschend
Danke das Du die Ehre der Damen ("grüssen") eisern verteidigst.
Für viele Online Printmedien hilft das Projekt 12ft.io weiter...
12ft.io/
Einen schattigen Sonntag wünschend
Hallo Uwe
Don't feed the sharks 😈🍖
Ich gehöre zur anderen Fraktion: Berge 🤩
Wenn ein Online Printmedium in einer Internetsuchmaschine gefunden werden will (Indexierung), dann kann sie es nicht verhindern. So die Erklärung in einem Artikel den ich vor Jahren las.
Last but not least:
Vielen Dank für diesen und die vielen anderen hochkarätigen Posts die Du verschenkst!
Beste Grüsse
Don't feed the sharks 😈🍖
Ich gehöre zur anderen Fraktion: Berge 🤩
Wenn ein Online Printmedium in einer Internetsuchmaschine gefunden werden will (Indexierung), dann kann sie es nicht verhindern. So die Erklärung in einem Artikel den ich vor Jahren las.
Last but not least:
Vielen Dank für diesen und die vielen anderen hochkarätigen Posts die Du verschenkst!
Beste Grüsse
Hallo Uwe,
ich habe ich heute angefangen meine privaten PowerShell Skripte von Chrome auf Edge umzustellen.
Bis dato nutze unter Chrome die Methode AddArguments() um z.B. meinen Proxy explizit anzugeben. Bei Edge (aktuelle Version) gibt es diese Methode nicht. Daher dachte ich mir, dass evtl. über eine Funktion an die Methode komme -> Fehlanzeige.
Bin ich blind oder ist der Edge Driver noch nicht auf dem Stand wie der Chrome Driver?
Gruß,
Dani
ich habe ich heute angefangen meine privaten PowerShell Skripte von Chrome auf Edge umzustellen.
Bis dato nutze unter Chrome die Methode AddArguments() um z.B. meinen Proxy explizit anzugeben. Bei Edge (aktuelle Version) gibt es diese Methode nicht. Daher dachte ich mir, dass evtl. über eine Funktion an die Methode komme -> Fehlanzeige.
Bin ich blind oder ist der Edge Driver noch nicht auf dem Stand wie der Chrome Driver?
Gruß,
Dani
Hallo Uwe,
auch laut deinem Link (https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/edge ..) muss es die Methode AddArguemts() geben. Daher bin ich nochmals auf die Ursachensuche gegangen.
In meinem Fall ist für das Verhalten das PowerShell Module Selenium (Install-Module Selenium) schuld. Sobald ich diese nicht mehr automatisch lade bzw. deinstalliere, steht die Methode AddArugments wieder zur Verfügung. Vermutlich ist es auch dem Umstand geschuldet, dass auf Powershell Gallery die Version 3.0.1 bereitgestellt ist. Hingegen auf NuGut bereits Version 4.14.x.
Gruß,
Dani
auch laut deinem Link (https://www.selenium.dev/selenium/docs/api/java/org/openqa/selenium/edge ..) muss es die Methode AddArguemts() geben. Daher bin ich nochmals auf die Ursachensuche gegangen.
In meinem Fall ist für das Verhalten das PowerShell Module Selenium (Install-Module Selenium) schuld. Sobald ich diese nicht mehr automatisch lade bzw. deinstalliere, steht die Methode AddArugments wieder zur Verfügung. Vermutlich ist es auch dem Umstand geschuldet, dass auf Powershell Gallery die Version 3.0.1 bereitgestellt ist. Hingegen auf NuGut bereits Version 4.14.x.
$item = $browser.FindElement([OpenQA.Selenium.By]::Id("btnSearch"))
Bei der Variable $item fehlt am Ende ein "s".Load-NugetAssembly "https://www.nuget.org/api/v2/package/Selenium.WebDriver.MSEdgeDriver/$driverversion" -name 'msedgedriver.exe' -zipinternalpath 'driver/win64/msedgedriver.exe' -downloadonly -EA Stop
Den Namen von "msedgedriver.exe" in "MicrosoftWebDriver.exe" ändern. Anderenfalls kommt bei mir eine Fehlermeldung.Load-NugetAssembly 'https://www.nuget.org/api/v2/package/Selenium.WebDriver/4.11.0' -name 'WebDriver.dll' -zipinternalpath 'lib/net45/WebDriver.dll' -EA Stop
Ist es Absicht, dass du eine Version im Downloadlink verankert hast. Alternativ die Versionsnummer weglassen. Damit wird automatisch die neuste Version heruntergeladen.Gruß,
Dani
Moin Uwe,
Gruß,
Dani
Man darf hier nicht mit Selenium-Modul und DLL mixen, sonst gibt es Durcheinander. Die Funktion ist rein ohne extra zu installierndes Selenium-Modul gedacht.
Danke für den Denkanstoß. Das war mir so nicht bewusst.Ja das war Absicht, die Version hatte noch keine Abhängigkeiten deswegen hatte ich die Version genommen
Super, danke dir.Gruß,
Dani
Hi Colinardo,
vielen Dank für deinen wunderbaren Thread zu diesem Thema. Finde ich sehr hilfreich und gut strukturiert.
Ein Frage habe ich dazu. Gibt es eine Möglichkeit den Code auszuführen ohne den Browser im Vordergrund zu halten? Sobald ich "$browser.Manage().Window.Minimize()" verwende, findet das Skript die Buttons etc. nicht mehr.
Danke und Grüße
vielen Dank für deinen wunderbaren Thread zu diesem Thema. Finde ich sehr hilfreich und gut strukturiert.
Ein Frage habe ich dazu. Gibt es eine Möglichkeit den Code auszuführen ohne den Browser im Vordergrund zu halten? Sobald ich "$browser.Manage().Window.Minimize()" verwende, findet das Skript die Buttons etc. nicht mehr.
Danke und Grüße
Hallo Uwe,
danke für die Antwort.
Mittlerweile hab ich es geschafft, indem ich den Browser "headless" gestartet habe. Das scheint zumindest für meinen Anwendungsfall zu funktionieren.
www.selenium.dev/blog/2023/headless-is-going-away/
Den GET/POST Ansatz hatte ich vor Selenium versucht, dass war für mein Projekt aber nicht umsetzbar bzw. zu kompliziert.
Grüße
Mauro
danke für die Antwort.
Mittlerweile hab ich es geschafft, indem ich den Browser "headless" gestartet habe. Das scheint zumindest für meinen Anwendungsfall zu funktionieren.
Add-Type -Path $PSScriptRoot"\WebDriver.dll"
$options = New-Object OpenQA.Selenium.Edge.EdgeOptions
$options.AddArgument("--headless")
$browser = Create-Browser -browser Edge -options $options
Den GET/POST Ansatz hatte ich vor Selenium versucht, dass war für mein Projekt aber nicht umsetzbar bzw. zu kompliziert.
Grüße
Mauro
Hallo @colinardo,
ist es irgendwie möglich den undetected_chromedriver mit in das Script aufzunehmen, da sehr viel Seiten eine Boterkennung haben?
Freundliche Grüße
Markus
ist es irgendwie möglich den undetected_chromedriver mit in das Script aufzunehmen, da sehr viel Seiten eine Boterkennung haben?
Freundliche Grüße
Markus
Hallo @colinardo,
ich kann dich gut verstehen. Hier geht es aber wirklich nur um eine Seite, wo es im normalen privaten Chrome Modus funktioniert aber nicht mit deinem Script und dem chromedriver. Die Seite wird nur einmal am Tag ohne VPN Verbindung aufgerufen und der Cloudflare Security Check schlägt immer fehl. Auch wenn es per Hand erfolgt. Sobald ich es aber manuell im privaten Fenster von Chrome ohne den Chromedriver mache, funktioniert alles ohne Probleme. Chromedriver und Chrome sind auf dem neuesten Stand.
Freundliche Grüße
Markus
ich kann dich gut verstehen. Hier geht es aber wirklich nur um eine Seite, wo es im normalen privaten Chrome Modus funktioniert aber nicht mit deinem Script und dem chromedriver. Die Seite wird nur einmal am Tag ohne VPN Verbindung aufgerufen und der Cloudflare Security Check schlägt immer fehl. Auch wenn es per Hand erfolgt. Sobald ich es aber manuell im privaten Fenster von Chrome ohne den Chromedriver mache, funktioniert alles ohne Probleme. Chromedriver und Chrome sind auf dem neuesten Stand.
Freundliche Grüße
Markus
@MarkusW95,
Gruß,
Dani (Mod)
ich kann dich gut verstehen. Hier geht es aber wirklich nur um eine Seite, wo es im normalen privaten Chrome Modus funktioniert aber nicht mit deinem Script und dem chromedriver.
das mag sein. Trotzdem gelten unsere Richtlinien uneingeschränkt.Gruß,
Dani (Mod)