Hola a todos! Mi nombre es
Artyom Sokovets . Quiero compartir la traducción de mi artículo sobre Atlas: la reencarnación del marco HTML Elements, que presenta un enfoque completamente diferente para trabajar con Page Object.
Antes de entrar en detalles, quiero preguntar: ¿cuántos contenedores para Page Object conoce? Elemento de página, ScreenPlay, Componente cargable, Cadena de invocaciones ...
¿Y qué sucederá si tomamos un objeto de página con una implementación en la interfaz, ajustamos el patrón de proxy y agregamos un poco de funcionalidad Java 8?
Si está interesado, sugiero cambiar a gato.

Introduccion
Cuando se utiliza el patrón de diseño estándar de PageObject, surgen una serie de problemas:
Duplicación de elementos.public class MainPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; } public class AnyOtherPage { @FindBy(xpath = ".//div[@class = 'header']") private Header header; }
Aquí, el bloque de encabezado se usa en varias clases de PageObject.
Falta de parametrización de elementos. public class EditUserPage { @FindBy(xpath = "//div[text()='Text_1']") private TextBlock lastActivity; @FindBy(xpath = "//div[text()='Text_2']") private TextBlock blockReason; }
Este ejemplo describe los elementos de la página de edición de configuración del usuario. Dos elementos TextBlock contienen un localizador casi idéntico con una diferencia solo en el valor del texto ("Text_1" y "Text_2").
El mismo 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; }
En el trabajo diario, puede encontrar Page Object, que consta de muchas líneas de código con los mismos elementos. En el futuro, tales clases son "inconvenientes" de mantener.
Clase grande con escalones 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)
Con el tiempo, la clase de pasos para trabajar con elementos crece. Se requiere más atención para que no haya métodos duplicados.
Su guía para el mundo de Page Object
La reencarnación del marco HTML Elements tiene como objetivo resolver los problemas anteriores, reducir la cantidad de líneas de código para un proyecto de prueba, un trabajo más atento con listas y expectativas, así como afinar la herramienta por usted mismo gracias al sistema de extensión.
Atlas es un marco Java de nueva generación para desarrollar pruebas automáticas de IU con la implementación del patrón Objeto de página a través de interfaces. Este enfoque ofrece la posibilidad de herencia múltiple en la construcción del árbol de elementos, lo que finalmente proporciona un código conciso para sus pruebas automáticas.
La principal innovación del marco es el uso de interfaces en lugar de clases estándar.
Así es como se ve la descripción de la página de inicio de github.com:
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free)]") AtlasWebElement trial(); }
El código anterior describe la página principal del sitio de GitHub con un elemento y herencia múltiple de las capas WebPage y WithHeader (el ejemplo se proporciona solo con fines educativos, por lo que se omiten la mayoría de los elementos web).
Arquitectura marco
Atlas actualmente consta de tres módulos:
- atlas-core
- atlas-webdriver
- atlas-appium
El
atlas-core describe la funcionalidad básica para procesar objetos de página utilizando interfaces. La idea misma de usar interfaces fue tomada de la famosa herramienta Retrofit.

Los otros dos
módulos atlas-webdriver y
atlas-appium se utilizan para desarrollar scripts web y UI móviles automatizados. El punto de entrada principal para describir páginas web es la interfaz de la página web y para pantallas móviles: pantalla. Conceptualmente, atlas-webdriver y atlas-appium se basan en extensiones (paquete * .extension).
Artículos

La herramienta viene con
dos clases especializadas para trabajar con elementos de la interfaz de usuario (un análogo de la clase WebElement).
AtlasWebElement y
AtlasMobileElement se complementan con los
métodos should y
waitUntil . (la consideración de estos métodos se detallará más adelante en el artículo).
La herramienta proporciona la capacidad de crear sus propios componentes al expandir las clases anteriores, lo que le permite crear un elemento multiplataforma.
Características clave
Consideremos con más detalle la funcionalidad de la herramienta:
Interfaces en lugar de clasesAl describir PageObjects estándar, se utilizan interfaces en lugar de clases.
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a free trial of Enterprise Server')]") AtlasWebElement trial(); }
Este ejemplo describe el enlace en la página de inicio de GitHub.
Parametrización de elementos.Imagine que tenemos un formulario con campos:

Para describirlo, debe crear 11 variables con la anotación @FindBy y, si es necesario, declarar getter.
Con Atlas, solo necesita
un elemento AtlasWebElement parametrizado.
public interface MainPage extends WebPage { @FindBy("//div[text()='{{ text }}']/input") AtlasWebElement input(@Param("text") String text); }
El código de prueba automatizado es el siguiente:
@Test public void simpleTest() { onMainPage().input("First Name").sendKeys("*"); onMainPage().input("Postcode").sendKeys("*"); onMainPage().input("Email").sendKeys("*"); }
Pasamos a la página deseada, llamamos al método con el parámetro y realizamos las acciones requeridas con el elemento. Un método con un parámetro describe un campo específico.
Herencia múltipleSe mencionó anteriormente que un bloque (por ejemplo, Encabezado), que se usa en diferentes Objetos de página, es una duplicación de código.
Hay un encabezado github.

Describimos este bloque (se omiten la mayoría de los elementos web):
public interface Header extends AtlasWebElement { @FindBy(".//input[contains(@class,'header-search-input')]") AtlasWebElement searchInput(); }
A continuación, cree una capa que se pueda conectar a cualquier página:
public interface WithHeader { @FindBy("//header[contains(@class,'Header')]") Header header(); }
Expandimos la página principal con el bloque de encabezado.
public interface MainPage extends WebPage, WithHeader { @FindBy("//a[contains(text(), 'Or start a)]") AtlasWebElement trial(); }
En general, puede crear más capas y conectarlas a la página deseada. En el siguiente ejemplo, conecte las capas de encabezado, pie de página y barra lateral desde la página principal.
public interface MainPage extends WithHeader, WithFooter, WithSidebar {}
Sigamos adelante. El encabezado contiene 4 botones, 3 menús desplegables y un campo de búsqueda:

Creemos nuestro propio elemento
Button y describamos cuatro botones con un elemento.
public interface Button extends AtlasWebElement { @FindBy(".//a[contains(., '{{ value }}')]") AtlasWebElement selectButton(@Param("value") String value); }
Conecte el botón a la capa de encabezado. Por lo tanto, ampliamos la funcionalidad del encabezado GitHub.
public interface Header extends WithButton { … }
Se puede conectar un elemento
Button separado a varias capas del sitio web y obtener rápidamente el elemento deseado en la página deseada.
Un ejemplo:
@Test public void simpleTest() { onMainPage().open("https://github.com"); onMainPage().header().button("Priing").click(); }
En la segunda línea de la prueba, se accede al encabezado del sitio, luego llamamos al botón parametrizado con el valor "Precios" y hacemos clic.
Puede haber bastantes elementos en el sitio probado que se repiten de una página a otra. Para no describirlos a todos con el enfoque estándar de Objeto de página, puede describirlos una vez y conectarlos cuando sea necesario. Ahorrar tiempo y la cantidad de líneas de código es obvio.
Métodos predeterminadosJava 8 introdujo métodos predeterminados, que se utilizan para predefinir la funcionalidad deseada.
Supongamos que tenemos un elemento "dañino": por ejemplo, una casilla de verificación que está activada o desactivada. Muchos escenarios pasan por él. Es necesario habilitar la casilla de verificación si está deshabilitada:
if(onMainPage().rentFilter().checkbox("").getAttribute("class").contains("disabled")) { onMainPage().rentFilter().checkbox("").click(); }
Para no almacenar todo este código en la clase de paso, es posible colocarlo junto al elemento como método predeterminado.
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(); } } }
Ahora el paso en la prueba se verá así:
onMainPage().rentFilter().selectCheckbox("");
Otro ejemplo en el que desea combinar la limpieza y la escritura de caracteres en un campo.
onMainPage().header().input("GitHub").clear(); onMainPage().header().input("GitHub").sendKeys("*");
Defina un método que borre el campo y devuelva el elemento en sí:
public interface Input extends AtlasWebElement { @FindBy("//xpath") AtlasWebElement input(@Param("value") String value); default AtlasWebElement withClearInput(String value) { input(value).clear(); return input(value); } }
En el método de prueba, el paso es el siguiente:
onMainPage().header().withClearInput("GitHub").sendKeys("Atlas");
De esta manera, se puede programar el comportamiento deseado en el elemento.
ReintentosAtlas tiene reintentos incorporados. No necesita preocuparse por excepciones como
NotFoundException ,
StaleElementReferenceException y
WebDriverException , y también puede olvidarse del uso de las expectativas explícitas e implícitas de la API de Selenium.
onSite().onSearchPage("Junit 5").repositories().waitUntil(hasSize(10));
Si en algún momento de la cadena captó una excepción, la fase se repite desde el principio.
Es posible ajustar independientemente el intervalo de tiempo durante el cual puede repetir, o la frecuencia de repetición.
Atlas atlas = new Atlas(new WebDriverConfiguration(driver)) .context(new RetryerContext(new DefaultRetryer(3000L, 1000L, Collections.singletonList(Throwable.class))));
Esperamos por tres segundos con una tasa de votación de una vez por segundo.
También podemos ajustar la espera para un artículo específico utilizando la anotación
Reintentar . Para todos los elementos, la búsqueda se realizará en 3 segundos y, en el caso de uno, será 20.
@Retry(timeout = 20_000L, polling = 2000L) @IOSFindBy(xpath = "//XCUIElementTypeSearchField[@name='Search Wikipedia']") @AndroidFindBy(xpath = "//*[contains(@text, 'Search Wikipedia')]") AtlasMobileElement searchWikipedia();
Trabajar con listasFuera de la caja, la herramienta proporciona trabajo con listas. ¿Qué significa esto? Hay un campo con una etiqueta de entrada donde ingresamos el texto, luego aparece una lista desplegable, los elementos no aparecen de inmediato.

Para tales casos hay una entidad ElementsCollection. Con su ayuda hay un trabajo con 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));
También es posible filtrar elementos y convertirlos en una lista de otro tipo.
Aserciones inteligentesComo se mencionó anteriormente, las
entidades AtlasWebElement y
AtlasMobileElement usan los métodos should, waitUntil para trabajar con cheques (declaraciones).
¿Por qué se hace esto? Para ahorrar tiempo al analizar informes de ejecución de scripts automatizados. La mayoría de las comprobaciones funcionales se realizan al final del script: son de interés para un especialista en pruebas funcionales, y las comprobaciones intermedias para un especialista en pruebas automatizadas. Por lo tanto, si la funcionalidad del producto no funciona, es lógico lanzar una excepción AssumptionError, de lo contrario, una RuntimeException.


Allure mostrará de inmediato a qué nos enfrentamos: o tenemos un defecto del producto (el especialista en FT toma el trabajo), o el autotest falla (el especialista en AT comprende).
Modelo de extensión
El usuario tiene la oportunidad de redefinir la funcionalidad básica de la herramienta o implementar su propia funcionalidad. El modelo de extensión Atlas es similar al modelo de extensión JUnit 5. Los módulos atlas-webdriver y atlas-appium están construidos sobre extensiones. Si está interesado, consulte el código fuente.
Examinemos un ejemplo abstracto: es necesario desarrollar pruebas de IU para Internet Explorer 11 (en algunos lugares todavía se usa). Hay momentos en que el clic estándar en los elementos no funciona, entonces puede usar el clic JS. Decide anular temporalmente el clic en todo el proyecto de prueba.
onMainPage().header().button("en").click();
Como hacerlo
Cree una extensión que implemente la interfaz 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 dos métodos. En el método test (), especificamos que anulamos el método click. El método invoke implementa la lógica requerida. Ahora el clic en el elemento ocurrirá a través de JavaScript.
Conectamos la extensión de la siguiente manera:
atlas = new Atlas(new WebDriverConfiguration(driver, "https://github.com")) .extension(new JSClickExt());
Con la ayuda de extensiones, es posible crear una búsqueda de localizadores para elementos en la base de datos e implementar otras características interesantes; todo depende de su imaginación y necesidades.
Punto de entrada único a PageObjects (WebSite)La herramienta le permite almacenar todas sus páginas en un solo lugar y solo trabajar a través de la entidad del sitio en el 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(); }
Además, es posible que las páginas establezcan una URL rápida, parámetros de consulta y segmentos de ruta.
onSite().onProjectPage("qameta", "atlas").contributors().click();
La línea anterior contiene dos segmentos de ruta (qameta y atlas), que se traduce en
github.com/qameta/atlas/tree/master . La principal ventaja de este enfoque es que es posible abrir inmediatamente la página deseada sin hacer clic en ella.
@Test public void usePathWebSiteTest() { onSite().onProjectPage("qameta", "atlas").contributors().click(); onSite().onContributorsPage().hovercards().waitUntil(hasSize(4)); }
Trabajar con un elemento móvil.Trabajar con un elemento móvil (AtlasMobileElement) es similar a trabajar con el elemento web AtlasWebElement. Además, se han agregado tres métodos a AtlasMobileElement: desplazar la pantalla hacia arriba / abajo (scrollUp / scrollDown) y hacer clic en un elemento con hold (longPress).
Déjame darte un ejemplo de la pantalla principal de la aplicación Wikipedia. Se describe un elemento tanto para la plataforma iOS como para Android. También describen un botón 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); }
Las pruebas se ven de la misma manera:
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"))); }
En el ejemplo anterior, abrimos la página principal de Wikipedia, hacemos clic en la barra de búsqueda, ingresamos el texto Atlas, luego nos desplazamos al elemento de la lista con el valor Atlas LV-3B y vamos a su vista. La última línea comprueba que se muestra el título y contiene el valor deseado.
OyenteEl registro de eventos se puede implementar utilizando un escucha especial (interfaz de escucha). Cada método tiene cuatro eventos cuando se llama:
Antes ,
Pasar ,
Fallar .
Después
Con esta interfaz, puede organizar informes. A continuación se muestra un ejemplo de Allure Listener, que se puede encontrar en el
enlace .

A continuación, conecte el oyente al inicializar la clase Atlas.
atlas = new Atlas(new WebDriverConfiguration(driver)).listener(new AllureListener());
De la manera anterior, es posible crear un oyente para varios sistemas de informes (por ejemplo, para ReportPortal).
Conexión
Para automatizar la interfaz de usuario web, es suficiente registrar la dependencia atlas-webdriver e indicar la última versión actual (la versión 1.6.0 es relevante en el momento de escribir este artículo).
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.+' }
Hacemos lo mismo si desea automatizar 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.+' }
Uso
Después de agregar la dependencia a su proyecto, debe inicializar la instancia de clase Atlas.
@Before public void startDriver() { driver = new ChromeDriver(); atlas = new Atlas(new WebDriverConfiguration(driver)); }
Pasamos al constructor de Atlas la instancia de la configuración, así como el controlador.
Actualmente hay dos configuraciones: WebDriverConfiguration y AppiumDriverConfiguration. Cada configuración contiene extensiones predeterminadas específicas.
A continuación, definimos un método que creará todos los PageObject.
private <T extends WebPage> T onPage(Class<T> page) { return atlas.create(driver, page); }
Un ejemplo de un caso de prueba 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(); }
Abrimos el sitio, pasamos a la capa de encabezado, buscamos un campo de texto (entrada de búsqueda), ingresamos el texto y presionamos enter.
Resumen
En conclusión, quiero señalar que Atlas es una herramienta flexible con excelentes características. Se puede personalizar para un proyecto de prueba específico de una manera conveniente para su equipo y para usted. Desarrollar pruebas multiplataforma, etc.
Hay disponibles videoclips de informes de
Heisenbug ,
Selenium Camp y
Nexign QA Meetup . Hay un chat de Telegram @atlashelp.
Con esta herramienta, puede reducir una cantidad significativa de líneas de código (probadas en proyectos por compañías como Yandex, SberTech y Tinkoff).