Para optimizar la mantenibilidad y escalabilidad en proyectos de automatización UI, es fundamental implementar una arquitectura desacoplada. Este enfoque permite separar la lógica de ejecución de la configuración del entorno, los localizadores de elementos y los datos de prueba. Las mejoras clave integradas en esta propuesta técnica incluyen:
- Configuración Unificada: Consolidación de URLs de página e información de localización en acrhivos XML únicos.
- Gestión de Drivers: Resolución de conflictos de binarios en entornos CI/CD (Jenkins) mediante rutas configurables.
- Abstracción mediante Anotaciones: Uso de anotaciones personalizadas (
@AutomationPage) para inyectar metadatos de página, mejorando la legibilidad del código. - Reportes Avanzados: Integración de Allure 2 para visualización de resultados y trazabilidad de fallos.
- Persistencia de Datos: Soporte para múltiples fuentes de datos, permitiendo alternar entre Excel y bases de datos MySQL según el entorno.
Estructura del Proyecto y Dependencias (Maven)
El archivo pom.xml define las librerías necesarias para el manejo de Selenium, TestNG y la generación de reportes con AspectJ.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tech.automation</groupId>
<artifactId>decoupled-framework</artifactId>
<version>1.0</version>
<properties>
<aspectj.version>1.8.10</aspectj.version>
<selenium.version>3.141.59</selenium.version>
</properties>
<dependencies>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>2.13.0</version>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>
Gestión Dinámica de WebDrivers
Para evitar dependencias rígidas del sistema operativo, el framework utiliza un XML de configuración para los drivers, permitiendo definir rutas binarias específicas para Chrome, Firefox e IE.
<?xml version="1.0" encoding="UTF-8"?>
<browser-configs active-id="0">
<browser id="0" class="org.openqa.selenium.chrome.ChromeDriver">
<system-properties>
<prop name="webdriver.chrome.driver" value="{res}/drivers/chromedriver.exe"/>
</system-properties>
<binary-path value="C:/Program Files/Google/Chrome/Application/chrome.exe"/>
</browser>
</browser-configs>
La clase DriverFactory se encarga de parsear este XML y devolver la instancia correspondiente del WebDriver mediente reflexión.
public class DriverFactory {
public static WebDriver createInstance() {
SAXReader reader = new SAXReader();
InputStream is = DriverFactory.class.getResourceAsStream("/browser-config.xml");
try {
Document doc = reader.read(is);
Element root = doc.getRootElement();
String activeId = root.attributeValue("active-id");
Element browserNode = (Element) root.selectSingleNode("//browser[@id='" + activeId + "']");
String driverClass = browserNode.attributeValue("class");
List<Element> props = browserNode.element("system-properties").elements("prop");
for (Element p : props) {
String path = p.attributeValue("value").replace("{res}", "src/test/resources");
System.setProperty(p.attributeValue("name"), path);
}
if (driverClass.contains("chrome")) {
ChromeOptions options = new ChromeOptions();
String bin = browserNode.element("binary-path").attributeValue("value");
options.setBinary(bin);
return new ChromeDriver(options);
}
return (WebDriver) Class.forName(driverClass).newInstance();
} catch (Exception e) {
throw new RuntimeException("Error inicializando el driver", e);
}
}
}
El Motor Base de Pruebas (BaseRunner)
Esta clase centraliza las acciones comunes (esperas inteligentes, clicks, entradas de texto) y gestiona el ciclo de vida del navegador.
public class BaseRunner {
protected static WebDriver driver;
@BeforeSuite
public void setup() {
driver = DriverFactory.createInstance();
}
@AfterSuite
public void tearDown() {
if (driver != null) driver.quit();
}
public WebElement findElement(String description) {
// Lógica para obtener localizador desde XML usando anotaciones de la clase hija
LocatorData info = LocatorManager.get(this.getClass(), description);
WebDriverWait wait = new WebDriverWait(driver, 5);
return wait.until(ExpectedConditions.presenceOfElementLocated(info.getBy()));
}
public void type(String desc, String text) {
findElement(desc).sendKeys(text);
}
public void click(String desc) {
findElement(desc).click();
}
}
Anotaciones Personalizadas para Desacoplamiento
Para eliminar la necesidad de pasar rutas de archivos en cada método de prueba, se definen anotaciones que vinculan una clase de prueba con su archivo de localizadores y su fuente de datos.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AutomationPage {
String xmlPath() default "";
String pageName() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String type() default "excel";
String path() default "";
String queryOrSheet() default "";
}
Gestión de Localizadores (Object Repository XML)
Los localizadores se definen en XML, permitiendo cambios en la UI sin modificar el código Java.
<pages>
<page name="InventoryPage" url="http://example.com/inventory">
<element name="SearchBox" type="id" value="search-input"/>
<element name="SearchBtn" type="css" value=".btn-submit"/>
</page>
</pages>
Implementación de Casos de Prueba
Gracias a la infraestructura anterior, el script de prueba se vuelve puramente semántico.
@Listeners(TestFailureListener.class)
public class InventoryTests extends BaseRunner {
@Test(dataProvider = "testData")
@AutomationPage(xmlPath = "/locators/inventory.xml", pageName = "InventoryPage")
@DataSource(path = "/data/inventory_items.xlsx", queryOrSheet = "Items")
public void searchProductTest(Map<String, String> data) {
driver.get("http://example.com/inventory");
type("SearchBox", data.get("ProductName"));
click("SearchBtn");
String result = findElement("FirstResultText").getText();
Assert.assertTrue(result.contains(data.get("ExpectedText")));
}
}
Manejo de Fallos y Caputras de Pantalla
El uso de un TestListenerAdapter junto con Allure permite adjuntar capturas de pantalla automáticamente cuando una prueba falla, facilitando el debugging en entornos desatendidos.
public class TestFailureListener extends TestListenerAdapter {
@Override
public void onTestFailure(ITestResult result) {
saveScreenshot();
}
@Attachment(value = "Screenshot de Error", type = "image/png")
public byte[] saveScreenshot() {
return ((TakesScreenshot) BaseRunner.driver).getScreenshotAs(OutputType.BYTES);
}
}