Révolution ou évolution du modèle objet page?

Bonjour à tous! Je m'appelle Artyom Sokovets . Je veux partager la traduction de mon article sur Atlas: la réincarnation du framework HTML Elements, qui présente une approche complètement différente du travail avec Page Object.

Avant d'entrer dans les détails, je veux demander: combien de wrappers pour objet de page connaissez-vous? Élément de page, ScreenPlay, composant chargeable, chaîne d'invocations ...

Et que se passera-t-il si nous prenons un objet Page avec une implémentation sur l'interface, visser le modèle de proxy et ajouter un peu de fonctionnalité Java 8?

Si vous êtes intéressé, je suggère de passer au chat.



Présentation


Lors de l'utilisation du modèle de conception PageObject standard, un certain nombre de problèmes se posent:

Duplication d'éléments

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

Ici, le bloc En-tête est utilisé dans diverses classes PageObject.

Manque de paramétrage pour les éléments

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

Cet exemple décrit les éléments de la page de modification des paramètres utilisateur. Deux éléments TextBlock contiennent un localisateur presque identique avec une différence uniquement dans la valeur du texte ("Text_1" et "Text_2").

Le même type de 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; } 

Dans le travail quotidien, vous pouvez trouver un objet Page, composé de nombreuses lignes de code avec les mêmes éléments. À l'avenir, de telles classes seront "peu pratiques" à maintenir.

Grande classe avec marches

 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) //... } 

Au fil du temps, la classe d'étapes pour travailler avec des éléments se développe. Une plus grande attention est requise afin qu'il n'y ait pas de méthodes en double.

Votre guide du monde de Page Object


La réincarnation du framework HTML Elements vise à résoudre les problèmes ci-dessus, à réduire le nombre de lignes de code pour un projet de test, un travail plus réfléchi avec les listes et les attentes, ainsi qu'à affiner l'outil par vous-même grâce au système d'extension.

Atlas est un framework Java de nouvelle génération pour développer des autotests UI avec l'implémentation du modèle Page Object via des interfaces. Cette approche offre la possibilité d'héritage multiple dans la construction de l'arbre des éléments, ce qui fournit finalement un code concis pour vos autotests.

La principale innovation du framework est l'utilisation d'interfaces au lieu de classes standard.

Voici à quoi ressemble la description de la page d'accueil de github.com:

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

Le code ci-dessus décrit la page principale du site GitHub avec un élément et plusieurs héritages des couches WebPage et WithHeader (l'exemple est donné à des fins éducatives uniquement, donc la plupart des éléments Web sont omis).

Architecture du framework


Atlas se compose actuellement de trois modules:

  • atlas-core
  • atlas-webdriver
  • atlas-appium

L' atlas-core décrit les fonctionnalités de base pour le traitement des objets de page à l'aide des interfaces. L'idée même d'utiliser des interfaces est tirée du célèbre outil Retrofit.



Les deux autres modules atlas-webdriver et atlas-appium sont utilisés pour développer des scripts UI web et UI mobile automatisés. Le principal point d'entrée pour décrire les pages Web est l'interface WebPage et pour les écrans mobiles - Écran. Conceptuellement, atlas-webdriver et atlas-appium sont construits sur des extensions (package * .extension).

Articles




L'outil est livré avec deux classes spécialisées pour travailler avec des éléments d'interface utilisateur (un analogue de la classe WebElement).



AtlasWebElement et AtlasMobileElement sont complétés par les méthodes should et waitUntil . (L'examen de ces méthodes sera développé dans l'article).

L'outil offre la possibilité de créer vos propres composants en développant les classes ci-dessus, ce qui vous permet de créer un élément multiplateforme.

Caractéristiques clés


Examinons plus en détail la fonctionnalité de l'outil:



Interfaces au lieu de classes

Lors de la description de PageObjects standard, des interfaces sont utilisées à la place des classes.

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

Cet exemple décrit le lien sur la page de démarrage de GitHub.

Paramétrage des éléments

Imaginez que nous ayons un formulaire avec des champs:



Pour le décrire, vous devez créer 11 variables avec l'annotation @FindBy et, si nécessaire, déclarer getter.

Avec Atlas, vous n'avez besoin que d' un seul élément AtlasWebElement paramétré.

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

Le code de test automatisé est le suivant:

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

Nous passons à la page souhaitée, appelons la méthode avec le paramètre et effectuons les actions requises avec l'élément. Une méthode avec un paramètre décrit un champ spécifique.

Héritage multiple

Il a été mentionné précédemment qu'un bloc (par exemple, en-tête), qui est utilisé dans différents objets de page, est une duplication de code.

Il y a un github d'en-tête.



Nous décrivons ce bloc (la plupart des éléments web sont omis):

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

Ensuite, créez un calque qui peut être connecté à n'importe quelle page:

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

Nous développons la page principale avec le bloc d'en-tête.

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

En général, vous pouvez créer plus de calques et les connecter à la page souhaitée. Dans l'exemple ci-dessous, connectez les couches d'en-tête, de pied de page et de barre latérale de la page principale.

 public interface MainPage extends WithHeader, WithFooter, WithSidebar {} 

Continuons. L'en-tête contient 4 boutons, 3 menus déroulants et un champ de recherche:



Créons notre propre élément Button et décrivons quatre boutons avec un élément.

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

Connectez le bouton bouton à la couche d'en-tête. Ainsi, nous étendons la fonctionnalité de l'en-tête GitHub.

 public interface Header extends WithButton { … } 

Un élément Button séparé peut être connecté à différentes couches du site Web et obtenir rapidement l'élément souhaité sur la page souhaitée.

Un exemple:

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

Dans la deuxième ligne du test, on accède à l'en-tête du site, puis on appelle le bouton paramétré avec la valeur «Tarification» et on clique.

Il peut y avoir plusieurs éléments sur le site testé qui se répètent de page en page. Afin de ne pas les décrire tous avec l'approche standard des objets Page, vous pouvez les décrire une fois et les connecter si nécessaire. Le gain de temps et de nombre de lignes de code est évident.



Méthodes par défaut

Java 8 a introduit des méthodes par défaut, qui sont utilisées pour prédéfinir la fonctionnalité souhaitée.

Supposons que nous ayons un élément «nuisible»: par exemple, une case à cocher activée ou désactivée. De nombreux scénarios le traversent. Il est nécessaire d'activer la case à cocher si elle est désactivée:

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

Afin de ne pas stocker tout ce code dans la classe step, il est possible de le placer à côté de l'élément comme méthode par défaut.

 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(); } } } 

Maintenant, l'étape du test ressemblera à ceci:

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

Un autre exemple dans lequel vous souhaitez combiner le nettoyage et la saisie de caractères dans un champ.

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

Définissez une méthode qui efface le champ et renvoie l'élément lui-même:

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

Dans la méthode de test, l'étape est la suivante:

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

De cette façon, le comportement souhaité dans l'élément peut être programmé.

Réessais

Atlas a des tentatives intégrées. Vous n'avez pas à vous soucier des exceptions telles que NotFoundException , StaleElementReferenceException et WebDriverException , et vous pouvez également oublier d'utiliser les attentes explicites et implicites de l'API Selenium.

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

Si, à un moment de la chaîne, vous avez détecté une exception, la phase se répète depuis le début.

Il est possible de régler indépendamment l'intervalle de temps pendant lequel vous pouvez répéter ou la fréquence de répétition.

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

Nous nous attendons à trois secondes avec un taux d'interrogation d'une fois par seconde.

Nous pouvons également ajuster l'attente d'un élément spécifique à l'aide de l'annotation Réessayer . Pour tous les éléments, la recherche aura lieu dans les 3 secondes, et dans le cas d'un, elle sera de 20.

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

Travailler avec des listes

Prêt à l'emploi, l'outil permet de travailler avec des listes. Qu'est-ce que cela signifie? Il y a un champ avec une balise d'entrée où nous entrons le texte, puis une liste déroulante apparaît, les éléments n'apparaissent pas immédiatement.



Pour de tels cas, il existe une entité ElementsCollection. Avec son aide, il existe un travail avec des listes.

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

Utilisation:

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

Il est également possible de filtrer les éléments et de les convertir en une liste d'un autre type.

Assertions intelligentes

Comme mentionné précédemment, les entités AtlasWebElement et AtlasMobileElement utilisent les méthodes should, waitUntil pour travailler avec les vérifications (instructions).

La méthodeLa description
devrait (...)Effectue diverses instructions (vérifications) avec un élément. Lance une AssertionError
attendre (...)Effectue diverses instructions (vérifications) avec un élément. Lève une exception RuntimeException

Pourquoi est-ce fait? Pour gagner du temps lors de l'analyse des rapports d'exécution pour les scripts automatisés. La plupart des contrôles fonctionnels sont effectués à la fin du script: ils intéressent un spécialiste des tests fonctionnels et les contrôles intermédiaires pour un spécialiste des tests automatisés. Par conséquent, si la fonctionnalité du produit ne fonctionne pas, il est logique de lever une exception AssumptionError, sinon une RuntimeException.





Allure montrera immédiatement à quoi nous avons affaire: soit nous avons un défaut de produit (le spécialiste FT prend le travail), soit l'autotest tombe en panne (le spécialiste AT le comprend).

Modèle d'extension



L'utilisateur a la possibilité de redéfinir les fonctionnalités de base de l'outil ou de mettre en œuvre sa propre fonctionnalité. Le modèle d'extension Atlas est similaire au modèle d'extension JUnit 5. Les modules atlas-webdriver et atlas-appium sont construits sur des extensions. Si vous êtes intéressé, consultez le code source.

Examinons un exemple abstrait: il est nécessaire de développer des tests d'interface utilisateur pour Internet Explorer 11 (dans certains endroits, il est encore utilisé). Il y a des moments où le clic standard sur les éléments ne fonctionne pas, alors vous pouvez utiliser le clic JS. Vous décidez de remplacer temporairement le clic sur l'ensemble du projet de test.

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

Comment faire

Créez une extension qui implémente l'interface MethodExtension.

 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"); } } 

Nous redéfinissons deux méthodes. Dans la méthode test (), nous spécifions que nous remplaçons la méthode click. La méthode invoke implémente la logique requise. Maintenant, le clic sur l'élément se fera via JavaScript.

Nous connectons l'extension comme suit:

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

Avec l'aide d'extensions, il est possible de créer une recherche de localisateurs d'éléments dans la base de données et de mettre en œuvre d'autres fonctionnalités intéressantes - tout dépend de votre imagination et de vos besoins.

Point d'entrée unique vers PageObjects (WebSite)

L'outil vous permet de stocker toutes vos pages en un seul endroit et de ne travailler que par le biais de l'entité Site à l'avenir.

 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(); } 

De plus, il est possible pour les pages de définir une URL rapide, des paramètres de requête et des segments de chemin.

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

La ligne ci-dessus contient deux segments de chemin (qameta et atlas), ce qui se traduit par github.com/qameta/atlas/tree/master . Le principal avantage de cette approche est qu'il est possible d'ouvrir immédiatement la page souhaitée sans cliquer dessus.

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

Travailler avec un élément mobile

L'utilisation d'un élément mobile (AtlasMobileElement) est similaire à l'utilisation de l'élément Web AtlasWebElement. De plus, trois méthodes ont été ajoutées à AtlasMobileElement: faire défiler l'écran vers le haut / bas (scrollUp / scrollDown) et cliquer sur un élément en attente (longPress).

Permettez-moi de vous donner un exemple de l'écran principal de l'application Wikipedia. Un élément est décrit à la fois pour la plate-forme iOS et Android. Ils décrivent également un bouton paramétré.

 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); } 

Les tests se ressemblent:

 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"))); } 

Dans l'exemple ci-dessus, nous ouvrons la page principale de Wikipédia, cliquez sur la barre de recherche, entrez le texte Atlas, puis faites défiler jusqu'à l'élément de liste avec la valeur Atlas LV-3B et accédez à sa vue. La dernière ligne vérifie que le titre est affiché et contient la valeur souhaitée.

Auditeur

La journalisation des événements peut être implémentée à l'aide d'un écouteur spécial (interface d'écoute). Chaque méthode a quatre événements lorsqu'elle est appelée: Before , Pass , Fail . Après .



À l'aide de cette interface, vous pouvez organiser les rapports. Voici un exemple de l'écouteur Allure, qui peut être trouvé sur le lien .



Ensuite, connectez l'écouteur lors de l'initialisation de la classe Atlas.

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


De la manière ci-dessus, il est possible de créer un écouteur pour différents systèmes de génération de rapports (par exemple, pour ReportPortal).

Connexion



Pour automatiser l'interface utilisateur Web, il suffit d'enregistrer la dépendance atlas-webdriver et d'indiquer la dernière version actuelle (la version 1.6.0 est pertinente au moment de la rédaction de ce texte).

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.+' } 


Nous faisons de même si vous souhaitez automatiser UI Mobile.

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.+' } 


Utiliser



Après avoir ajouté la dépendance à votre projet, vous devez initialiser l'instance de classe Atlas.

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

Nous transmettons l'instance de configuration et le pilote au constructeur Atlas.

Il existe actuellement deux configurations: WebDriverConfiguration et AppiumDriverConfiguration. Chaque configuration contient des extensions par défaut spécifiques.

Ensuite, nous définissons une méthode qui créera tous les PageObject.

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


Un exemple de scénario de test simple:

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

Nous ouvrons le site, tournons-nous vers la couche d'en-tête, recherchons un champ de texte (entrée de recherche), entrez le texte et appuyez sur Entrée.

Résumé


En conclusion, je tiens à noter que Atlas est un outil flexible avec de grandes fonctionnalités. Il peut être personnalisé pour un projet de test spécifique d'une manière qui convient à votre équipe et à vous. Développer des tests multiplateformes, etc.

Des clips vidéo des rapports de Heisenbug , Selenium Camp et Nexign QA Meetup sont disponibles . Il y a un chat Telegram @atlashelp.

En utilisant cet outil, vous pouvez réduire un nombre important de lignes de code (testé sur des projets d'entreprises telles que Yandex, SberTech et Tinkoff).

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


All Articles