Sesión 7 de mi lab de Selenium + Java: Page Object Model — por qué separar Pages de Tests

La Page interactúa con la UI, el Test decide si está bien o mal. Anatomía de LoginPage, DashboardPage, validaciones con check.java y logs claros.

Código Java del LoginPage usando Page Object Model, con imports separados por responsabilidad, locators privados final y WebDriverWait centralizado.
Código Java del LoginPage usando Page Object Model, con imports separados por responsabilidad, locators privados final y WebDriverWait centralizado.

Qué es Page Object Model

POM es un patrón de diseño, no una librería.

La idea central es simple:

👉 Cada página (o vista importante) de la aplicación se representa como una clase.
👉 Esa clase conoce cómo interactuar con la UI.
👉 Luego los tests solo dicen qué quieren validar, no cómo se hace.

En POM:

  • los selectores viven en las páginas
  • las acciones viven en las páginas
  • las esperas viven en las páginas
  • los tests no usan By ni WebElement directamente

El problema que POM viene a resolver

Un test sin POM suele verse así:

driver.findElement(By.id("LoginPanel0_Username")).sendKeys("admin");
driver.findElement(By.id("LoginPanel0_Password")).sendKeys("serenity");
driver.findElement(By.id("LoginPanel0_LoginButton")).click();

String title = driver.findElement(By.cssSelector("h1")).getText();
assertEquals(title, "Tablero");

Esto tiene varios problemas:

  • el test sabe demasiado de la UI
  • si cambia un ID, hay que tocar todos los tests
  • la intención del test no se lee fácil
  • mezclar validaciones + navegación + selectores hace ruido

La idea clave de POM

Con POM, el test debería leerse así:

LoginPage loginPage = new LoginPage();
loginPage.loginComo("admin", "serenity");

DashboardPage dashboard = new DashboardPage();
check.visible(dashboard.estaVisible(), "Dashboard visible luego de login válido");
check.equals(dashboard.obtenerTitulo(), "Tablero", "Título del dashboard");

El test narra una historia, no ejecuta Selenium.


Estructura general del proyecto

Como va mi prj:

Estructura de proyecto Selenium Java con separación entre main y test, pages, config, utils y tests de login.
Estructura de proyecto Selenium Java con separación entre main y test, pages, config, utils y tests de login.
src/main/java
  com.cesar.qa
    ├─ config
    │   ├─ ConfigReader
    │   └─ DriverManager
    ├─ pages              //Pages object model
    │   ├─ DashboardPage
    │   └─ LoginPage
    └─ utils
        └─ check
src/test/java
  com.cesar.qa.tests.login  //Tests
    ├─ LoginPositiveTests
    ├─ LoginNegativeTests
    └─ LoginTestRunner

Qué contiene una Page (ejemplo real)

LoginPage.java

Una Page no valida resultados finales.
Solo sabe interactuar con la página.

Así armé page del Login:

Definición de locators en LoginPage usando private final By, organizados y documentados para mantenimiento del DOM.
Los locators viven en la Page: un único lugar para cambiar cuando la UI evoluciona.
Definición de locators en LoginPage usando private final By, organizados y documentados para mantenimiento del DOM.

📌 Importante
La Page:

  • no hace asserts
  • no imprime logs
  • no decide si algo es correcto o no

Solo devuelve datos o estados.


Anatomía de una Page Object (LoginPage)

Una Page Object no es solo “una clase con selectores”.
Es una pieza estructural del framework y tiene reglas claras sobre qué debe contener y qué no.

Voy a desglosar LoginPage por partes y explicar por qué está hecha así.

Imports: qué importa una Page Object (y qué no)

Una Page Object debería importar solo lo necesario para interactuar con la UI, nada más.

Ejemplo típico de imports correctos:

// Infraestructura del framework
import com.cesar.qa.config.DriverManager;

// Selenium: localizadores y elementos
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

// Selenium: esperas explícitas
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

// Java
import java.time.Duration;

Qué debe importar una Page

  • By, WebElement → porque conoce el DOM
  • WebDriverWait, ExpectedConditions → porque maneja esperas
  • DriverManager → para leer el driver ya inicializado

Qué no debe importar una Page

❌ TestNG / JUnit
❌ asserts
❌ clases de test
❌ lógica de negocio
❌ no debe navegar a URLs
❌ inicialización o cierre del driver

📌 Regla mental

Si un import tiene que ver con ejecución de pruebas o validación, no va en una Page.

La Page interactúa.
El test decide si algo está bien o mal.


Locators: pocos, importantes y bien definidos

En una Page Object, los locators son contratos con la UI.

private final By usernameInput = By.id("LoginPanel0_Username");
private final By passwordInput = By.id("LoginPanel0_Password");
private final By loginButton   = By.id("LoginPanel0_LoginButton");
private final By errorMessage  = By.cssSelector(".toast-message");

Por qué private

  • Nadie fuera de la Page debería acceder a los selectores
  • El test no tiene que saber cómo está construido el DOM
  • Si el selector cambia, solo se cambia acá

Por qué final

  • Un locator no debería mutar
  • Representa un elemento fijo de la UI
  • Evita errores accidentales y refuerza la idea de “contrato”

📌 Idea clave

Si mañana cambia la UI por una nueva versión,
este es el archivo al que se viene primero.

Por eso:

  • conviene tener solo los locators relevantes
  • bien nombrados
  • ordenados
  • y no duplicados

Esperas: por qué viven en la Page

En este proyecto se usa WebDriverWait, y no Thread.sleep.

private final WebDriverWait wait =
    new WebDriverWait(DriverManager.getDriver(), Duration.ofSeconds(10));

Por qué WebDriverWait

  • espera condiciones reales, no tiempo fijo
  • hace los tests más estables
  • evita flakiness innecesaria

Por qué la Page 'lee' el driver y no lo crea

[Test]
  |
  | 1) initDriver()
  | 2) get(baseUrl)
  v
[DriverManager]
  |
  | getDriver()
  v
[Page Object]
  |
  | interactúa con UI
  | (click, sendKeys, getText, isVisible)
  v
[Browser / DOM]

La Page asume que:

  • el driver ya fue inicializado en otro lugar
  • la URL ya fue abierta en otro lugar

Esto es intencional.

Si una Page:

  • inicializara el driver
  • o navegara a una URL

entonces rompería el POM, porque:

  • ya no sería reutilizable
  • mezclaría responsabilidades
  • sería imposible controlar flujos desde los tests

✅ Diagrama correcto (responsabilidades separadas)

DriverManager ──► Infraestructura
Test          ──► Flujo y validación
Page          ──► Interacción UI

Cada pieza:

  • hace una sola cosa
  • la hace bien
  • no invade a las demás
El Page Object Model no trata de dónde escribir código,
sino de quién debe tomar cada decisión.

Métodos de acción: cómo interactúa la Page

Los métodos de una Page son acciones atómicas o acciones compuestas.

Acciones simples

public void enterUsername(String username) {
    WebElement el =
        wait.until(ExpectedConditions.visibilityOfElementLocated(usernameInput));
    el.clear();
    el.sendKeys(username);
}

Este método:

  • espera que el elemento exista
  • limpia el campo
  • escribe el valor
  • oculta toda la complejidad al test

El test no sabe:

  • cómo se localiza el input
  • qué espera se usa
  • si hay que limpiar o no

Solo dice: “ingresar usuario”.


Acciones compuestas

public void loginComo(String user, String pass) {
    enterUsername(user);
    enterPassword(pass);
    clickLogin();
}

Esto permite que los tests sean expresivos:

loginPage.loginComo("admin", "serenity");

Y no una secuencia de pasos técnicos.


Métodos de estado: devolver información al test

Una Page también puede devolver estados, no solo ejecutar acciones.

Ejemplo:

public String obtenerMensajeError() {
    return wait.until(
        ExpectedConditions.visibilityOfElementLocated(errorMessage)
    ).getText();
}

Acá la Page:

  • obtiene información
  • pero no decide si es correcta o no

La validación vive en el test.


Métodos “safe”: pensar en tests negativos

Para escenarios negativos, a veces esperar algo es incorrecto.

Ejemplo:
Si el login falla, el dashboard no debería aparecer.

Para eso se crean métodos seguros:

public boolean estaVisibleSafe() {
    try {
        wait.until(ExpectedConditions.visibilityOfElementLocated(dashboardBody));
        return true;
    } catch (Exception e) {
        return false;
    }
}

Esto permite:

  • validar ausencia de navegación
  • evitar que el test explote por timeout
  • escribir tests negativos claros
check.isFalse(
    dashboard.estaVisibleSafe(),
    "El dashboard NO debería aparecer"
);

📌 Esto es diseño consciente, no un hack.


Resumen mental de una Page Object

Una Page Object:

  • ✔ importa solo Selenium + infraestructura
  • ✔ tiene locators private final
  • ✔ usa esperas explícitas
  • ✔ no inicializa driver
  • ✔ no navega a URLs
  • ✔ no valida resultados finales
  • ✔ devuelve estados o ejecuta acciones
  • ✔ protege al test de cambios de UI

Page Object vs Test

Separación de responsabilidades en POM

Una de las ideas centrales del Page Object Model es separar responsabilidades.
La Page interactúa con la UI.
El Test decide si el comportamiento es correcto.

La siguiente tabla resume qué va en cada uno y por qué:

ConceptoPage Object (LoginPage, DashboardPage)Test (LoginPositiveTests, etc.)
Responsabilidad principalInteractuar con la UIVerificar comportamiento
Conocimiento del DOM✅ Sí❌ No
Selectores (By)✅ Sí❌ No
Esperas explícitas (WebDriverWait)✅ Sí❌ No
Acciones (click, sendKeys, getText)✅ Sí❌ No
Estados (visible, texto, existe)✅ Devuelve❌ No calcula
Validaciones (asserts, checks)❌ No✅ Sí
Decisión de OK / FAIL❌ No✅ Sí
Navegar a URLs (driver.get)❌ No✅ Sí
Inicializar / cerrar driver❌ No✅ Sí
Flujo de negocio❌ No✅ Sí
Uso de frameworks de test (TestNG, JUnit)❌ No✅ Sí

Regla de oro mental

La Page responde “qué hay y qué puedo hacer”
El Test responde “esto está bien o está mal”

Si una Page:

  • valida resultados
  • lanza asserts (validaciones)
  • decide si algo falló

➡️ dejó de ser una Page y se volvió un test mal ubicado.


Ejemplo práctico

❌ Mal (validación dentro de la Page)

public void validarTituloDashboard() {
    String titulo = driver.findElement(...).getText();
    if (!titulo.equals("Tablero")) {
        throw new RuntimeException("Error");
    }
}

Problemas:

  • la Page decide el resultado
  • no es reutilizable
  • mezcla responsabilidades

✅ Bien (Page devuelve estado, Test valida)

Page

public String obtenerTitulo() {
    return wait.until(
        ExpectedConditions.visibilityOfElementLocated(dashboardHeader)
    ).getText();
}

Test

check.equals(
    dashboard.obtenerTitulo(),
    "Tablero",
    "Título del dashboard"
);

La Page:

  • obtiene información
    El Test:
  • decide si es correcta

Por qué esta separación importa (mucho)

Esta división permite que:

  • la UI cambie sin romper todos los tests
  • los tests sean más legibles
  • las Pages sean reutilizables
  • el framework escale
  • el mantenimiento sea más barato
  • los errores sean más fáciles de diagnosticar

Y, sobre todo:

El test lee como un caso de negocio, no como código Selenium.

Conclusión

El Page Object Model no es una moda ni una convención arbitraria.
Es una forma de controlar la complejidad cuando los tests crecen.

Separar Page y Test es lo que hace que un proyecto de Selenium:

  • pase de “funciona”
  • a “es mantenible”.

DashboardPage: validar navegación correcta

Así armé page del Dashboard:

Clase DashboardPage con métodos para validar visibilidad y obtener texto usando WebDriverWait.
Las Pages no “assertan”: exponen estado y datos para que el test decida.

Esto permite:

  • positivos → estaVisible()
  • negativos → estaVisibleSafe()

El rol de check.java: validaciones sin cortar la ejecución

En este lab decidí no usar assertions tradicionales (assert, AssertionError, TestNG, etc.) y crear una clase propia llamada check.

Esto no es casual ni “reinventar la rueda”: responde a un objetivo concreto del lab.

🎯 Objetivo del check

Quería que los tests:

  • sigan ejecutándose aunque haya fallos
  • muestren todos los errores en una sola corrida
  • dejen un log legible, similar a TestComplete (log.warning, log.error, checkpoints)

En otras palabras:
👉 prefiero visibilidad total del estado del sistema antes que cortar en el primer error.


Por qué no uso assert (por ahora)

En Java, y especialmente en frameworks como TestNG o JUnit:

  • una assert fallida
    → lanza una excepción
    corta el test
    → y muchas veces corta el resto del flujo

Eso es correcto y deseable en muchos contextos (CI, pipelines, smoke tests).

Pero en este laboratorio inicial, no es lo que quiero.

Vengo de TestComplete, donde podía configurar:

  • Stop on error
  • Stop on warning

y normalmente los desactivaba para:

  • ejecutar toda la batería
  • ver todos los problemas juntos
  • luego decidir qué corregir

check.java replica ese mismo enfoque, pero en Selenium puro.


Qué es check.java

check es una clase utilitaria de validaciones.

No interactúa con la UI.
No conoce Selenium.
No conoce Pages.

Solo responde a una pregunta:

“¿Lo que esperaba ocurrió o no?”

Y lo informa por consola de forma clara.


Estructura general

public class check {
    // comparaciones de valores
    // validaciones booleanas
    // validaciones negativas (isFalse)
}

Es stateless, todo es static, y puede ser llamada desde:

  • tests
  • pages (si se quisiera, aunque yo la uso desde tests)

1️⃣ Comparar valores: equals(actual, expected, context)

check.equals(
    dashboard.obtenerTitulo(),
    "Tablero",
    "Título del dashboard"
);

Qué hace

  • compara actual vs expected
  • no lanza excepción
  • imprime un resultado claro:

✔ OK si coincide
❌ FALLO si no coincide, mostrando ambos valores

Ejemplo de salida:

❌ fallo. Título del dashboard
Expected: [Tablero]
Actual:   [Tablero principal]

Esto es extremadamente útil cuando:

  • validás textos
  • montos
  • estados visibles en pantalla
  • valores formateados

2️⃣ Validar visibilidad: visible(boolean, context)

check.visible(
    dashboard.estaVisible(),
    "Dashboard visible luego de login válido"
);

Importante

La lógica de cómo se obtiene la visibilidad no está acá.
Eso vive en la Page:

public boolean estaVisible() {
    return wait
        .until(ExpectedConditions.visibilityOfElementLocated(...))
        .isDisplayed();
}

check solo informa el resultado.

Esto refuerza una idea central del POM:

la Page obtiene estados
el Test decide qué validar
check solo reporta

3️⃣ Validación negativa: isFalse(condition, context)

Este método aparece cuando queremos expresar algo muy común en tests negativos:

“esto NO debería pasar”

Ejemplo:

check.isFalse(
    dashboard.estaVisibleSafe(),
    "El dashboard NO debería aparecer"
);

Qué valida realmente

  • si la condición es true → ❌ fallo
  • si la condición es false → ✔ OK

Y el mensaje lo deja explícito:

❌ fallo. El dashboard NO debería aparecer (se esperaba FALSE y fue TRUE)

Esto es mucho más semántico y legible que escribir:

if (dashboard.estaVisible()) {
    ...
}

Por qué esto no rompe el POM

check.java:

  • no sabe nada del DOM
  • no tiene By
  • no usa WebDriver
  • no tiene esperas

Solo consume valores ya calculados.

Por eso:

  • no contamina las Pages
  • no mezcla responsabilidades
  • no acopla el test a Selenium

¿Esto es definitivo?

No.

Esto es intencionalmente transitorio.

Más adelante, al incorporar TestNG, este check puede:

  • mapearse a soft asserts (SoftAssert)
  • integrarse con reportes (Allure / Extent)
  • o directamente reemplazarse

Pero como primer paso, cumple algo fundamental:

me permite pensar en estructura, flujo y diseño,
sin que el framework me tape los errores.

Idea clave de esta sección

check.java no existe para reemplazar TestNG.
Existe para entender qué estoy validando y por qué.

Y eso, en un lab de aprendizaje, vale muchísimo más que correr rápido.


Los tests: donde vive la intención

Tests positivos: validar que SI pase algo

Test de login válido en Selenium usando Page Object Model, DriverManager y validaciones desacopladas.
El test describe el flujo; la Page ejecuta los detalles.

Tests negativos: validar que NO pase algo

Tests negativos de login validando mensajes de error y ausencia del dashboard tras credenciales inválidas.
Validar lo que no debe pasar también es parte del contrato del sistema.

Estos tests:

  • no saben qué selector tiene el dashboard
  • no usan By
  • no usan WebElement
  • se leen como una historia de negocio

Por qué cada test abre el browser y lo cierra

En este punto del laboratorio decidí que cada test haga esto:

  1. initDriver()
  2. get(baseUrl)
  3. ejecutar acciones + validaciones
  4. quitDriver()

Por qué elegí esto (y por qué está bien)

Porque me garantiza aislamiento total.
Cada test empieza desde cero, sin depender de lo que haya hecho el test anterior.

Eso trae ventajas concretas:

  • Tests más confiables: si uno deja algo “raro” (sesión, cookies, estado del UI), no ensucia al siguiente.
  • Menos debugging raro: cuando un test falla, sabés que falla por lo que hace ese test, no por residuos de otro.
  • Más parecido a CI: en pipelines suele ejecutarse con entornos limpios o lo más limpios posible.
  • Más fácil de paralelizar en el futuro: si cada test es independiente, después TestNG puede correrlos en paralelo.

📌 En resumen: prefiero pagar el costo de abrir Chrome varias veces a cambio de estabilidad y repetibilidad.


La alternativa: abrir una vez y correr todo “en serie” (una sola sesión)

También probé el otro enfoque:

  • abrir el browser una sola vez
  • ejecutar todos los tests
  • cerrar al final

Eso puede servir si:

  • estás explorando rápido
  • querés velocidad en una demo
  • estás haciendo un “happy path” largo como si fuera un flujo e2e

Pero tiene un costo:

  • tests acoplados: el test 2 depende de que el test 1 deje todo bien
  • estado compartido (cookies, UI, sesión, datos), que genera flakiness
  • si algo se rompe a mitad de camino, perdés la corrida completa

👉 Para un suite real, esto termina siendo “un test gigante” disfrazado de muchos tests.


Entonces… ¿qué es “mejor”?

Depende del objetivo, pero como regla general:

Para suites de regresión y automatización mantenible:
cada test independiente, browser limpio
.

Para un flujo e2e puntual (tipo smoke/happy path):
puede tener sentido mantener una sola sesión, pero incluso ahí muchos equipos prefieren independencia.


Conclusión

Lo importante es esto:

  • La Page no abre ni cierra nada.
  • La Page solo interactúa con UI.
  • El Test controla el flujo (setup → acciones → checks → teardown).

Eso mantiene responsabilidades separadas y hace que el POM sea reutilizable.


El runner: ejecución en serie y control total

Clase LoginTestRunner ejecutando tests de login en orden desde el método main.
Runner manual: control total del flujo antes de introducir un framework de testing.

Este runner:

  • ejecuta en orden
  • permite ver todo el log
  • no corta al primer error

Diagrama: “ruta del código” al debuggear (IntelliJ)

Esto es lo que seguirías con Step Into / Step Over:

LoginTestRunner.main()
  |
  |-- new LoginPositiveTests()
  |     |
  |     |-- loginValido_deberiaIngresar()
  |           |
  |           |-- DriverManager.initDriver()
  |           |-- baseUrl = ConfigReader.getProperty("base.url")
  |           |-- DriverManager.getDriver().get(baseUrl)
  |           |
  |           |-- new LoginPage()
  |           |     |
  |           |     |-- loginComo("admin","serenity")
  |           |           |
  |           |           |-- enterUsername()
  |           |           |-- enterPassword()
  |           |           |-- clickLogin()
  |           |
  |           |-- new DashboardPage()
  |           |     |
  |           |     |-- estaVisible()
  |           |     |-- obtenerTitulo()
  |           |
  |           |-- check.visible(...)
  |           |-- check.equals(...)
  |           |
  |           |-- DriverManager.quitDriver()
  |
  |-- new LoginNegativeTests()
        |
        |-- loginInvalido_passwordIncorrecta_muestraError()
              |
              |-- initDriver() -> get(baseUrl)
              |-- LoginPage.loginComo("admin","mal")
              |-- check.equals(LoginPage.obtenerMensajeError(), expected)
              |-- DashboardPage.estaVisibleSafe()
              |-- check.isFalse(...)
              |-- quitDriver()
              etc.

Correr los tests con POM

Al ejecutar LoginTestRunner el log es:

Salida de consola al ejecutar los tests Selenium mostrando logs, advertencias y resultados de validaciones.
El log cuenta la historia completa de la ejecución, incluso cuando hay fallos.

Aquí se vé:


1️⃣ Ejecución en serie, controlada por el runner

Cada bloque de logs corresponde a un test independiente, pero ejecutado en orden:

Login válido → dashboard visible
Login inválido → mensaje de error
Login inválido → dashboard NO aparece

No hay magia:

  • el LoginTestRunner decide el orden
  • cada test abre y cierra su propio driver
  • el flujo es 100% predecible

📌 Esto es clave cuando todavía no usás TestNG/JUnit:
sabés exactamente qué se ejecuta y cuándo.


2️⃣ El driver se crea y destruye correctamente

Se repite este patrón varias veces:

Using chromedriver 144...
Exporting webdriver...

Eso indica que:

  • cada test arranca con un navegador limpio
  • no hay estado compartido entre tests
  • si un test falla, no contamina al siguiente

💡 Esto es buena práctica incluso cuando después migre a TestNG.


3️⃣ Warnings en rojo ≠ tests fallando

En la consola aparecen mensajes en rojo como:

WARNING: Unable to find CDP implementation matching 144

Esto NO es un fallo del test.

Qué significa:

  • Selenium intenta engancharse al Chrome DevTools Protocol
  • no encuentra una versión exacta
  • muestra el warning
  • continúa normalmente

Prueba de eso:

  • los tests siguen
  • los checks dan OK
  • el proceso termina con exit code 0

📌 Importante para lectores junior:
no todo lo rojo en consola es un error funcional.


4️⃣ Validaciones legibles (gracias a check.java)

Las líneas importantes son estas:

✔ Dashboard visible luego de login válido -> visible
✔ Título del dashboard -> OK
✔ Mensaje de error -> OK
✔ El dashboard NO debería aparecer -> OK

Esto muestra algo clave del enfoque:

  • los tests no se cortan al primer error
  • cada validación deja una evidencia clara
  • el log se puede leer como una historia

Muy parecido a:

  • TestComplete logs
  • Allure steps
  • o un reporte manual bien hecho

5️⃣ Separación clara entre infraestructura y negocio

En el log se distinguen capas:

  • infraestructura
    (WebDriverManager, Chrome, CDP warnings)
  • lógica de negocio del test
    (Dashboard visible, Mensaje de error)

Eso es consecuencia directa del POM:

  • la Page se encarga del “cómo”
  • el Test expresa el “qué debería pasar”

6️⃣ El proceso termina limpio

El final:

Process finished with exit code 0

Significa:

  • no hubo excepciones no controladas
  • el runner terminó su flujo
  • el sistema quedó estable

📌 Esto es importante si mañana:

  • corrés esto en CI
  • lo integrás con reportes
  • lo ejecutás en headless: Ejecutar en headless (sin cabeza) en testing significa automatizar pruebas web utilizando un navegador (como Chrome o Firefox) sin renderizar su interfaz gráfica de usuario (GUI). Las pruebas corren en segundo plano, lo que las hace más rápidas y eficientes al no consumir recursos gráficos, ideales para entornos de integración continua. 
Esta salida de ejecución confirma que el Page Object Model no es solo una cuestión de ordenar clases.
Es una forma de construir un sistema de pruebas predecible, legible y controlable.

Cada test arranca desde cero, ejecuta su flujo, valida lo esperado y deja evidencia clara en el log.
Incluso ante warnings de Selenium, el framework se mantiene estable y continúa la ejecución.

A partir de esta base, el siguiente paso natural será integrar un runner formal (TestNG) y reportes (Allure), pero la arquitectura ya está preparada para eso.


Qué NO es POM (y errores comunes)

❌ Poner asserts dentro de la Page
❌ Usar driver.findElement en los tests
❌ Mezclar lógica de negocio con selectores
❌ Validar textos “hardcodeados” en múltiples tests
❌ Pages gigantes con lógica condicional compleja


Qué sigue después de esto

Este POM ya es válido y sano.

Los próximos pasos naturales son:

  1. Reemplazar el runner por TestNG
  2. Mover initDriver / quitDriver a @BeforeMethod / @AfterMethod
  3. Reemplazar check.java por asserts + listeners
  4. Agregar reportes (Allure)
  5. Screenshots automáticos en fallo
  6. Agregar más tests y validaciones, talvez agregar otra page para realizar más pruebas.

Pero conceptualmente, el POM ya está hecho.


🔗 Todo el código de esta serie está en: github.com/cesarbeassuarez/qa-automation-lab
📂 selenium-java

Temas conectados:

Sesión 5 de este lab y Sesión 6 de este lab de Selenium con java.