Cómo implementé Allure Reports en mi framework de Selenium + Java
Allure Reports con TestNG: @Step, @Description, @Severity, screenshots automáticos en fallo. Implementación completa con los errores que cometí.
Contexto: el problema del reporting
Este post documenta la integración de Allure Reports en mi framework de Selenium + Java.
Hasta Validar grilla de clientes contra Excel, los resultados de mis tests eran esto:
Tests run: 96, Failures: 0, Errors: 0, Skipped: 0
Una línea. En la consola. Con TestNG ejecutando desde mvn clean test.
Si un test fallaba, veía el stack trace en texto plano. Para entender qué pasó tenía que: leer el nombre del método, buscar el assert que falló, imaginar en qué estado estaba la app.
No había screenshots. No había pasos visibles. No había forma de compartir resultados con alguien que no tenga IntelliJ abierto.
En un trabajo real, un lead o un PM no va a leer stack traces. Necesita ver: qué se probó, qué falló, con qué evidencia, y en cuánto tiempo.
Para eso existe Allure.
Qué es Allure
Allure es un framework de reporting que se integra con TestNG (y otros). Toma los resultados de los tests y genera un reporte HTML interactivo.
Lo que agrega sobre el reporte básico de TestNG:
- Pasos visibles dentro de cada test (@Step)
- Descripciones legibles (@Description)
- Severidad por test (@Severity)
- Screenshots adjuntos automáticamente en fallos
- Gráficos de duración, severidad, tendencias
- Categorías de errores (defectos de producto vs defectos de test)
- Timeline de ejecución
- Vista por suites, behaviors, packages
Todo en un HTML que se abre en el browser. Sin instalar nada.
Dependencias y plugins
En pom.xml
Agregué dos properties nuevas:
<allure.version>2.25.0</allure.version>
<aspectj.version>1.9.22</aspectj.version>
Una dependencia:
<!-- Allure TestNG -->
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-testng</artifactId>
<version>${allure.version}</version>
</dependency>
Y dos plugins. Acá tuve el primer error.
El error del plugin duplicado
Yo ya tenía maven-surefire-plugin configurado para ejecutar el testng.xml. Cuando agregué la configuración de AspectJ, la puse como un segundo bloque de surefire-plugin. Dos declaraciones del mismo plugin en el pom.
Maven no las fusiona. Se pisan.
La solución: un solo bloque de surefire-plugin con todo adentro.
<build>
<plugins>
<!-- Surefire con AspectJ para que @Step funcione -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<configuration>
<suiteXmlFiles>
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile>
</suiteXmlFiles>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<!-- Allure Maven plugin para generar reportes -->
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.12.0</version>
<configuration>
<reportVersion>${allure.version}</reportVersion>
</configuration>
</plugin>
</plugins>
</build>

Por qué AspectJ
Allure usa AspectJ para interceptar los métodos marcados con @Step. Sin el agente, ponés @Step en un método y no pasa nada — Allure no lo detecta. AspectJ es el que hace la magia de capturar esos métodos en runtime.
El argLine en surefire le dice a Maven: "cuando ejecutes los tests, usá este agente Java". Solo aplica cuando corrés con mvn test. Desde IntelliJ el agente no se aplica automáticamente.
La dependencia que IntelliJ no encontraba
Después de guardar el pom.xml, IntelliJ marcaba en rojo la dependencia de aspectjweaver:
Dependency 'org.aspectj:aspectjweaver:1.9.22' not found

Hice reload de Maven desde IntelliJ. Seguía en rojo.
La solución: correr mvn clean test desde terminal. Maven descargó la dependencia correctamente. El rojo en IntelliJ era cosmético — la dependencia estaba dentro del plugin, no del proyecto, e IntelliJ no siempre resuelve esas automáticamente.
Después de ese mvn clean test, el rojo desapareció.
allure.properties
Creé este archivo en src/test/resources/:
allure.results.directory=target/allure-results

Le dice a Allure dónde guardar los resultados crudos (archivos JSON, uno por cada test) que después se convierten en el reporte HTML.
Anotaciones en las Pages
Acá es donde Allure se conecta con el código. Cada método de Page que marques con @Step aparece como un paso visible en el reporte.
LoginPage
import io.qameta.allure.Step;
@Step("Login como {username}")
public void loginComo(String username, String password) {
enterUsername(username);
enterPassword(password);
clickLogin();
}
@Step("Click en botón Login")
public void clickLogin() {
// ...
}
@Step("Obtener mensaje de error")
public String obtenerMensajeError() {
// ...
}

Los parámetros entre {} se reemplazan con los valores reales. En el reporte, en vez de ver "loginComo", ves "Login como admin" o "Login como mal".
El error del parámetro
Primero puse @Step("Login como {usuario}") pero el parámetro del método se llamaba username. Allure no encontraba usuario y tiraba:
ERROR io.qameta.allure.util.NamingUtils - Could not find parameter usuario
El nombre en el @Step tiene que coincidir exactamente con el nombre del parámetro Java. Cambié {usuario} a {username} y se resolvió.
DashboardPage
@Step("Verificar que el dashboard está visible")
public boolean estaVisible() { ... }
@Step("Obtener título del dashboard")
public String obtenerTitulo() { ... }
@Step("Navegar a Clientes desde menú")
public void irAClientes() { ... }

ClientesPage
@Step("Leer grilla completa de clientes")
public void leerGrillaCompleta() { ... }
@Step("Obtener valor de cliente {clienteId} en columna {indiceColumna}")
public String obtenerValorPorId(String clienteId, int indiceColumna) { ... }
No cambié la lógica de ningún método. Solo agregué anotaciones.
Anotaciones en los Tests
Dos anotaciones nuevas: @Description para explicar qué hace el test, y @Severity para clasificar su importancia.
LoginPositiveTests
import io.qameta.allure.Description;
import io.qameta.allure.Severity;
import io.qameta.allure.SeverityLevel;
@Test
@Description("Verifica login exitoso con credenciales válidas")
@Severity(SeverityLevel.CRITICAL)
public void loginValido_deberiaIngresar() { ... }
@Test
@Description("Verifica que click en Login sin credenciales no rompe la app")
@Severity(SeverityLevel.NORMAL)
public void clickLoginSinTipear() { ... }

LoginNegativeTests
@Test(dataProvider = "credencialesInvalidas", dataProviderClass = TestData.class)
@Description("Verifica que credenciales inválidas muestran mensaje de error")
@Severity(SeverityLevel.CRITICAL)
public void loginInvalido_muestraError(...) { ... }

ClientesTests
@Test(dataProvider = "datosClientes", dataProviderClass = ClientesTestData.class)
@Description("Valida datos de cliente en grilla contra Excel")
@Severity(SeverityLevel.NORMAL)
public void validarDatosCliente(...) { ... }
Niveles disponibles: BLOCKER, CRITICAL, NORMAL, MINOR, TRIVIAL. El login es CRITICAL (si falla, no se puede usar la app). La validación de datos es NORMAL.

Screenshots automáticos en fallo
Dos archivos nuevos.
AllureScreenshot.java
package com.cesar.qa.utils;
import com.cesar.qa.config.DriverManager;
import io.qameta.allure.Attachment;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
public class AllureScreenshot {
@Attachment(value = "Screenshot en fallo", type = "image/png")
public static byte[] capturarPantalla() {
return ((TakesScreenshot) DriverManager.getDriver())
.getScreenshotAs(OutputType.BYTES);
}
}
@Attachment es de Allure. El método devuelve bytes de una imagen PNG y Allure los adjunta automáticamente al test que está corriendo.

AllureListener.java
package com.cesar.qa.listeners;
import com.cesar.qa.utils.AllureScreenshot;
import org.testng.ITestListener;
import org.testng.ITestResult;
public class AllureListener implements ITestListener {
@Override
public void onTestFailure(ITestResult result) {
AllureScreenshot.capturarPantalla();
}
}
Un TestNG Listener que escucha eventos de los tests. Cuando un test falla, captura screenshot. No hay que llamarlo manualmente — TestNG lo ejecuta automáticamente.

Para que funcione, lo registré en testng.xml:
<suite name="QA Automation Lab">
<listeners>
<listener class-name="com.cesar.qa.listeners.AllureListener"/>
</listeners>
<!-- tests existentes -->
</suite>
El reporte
Corrí los tests con errores deliberados en los datos esperados para ver cómo Allure reporta fallos.
Ejecución
mvn clean test
mvn allure:serve
El primer comando ejecuta los tests y genera los resultados en target/allure-results/. El segundo levanta un servidor local y abre el reporte HTML en el browser.
Resultado: 96 tests, 8 failures deliberados

Los 8 fallos fueron intencionales:
- 2 en LoginPositiveTests: cambié "Tablero" por "Tableros" en el expected
- 3 en LoginNegativeTests: cambié "Nombre" por "Nombres" y "vacíos" por "vacíoss"
- 3 en ClientesTests: cambié "Mexico" por "Mexicos", "Accounting Manager" por "Accounting Managers" y cambié "EASTC" por "EASTCs" (ID que no existe en la grilla)
Categories

Allure distingue entre "Product defects" (el assert falló — posible bug) y "Test defects" (excepción en el código — posible error del test). El caso del ID "EASTCs" que no existe en la grilla cayó en Test defects porque tiró RuntimeException, no AssertionError.
Cambié el ID en el Excel pensando que fallaría como los demás. Pero el ID es la clave del HashMap — si no existe, el test no llega al assert, tira excepción. Allure lo clasificó como Test defect, no Product defect. La diferencia importa.
Suites: los @Step en acción

Sin los @Step, el reporte solo mostraría "validarDatosCliente — FAILED". Con ellos, se ve exactamente en qué paso falló y qué estaba haciendo.
Screenshots en fallo

El screenshot se captura en el momento exacto del fallo. No hay que imaginarse qué estaba pasando — se ve.
Graphs

El gráfico de Duration muestra que los 91 tests de clientes terminan en menos de 500ms (porque leen de memoria, no del DOM). Los tests de login tardan 4-6 segundos cada uno porque abren y cierran Chrome.
Timeline

Los bloques rojos al inicio son los tests de login (cada uno abre Chrome). Después vienen los 91 de clientes, que corren rápido sobre una sola sesión.
Estructura actual del proyecto
selenium-java/
├── src/
│ ├── main/java/com/cesar/qa/
│ │ ├── base/
│ │ │ └── BasePage.java
│ │ ├── config/
│ │ │ ├── ConfigReader.java
│ │ │ └── DriverManager.java
│ │ ├── pages/
│ │ │ ├── ClientesPage.java (@Step agregados)
│ │ │ ├── DashboardPage.java (@Step agregados)
│ │ │ └── LoginPage.java (@Step agregados)
│ │ └── utils/
│ │ ├── AllureScreenshot.java ← NUEVO
│ │ ├── check/
│ │ └── ExcelReader.java
│ └── test/
│ ├── java/com/cesar/qa/
│ │ ├── base/
│ │ │ └── BaseTest.java
│ │ ├── data/
│ │ │ ├── ClientesTestData.java
│ │ │ └── TestData.java
│ │ ├── listeners/
│ │ │ └── AllureListener.java ← NUEVO
│ │ └── tests/
│ │ ├── clientes/
│ │ │ └── ClientesTests.java (@Description, @Severity)
│ │ └── login/
│ │ ├── LoginNegativeTests.java (@Description, @Severity)
│ │ └── LoginPositiveTests.java (@Description, @Severity)
│ └── resources/
│ ├── testdata/
│ │ └── clientes-data.xlsx
│ ├── allure.properties ← NUEVO
│ ├── config.properties
│ ├── logback.xml
│ └── testng.xml (listener agregado)
└── pom.xml (Allure + AspectJ agregados)
Lo que aprendí
No duplicar plugins en Maven. Dos declaraciones del mismo plugin no se fusionan — se pisan. Un solo bloque con toda la configuración.
IntelliJ puede marcar rojo cosas que funcionan. La dependencia de aspectjweaver dentro del plugin no la resolvía IntelliJ, pero Maven sí. mvn clean test la descargó correctamente.
Los nombres de parámetros en @Step importan. {usuario} no es lo mismo que {username}. Si el nombre no coincide con el parámetro del método, Allure tira error y no muestra el valor en el reporte.
@Step transforma el reporte. Sin él, ves "test — PASSED". Con él, ves cada acción: "Login como admin", "Verificar dashboard visible", "Obtener título". La diferencia entre un log y evidencia de testing.
Screenshots en fallo son la feature más valiosa. En un equipo, nadie va a reproducir un fallo para ver qué pasaba. El screenshot lo muestra al instante.
Estado actual
Tengo:
- Allure Reports generando reporte HTML interactivo
- @Step en todas las Pages (LoginPage, DashboardPage, ClientesPage)
- @Description y @Severity en todos los tests
- Screenshots automáticos en cada test que falla
- Categorización automática: Product defects vs Test defects
- Ejecución:
mvn clean test→mvn allure:serve
Próximo paso
CI/CD con GitHub Actions — que los tests se ejecuten automáticamente y el reporte se publique sin intervención manual.
🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/qa-automation-lab
📂 selenium-java
—