Revolution oder Evolution des Seitenobjektmodells?

Hallo allerseits! Ich heiße Artyom Sokovets . Ich möchte die Übersetzung meines Artikels über Atlas teilen: die Reinkarnation des HTML Elements-Frameworks, das einen völlig anderen Ansatz für die Arbeit mit Page Object darstellt.

Bevor ich auf die Details eingehe, möchte ich fragen: Wie viele Wrapper für Page Object kennen Sie? Seitenelement, ScreenPlay, ladbare Komponente, Aufrufkette ...

Und was passiert, wenn wir ein Seitenobjekt mit einer Implementierung auf der Schnittstelle nehmen, das Proxy-Muster befestigen und ein bisschen Java 8-Funktionalität hinzufügen?

Bei Interesse schlage ich vor, zu Katze zu wechseln.



Einleitung


Bei Verwendung des Standard-PageObject-Entwurfsmusters treten eine Reihe von Problemen auf:

Vervielfältigung von Elementen

public class MainPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } public class AnyOtherPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } 

Hier wird der Header-Block in verschiedenen PageObject-Klassen verwendet.

Fehlende Parametrierung für Elemente

 public class EditUserPage { @FindBy(xpath = "//div[text()='Text_1']") private TextBlock lastActivity; @FindBy(xpath = "//div[text()='Text_2']") private TextBlock blockReason; } 

In diesem Beispiel werden die Elemente der Bearbeitungsseite für Benutzereinstellungen beschrieben. Zwei TextBlock-Elemente enthalten einen nahezu identischen Locator mit einem Unterschied nur im Textwert ("Text_1" und "Text_2").

Dieselbe Art von Code

 public class UserPage { @FindBy(xpath = "//div[text()='']/input") private UfsTextInput innerPhone; @FindBy(xpath = "//div[text()='Email']/input") private UfsTextInput email; @FindBy(xpath = "//button[text()='']") private UfsButton save; @FindBy(xpath = "//button[text()='']") private UfsButton toUsersList; } 

In der täglichen Arbeit finden Sie Page Object, das aus vielen Codezeilen mit denselben Elementen besteht. In Zukunft sind solche Klassen "unpraktisch" zu pflegen.

Große Klasse mit Stufen

 public class MainSteps { public void hasText(HtmlElement e, Matcher m) public void hasValue(HtmlElement e, Matcher m) public void linkContains(HtmlElement e, String s) public void hasSize(List<HtmlElement> e, Matcher m) public void hasItem(List<HtmlElement> e, Matcher m) //... } 

Mit der Zeit wächst die Klasse der Schritte zum Arbeiten mit Elementen. Es ist mehr Aufmerksamkeit erforderlich, damit es keine doppelten Methoden gibt.

Ihr Leitfaden für die Welt von Page Object


Die Reinkarnation des HTML Elements-Frameworks zielt darauf ab, die oben genannten Probleme zu lösen, die Anzahl der Codezeilen für ein Testprojekt zu reduzieren, mit Listen und Erwartungen sorgfältiger zu arbeiten und das Tool dank des Erweiterungssystems selbst zu optimieren.

Atlas ist ein Java-Framework der neuen Generation für die Entwicklung von UI-Autotests mit der Implementierung des Seitenobjektmusters über Schnittstellen. Dieser Ansatz bietet die Möglichkeit einer Mehrfachvererbung bei der Erstellung des Elementbaums, wodurch letztendlich ein präziser Code für Ihre Autotests bereitgestellt wird.

Die Hauptinnovation des Frameworks ist die Verwendung von Schnittstellen anstelle von Standardklassen.

So sieht die Homepage-Beschreibung von github.com aus:

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free)]") AtlasWebElement trial(); } 

Der obige Code beschreibt die Hauptseite der GitHub-Site mit einem Element und Mehrfachvererbung von den Ebenen WebPage und WithHeader (das Beispiel dient nur zu Bildungszwecken, sodass die meisten Webelemente weggelassen werden).

Framework-Architektur


Atlas besteht derzeit aus drei Modulen:

  • Atlas-Kern
  • Atlas-Webdriver
  • Atlas-Appium

Der Atlas-Core beschreibt die Grundfunktionen für die Verarbeitung von Seitenobjekten über Schnittstellen. Die Idee, Schnittstellen zu verwenden, stammt aus dem berühmten Retrofit-Tool.



Die beiden anderen Module atlas-webdriver und atlas-appium werden zur Entwicklung automatisierter UI-Web- und UI-Mobile-Skripte verwendet. Der Haupteinstiegspunkt für die Beschreibung von Webseiten ist die WebPage-Oberfläche und für mobile Bildschirme - Bildschirm. Konzeptionell basieren atlas-webdriver und atlas-appium auf Erweiterungen (Paket * .extension).

Gegenstände




Das Tool enthält zwei spezielle Klassen für die Arbeit mit UI-Elementen (ein Analogon zur WebElement-Klasse).



AtlasWebElement und AtlasMobileElement werden durch die Methoden should und waitUntil ergänzt. (Überlegungen zu diesen Methoden finden Sie weiter unten im Artikel).

Das Tool bietet die Möglichkeit, eigene Komponenten zu erstellen, indem die oben genannten Klassen erweitert werden, sodass Sie ein plattformübergreifendes Element erstellen können.

Hauptmerkmale


Lassen Sie uns die Funktionalität des Tools genauer betrachten:



Schnittstellen statt Klassen

Bei der Beschreibung von Standard-PageObjects werden Schnittstellen anstelle von Klassen verwendet.

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]") AtlasWebElement trial(); } 

Dieses Beispiel beschreibt den Link auf der GitHub-Startseite.

Parametrierung von Elementen

Stellen Sie sich vor, wir haben ein Formular mit Feldern:



Um dies zu beschreiben, müssen Sie 11 Variablen mit der Annotation @FindBy erstellen und ggf. den Getter deklarieren.

Mit Atlas benötigen Sie nur ein parametrisiertes AtlasWebElement-Element.

 public interface MainPage extends WebPage { @FindBy("//div[text()='{{ text }}']/input") AtlasWebElement input(@Param("text") String text); } 

Der automatisierte Testcode lautet wie folgt:

 @Test public void simpleTest() { onMainPage().input("First Name").sendKeys("*"); onMainPage().input("Postcode").sendKeys("*"); onMainPage().input("Email").sendKeys("*"); } 

Wir wenden uns der gewünschten Seite zu, rufen die Methode mit dem Parameter auf und führen die erforderlichen Aktionen mit dem Element aus. Eine Methode mit einem Parameter beschreibt ein bestimmtes Feld.

Mehrfachvererbung

Es wurde bereits erwähnt, dass ein Block (z. B. Header), der in verschiedenen Seitenobjekten verwendet wird, eine Duplizierung von Code ist.

Es gibt einen Header-Github.



Wir beschreiben diesen Block (die meisten Webelemente werden weggelassen):

 public interface Header extends AtlasWebElement { @FindBy(".//input[contains(@class,'header-search-input')]") AtlasWebElement searchInput(); } 

Erstellen Sie als Nächstes eine Ebene, die mit einer beliebigen Seite verbunden werden kann:

 public interface WithHeader { @FindBy("//header[contains(@class,'Header')]") Header header(); } 

Wir erweitern die Hauptseite mit dem Headerblock.

 public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a)]") AtlasWebElement trial(); } 

Im Allgemeinen können Sie weitere Ebenen erstellen und diese mit der gewünschten Seite verbinden. Verbinden Sie im folgenden Beispiel die Ebenen Kopf-, Fußzeile und Seitenleiste der Hauptseite.

 public interface MainPage extends WithHeader, WithFooter, WithSidebar {} 

Lass uns weitermachen. Die Kopfzeile enthält 4 Schaltflächen, 3 Dropdown-Menüs und ein Suchfeld:



Lassen Sie uns unser eigenes Button- Element erstellen und vier Buttons mit einem Element beschreiben.

 public interface Button extends AtlasWebElement { @FindBy(".//a[contains(., '{{ value }}')]") AtlasWebElement selectButton(@Param("value") String value); } 

Verbinden Sie die Schaltfläche mit der Kopfzeilenebene. Somit erweitern wir die Funktionalität des GitHub-Headers.

 public interface Header extends WithButton { … } 

Ein separates Button- Element kann mit verschiedenen Ebenen der Website verbunden werden und bringt schnell das gewünschte Element auf die gewünschte Seite.

Ein Beispiel:

 @Test public void simpleTest() { onMainPage().open("https://github.com"); onMainPage().header().button("Priing").click(); } 

In der zweiten Zeile des Tests wird auf die Kopfzeile der Site zugegriffen, dann rufen wir die parametrisierte Schaltfläche mit dem Wert "Pricing" auf und klicken.

Es kann einige Elemente auf der getesteten Site geben, die sich von Seite zu Seite wiederholen. Um sie nicht alle mit dem Standardansatz für Seitenobjekte zu beschreiben, können Sie sie einmal beschreiben und bei Bedarf verbinden. Das Sparen von Zeit und der Anzahl der Codezeilen ist offensichtlich.



Standardmethoden

In Java 8 wurden Standardmethoden eingeführt, mit denen die gewünschte Funktionalität vordefiniert wird.

Angenommen, wir haben ein "schädliches" Element: Zum Beispiel ein Kontrollkästchen, das entweder aktiviert oder deaktiviert ist. Viele Szenarien durchlaufen es. Es ist erforderlich, das Kontrollkästchen zu aktivieren, wenn es deaktiviert ist:

 if(onMainPage().rentFilter().checkbox("").getAttribute("class").contains("disabled")) { onMainPage().rentFilter().checkbox("").click(); } 

Um diesen gesamten Code nicht in der Schrittklasse zu speichern, kann er als Standardmethode neben dem Element platziert werden.

 public interface Checkbox extends AtlasWebElement { @FindBy("//...") AtlasWebElement checkBox((@Param("value") String value); default void selectCheckbox(String value) { if (checkBox(value).getAttribute("class").contains("disabled")) { checkBox(value).click(); } } } 

Nun sieht der Testschritt folgendermaßen aus:

 onMainPage().rentFilter().selectCheckbox(""); 

Ein weiteres Beispiel, in dem Sie die Bereinigung und Eingabe von Zeichen in einem Feld kombinieren möchten.

 onMainPage().header().input("GitHub").clear(); onMainPage().header().input("GitHub").sendKeys("*"); 

Definieren Sie eine Methode, die das Feld löscht und das Element selbst zurückgibt:

 public interface Input extends AtlasWebElement { @FindBy("//xpath") AtlasWebElement input(@Param("value") String value); default AtlasWebElement withClearInput(String value) { input(value).clear(); return input(value); } } 

Bei der Testmethode ist der Schritt wie folgt:

 onMainPage().header().withClearInput("GitHub").sendKeys("Atlas"); 

Auf diese Weise kann das gewünschte Verhalten im Element programmiert werden.

Wiederholungen

Atlas hat Wiederholungsversuche eingebaut. Sie müssen sich nicht um Ausnahmen wie NotFoundException , StaleElementReferenceException und WebDriverException kümmern , und Sie können auch vergessen, die expliziten und impliziten Erwartungen der Selenium-API zu verwenden.

 onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10)); 

Wenn Sie irgendwann in der Kette eine Ausnahme festgestellt haben, wird die Phase von Anfang an wiederholt.

Es ist möglich, das Zeitintervall, in dem Sie wiederholen können, oder die Wiederholungshäufigkeit unabhängig einzustellen.

 Atlas atlas = new Atlas(new WebDriverConfiguration(driver)) .context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class)))); 

Wir erwarten drei Sekunden mit einer Abfragerate von einmal pro Sekunde.

Wir können die Wartezeit für ein bestimmtes Element auch mithilfe der Annotation " Wiederholen" anpassen. Für alle Elemente erfolgt die Suche innerhalb von 3 Sekunden und bei einem Element sind es 20.

 @Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia(); 

Mit Listen arbeiten

Das Tool bietet sofort die Möglichkeit, mit Listen zu arbeiten. Was bedeutet das? Es gibt ein Feld mit einem Eingabe-Tag, in das wir den Text eingeben. Dann wird eine Dropdown-Liste angezeigt. Die Elemente werden nicht sofort angezeigt.



Für solche Fälle gibt es eine Entität ElementsCollection. Mit seiner Hilfe gibt es eine Arbeit mit Listen.

 public interface ContributorsPage extends WebPage, WithHeader { @FindBy(".//ol[contains(@class, 'contrib-data')]//li[contains(@class, 'contrib-person')]") ElementsCollection<RepositoryCard> hovercards(); } 

Verwendung:

 onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); 

Es ist auch möglich, Elemente zu filtern und in eine Liste anderer Art zu konvertieren.

Kluge Behauptungen

Wie bereits erwähnt, verwenden die Entitäten AtlasWebElement und AtlasMobileElement die Methoden should, waitUntil, um mit Prüfungen (Anweisungen) zu arbeiten.

MethodeBeschreibung
sollte (...)Führt verschiedene Anweisungen (Prüfungen) mit einem Element aus. Wirft einen AssertionError
waitUntil (...)Führt verschiedene Anweisungen (Prüfungen) mit einem Element aus. Löst eine RuntimeException aus

Warum wird das gemacht? So sparen Sie Zeit beim Parsen von Laufberichten für automatisierte Skripts. Die meisten Funktionsprüfungen werden am Ende des Skripts durchgeführt: Sie sind für einen Spezialisten für Funktionstests von Interesse und Zwischenprüfungen für einen Spezialisten für automatisierte Tests. Wenn die Funktionalität des Produkts nicht funktioniert, ist es daher logisch, eine AssumptionError-Ausnahme auszulösen, andernfalls eine RuntimeException.





Allure wird sofort zeigen, womit wir es zu tun haben: Entweder haben wir einen Produktfehler (der FT-Spezialist übernimmt den Job) oder der Autotest fällt aus (der AT-Spezialist versteht).

Erweiterungsmodell



Der Benutzer hat die Möglichkeit, die Grundfunktionalität des Tools neu zu definieren oder seine eigene Funktionalität zu implementieren. Das Atlas-Erweiterungsmodell ähnelt dem JUnit 5-Erweiterungsmodell. Die Module atlas-webdriver und atlas-appium basieren auf Erweiterungen. Wenn Sie interessiert sind, lesen Sie den Quellcode.

Lassen Sie uns ein abstraktes Beispiel untersuchen: Es ist erforderlich, UI-Tests für Internet Explorer 11 zu entwickeln (an einigen Stellen wird es noch verwendet). Es gibt Zeiten, in denen der Standardklick auf die Elemente nicht funktioniert, dann können Sie den JS-Klick verwenden. Sie beschließen, den Klick auf das gesamte Testprojekt vorübergehend zu überschreiben.

 onMainPage().header().button("en").click(); 

Wie kann man das machen?

Erstellen Sie eine Erweiterung, die die MethodExtension-Schnittstelle implementiert.

 public class JSClickExt implements MethodExtension { @Override public Object invoke(Object proxy, MethodInfo methodInfo, Configuration config) { final WebDriver driver = config.getContext(WebDriverContext.class) .orElseThrow(() -> new AtlasException("Context doesn't exist")).getValue(); final JavascriptExecutor js = (JavascriptExecutor) driver; js.executeScript("arguments[0].click();", proxy); return proxy; } @Override public boolean test(Method method) { return method.getName().equals("click"); } } 

Wir definieren zwei Methoden neu. In der test () -Methode geben wir an, dass wir die click-Methode überschreiben. Die Aufrufmethode implementiert die erforderliche Logik. Jetzt erfolgt das Klicken auf das Element über JavaScript.

Wir verbinden die Erweiterung wie folgt:

 atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com")) .extension(new JSClickExt()); 

Mit Hilfe von Erweiterungen ist es möglich, nach Locators für Elemente in der Datenbank zu suchen und andere interessante Funktionen zu implementieren - alles hängt von Ihrer Vorstellungskraft und Ihren Bedürfnissen ab.

Einzelner Einstiegspunkt für PageObjects (WebSite)

Mit dem Tool können Sie alle Ihre Seiten an einem Ort speichern und in Zukunft nur noch die Site-Entität bearbeiten.

 public interface GitHubSite extends WebSite { @Page MainPage onMainPage(); @Page(url = "search") SearchPage onSearchPage(@Query("q") String value); @Page(url = "{profile}/{project}/tree/master/") ProjectPage onProjectPage(@Path("profile") String profile, @Path("project") String project); @Page ContributorsPage onContributorsPage(); } 

Darüber hinaus können Seiten schnelle URLs, Abfrageparameter und Pfadsegmente festlegen.

 onSite().onProjectPage("qameta", "atlas").contributors().click(); 

Die obige Zeile enthält zwei Pfadsegmente (Qameta und Atlas), die in github.com/qameta/atlas/tree/master übersetzt werden. Der Hauptvorteil dieses Ansatzes besteht darin, dass die gewünschte Seite sofort geöffnet werden kann, ohne darauf zu klicken.

 @Test public void usePathWebSiteTest() { onSite().onProjectPage("qameta", "atlas").contributors().click(); onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); } 

Arbeiten Sie mit einem mobilen Element

Das Arbeiten mit einem mobilen Element (AtlasMobileElement) ähnelt dem Arbeiten mit dem AtlasWebElement-Webelement. Zusätzlich wurden AtlasMobileElement drei Methoden hinzugefügt: Scrollen des Bildschirms nach oben / unten (scrollUp / scrollDown) und Klicken auf ein Element mit Hold (longPress).

Lassen Sie mich Ihnen ein Beispiel für den Hauptbildschirm der Wikipedia-Anwendung geben. Ein Element wird sowohl für die iOS-Plattform als auch für Android beschrieben. Sie beschreiben auch eine parametrierte Schaltfläche.

 public interface MainScreen extends Screen { @Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia(); @IOSFindBy(id = "{{ value }}") AtlasMobileElement button(@Param("value") String value); } 

Tests sehen genauso aus:

 public void simpleExample() { onMainScreen().searchWikipedia().click(); onSearchScreen().search().sendKeys("Atlas"); onSearchScreen().item("Atlas LV-3B").swipeDownOn().click(); onArticleScreen().articleTitle().should(allOf(displayed(), text("Atlas LV-3B"))); } 

Im obigen Beispiel öffnen wir die Wikipedia-Hauptseite, klicken auf die Suchleiste, geben den Text Atlas ein, scrollen dann zum Listenelement mit dem Wert Atlas LV-3B und gehen zu seiner Ansicht. Die letzte Zeile prüft, ob der Titel angezeigt wird und den gewünschten Wert enthält.

Zuhörer

Die Ereignisprotokollierung kann mithilfe eines speziellen Listeners (Listener-Schnittstelle) implementiert werden. Jede Methode hat beim Aufruf vier Ereignisse: Vorher , Bestanden , Nicht bestanden . Nachher .



Über diese Schnittstelle können Sie die Berichterstellung organisieren. Unten finden Sie ein Beispiel für den Allure Listener, den Sie unter dem Link finden .



Verbinden Sie als Nächstes den Listener, wenn Sie die Atlas-Klasse initialisieren.

 atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener()); 


Auf die oben beschriebene Weise ist es möglich, einen Listener für verschiedene Berichtssysteme (z. B. für ReportPortal) zu erstellen.

Verbindung



Um das UI-Web zu automatisieren, reicht es aus, die Atlas-Webdriver-Abhängigkeit zu registrieren und die neueste aktuelle Version anzugeben (Version 1.6.0 ist zum Zeitpunkt dieses Schreibens relevant).

Maven:
  <dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-webdriver</artifactId> <version>${atlas.version}</version> </dependency> 


Gradle:
 dependencies { ompile 'io.qameta.atlas:atlas-webdriver:1.+' } 


Wir tun dasselbe, wenn Sie UI Mobile automatisieren möchten.

Maven:
 <dependency> <groupId>io.qameta.atlas</groupId> <artifactId>atlas-appium</artifactId> <version>${atlas.version}</version> </dependency> 


Gradle:
 dependencies { ompile 'io.qameta.atlas:atlas-appium:1.+' } 


Verwenden Sie



Nachdem Sie die Abhängigkeit zu Ihrem Projekt hinzugefügt haben, müssen Sie die Atlas-Klasseninstanz initialisieren.

 @Before public void startDriver() { driver = new ChromeDriver(); atlas = new Atlas(new WebDriverConfiguration(driver)); } 

Wir übergeben die Konfigurationsinstanz und den Treiber an den Atlas-Konstruktor.

Derzeit gibt es zwei Konfigurationen: WebDriverConfiguration und AppiumDriverConfiguration. Jede Konfiguration enthält bestimmte Standarderweiterungen.

Als nächstes definieren wir eine Methode, die alle PageObject erstellt.

 private <T extends WebPage> T onPage(Class<T> page) { return atlas.create(driver, page); } 


Ein Beispiel für einen einfachen Testfall:

 @Test public void simpleTest() { onPage(MainPage.class).open("https://github.com"); onPage(MainPage.class).header().searchInput().sendKeys("Atlas"); onPage(MainPage.class).header().searchInput().submit(); } 

Wir öffnen die Site, wenden uns der Kopfzeile zu, suchen ein Textfeld (Sucheingabe) darin, geben den Text ein und drücken die Eingabetaste.

Zusammenfassung


Abschließend möchte ich darauf hinweisen, dass Atlas ein flexibles Tool mit großartigen Funktionen ist. Es kann für ein bestimmtes Testprojekt auf eine Weise angepasst werden, die für Ihr Team und Sie bequem ist. Entwickeln Sie plattformübergreifende Tests usw.

Videoclips mit Berichten von Heisenbug , Selenium Camp und Nexign QA Meetup sind verfügbar . Es gibt einen Telegramm-Chat @atlashelp.

Mit diesem Tool können Sie eine erhebliche Anzahl von Codezeilen reduzieren (getestet an Projekten von Unternehmen wie Yandex, SberTech und Tinkoff).

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


All Articles