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 elementospublic 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 classesAo 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 elementosImagine 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últiplaFoi 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ãoO 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.
TentativasO 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 listasFora 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 inteligentesComo mencionado anteriormente, as
entidades AtlasWebElement e
AtlasMobileElement usam os métodos should, waitUntil para trabalhar com verificações (instruções).
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óvelTrabalhar 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.
OuvinteO 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).