Page Factory

Intermedio
Selenium
Selenium
Actualizado: 05/09/2025

¡Desbloquea el curso de Selenium completo!

IA
Ejercicios
Certificado
Entrar

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 Plus

Introducció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.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

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

⭐⭐⭐⭐⭐
4.9/5 valoración