Informationen zur Angemessenheit von Selenium WebDriverWait

Je näher ich Selenium WebDriver kennen lerne, desto mehr Fragen habe ich, warum diese oder jene Funktionalität auf diese Weise und nicht anders implementiert wird. In seiner Rede „Probleme in Selenium WebDriver“ beleuchtet Alexey Barantsev die Feinheiten der Implementierung dieses Automatisierungstools und unterscheidet zwischen „Bugs“ und „Features“. Sie werden im Video viele interessante Dinge finden, aber dennoch bleiben einige Punkte (zumindest für mich) im Schatten.

In diesem Artikel möchte ich das häufig verwendete Tool zum Warten auf ein Ereignis auf einer Seite erläutern , das mithilfe der WebDriverWait- Klasse und ihrer Hauptmethode Until implementiert wurde. Ich frage mich, ob WebDriverWait überhaupt benötigt wird und ob es möglich ist, es abzulehnen.

Gedanken werden im Kontext von C # präsentiert, obwohl ich nicht denke, dass die Implementierungslogik dieser Klasse für andere Programmiersprachen anders sein wird.

Beim Erstellen einer WebDriverWait-Instanz wird eine Treiberinstanz an den Konstruktor übergeben, die im Eingabe-Eingabefeld gespeichert wird. Die Until-Methode setzt einen Delegaten voraus, dessen Eingabeparameter IWebDriver sein soll, dessen Eingabe eine Instanz ist.

Schauen wir uns den Quellcode der Bis- Methode an. Das Rückgrat seiner Logik ist ein endloser Zyklus mit zwei Bedingungen zum Beenden: dem Einsetzen des gewünschten Ereignisses oder einer Zeitüberschreitung. Zusätzliche „Goodies“ ignorieren die vordefinierten Ausnahmen und geben das Objekt zurück, wenn das bes nicht als TResult fungiert (dazu später mehr).

Die erste Einschränkung, die ich sehe, ist, dass wir immer genau eine Instanz von IWebDriver benötigen, obwohl wir innerhalb der Until-Methode (genauer gesagt als Eingabeparameter für die Bedingung) ISearchContext vollständig verwalten konnten. In der Tat erwarten wir in den meisten Fällen ein Element oder eine Änderung seiner Eigenschaft und verwenden FindElement (s), um danach zu suchen.

Ich würde sagen, dass die Verwendung von ISearchContext noch logischer wäre, da Clientcode (Klasse) nicht nur ein Seitenobjekt ist, das bei der Suche nach untergeordneten Elementen vom Stamm der Seite abgestoßen wird. Manchmal ist dies eine Klasse, die ein zusammengesetztes Element beschreibt, dessen Stamm ein anderes Element der Seite ist und nicht die Seite selbst. Ein Beispiel hierfür ist SelectElement , das einen Verweis auf das übergeordnete IWebElement im Konstruktor akzeptiert.

Kommen wir zurück zum Problem der Initialisierung von WebDriverWait. Diese Aktion erfordert eine Instanz des Treibers. Das heißt, Auf die eine oder andere Weise müssen wir immer eine Instanz von IWebDriver von außerhalb des Client-Codes werfen, selbst wenn es sich um eine Klasse eines zusammengesetzten Elements handelt (ein Beispiel für SelectElement), das bereits ein "übergeordnetes Element" akzeptiert. Dies ist aus meiner Sicht unnötig.

Natürlich können wir eine Klasse analog deklarieren
SearchContextWait : DefaultWait<ISearchContext> 
Aber lasst uns nicht eilen. Wir brauchen ihn nicht.

Mal sehen, wie die an die Bedingung übergebene Treiberinstanz verwendet wird. Normalerweise sieht es ungefähr so ​​aus:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => d.FindElements(By.XPath("locator")).Count > 0 ); 

Es stellt sich die Frage, warum eine „lokale“ Version des Treibers erforderlich ist, wenn die Bedingung immer aus dem Clientcode verfügbar ist. Darüber hinaus ist dies dieselbe Instanz, die zuvor durch den Konstruktor übergeben wurde. Das heißt, Der Code könnte ungefähr so ​​aussehen:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.Until( d => Driver.FindElements(By.XPath("locator")).Count > 0 ); 

Sogar Simon Stewart verwendet diesen Ansatz in seiner Rede .

Bild

Er schreibt nicht "d -> d", sondern "d -> Treiber", d. H. Die an die Methode übergebene Treiberinstanz wird einfach ignoriert. Es ist aber notwendig, es zu übertragen, denn dies wird von der Methodensignatur verlangt!

Warum den Fahrer in den Zustand der Methode überführen? Es ist möglich, die Suche innerhalb dieser Methode zu isolieren, wie in ExpectedConditions ? Implementiert. Sehen Sie sich die Implementierung der TextToBePresentInElement- Methode an. Oder VisibilityOfAllElementsLocatedBy . Oder TextToBePresentInElementValue . Der übertragene Treiber wird in ihnen nicht einmal verwendet!

Der erste Gedanke: Wir benötigen die Bis-Methode mit dem vom Treiber akzeptierten Delegate-Parameter nicht.

Lassen Sie uns nun herausfinden, ob die Bis-Methode einen Rückgabewert benötigt. Wenn bool als TResult fungiert, ist dies nicht erforderlich. In der Tat werden Sie im Erfolgsfall wahr und im Falle eines Misserfolgs erhalten Sie eine TimeoutException. Was ist der Informationsgehalt eines solchen Verhaltens?

Aber was ist, wenn das Objekt TResult ist? Nehmen Sie die folgende Konstruktion an:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); wait.IgnoreExceptionTypes(typeof(NoSuchElementException)); var element = wait.Until(d => d.FindElement(By.XPath("locator"))); 

Das heißt, Wir warten nicht nur auf das Erscheinen des Elements, sondern verwenden es auch (wenn wir gewartet haben), wodurch ein unnötiger Aufruf des DOM entfernt wird. Gut.

Schauen wir uns diese drei Codezeilen genauer an. Innerhalb der Implementierung der Bis-Methode läuft es auf eine Art Ähnlichkeit hinaus (bedingter Code).

 try { FindElement } catch (NoSuchElementException) {} 

Das heißt, Jedes Mal wird eine Ausnahme ausgelöst, bis das Element im DOM angezeigt wird. Da die Erzeugung von Ausnahmen ein ziemlich teures Ereignis ist, würde ich es vorziehen, sie zu vermeiden, insbesondere an Orten, an denen es nicht schwierig ist. Wir können den Code wie folgt umschreiben:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var elements = wait.Until(d => d.FindElements(By.XPath("locator"))); 

Das heißt, Wir verwenden FindElements, das keine Ausnahme auslöst. Warten Sie, wird dieses Design auf das Erscheinen der Elemente warten? NEIN! Denn wenn Sie sich den Quellcode ansehen, wird eine Endlosschleife sofort abgeschlossen, sobald die Bedingung ungleich Null zurückgibt. Und FindElements gibt im Fehlerfall eine leere Sammlung zurück, die jedoch in keiner Weise null ist. Das heißt, Für eine Liste von Elementen macht die Verwendung von Bis keinen Sinn.

Ok, die Liste ist klar. Aber wie kann man das gefundene Element zurückgeben und keine Ausnahme auslösen? Der Code könnte folgendermaßen aussehen:

 var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10)); var element = wait.Until(d => d.FindElements(By.XPath("locator")).FirstOrDefault()); 

In diesem Fall erhalten wir bei jeder Iteration der Schleife nicht nur die IWebElement-Liste (die möglicherweise leer ist), sondern versuchen auch, das erste Element daraus zu extrahieren. Wenn die Elemente immer noch nicht auf der Seite angezeigt werden, erhalten wir null (Standardwert für Objekt) und fahren mit der nächsten Iteration der Schleife fort. Wenn das Element gefunden wird, beenden wir die Methode und die Elementvariable wird mit dem Rückgabewert initialisiert.

Der zweite Gedanke ist jedoch, dass der Rückgabewert der Bis-Methode in den meisten Fällen nicht verwendet wird.

Der übergebene Wert ist nicht erforderlich, der Rückgabewert wird nicht verwendet. Was ist der Nutzen von Bis? Nur im Zyklus und in der Häufigkeit des Aufrufs der Bedingungsmethode? Dieser Ansatz wurde bereits in C # in der SpinWait.SpinUntil- Methode implementiert . Der einzige Unterschied besteht darin, dass keine Timeout-Ausnahme ausgelöst wird. Dies kann wie folgt behoben werden:

 public void Wait(Func<bool> condition, TimeSpan timeout) { var waited = SpinWait.SpinUntil(condition, timeout); if (!waited) { throw new TimeoutException(); } } 

Das heißt, Diese wenigen Codezeilen ersetzen in den meisten Fällen die Logik der gesamten WebDriverWait-Klasse. Sind die Anstrengungen das Ergebnis wert?

Update

In den Kommentaren zum Artikel machte der KSA- Benutzer eine vernünftige Bemerkung über den Unterschied zwischen SpinUntil und Until in Bezug auf die Häufigkeit der Ausführung der Bedingung. Für WebDriverWait ist dieser Wert einstellbar und standardmäßig 500 Millisekunden. Das heißt, Die Bis-Methode hat eine Verzögerung zwischen den Schleifeniterationen. Während für SpinUntil die Logik etwas kompliziert ist und die Wartezeit oft 1 Millisekunde nicht überschreitet.

In der Praxis führt dies zu einer Situation, in der die Unitl-Methode beim Warten auf ein Element, das innerhalb von 2 Sekunden angezeigt wird, 4 Iterationen ausführt und die SpinUntil-Methode etwa 200 oder mehr benötigt.

Lassen Sie uns SpinUntil verwerfen und die Wait-Methode wie folgt umschreiben.

 public void Wait(Func<bool> condition, TimeSpan timeout, int evaluatedInterval = 500) { Stopwatch sw = Stopwatch.StartNew(); while (sw.Elapsed < timeout) { if (condition()) { return; } Thread.Sleep(evaluatedInterval); } throw new TimeoutException(); } 


Wir haben ein paar Codezeilen hinzugefügt und sind gleichzeitig der Logik der Bis-Methode näher gekommen.

Source: https://habr.com/ru/post/de443754/


All Articles