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í.

Cómo implementé Allure Reports en mi framework de Selenium + Java

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>
Archivo pom.xml en IntelliJ mostrando un solo bloque de maven-surefire-plugin con configuración de testng.xml y argLine de AspectJ, seguido del plugin allure-maven. Estructura del proyecto visible a la izquierda con AllureScreenshot y AllureListener nuevos
pom.xml corregido: un solo bloque de surefire-plugin con testng.xml + AspectJ. Abajo, el plugin de Allure Maven.

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
Archivo pom.xml en IntelliJ mostrando dos bloques duplicados de maven-surefire-plugin, el segundo con AspectJ, y un error en rojo en la línea de version de aspectjweaver marcando Dependency not found
El error: dos declaraciones de maven-surefire-plugin. Maven no las fusiona — se pisan. La dependencia de aspectjweaver queda en rojo.

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
Panel Project de IntelliJ mostrando la estructura completa del proyecto selenium-java con AllureScreenshot en utils, AllureListener en listeners, allure.properties seleccionado en resources mostrando allure.results.directory igual a target/allure-results
Estructura del proyecto con los archivos nuevos: AllureScreenshot, AllureListener, allure.properties. El archivo define dónde se guardan los resultados.

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() {
    // ...
}
Código de LoginPage.java en IntelliJ mostrando anotaciones @Step en los métodos clickLogin con texto Click en botón Login y loginComo con texto Login como username, con flechas verdes señalando las anotaciones
LoginPage con @Step. Cada método anotado aparece como un paso visible en el reporte de Allure.

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() { ... }
Código de DashboardPage.java en IntelliJ mostrando anotaciones @Step en los métodos estaVisible, obtenerTitulo y irAClientes, con flechas verdes señalando cada anotación
DashboardPage con @Step en los tres métodos: verificar visibilidad, obtener título, navegar a Clientes.

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() { ... }
Código de LoginPositiveTests.java en IntelliJ mostrando imports de Allure Description, Severity y SeverityLevel, con @Description y @Severity CRITICAL en loginValido_deberiaIngresar y @Severity NORMAL en clickLoginSinTipear
LoginPositiveTests: @Description explica qué hace el test, @Severity lo clasifica. Login válido es CRITICAL.

LoginNegativeTests

@Test(dataProvider = "credencialesInvalidas", dataProviderClass = TestData.class)
@Description("Verifica que credenciales inválidas muestran mensaje de error")
@Severity(SeverityLevel.CRITICAL)
public void loginInvalido_muestraError(...) { ... }
Código de LoginNegativeTests.java en IntelliJ mostrando @Test con dataProvider credencialesInvalidas, @Description Verifica que credenciales inválidas muestran mensaje de error y @Severity CRITICAL
LoginNegativeTests: un solo método con DataProvider, @Description y @Severity CRITICAL.

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.

Código de ClientesTests.java en IntelliJ mostrando @BeforeClass con setupYNavegar, y @Test con dataProvider datosClientes, @Description Valida datos de cliente en grilla contra Excel y @Severity NORMAL
ClientesTests: @BeforeClass ejecuta login y lectura de grilla una vez. Cada iteración del DataProvider tiene @Description y @Severity 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.

Código de AllureScreenshot.java en IntelliJ mostrando la clase con anotación @Attachment value Screenshot en fallo type image/png y el método capturarPantalla que usa TakesScreenshot para capturar en OutputType.BYTES
AllureScreenshot: @Attachment convierte los bytes del screenshot en un adjunto del reporte. Se dispara automáticamente desde el listener.

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.

Código de AllureListener.java en IntelliJ mostrando la clase que implementa ITestListener con el método onTestFailure que llama a AllureScreenshot.capturarPantalla. Estructura del proyecto visible a la izquierda con AllureListener seleccionado en la carpeta listeners
AllureListener: implementa ITestListener de TestNG. Cuando un test falla, captura screenshot automáticamente. 13 líneas.

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

Reporte Allure Overview mostrando 96 test cases con 91.66% de pass rate, 7 Product defects y 1 Test defect
Overview de Allure: 96 tests, 8 failures deliberados. El donut chart separa passed (verde) de failed (rojo) y broken (amarillo).

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

Vista Categories del reporte Allure mostrando Product defects en rojo y Test defects en amarillo, con el test de EASTCs seleccionado a la derecha mostrando estado Broken, categoría Test defects, mensaje cliente con ID EASTCs no encontrado en la grilla, y el step Obtener valor de cliente EASTCs en columna 1 con Screenshot en fallo
Categories: EASTCs cayó en Test defects (amarillo) porque tiró RuntimeException, no AssertionError. Allure distingue automáticamente entre bugs de la app y errores del test.

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

Vista Suites de Allure mostrando test de validación de FISSA con steps Obtener valor de cliente FISSA en columna 1, 2, 3 y el mensaje de error con screenshot adjunto
Cada @Step aparece como un paso del test. Acá se ve: obtener valor columna 1, columna 2, columna 3... y el fallo con screenshot.

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

Detalle del reporte Allure mostrando sección Execution con Test body donde el step dice Login como admin con 2 parameters y 1 sub-step en 1s 106ms, Obtener mensaje de error en verde en 777ms, y Screenshot en fallo expandido mostrando la pantalla de login de StartSharp con banner rojo Error de validación Nombre de usuario o contraseña inválidos y el mensaje de assertion comparando Nombres vs Nombre
Detalle del reporte Allure mostrando sección Execution con Test body donde el step dice Login como admin con 2 parameters y 1 sub-step en 1s 106ms, Obtener mensaje de error en verde en 777ms, y Screenshot en fallo expandido

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

Graphs

Vista Graphs de Allure mostrando donut chart de Status 91.66% passed, bar chart de Severity con mayoría en normal, y histograma de Duration concentrado en menos de 500ms
Severity: la mayoría de tests son NORMAL (clientes), los CRITICAL son login. Duration: 90+ tests terminan en menos de 500ms.

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

Vista Timeline de Allure mostrando 96 tests seleccionados ejecutados secuencialmente en una línea de tiempo de 0s a 2m 09s
Timeline: ejecución secuencial, un solo thread. Los bloques rojos al inicio son los tests de login. Después viene la ejecución rápida de clientes.

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 testmvn 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

Temas conectados:

DataProviders y assertions reales 

Validar grilla de clientes contra Excel