Elemente lokalisieren

Locating one element

Eine der grundlegendsten Techniken, die bei der Verwendung des WebDriver erlernt werden müssen, ist wie man Elemente auf der Webseite findet. WebDriver bietet eine Reihe von verschiedenen Möglichkeiten um Elemente zu finden, darunter die Suche nach einem Element anhand des ID-Attributs:

WebElement cheese = driver.findElement(By.id("cheese"));
  
driver.find_element(By.ID, "cheese")
  
IWebElement element = driver.FindElement(By.Id("cheese"));
  
cheese = driver.find_element(id: 'cheese')
  
const cheese = driver.findElement(By.id('cheese'));
  
val cheese: WebElement = driver.findElement(By.id("cheese"))
  

Wie das Beispiel zeigt, wird die Lokalisierung der Elemente mit dem WebDriver direkt an einer Instanz des WebDriver Objektes durchgeführt. Die findElement(By) Methode liefert ein Objekt des Types ẀebElement.

  • WebDriver repräsentiert den Browser
  • WebElement repräsentiert einen bestimmten DOM Knoten (z.B. einen Link, ein Eingabefeld, etc.)

Ab dem Zeitpunkt, ab dem eine Referenz zu einem WebElement “gefunden” wurde, kann der Suchumfang auf dieses Element eingeschränkt werden. Es können weitere eingegrenzte Suchen auf Basis des ausgewählten Elements durchgeführt werden, indem die gleiche Methode angewandt wird:

WebElement cheese = driver.findElement(By.id("cheese"));
WebElement cheddar = cheese.findElement(By.id("cheddar"));
  
cheese = driver.find_element(By.ID, "cheese")
cheddar = cheese.find_elements_by_id("cheddar")
  
IWebElement cheese = driver.FindElement(By.Id("cheese"));
IWebElement cheddar = cheese.FindElement(By.Id("cheddar"));
  
cheese = driver.find_element(id: 'cheese')
cheddar = cheese.find_element(id: 'cheddar')
  
const cheese = driver.findElement(By.id('cheese'));
const cheddar = cheese.findElement(By.id('cheddar'));
  
val cheese = driver.findElement(By.id("cheese"))
val cheddar = cheese.findElement(By.id("cheddar"))
  

Dies wird ermöglicht weil sowohl der WebDriver als auch das WebElement das Interface SearchContext implementieren. Wir verstehen dies im WebDriver als role-based interface (rollenbasiertes Interface). Dieses Interface ermöglicht um herauszufinden ob eine driver Implementierung ein bestimmtes Feature unterstützt oder nicht. Diese Schnittstellen (Interface) sind klar definiert und versuchen daran festzuhalten, dass es nur eine Verantwortlichkeit dafür gibt. Mehr über den Aufbau und die Verantwortlichkeiten der Driver können hier nachgelesen werden Link zu einer Sektion die noch definiert werden muss

Folglich untersützt das By Interface zahlreich zusätzliche Suchstrategien. Eine verschachtelte Suche ist nicht die effektivste Methode um die den gewünschten Käse zu finden. Es werden zwei getrennte Befehle an den Browser gesendet. Der erste der den gesamten DOM nach dem Element mit der ID “cheese” sucht, gefolgt von der Suche nach “cheddar” mit einem eingeschränkten Kontext.

Um die Effektivität zu erhöhen sollte ein präziserer Locator (Identifizierungsstrategie) gewählt werden; WebDriver unterstützt die Suche nach Elementen auch mit Hilfe eines CSS-locators, mit dem es auch möglich ist Kombinationen in einer einzelnen Suche durchzuführen:

driver.findElement(By.cssSelector("#cheese #cheddar"));
  
cheddar = driver.find_element_by_css_selector("#cheese #cheddar")
  
driver.FindElement(By.CssSelector("#cheese #cheddar"));
  
driver.find_element(css: '#cheese #cheddar')
  
const cheddar = driver.findElement(By.css('#cheese #cheddar'));
  
driver.findElement(By.cssSelector("#cheese #cheddar"))
  

Finden mehrerer Elemente

Angenommen das Dokument in dem wir suchen wollen beinhaltet eine sortierte Liste mit Käsesorten die uns am besten schmecken:

<ol id=cheese>
 <li id=cheddar><li id=brie><li id=rochefort><li id=camembert></ol>

Es steht außer Frage, je mehr Käse desto besser, es wäre aber umständlich jedes Element einzeln abrufen zu müssen. Daher gibt es die Möglichkeit mit findElements(By) mehrere Elemente gleichzeitig zu finden. Diese Methode liefert eine Sammlung (Collection) von WebElementen. Wird nur ein Element gefunden, wird trotzdem eine Sammlung (mit einem Element) retourniert. Wird kein Element gefunden ist die Liste leer.

List<WebElement> muchoCheese = driver.findElements(By.cssSelector("#cheese li"));
  
mucho_cheese = driver.find_elements_by_css_selector("#cheese li")
  
IReadOnlyList<IWebElement> muchoCheese = driver.FindElements(By.CssSelector("#cheese li"));
  
mucho_cheese = driver.find_elements(css: '#cheese li')
  
const muchoCheese = driver.findElements(By.css('#cheese li'));
  
val muchoCheese: List<WebElement>  = driver.findElements(By.cssSelector("#cheese li"))
  

Strategien der Elementsuche

Im WebDriver existieren acht unterschiedliche Möglichkeiten um Elemente zu lokalisieren:

Lokator/Suchmethode (locator)Beschreibung
class nameLokalisiert Elemente mit dem gewünschten Klassennamen (Zusammengesetzte Klassennamen sind nicht erlaubt)
css selectorLokalisiert Elemente die dem CSS-Selektor entsprechen
idLokalisiert Elemente deren ID dem Suchwert entsprechen
nameLokalisiert Elemente die den entsprechenden Wert im NAME Attribut haben
link textLokalisiert Link-Elemente deren sichtbarer Text dem Suchwert entsprechen
partial link textLokalisiert Link-Elemente die den Suchwert im sichtbaren Text vorkommt
tag nameLokalisiert Elemente mit den entsprechenden HTML-Tags
xpathLokalisiert Elemente die auf dem xpath-Selektor entsprechen

Tips zur Verwendung von Selektoren

Die bevorzugte Methode um Elemente zu identifizieren ist mit Sicherheit mit Hilfe der HTML IDs. Diese sind eindeutig, konsitent und vorhersehbar, weiters arbeitet diese Methode sehr schnell, da hierbei auf komplizierte DOM Verarbeitungen verzichtet wird.

Wenn eindeutige IDs nicht verfügbar sind, ist ein gut definierter CSS selector die bevorzugte Methode um Elemente zu lokalisieren. XPath-Suchen funktionieren gleich dem CSS-Selektoren, allerdings ist die Syntax komplizierter und schwieriger zu debuggen. Obwohl XPath-Selektoren sehr flexibel sind, sind sie in der Regel nicht von den Browser-Herstellern auf Leistung getestet und sind tendenziell recht langsam.

Selektorstrategien die linkText oder partialLinkText verwenden haben den Nachteil das diese nur für Link-Elemente angewandt werden können. Weiters werden diese Selektoren intern im WebDriver als XPath-Selektoren aufgelöst.

Den HTML-Tag als Identifizierungsmerkmal zu verwenden kann gefährlich sein. Meistens sind viele Elemente mit dem gleichen HTML-Tag auf einer Webseite. Eine sinnvolle Verwendung könnte sein, diese Strategie mit der findElements(By) Methode zu verwenden, die eine Sammlung von WebElementen retourniert.

Empfohlen wird die Suchen so kompackt und einfach lesbar wie möglich zu halten. Den DOM abzufragen ist eine aufwändige Operation für den WebDriver, und je präziser der Suchbegriff ist, desto besser.

Relative Suchstrategien

Selenium 4 führt relative Locators ein, die zuvor als Friendly Locators bekannt waren. Diese Funktionalität wurde hinzugefügt um Elemente zu finden, die sicht in der Nähe zu anderen Elementen befinden. Folgende relative Locators sind verfügbar:

  • above (oberhalb)
  • below (unterhalb)
  • toLeftOf (links)
  • toRightOf (rechts)
  • near (nahe/nächst)

Die findElement Methode akzeptiert nun eine weitere Möglichkeit with(By) die einen relativen Locator liefert.

Wie funktionieren die relativen Suchemethoden

Selenium verwendet folgende JavaScript Funktion getBoundingClientRect() um das entsprechende Element zu finden. Diese Funktion retourniert Eigenschaften eines Elements wie z.B right, left, bottom und top (links, rechts, oben, unten)

Betrachten wir das folgende Beispiel um die Funktionalität der relativen Locators besser zu verstehen:

Relative Locators

above() - oberhalb

Liefert das WebElement, welches sich über dem spezifiziertem Element befindet.

import static org.openqa.selenium.support.locators.RelativeLocator.with;

WebElement passwordField = driver.findElement(By.id("password"));
WebElement emailAddressField = driver.findElement(with(By.tagName("input"))
.above(passwordField));
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

passwordField = driver.find_element(By.ID, "password")
emailAddressField = driver.find_element(locate_with(By.TAG_NAME, "input").above(passwordField))
using static OpenQA.Selenium.RelativeBy;

IWebElement passwordField = driver.FindElement(By.Id("password"));
IWebElement emailAddressField = driver.FindElement(RelativeBy(By.TagName("input")).Above(passwordField));
password_field= driver.find_element(:id, "password")
email_address_field = driver.find_element(relative: {tag_name: 'input', above:password_field})
let passwordField = driver.findElement(By.id('password'));
let emailAddressField = await driver.findElement(locateWith(By.tagName('input')).above(passwordField));
val passwordField = driver.findElement(By.id("password"))
val emailAddressField = driver.findElement(with(By.tagName("input")).above(passwordField))

below() - unterhalb

Findet das WebElement, welches sich unter dem spezifiziertem Element befindet.

import static org.openqa.selenium.support.locators.RelativeLocator.with;

WebElement emailAddressField = driver.findElement(By.id("email"));
WebElement passwordField = driver.findElement(with(By.tagName("input"))
.below(emailAddressField));
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

emailAddressField = driver.find_element(By.ID, "email")
passwordField = driver.find_element(locate_with(By.TAG_NAME, "input").below(emailAddressField))
using static OpenQA.Selenium.RelativeBy;

IWebElement emailAddressField = driver.FindElement(By.Id("email"));
IWebElement passwordField = driver.FindElement(RelativeBy(By.TagName("input")).Below(emailAddressField));
email_address_field = driver.find_element(:id, "email")
password_field = driver.find_element(relative: {tag_name: 'input', below: email_address_field})
let emailAddressField = driver.findElement(By.id('email'));
let passwordField = await driver.findElement(locateWith(By.tagName('input')).below(emailAddressField));
val emailAddressField = driver.findElement(By.id("email"))
val passwordField = driver.findElement(with(By.tagName("input")).below(emailAddressField))

Liefert das WebElement, welches sich links vom spezifizierten Element befindet.

import static org.openqa.selenium.support.locators.RelativeLocator.with;

WebElement submitButton = driver.findElement(By.id("submit"));
WebElement cancelButton = driver.findElement(with(By.tagName("button"))
.toLeftOf(submitButton));
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

submitButton = driver.find_element(By.ID, "submit")
cancelButton = driver.find_element(locate_with(By.TAG_NAME, "button").
to_left_of(submitButton))
using static OpenQA.Selenium.RelativeBy;

IWebElement submitButton = driver.FindElement(By.Id("submit"));
IWebElement cancelButton = driver.FindElement(RelativeBy(By.TagName("button")).LeftOf(submitButton));
submit_button= driver.find_element(:id, "submit")
cancel_button = driver.find_element(relative: {tag_name: 'button', left:submit_button})
let submitButton = driver.findElement(By.id('submit'));
let cancelButton = await driver.findElement(locateWith(By.tagName('button')).toLeftOf(submitButton));
val submitButton = driver.findElement(By.id("submit"))
val cancelButton = driver.findElement(with(By.tagName("button")).toLeftOf(submitButton))

toRightOf() - rechts davon

Liefert das WebElement, das sich rechts des spezifierten Elements befindet.

import static org.openqa.selenium.support.locators.RelativeLocator.with;

WebElement cancelButton = driver.findElement(By.id("cancel"));
WebElement submitButton = driver.findElement(with(By.tagName("button")).toRightOf(cancelButton));
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

cancelButton = driver.find_element(By.ID, "cancel")
submitButton = driver.find_element(locate_with(By.TAG_NAME, "button").
to_right_of(cancelButton))
using static OpenQA.Selenium.RelativeBy;

IWebElement cancelButton = driver.FindElement(By.Id("cancel"));
IWebElement submitButton = driver.FindElement(RelativeBy(By.TagName("button")).RightOf(cancelButton));
cancel_button = driver.find_element(:id, "cancel")
submit_button = driver.find_element(relative: {tag_name: 'button', right:cancel_button})
let cancelButton = driver.findElement(By.id('cancel'));
let submitButton = await driver.findElement(locateWith(By.tagName('button')).toRightOf(cancelButton));
val cancelButton = driver.findElement(By.id("cancel"))
val submitButton = driver.findElement(with(By.tagName("button")).toRightOf(cancelButton))

near() - in der Nähe von

Liefert das WebElement, welches maximal 50px vom spezifizierten Element entfernt ist.

import static org.openqa.selenium.support.locators.RelativeLocator.with;

WebElement emailAddressLabel = driver.findElement(By.id("lbl-email"));
WebElement emailAddressField = driver.findElement(with(By.tagName("input")).near(emailAddressLabel));
from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

emailAddressLabel = driver.find_element(By.ID, "lbl-email")
emailAddressField = driver.find_element(locate_with(By.TAG_NAME, "input").
near(emailAddressLabel))
using static OpenQA.Selenium.RelativeBy;

IWebElement emailAddressLabel = driver.FindElement(By.Id("lbl-email"));
IWebElement emailAddressField = driver.FindElement(RelativeBy(By.TagName("input")).Near(emailAddressLabel));
email_address_label = driver.find_element(:id, "lbl-email")
email_address_field = driver.find_element(relative: {tag_name: 'input', near: email_address_label})
let emailAddressLabel = driver.findElement(By.id("lbl-email"));
let emailAddressField = await driver.findElement(locateWith(By.tagName("input")).near(emailAddressLabel));
val emailAddressLabel = driver.findElement(By.id("lbl-email"))
val emailAddressField = driver.findElement(with(By.tagName("input")).near(emailAddressLabel))