Mira la lección en vídeo
Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.
Desbloquear Plan PlusQué es Page Object Model
El Page Object Model (POM) es un patrón de diseño fundamental en la automatización de pruebas web que resuelve uno de los principales desafíos al escribir tests con Selenium: el mantenimiento y la organización del código de pruebas.
Imagina que tienes docenas de tests que interactúan con la misma página web. Si escribes estos tests de forma lineal, cada uno contendrá los mismos localizadores (By.id("email")
, By.className("submit-button")
) y las mismas secuencias de acciones. Cuando la interfaz cambie, tendrás que actualizar cada test individualmente, lo que se convierte en una pesadilla de mantenimiento.
Principios fundamentales del patrón
Page Object Model se basa en tres principios esenciales que transforman la forma en que estructuramos nuestros tests de Selenium:
Encapsulación de localizadores: Todos los selectores y localizadores de una página específica se centralizan en una sola clase. En lugar de dispersar By.id("username")
por múltiples tests, este localizador vive únicamente en la clase que representa esa página.
Métodos que representan acciones de usuario: Cada interacción posible con la página se modela como un método público. Por ejemplo, en lugar de escribir driver.findElement(By.id("email")).sendKeys("user@example.com")
en cada test, tendrás un método login(String email, String password)
que encapsula esta lógica.
Separación entre lógica de test y lógica de página: Los tests se enfocan en qué verificar (las aserciones y el flujo de negocio), mientras que los Page Objects se encargan del cómo interactuar con la interfaz.
El problema que resuelve
Consideremos un ejemplo práctico. Sin Page Object Model, un test de login típico se vería así:
@Test
void shouldLoginSuccessfully() {
driver.get("https://example.com/login");
driver.findElement(By.id("email")).sendKeys("user@example.com");
driver.findElement(By.id("password")).sendKeys("password123");
driver.findElement(By.className("login-button")).click();
WebElement welcome = driver.findElement(By.id("welcome-message"));
assertEquals("Bienvenido", welcome.getText());
}
Este enfoque genera varios problemas críticos:
- Duplicación masiva: Cada test que interactúe con el login repetirá los mismos localizadores
- Fragilidad extrema: Un cambio en el
id
del botón de login rompe todos los tests que lo usen - Dificultad para leer: La lógica de negocio se mezcla con detalles técnicos de la interfaz
- Mantenimiento costoso: Actualizar localizadores requiere tocar múltiples archivos
Ventajas del patrón Page Object Model
La implementación de Page Object Model transforma estos tests problemáticos en código mantenible y expresivo. Las ventajas principales incluyen:
Mantenimiento centralizado: Cuando un localizador cambia, solo necesitas actualizarlo en un lugar: la clase Page Object correspondiente. Esto reduce drásticamente el tiempo invertido en mantenimiento.
Reutilización de código: Los métodos de interacción se pueden usar en múltiples tests, eliminando la duplicación y promoviendo un desarrollo más eficiente.
Legibilidad mejorada: Los tests se vuelven más expresivos al usar métodos con nombres descriptivos como loginPage.loginWith(email, password)
en lugar de múltiples llamadas a findElement()
.
Abstracción de la interfaz: Los tests se vuelven menos dependientes de los detalles de implementación de la UI, lo que los hace más resistentes a cambios menores en la interfaz.
El mismo test de login, aplicando Page Object Model, se transformaría en algo mucho más limpio y mantenible:
@Test
void shouldLoginSuccessfully() {
LoginPage loginPage = new LoginPage(driver);
DashboardPage dashboard = loginPage.loginWith("user@example.com", "password123");
assertEquals("Bienvenido", dashboard.getWelcomeMessage());
}
Esta transformación no solo mejora la legibilidad, sino que establece una base sólida para el crecimiento y mantenimiento a largo plazo de tu suite de pruebas automatizadas.
Implementación de Page Objects
Guarda tu progreso
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
La implementación práctica de Page Objects requiere seguir una estructura bien definida que maximice la reutilización y minimice el acoplamiento. Una clase Page Object efectiva encapsula tanto los elementos de la página como las interacciones que los usuarios pueden realizar con ellos.
Estructura básica de un Page Object
Toda clase Page Object sigue un patrón estructural consistente que incluye tres componentes esenciales: la gestión del WebDriver, la definición de localizadores privados y los métodos públicos de interacción.
public class LoginPage {
private final WebDriver driver;
// Localizadores privados
private final By emailField = By.id("email");
private final By passwordField = By.id("password");
private final By loginButton = By.className("login-button");
private final By errorMessage = By.id("error-message");
public LoginPage(WebDriver driver) {
this.driver = driver;
}
// Métodos públicos para interacciones
public LoginPage enterEmail(String email) {
driver.findElement(emailField).sendKeys(email);
return this;
}
public LoginPage enterPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
return this;
}
public DashboardPage clickLogin() {
driver.findElement(loginButton).click();
return new DashboardPage(driver);
}
}
El constructor recibe siempre una instancia de WebDriver que se almacena como campo final, garantizando que cada Page Object tenga acceso controlado al navegador. Los localizadores se definen como constantes privadas, centralizando toda la lógica de localización en un lugar específico.
Definición de localizadores y elementos
Los localizadores deben declararse como campos privados y finales, utilizando la clase By
de Selenium para definir las estrategias de localización. Esta aproximación ofrece mejor rendimiento que almacenar WebElements directamente, ya que evita problemas de elementos obsoletos.
private final By navigationMenu = By.cssSelector("nav.main-menu");
private final By searchInput = By.xpath("//input[@placeholder='Buscar productos...']");
private final By shoppingCart = By.id("shopping-cart");
private final By userProfile = By.cssSelector("[data-testid='user-profile']");
Cuando necesites trabajar con listas de elementos, utiliza localizadores que permitan encontrar múltiples elementos:
private final By productItems = By.className("product-item");
private final By categoryFilters = By.cssSelector(".filter-category input[type='checkbox']");
public List<String> getProductNames() {
return driver.findElements(productItems)
.stream()
.map(WebElement::getText)
.toList();
}
Métodos de interacción y navegación
Los métodos públicos representan las acciones que un usuario real puede realizar en la página. Cada método debe tener un nombre descriptivo que exprese claramente la intención de la acción, no los detalles técnicos de su implementación.
public class ProductPage {
private final WebDriver driver;
private final By addToCartButton = By.id("add-to-cart");
private final By quantitySelector = By.name("quantity");
private final By priceLabel = By.className("price");
public ProductPage(WebDriver driver) {
this.driver = driver;
}
public ProductPage selectQuantity(int quantity) {
var select = new Select(driver.findElement(quantitySelector));
select.selectByValue(String.valueOf(quantity));
return this;
}
public CartPage addToCart() {
driver.findElement(addToCartButton).click();
return new CartPage(driver);
}
public String getPrice() {
return driver.findElement(priceLabel).getText();
}
}
Los métodos que modifican el estado de la página actual deben retornar this
para permitir el encadenamiento fluido de operaciones. Los métodos que navegan a otras páginas deben retornar una nueva instancia del Page Object correspondiente.
Patrón de retorno y flujo de navegación
El flujo de navegación entre pages se modela mediante el tipo de retorno de los métodos. Esta aproximación hace que los tests sean más expresivos y ayuda a detectar errores de navegación en tiempo de compilación.
public class HomePage {
private final WebDriver driver;
private final By loginLink = By.linkText("Iniciar sesión");
private final By registerLink = By.linkText("Registrarse");
private final By searchButton = By.id("search-btn");
public HomePage(WebDriver driver) {
this.driver = driver;
}
public LoginPage goToLogin() {
driver.findElement(loginLink).click();
return new LoginPage(driver);
}
public RegisterPage goToRegister() {
driver.findElement(registerLink).click();
return new RegisterPage(driver);
}
public SearchResultsPage search(String query) {
driver.findElement(By.id("search-input")).sendKeys(query);
driver.findElement(searchButton).click();
return new SearchResultsPage(driver);
}
}
Esta aproximación permite escribir tests fluidos que expresan claramente el flujo de navegación:
@Test
void shouldCompleteUserRegistration() {
HomePage homePage = new HomePage(driver);
RegisterPage registerPage = homePage.goToRegister();
ConfirmationPage confirmation = registerPage
.enterUsername("newuser")
.enterEmail("newuser@example.com")
.enterPassword("securePassword123")
.submitRegistration();
assertTrue(confirmation.isRegistrationSuccessful());
}
Buenas prácticas de implementación
Los Page Objects deben evitar contener aserciones directas. Su responsabilidad es proporcionar acceso a los datos y permitir interacciones, mientras que las aserciones pertenecen a los tests. En lugar de métodos como assertWelcomeMessageIsVisible()
, implementa getWelcomeMessage()
y deja que el test haga la aserción.
// ❌ Incorrecto: Page Object con aserción
public void verifyLoginSuccess() {
WebElement message = driver.findElement(successMessage);
assertEquals("Login exitoso", message.getText());
}
// ✅ Correcto: Page Object retorna datos
public String getSuccessMessage() {
return driver.findElement(successMessage).getText();
}
// El test hace la aserción
@Test
void shouldShowSuccessMessage() {
LoginPage loginPage = new LoginPage(driver);
DashboardPage dashboard = loginPage.loginWith("user@test.com", "password");
assertEquals("Login exitoso", dashboard.getSuccessMessage());
}
Los nombres de métodos deben reflejar acciones del usuario, no implementaciones técnicas. Utiliza verbos como click
, enter
, select
, navigate
seguidos de nombres descriptivos del elemento o acción.
// ✅ Nombres descriptivos y orientados al usuario
public ProductPage addProductToWishlist() { /* ... */ }
public CheckoutPage proceedToCheckout() { /* ... */ }
public ProfilePage updatePersonalInformation(String name, String email) { /* ... */ }
// ❌ Nombres técnicos y poco descriptivos
public void clickElement() { /* ... */ }
public void sendKeysToField(String text) { /* ... */ }
public void executeAction() { /* ... */ }
Organización del proyecto con Page Objects
La estructura de carpetas debe reflejar la organización lógica de tu aplicación web. Una estructura típica separa los Page Objects en paquetes que corresponden a las áreas funcionales de la aplicación:
src/test/java/
├── pages/
│ ├── auth/
│ │ ├── LoginPage.java
│ │ ├── RegisterPage.java
│ │ └── ForgotPasswordPage.java
│ ├── shopping/
│ │ ├── ProductListPage.java
│ │ ├── ProductDetailPage.java
│ │ └── CartPage.java
│ └── user/
│ ├── ProfilePage.java
│ └── SettingsPage.java
└── tests/
├── AuthenticationTests.java
├── ShoppingTests.java
└── UserManagementTests.java
Cada Page Object debe representar una página completa o una sección significativa de la interfaz. Evita crear Page Objects demasiado granulares para componentes pequeños, pero tampoco los hagas tan amplios que se vuelvan difíciles de mantener.
Esta estructura facilita la navegación del código y hace que sea intuitivo encontrar el Page Object correspondiente a cualquier parte de la aplicación que necesites automatizar.
Aprendizajes de esta lección de Selenium
- Comprender qué es el patrón Page Object Model y su importancia en la automatización de pruebas.
- Identificar los principios fundamentales del patrón para estructurar tests más mantenibles.
- Aprender a implementar clases Page Object con localizadores y métodos de interacción.
- Conocer buenas prácticas para la organización del código y evitar errores comunes.
- Entender cómo modelar el flujo de navegación entre páginas usando métodos con retornos adecuados.
Completa este curso de Selenium y certifícate
Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs