Jump to content

Dropdown-Menü (Webelement) richtig auslesen


Direkt zur Lösung Gelöst von mwiederkehr,

Empfohlene Beiträge

Hallo zusammen,

 

ich verwende Powershell und Selenium um Webelemente auf einer Webseite anzusteuern. Bei mehreren Dropdown-Menüs wird eine Auswahl getroffen und dann wird ein Button geklickt um Daten zu erhalten. Funktioniert soweit auch ganz gut, allerdings macht eines der Dropdown-Menüs regelmäßig Zicken und springt nach der Auswahl einfach zurück auf den Ursprungswert und mein Code rennt weiter.

 

Also war mein Gedanke den Wert des Dropdown-Menüs zu prüfen bevor ich den Code weiter laufen lasse, mit Hilfe einer Do-Until Schleife.

 

Nun hat sich dabei aber herausgestellt, dass das verflixte Dropdown-Menü zu allen Zeiten immer die gleiche Text-Eigenschaft besitzt, selbst dann wenn eigentlich ein ganz anderer Wert darin angezeigt wird.

 

Beispiel: Im Dropdown-Menü steht per default "2024 Juli". Wenn ich jedoch mit "Write-Host $Element.Text" die Text-Eigenschaft abfrage dann bekomme ich als Rückgabewert "All". 

 

Das Blöde ist, "All" ist genau der Wert, den ich auswählen möchte und die Bedingung, dass mein Code weiterläuft. Also erst wenn "All" wirklich ausgewählt wurde, soll mein Code weiterlaufen.

 

Hier der ganze Code, inklusive dem Laden der Triber für diverse Browser:

 

# Browsertreiber laden
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' -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/$driverversion" -name 'msedgedriver.exe' -zipinternalpath 'driver/win64/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
}

$url = 'https://www.cboe.com/delayed_quotes/nsc/quote_table'

Write-Host 'Url: '  $url

# $browser = Create-Browser -browser Chrome
$browser = Create-Browser -browser Edge
$browser.Url = $url
$navi = $browser.Navigate() 

Start-Sleep -Seconds 1

$browser.Manage().Window.FullScreen()

#Cookie-Meldung ausblenden (Agree Button)

$agreeButton = $browser.FindElement([OpenQA.Selenium.By]::XPath("//button[contains(@class, 'button--solid') and contains(@class, 'privacy-alert__agree-button') and text()='I agree']"))
$agreeButton.Click()

#Optionsrange (Drowpdown-Menü) auf "All" stellen

$optionsrange = $browser.FindElement([OpenQA.Selenium.By]::XPath("//input[contains(@class, 'ReactSelect__input') and contains(@id, 'select_5')]"))

$actions = [OpenQA.Selenium.Interactions.Actions]::new($browser)
$actions.SendKeys($optionsrange,'All')
$actions.SendKeys($optionsrange,[OpenQA.Selenium.Keys]::Enter)
$actions.Build()
$actions.Perform()

Start-Sleep -Seconds 3

#Expirations (Drowpdown-Menü) auf "All" stellen

$expirations = $browser.FindElement([OpenQA.Selenium.By]::XPath("//input[contains(@class, 'ReactSelect__input') and contains(@id, 'select_7')]"))

$singlevalue = $browser.FindElement([OpenQA.Selenium.By]::XPath("//div[contains(@class, 'ReactSelect__single-value') and contains(@class, 'css-1dimb5e-singleValue')]"))

write-host $singlevalue.Text

do {

$actions = [OpenQA.Selenium.Interactions.Actions]::new($browser)
$actions.SendKeys($expirations,'All')
$actions.SendKeys($expirations,[OpenQA.Selenium.Keys]::Enter)
$actions.Build()
$actions.Perform()

Start-Sleep -Seconds 1

write-host $singlevalue.Text

} until ($singlevalue.Text -eq 'All')

# Warten bis der Browser die Daten (im Hintergrund) geladen hat

Start-Sleep -Seconds 2

# Dann erst die Optionskette anzeigen 

$viewchainbutton = $browser.FindElement([OpenQA.Selenium.By]::XPath("//button[contains(@class, 'Button__StyledButton-cui__sc-1ahwe65-2') and contains(@class, 'hMfKkd')]"))
Start-Sleep -Seconds 1
$viewchainbutton.Click()

# Link-Button klicken

$browser.FindElement([OpenQA.Selenium.By]::XPath("//a[contains(@type, 'button') and contains(@download, 'quotedata.csv')]")).click()

Start-Sleep -Seconds 2

Write-Output "Ready"

$browser.Quit()

 

 

Hier nochmal explizit die Codezeilen um die es geht:

 

$singlevalue = $browser.FindElement([OpenQA.Selenium.By]::XPath("//div[contains(@class, 'ReactSelect__single-value') and contains(@class, 'css-1dimb5e-singleValue')]"))

write-host $singlevalue.Text

do {

$actions = [OpenQA.Selenium.Interactions.Actions]::new($browser)
$actions.SendKeys($expirations,'All')
$actions.SendKeys($expirations,[OpenQA.Selenium.Keys]::Enter)
$actions.Build()
$actions.Perform()

Start-Sleep -Seconds 1

write-host $singlevalue.Text

} until ($singlevalue.Text -eq 'All')

 

Hat jemand eine Ahnung, wie man das Dropdown-Menü so abfragt, dass man tatsächlich den darin ausgewählten Wert erhält?

 

Es würde mir auch helfen wenn mir jemand sagen kann weshalb dieses Dropdown-Menü in vielen Fällen einfach wieder auf den default Wert zurückspringt.

 

Beste Grüße

Marc

bearbeitet von --Marc--
Link zu diesem Kommentar

Ob Du da hier im richtigen Forum bist? Das klingt eher nach CSS/Webdev - an den Elementen hängen ja auch noch ziemlich viele Events, die Dein Skript nicht auslösen wird. Und dahinter stecken dann noch ein paar 100k Javascript. Und auch wenn ich bei Powershell nicht ganz Noob bin, bei Web bin ich es :-)  Selenium dürfte hier eher unbekannt sein, ich habe noch nie davon gehört. Und React ist auch nicht in meinem Werkzeugkasten.

 

Ich verbuche das mal als Reverse Engineering Problem - Website-Funktionen aufzudröseln ist je nach Framework nahezu unmöglich. Siehe das invalid-Event des Dropdowns:

 

function) Dt(e, r);
else if (-1 < Ct.indexOf(e)) e = Tt(n, e, t, i, r), yt.push(e);
else if (!fu

 

Was ist Ct, was ist Tt, was yt?

 

PS: "Damit überhaupt mal jemand antwortet"... ;-)

Link zu diesem Kommentar
  • Beste Lösung

Die Vermutung von Martin ist richtig. Frameworks wie React stellen Steuerelemente wie eine Auswahlliste gerne aus anderen Elementen zusammen. In diesem Fall ist es im Sourcecode kein "<select>", sondern ein "<div>" mit "<input>". Beim Ausklappen werden die Elemente erzeugt, die Daten werden allenfalls erst dann dynamisch abgerufen.

 

Möchte man das fernsteuern, muss man möglichst genau machen, was der Benutzer auch macht. Also Klick aufs Dropdown, über Optionen iterieren, gewünschte Option anklicken.

 

In Deinem Fall scheint es einfacher zu gehen: Die gewünschten Daten werden nicht beim Klick auf die Schaltfläche geladen, sondern sind von Anfang an im Sourcecode vorhanden: "CTX.contextOptionsData" enthält alle Daten als JSON. Das solltest Du per RegEx extrahieren und "ConvertFrom-Json" übergeben können.

Link zu diesem Kommentar
Am 25.7.2024 um 09:16 schrieb mwiederkehr:

In Deinem Fall scheint es einfacher zu gehen: Die gewünschten Daten werden nicht beim Klick auf die Schaltfläche geladen, sondern sind von Anfang an im Sourcecode vorhanden: "CTX.contextOptionsData" enthält alle Daten als JSON. Das solltest Du per RegEx extrahieren und "ConvertFrom-Json" übergeben können.

 

Erstmal herzlichen Dank für eure Rückmeldungen!

 

@mwiederkehr: Nach einer solchen Lösung hatte ich zu Beginn gesucht, aufgrund meiner mangelhaften Kenntnisse wurde ich jedoch nicht fündig. Das ist auf jeden Fall mega! So brauche ich die Dropdowns gar nicht mehr anzusteuern, sondern ich muss nur das Element "CTX.contextOptionsData" auslesen. Wird vermutlich auch ein paar Herausforderungen mit sich bringen das zu realisieren aber am Ende ist es wesentlich komfortabler.

 

Tausend Dank! Hat sich also doch gelohnt die Frage hier zu stellen. :-) :thumb1:

 

P.S. Ich hatte das Problem zwischenzeitlich übrigens gelöst indem ich einen Teil des genannten Codes einfach mehrfach ausführe:

 

$actions = [OpenQA.Selenium.Interactions.Actions]::new($browser)
$actions.SendKeys($expirations,'All')
$actions.SendKeys($expirations,[OpenQA.Selenium.Keys]::Enter)
$actions.Build()
$actions.Perform()

Start-Sleep -Seconds 1

$actions.Perform()

Start-Sleep -Seconds 1

$actions.Perform()

 

Ist zwar nicht schön aber hat seinen Zweck bislang erfüllt. Der Fehler ist bislang nicht mehr aufgetreten.

Link zu diesem Kommentar

Schreibe einen Kommentar

Du kannst jetzt antworten und Dich später registrieren. Falls Du bereits ein Mitglied bist, logge Dich jetzt ein.

Gast
Auf dieses Thema antworten...

×   Du hast formatierten Text eingefügt.   Formatierung jetzt entfernen

  Only 75 emoji are allowed.

×   Dein Link wurde automatisch eingebettet.   Einbetten rückgängig machen und als Link darstellen

×   Dein vorheriger Inhalt wurde wiederhergestellt.   Editor-Fenster leeren

×   Du kannst Bilder nicht direkt einfügen. Lade Bilder hoch oder lade sie von einer URL.

×
×
  • Neu erstellen...