Revolução ou evolução do Modelo de Objetos de Página?

Olá pessoal! Meu nome é Artyom Sokovets . Quero compartilhar a tradução do meu artigo sobre o Atlas: a reencarnação da estrutura HTML Elements, que apresenta uma abordagem completamente diferente para trabalhar com o Objeto de Página.

Antes de entrar em detalhes, quero perguntar: quantos invólucros para o objeto de página você conhece? Elemento da página, ScreenPlay, componente carregável, cadeia de invocações ...

E o que acontecerá se pegarmos um Objeto de Página com uma implementação na interface, fixarmos o Proxy Pattern e adicionarmos um pouco da funcionalidade do Java 8?

Se estiver interessado, sugiro mudar para gato.



1. Introdução


Ao usar o padrão de design padrão do PageObject, surgem vários problemas:

Duplicação de elementos

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

Aqui, o bloco Header é usado em várias classes PageObject.

Falta de parametrização para elementos

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

Este exemplo descreve os elementos da página de edição das configurações do usuário. Dois elementos TextBlock contêm um localizador quase idêntico, com diferença apenas no valor do texto ("Texto_1" e "Texto_2").

O mesmo tipo de código

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

No trabalho diário, você pode encontrar o Objeto de Página, que consiste em muitas linhas de código com os mesmos elementos. No futuro, essas classes são "inconvenientes" para manter.

Classe grande com etapas

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

Com o tempo, a classe de etapas para trabalhar com elementos aumenta. É necessária mais atenção para que não haja métodos duplicados.

Seu guia para o mundo do objeto de página


A reencarnação da estrutura HTML Elements visa solucionar os problemas acima, reduzindo o número de linhas de código para um projeto de teste, um trabalho mais cuidadoso com listas e expectativas, além de aprimorar a ferramenta por conta própria, graças ao sistema de extensão.

O Atlas é uma estrutura Java de nova geração para o desenvolvimento de auto-testes de UI com a implementação do padrão Objeto de Página por meio de interfaces. Essa abordagem fornece a possibilidade de herança múltipla na construção da árvore de elementos, que finalmente fornece um código conciso para seus autotestes.

A principal inovação da estrutura é o uso de interfaces em vez de classes padrão.

É assim que a descrição da página inicial do github.com se parece:

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

O código acima descreve a página principal do site GitHub com um elemento e herança múltipla das camadas WebPage e WithHeader (o exemplo é fornecido apenas para fins educacionais, portanto, a maioria dos elementos da Web é omitida).

Arquitetura de estrutura


Atualmente, o Atlas consiste em três módulos:

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

O atlas-core descreve a funcionalidade básica para processar objetos de página usando interfaces. A própria idéia de usar interfaces foi tirada da famosa ferramenta Retrofit.



Os outros dois módulos atlas-webdriver e atlas-appium são usados ​​para desenvolver scripts automatizados da Web da interface do usuário e móveis da UI. O principal ponto de entrada para descrever páginas da Web é a interface da Página da Web e para telas móveis - Tela. Conceitualmente, atlas-webdriver e atlas-appium são construídos sobre extensões (pacote * .extension).

Itens




A ferramenta vem com duas classes especializadas para trabalhar com elementos da interface do usuário (um análogo da classe WebElement).



AtlasWebElement e AtlasMobileElement são complementados pelos métodos should e waitUntil . (a consideração desses métodos será detalhada no artigo).

A ferramenta fornece a capacidade de criar seus próprios componentes expandindo as classes acima, o que permite criar um elemento de plataforma cruzada.

Principais recursos


Vamos considerar com mais detalhes a funcionalidade da ferramenta:



Interfaces em vez de classes

Ao descrever os PageObjects padrão, as interfaces são usadas em vez de classes.

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

Este exemplo descreve o link na página inicial do GitHub.

Parametrização de elementos

Imagine que temos um formulário com campos:



Para descrevê-lo, você precisa criar 11 variáveis ​​com a anotação @FindBy e, se necessário, declarar getter.

Usando o Atlas, você precisa apenas de um elemento AtlasWebElement com parâmetros.

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

O código de teste automatizado é o seguinte:

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

Passamos para a página desejada, chamamos o método com o parâmetro e executamos as ações necessárias com o elemento Um método com um parâmetro descreve um campo específico.

Herança múltipla

Foi mencionado anteriormente que um bloco (por exemplo, Cabeçalho), usado em diferentes Objetos de Página, é duplicação de código.

Existe um cabeçalho no github.



Nós descrevemos este bloco (a maioria dos elementos da web é omitida):

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

Em seguida, crie uma camada que possa ser conectada a qualquer página:

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

Expandimos a página principal com o bloco de cabeçalho.

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

Em geral, você pode criar mais camadas e conectá-las à página desejada. No exemplo abaixo, conecte as camadas de cabeçalho, rodapé e barra lateral na página principal.

 public interface MainPage extends WithHeader, WithFooter, WithSidebar {} 

Vamos seguir em frente. O cabeçalho contém 4 botões, 3 menus suspensos e um campo de pesquisa:



Vamos criar nosso próprio elemento Button e descrever quatro botões com um elemento.

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

Conecte o botão botão à camada de cabeçalho. Assim, expandimos a funcionalidade do cabeçalho do GitHub.

 public interface Header extends WithButton { … } 

Um elemento Button separado pode ser conectado a várias camadas do site e obter rapidamente o elemento desejado na página desejada.

Um exemplo:

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

Na segunda linha do teste, o cabeçalho do site é acessado, então chamamos o botão parametrizado com o valor “Pricing” e clicamos.

Pode haver alguns elementos no site testado que se repetem de uma página para outra. Para não descrever todos eles com a abordagem padrão de Objeto de Página, você pode descrevê-los uma vez e conectá-los sempre que necessário. Economizando tempo e o número de linhas de código é óbvio.



Métodos padrão

O Java 8 introduziu métodos padrão, que são usados ​​para predefinir a funcionalidade desejada.

Suponha que tenhamos um elemento "prejudicial": por exemplo, uma caixa de seleção que esteja ativada ou desativada. Muitos cenários passam por ele. É necessário ativar a caixa de seleção se estiver desativada:

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

Para não armazenar todo esse código na classe step, é possível colocá-lo próximo ao elemento como um método padrão.

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

Agora, a etapa do teste será assim:

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

Outro exemplo no qual você deseja combinar limpeza e digitação de caracteres em um campo.

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

Defina um método que limpa o campo e retorna o próprio elemento:

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

No método de teste, a etapa é a seguinte:

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

Dessa maneira, o comportamento desejado no elemento pode ser programado.

Tentativas

O Atlas possui novas tentativas internas. Você não precisa se preocupar com exceções, como NotFoundException , StaleElementReferenceException e WebDriverException , e também pode esquecer o uso das expectativas explícitas e implícitas da API Selenium.

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

Se em algum momento da cadeia você detectou uma exceção, a fase se repete desde o início.

É possível ajustar independentemente o intervalo de tempo durante o qual você pode repetir ou a frequência da repetição.

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

Esperamos por três segundos com uma taxa de polling de uma vez por segundo.

Também podemos ajustar a espera de um item específico usando a anotação Repetir . Para todos os elementos, a pesquisa ocorrerá em 3 segundos e, no caso de um, será 20.

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

Trabalhar com listas

Fora da caixa, a ferramenta fornece trabalho com listas. O que isso significa? Há um campo com uma tag de entrada onde inserimos o texto e, em seguida, uma lista suspensa aparece; os elementos não aparecem imediatamente.



Para esses casos, há uma entidade ElementsCollection. Com sua ajuda, há um trabalho com listas.

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

Uso:

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

Também é possível filtrar elementos e convertê-los em uma lista de outro tipo.

Asserções inteligentes

Como mencionado anteriormente, as entidades AtlasWebElement e AtlasMobileElement usam os métodos should, waitUntil para trabalhar com verificações (instruções).

MétodoDescrição do produto
deve (...)Executa várias instruções (verificações) com um elemento. Lança um AssertionError
espera (...)Executa várias instruções (verificações) com um elemento. Lança uma RuntimeException

Por que isso é feito? Para economizar tempo ao analisar, execute relatórios para scripts automatizados. A maioria das verificações funcionais é executada no final do script: elas são do interesse de um especialista em testes funcionais e verificações intermediárias para um especialista em testes automatizados. Portanto, se a funcionalidade do produto não funcionar, é lógico lançar uma exceção AssumptionError, caso contrário, uma RuntimeException.





O Allure mostrará imediatamente com o que estamos lidando: ou temos um defeito no produto (o especialista em FT aceita o trabalho) ou o teste automático é interrompido (o especialista em AT entende).

Modelo de Extensão



O usuário tem a oportunidade de redefinir a funcionalidade básica da ferramenta ou implementar sua própria funcionalidade. O modelo de extensão Atlas é semelhante ao modelo de extensão JUnit 5. Os módulos atlas-webdriver e atlas-appium são construídos sobre extensões. Se você estiver interessado, consulte o código fonte.

Vamos examinar um exemplo abstrato: é necessário desenvolver testes de interface do usuário para o Internet Explorer 11 (em alguns lugares ainda é usado). Há momentos em que o clique padrão nos elementos não funciona, e você pode usar o JS-click. Você decide substituir temporariamente o clique em todo o projeto de teste.

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

Como fazer isso?

Crie uma extensão que implemente a 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"); } } 

Redefinimos dois métodos. No método test (), especificamos que substituímos o método click. O método de chamada implementa a lógica necessária. Agora, o clique no elemento ocorrerá através do JavaScript.

Conectamos a extensão da seguinte maneira:

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

Com a ajuda de extensões, é possível criar uma busca por localizadores de elementos no banco de dados e implementar outros recursos interessantes - tudo depende da sua imaginação e necessidades.

Ponto de entrada único para PageObjects (WebSite)

A ferramenta permite que você armazene todas as suas páginas em um único local e trabalhe apenas com a entidade Site no futuro.

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

Além disso, é possível para as páginas definir URLs rápidas, parâmetros de consulta e segmentos de caminho.

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

A linha acima contém dois segmentos de caminho (qameta e atlas), que se traduz em github.com/qameta/atlas/tree/master . A principal vantagem dessa abordagem é que é possível abrir imediatamente a página desejada sem clicar nela.

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

Trabalhar com um elemento móvel

Trabalhar com um elemento móvel (AtlasMobileElement) é semelhante a trabalhar com o elemento da web AtlasWebElement. Além disso, três métodos foram adicionados ao AtlasMobileElement: rolando a tela para cima / baixo (scrollUp / scrollDown) e clicando em um item em espera (longPress).

Deixe-me dar um exemplo da tela principal do aplicativo Wikipedia. Um elemento é descrito para a plataforma iOS e o Android. Eles também descrevem um botão parametrizado.

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

Os testes têm a mesma aparência:

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

No exemplo acima, abrimos a página principal da Wikipedia, clique na barra de pesquisa, digite o texto Atlas, depois role para o item da lista com o valor Atlas LV-3B e vá para a visualização. A última linha verifica se o título é exibido e contém o valor desejado.

Ouvinte

O log de eventos pode ser implementado usando um ouvinte especial (interface do Ouvinte). Cada método possui quatro eventos quando chamados: Antes , Passar , Falhar . Depois .



Usando essa interface, você pode organizar os relatórios. Abaixo está um exemplo do Allure Listener, que pode ser encontrado no link .



Em seguida, conecte o ouvinte ao inicializar a classe Atlas.

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


Da maneira acima, é possível criar um ouvinte para vários sistemas de relatórios (por exemplo, para ReportPortal).

Ligação



Para automatizar a web da interface do usuário, basta registrar a dependência do atlas-webdriver e indicar a versão atual mais recente (a versão 1.6.0 é relevante no momento da redação deste documento).

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


Fazemos o mesmo se você deseja automatizar a 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.+' } 


Use



Depois de adicionar a dependência ao seu projeto, você deve inicializar a instância da classe Atlas.

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

Passamos ao construtor Atlas a instância da configuração, bem como o driver.

Atualmente, existem duas configurações: WebDriverConfiguration e AppiumDriverConfiguration. Cada configuração contém extensões padrão específicas.

Em seguida, definimos um método que criará todo o PageObject.

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


Um exemplo de um caso de teste simples:

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

Abrimos o site, voltamos para a camada de cabeçalho, procuramos um campo de texto (entrada de pesquisa), inserimos o texto e pressione enter.

Sumário


Concluindo, quero observar que o Atlas é uma ferramenta flexível com ótimos recursos. Ele pode ser personalizado para um projeto de teste específico de maneira conveniente para você e sua equipe. Desenvolver testes multiplataforma, etc.

Estão disponíveis vídeos de relatórios de Heisenbug , Selenium Camp e Nexign QA Meetup . Há um bate-papo no Telegram @atlashelp.

Usando esta ferramenta, você pode reduzir um número significativo de linhas de código (testadas em projetos de empresas como Yandex, SberTech e Tinkoff).

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


All Articles