Page Object Model (POM) vs Page Factory en Automatización de Selenium

Cuando se construye un marco de pruebas de automatización, uno de los aspectos cruciales es determinar cómo diseñar las pruebas de forma que permitan distribuir la lógica de ejecución independientemente de la implementación.

A medida que los proyectos crecen rápidamente con nuevas funcionalidades e implican cambios en las características existentes, los ingenieros de automatización de QA se enfrentan a las siguientes tareas:

  • Creación de nuevas pruebas de alta calidad
  • Apoyo y refactorización de pruebas existentes con cambios mínimos en el código y la estructura

Para llevar a cabo estas tareas, utilizamos el Page Object Model (POM).

En este artículo, hablaremos sobre la importancia del patrón Page Object Model en la automatización de Selenium. El artículo explora varios métodos de implementación del patrón, incluyendo el enfoque estándar y la utilización de la clase Page Factory. Profundiza en las principales ventajas de POM y proporciona un análisis comparativo entre POM y Page Factory.

¿Qué es el Page Object Model (POM)?

Page Object Model (POM) es un patrón de diseño que ha ganado popularidad en las pruebas de automatización para mejorar el mantenimiento de las pruebas y reducir la duplicación de código. Las principales especificaciones de este patrón son:

  • Creación de una clase independiente en el proyecto que corresponda a la página web adecuada de la aplicación.
  • La clase de página creada incluye una declaración de los elementos web correspondientes ubicados en esta página, junto con una lista de métodos que interactúan con estos elementos

Como posibilidades adicionales útiles para mejorar el patrón, se pueden definir las siguientes:

  • Creación de una clase padre que incluya todo el contenido común para las clases de páginas. Es una buena práctica definir dicha clase como abstracta para evitar la posibilidad de crear objetos de páginas web inexistentes en la aplicación
  • Preparación de una clase separada para acciones con elementos – como introducir datos, hacer clic en elementos y desplazarse a elementos

Para comprender mejor la eficacia del enfoque POM, veamos un ejemplo de una prueba automatizada típica sin utilizar el Page Object Model.

He aquí un fragmento de código sin POM (prueba de inicio de sesión simple):

public class LoginWithOutPOMTest {
   WebDriver webDriver;

   @Before
   public void setUp() {
       try{
           WebDriverManager.chromedriver().setup();
           webDriver = new ChromeDriver();

           webDriver.manage().window().maximize();
           webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20));

       }catch (Exception e){
           Assert.fail("Can not create driver session");
       }
   }

   @Test
   public void validLogin() {
       webDriver.get("https://www.saucedemo.com");

       webDriver.findElement(By.id("user-name")).clear();
       webDriver.findElement(By.id("user-name")).sendKeys("standard_user");

       webDriver.findElement(By.id("password")).clear();
       webDriver.findElement(By.id("password")).sendKeys("secret_sauce");

       webDriver.findElement(By.id("login-button")).click();

       String text = webDriver.findElement(
               By.cssSelector("div.header_secondary_container span.title")).getText();
       Assert.assertTrue("Login was not successful", text.contains("Products"));
   }

   @After
   public void tearDown() {
       webDriver.quit();
   }

}

El planteamiento anterior tiene varios inconvenientes, entre ellos:

  • El controlador web se ha inicializado directamente en la clase de prueba
  • Falta de una separación limpia entre el código de prueba y el código específico de la página (localizadores)
  • Con pruebas más complejas, el número de elementos y acciones necesarios aumentará significativamente, lo que provocará complicaciones en la legibilidad del código
  • Si los elementos web se utilizan en varias pruebas, deben declararse por separado para cada una de ellas.
  • Si se modifican los localizadores de elementos, las actualizaciones deben aplicarse a todos los lugares del proyecto en los que se utilicen dichos elementos.

Implementación del Page Object Model

Ahora, apliquemos el patrón POM a la prueba descrita anteriormente y observemos la diferencia en la implementación. La implementación del POM incluirá los siguientes pasos:

  • Creación de clases separadas que coincidan con las páginas web de la aplicación (en nuestro caso habrá 2 clases – LoginPage y ProductsPage)
  • Declaración de elementos web en ambas clases de páginas usando By (clase abstracta en Selenium)
  • Preparación de métodos para la interacción con los elementos en cada clase
  • Creación de la clase padre para los tests

Eche un vistazo a Estructura de proyecto de muestra con POM:

Page Object Model (POM) vs Page Factory en Automatización de Selenium

La clase LoginPage define los elementos web presentes en la página de inicio de sesión, los métodos para interactuar con ellos, el objeto webdriver y un constructor.

public class LoginPage {

   WebDriver webDriver;

   /**
    * create page constructor
    */
   public LoginPage(WebDriver webDriver) {
       this.webDriver = webDriver;
   }

   /**
    * define web elements for Login page
    */
   By userNameField = By.id("user-name");
   By passWordField = By.id("password");
   By loginBtn = By.id("login-button");


   // method for entering username
   public void enterUserName(String userName) {
       webDriver.findElement(userNameField).clear();
       webDriver.findElement(userNameField).sendKeys(userName);
   }

   // method for entering password
   public void enterPassWord(String passWord) {
       webDriver.findElement(passWordField).clear();
       webDriver.findElement(passWordField).sendKeys(passWord);
   }

   // method for clicking on Login button
   public void clickOnLogin() {
       webDriver.findElement(loginBtn).click();
   }

   // method for opening login page
   public void openLoginPage() {
       try{
           webDriver.get("https://www.saucedemo.com/");
       }catch (Exception e){
           Assert.fail("Impossible to open Login page");
       }
   }

}

La clase ProductsPage define los elementos web presentes en la página Productos, junto con los métodos para interactuar con ellos, el objeto webdriver y el constructor.

public class ProductsPage {

   WebDriver webDriver;

   /**
    * create page constructor
    */
   public ProductsPage(WebDriver webDriver) {
       this.webDriver = webDriver;
   }

   /**
    * define web elements for Products page
    */
   By productsTitle = By.cssSelector("div.header_secondary_container span.title");

   public void checkProductsPageOpened() {
       String title = webDriver.findElement(productsTitle).getText();
       Assert.assertTrue("Products page was not opened", title.contains("Products"));
   }

}

ParentTest es la clase padre de las clases de prueba. Esta clase proporciona las siguientes acciones:

  • Métodos de preparación para la creación de una sesión de controlador (antes del inicio de cada prueba) y su cierre (una vez finalizada la ejecución de la prueba)
  • Creación e inicialización de objetos de clases de páginas
public class ParentTest {

   WebDriver webDriver;
   protected LoginPage loginPage;
   protected ProductsPage productsPage;

   @Before
   public void setUp() {
       try{
           WebDriverManager.chromedriver().setup();
           webDriver = new ChromeDriver();

           webDriver.manage().window().maximize();
           webDriver.manage().timeouts().implicitlyWait(Duration.ofSeconds(20));

           loginPage = new LoginPage(webDriver);
           productsPage = new ProductsPage(webDriver);
       }catch (Exception e){
           Assert.fail("Can not create driver session");
       }
   }

   @After
   public void tearDown() {
       webDriver.quit();
   }

}

La Clase de Prueba describe sólo la lógica de ejecución de la prueba (sin inicialización de controladores, búsqueda de elementos web y acciones con ellos).

public class LoginWithPOMTest extends ParentTest {

   String username = "standard_user";
   String password = "secret_sauce";

   @Test
   public void validLogin() {
       loginPage.openLoginPage();
       loginPage.enterUserName(username);
       loginPage.enterPassWord(password);
       loginPage.clickOnLogin();

       productsPage.checkProductsPageOpened();
   }

}

Ventajas de utilizar POM

Tras comparar las implementaciones de pruebas con POM y sin POM, podemos identificar las siguientes ventajas de POM:

  • Reutilización del código. Podemos utilizar variables y métodos de la clase page en todas las pruebas sin redefinir los elementos web
  • Fácil mantenimiento y refactorización de las pruebas
  • Legibilidad del código. Esto se consigue separando el código de la página del código de las pruebas.

¿Qué es Page Factory?

Otra variante para la implementación del Page Object Model es utilizar la clase Page Factory proporcionada por Selenium WebDriver. Las principales especificaciones de la Page Factory son:

  • Uso de la anotación @FindBy para localizar y declarar elementos (con diferentes estrategias de localización)
  • Uso del método estático initElements() para inicializar elementos de la página web actual que fueron declarados con la anotación @FindBy
  • Soportar el concepto de inicialización perezosa (usando la clase AjaxElementLocatorFactory) para identificar elementos web sólo cuando son usados en cualquier operación y acción

Implementación de la Page Factory

Como base, tomaremos el proyecto creado anteriormente y lo actualizaremos utilizando Page Factory. La estructura del proyecto, las clases Test y Parent permanecerán sin modificar. Los cambios sólo se harán en las clases Page.

Los cambios realizados son los siguientes:

  • Elementos web declarados utilizando la anotación @FindBy y la interfaz WebElement
  • Inicialización de elementos web en el constructor de la clase utilizando el método initElements() de la clase PageFactory
  • Actualizados los métodos que interactúan con los elementos

Para LoginPage:

public class LoginPage {

   WebDriver webDriver;

   public LoginPage(WebDriver webDriver) {
       this.webDriver = webDriver;
       PageFactory.initElements(webDriver, this);
   }

   /**
    * define web elements for Login page using @FindBy annotation
    */
   @FindBy(id = "user-name")
   WebElement userNameField;

   @FindBy(id = "password")
   WebElement passWordField;

   @FindBy(id = "login-button")
   WebElement loginBtn;


   // method for entering username
   public void enterUserName(String userName) {
       userNameField.clear();
       userNameField.sendKeys(userName);
   }


   // method for entering password
   public void enterPassWord(String passWord) {
       passWordField.clear();
       passWordField.sendKeys(passWord);
   }

   // method for clicking on Login button
   public void clickOnLogin() {
       loginBtn.click();
   }

   // method for opening login page
   public void openLoginPage() {
       try {
           webDriver.get("https://www.saucedemo.com/");
       } catch (Exception e) {
           Assert.fail("Impossible to open Login page");
       }
   }



For ProductsPage:

public class ProductsPage {

   WebDriver webDriver;

   /**
    * create page constructor
    */
   public ProductsPage(WebDriver webDriver) {
       this.webDriver = webDriver;
       PageFactory.initElements(webDriver, this);
   }

   /**
    * define web elements for Products page using @FindBy annotation
    */
   @FindBy(css = "div.header_secondary_container span.title")
   WebElement productsTitle;

   public void checkProductsPageOpened() {
       String title = productsTitle.getText();
       Assert.assertTrue("Products page was not opened", title.contains("Products"));
   }

}

Page Object Model vs Page Factory

Resumamos y definamos las principales diferencias entre POM y Page Factory:

POM
Page Factory
POM

Es un patrón de diseño

Page Factory

Es una clase proporcionada por Selenium WebDriver para la implementación del POM

POM

Para encontrar elementos web, se utiliza la clase By

Page Factory

Para encontrar elementos web, se utiliza la anotación @FindBy

POM

Cada objeto de clase de página debe inicializarse individualmente

Page Factory

Todos los elementos de la página se inicializan mediante el método estático initElements()

POM

No proporciona inicialización retardada

Page Factory

Proporciona inicialización retardada

En conclusión

Antes de establecer un marco para las pruebas de automatización, hay que considerar la forma eficaz de separar la lógica de negocio de las pruebas del código para su implementación. Este enfoque hace que las pruebas sean más legibles, reutilizables y fáciles de mantener.

Para lograr estos objetivos, puede utilizar el Page Object Model (POM). Este patrón puede implementarse de manera estándar o a través de la clase Page Factory.

En mi opinión, la implementación del POM utilizando Page Factory tiene más ventajas en comparación con el POM típico. Algunas de estas ventajas incluyen la posibilidad de inicializar todos los elementos web a la vez, soporte para el concepto lazy load, y una declaración de elementos más precisa usando la anotación @FindBy.