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 PlusIntroducción a PageFactory
PageFactory es una extensión del patrón Page Object Model que forma parte del conjunto de utilidades de soporte de Selenium WebDriver. Esta clase proporciona una metodología declarativa para inicializar elementos web mediante anotaciones, simplificando significativamente la creación y mantenimiento de Page Objects.
La principal ventaja de PageFactory radica en su capacidad para automatizar la inicialización de elementos web. En lugar de escribir manualmente cada driver.findElement()
en el constructor de nuestras clases Page Object, PageFactory utiliza reflexión para detectar campos anotados y crear automáticamente los WebElement
correspondientes.
Diferencias con Page Object tradicional
Mientras que en un Page Object tradicional definimos elementos de esta manera:
public class LoginPageTraditional {
private WebDriver driver;
private WebElement usernameInput;
private WebElement passwordInput;
private WebElement loginButton;
public LoginPageTraditional(WebDriver driver) {
this.driver = driver;
this.usernameInput = driver.findElement(By.id("username"));
this.passwordInput = driver.findElement(By.id("password"));
this.loginButton = driver.findElement(By.className("login-btn"));
}
}
Con PageFactory, el mismo resultado se consigue de forma más elegante:
public class LoginPage {
private WebDriver driver;
@FindBy(id = "username")
private WebElement usernameInput;
@FindBy(id = "password")
private WebElement passwordInput;
@FindBy(className = "login-btn")
private WebElement loginButton;
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
}
Inicialización con PageFactory.initElements()
El método PageFactory.initElements()
es el núcleo de esta funcionalidad. Este método estático recibe dos parámetros: la instancia de WebDriver
y el objeto que contiene los campos anotados (típicamente this
cuando se llama desde el constructor).
public class ProductPage {
private WebDriver driver;
@FindBy(css = ".product-title")
private WebElement productTitle;
@FindBy(xpath = "//button[text()='Add to Cart']")
private WebElement addToCartButton;
public ProductPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
public String getProductTitle() {
return productTitle.getText();
}
public void addToCart() {
addToCartButton.click();
}
}
Ventajas del enfoque declarativo
La aproximación declarativa de PageFactory ofrece múltiples beneficios. El código resulta más limpio y expresivo, ya que la intención de cada elemento queda clara desde su declaración. La separación entre la definición de elementos y la lógica de negocio mejora la legibilidad del código.
Además, PageFactory implementa un patrón proxy que retrasa la búsqueda de elementos hasta que realmente se necesitan. Esto significa que los elementos no se buscan en el DOM durante la inicialización, sino cuando se invoca algún método sobre ellos.
@Test
void testProductInteraction() {
var productPage = new ProductPage(driver);
// En este punto, los elementos aún no se han buscado en el DOM
var title = productPage.getProductTitle();
// Ahora sí se ejecuta la búsqueda del elemento productTitle
productPage.addToCart();
// Y aquí se busca addToCartButton
}
Integración con JUnit 5
En el contexto de pruebas automatizadas con JUnit 5, PageFactory se integra naturalmente en nuestros métodos de prueba:
class ProductTestSuite {
private WebDriver driver;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
driver.get("https://example-shop.com/products/1");
}
@Test
void shouldDisplayCorrectProductInformation() {
var productPage = new ProductPage(driver);
assertThat(productPage.getProductTitle())
.isNotEmpty()
.contains("Premium");
}
@AfterEach
void tearDown() {
driver.quit();
}
}
Esta aproximación mantiene nuestras pruebas enfocadas en la lógica de verificación, mientras que PageFactory se encarga transparentemente de la gestión de elementos web, proporcionando una base sólida para construir suites de pruebas mantenibles y escalables.
Anotaciones @FindBy y @CacheLookup
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 anotación @FindBy
La anotación @FindBy constituye el elemento central de PageFactory, permitiendo definir estrategias de localización de elementos web de forma declarativa. Esta anotación acepta los mismos localizadores que utilizaríamos con By
en Selenium WebDriver tradicional, pero de manera más expresiva y mantenible.
Localizadores básicos con @FindBy:
public class SearchPage {
@FindBy(id = "search-input")
private WebElement searchField;
@FindBy(name = "category")
private WebElement categoryDropdown;
@FindBy(className = "search-button")
private WebElement searchButton;
@FindBy(tagName = "h1")
private WebElement pageTitle;
}
Localizadores avanzados con CSS y XPath
Los selectores CSS y XPath ofrecen mayor flexibilidad para elementos complejos:
public class NavigationPage {
@FindBy(css = ".navbar-nav .dropdown-toggle")
private WebElement userMenu;
@FindBy(css = "ul.menu-items li:nth-child(3)")
private WebElement thirdMenuItem;
@FindBy(xpath = "//button[contains(@class, 'primary') and text()='Submit']")
private WebElement submitButton;
@FindBy(xpath = "//div[@data-testid='notification']//span[@class='message']")
private WebElement notificationMessage;
}
Localización de listas de elementos
PageFactory también soporta la inicialización de listas de elementos mediante List<WebElement>
:
public class ProductListPage {
@FindBy(css = ".product-card")
private List<WebElement> productCards;
@FindBy(xpath = "//div[@class='filter-option']//input[@type='checkbox']")
private List<WebElement> filterCheckboxes;
public int getProductCount() {
return productCards.size();
}
public List<String> getProductTitles() {
return productCards.stream()
.map(card -> card.findElement(By.className("product-title")))
.map(WebElement::getText)
.toList();
}
}
Combinando múltiples estrategias
En ocasiones necesitamos estrategias alternativas de localización. @FindBy permite definir múltiples localizadores:
public class FlexiblePage {
@FindBy(id = "submit-btn")
@FindBy(css = "button[type='submit']")
@FindBy(xpath = "//input[@value='Submit']")
private WebElement submitElement;
}
PageFactory intentará cada estrategia en orden hasta encontrar el elemento, proporcionando mayor robustez ante cambios en el DOM.
La anotación @CacheLookup
La anotación @CacheLookup es una optimización de rendimiento que instruye a PageFactory para almacenar en caché la referencia del elemento web después de la primera búsqueda. Esto resulta especialmente útil para elementos que no cambian durante la sesión del navegador.
public class HeaderPage {
@FindBy(css = ".site-logo")
@CacheLookup
private WebElement siteLogo;
@FindBy(id = "main-navigation")
@CacheLookup
private WebElement mainNavigation;
// Elemento dinámico - NO usar @CacheLookup
@FindBy(css = ".user-notifications .badge")
private WebElement notificationCount;
}
Cuándo usar @CacheLookup
El uso de @CacheLookup debe aplicarse cuidadosamente. Es apropiado para elementos estáticos como:
- Elementos de navegación que permanecen constantes
- Logos y elementos de branding
- Menús principales que no cambian
- Elementos estructurales del layout
public class StaticElementsPage {
@FindBy(id = "footer-copyright")
@CacheLookup
private WebElement copyrightText;
@FindBy(css = ".breadcrumb-container")
@CacheLookup
private WebElement breadcrumbContainer;
public String getCopyrightYear() {
return copyrightText.getText()
.replaceAll(".*?(\\d{4}).*", "$1");
}
}
Elementos que NO deben usar @CacheLookup
Evita @CacheLookup en elementos que pueden cambiar su estado o contenido:
public class DynamicContentPage {
// ❌ NO usar @CacheLookup - el contenido cambia
@FindBy(css = ".live-score")
private WebElement liveScore;
// ❌ NO usar @CacheLookup - puede aparecer/desaparecer
@FindBy(css = ".error-message")
private WebElement errorMessage;
// ❌ NO usar @CacheLookup - contenido dinámico
@FindBy(css = ".shopping-cart-count")
private WebElement cartItemCount;
}
Implementación práctica en pruebas
En el contexto de pruebas JUnit 5, la combinación de estas anotaciones proporciona un equilibrio entre rendimiento y flexibilidad:
class EcommercePageTest {
private WebDriver driver;
private ProductCatalogPage catalogPage;
@BeforeEach
void setUp() {
driver = new ChromeDriver();
catalogPage = new ProductCatalogPage(driver);
driver.get("https://example-store.com/catalog");
}
@Test
void shouldFilterProductsByCategory() {
catalogPage.selectCategory("Electronics");
catalogPage.applyFilters();
var products = catalogPage.getVisibleProducts();
assertThat(products)
.hasSizeGreaterThan(0)
.allMatch(product -> product.contains("Electronics"));
}
}
La página correspondiente aprovecha ambas anotaciones estratégicamente:
public class ProductCatalogPage {
private WebDriver driver;
@FindBy(css = ".category-sidebar")
@CacheLookup
private WebElement categorySidebar;
@FindBy(css = ".product-grid .product-item")
private List<WebElement> productItems;
@FindBy(id = "apply-filters")
private WebElement applyFiltersButton;
public ProductCatalogPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
public void selectCategory(String categoryName) {
var categoryOption = categorySidebar.findElement(
By.xpath(String.format(".//input[@value='%s']", categoryName))
);
categoryOption.click();
}
public List<String> getVisibleProducts() {
return productItems.stream()
.filter(WebElement::isDisplayed)
.map(item -> item.findElement(By.className("product-name")))
.map(WebElement::getText)
.toList();
}
}
Esta aproximación optimiza el rendimiento mediante el cacheo de elementos estáticos como la barra lateral de categorías, mientras mantiene la flexibilidad necesaria para elementos dinámicos como la lista de productos que cambia según los filtros aplicados.
Aprendizajes de esta lección de Selenium
- Comprender el patrón PageFactory y su diferencia con el Page Object tradicional.
- Aprender a utilizar la anotación @FindBy para localizar elementos web de forma declarativa.
- Conocer el funcionamiento del método PageFactory.initElements() y el patrón proxy para la búsqueda diferida de elementos.
- Identificar cuándo y cómo usar la anotación @CacheLookup para optimizar el rendimiento.
- Integrar PageFactory con JUnit 5 para construir pruebas automatizadas mantenibles y escalables.
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