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:
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:
Es un patrón de diseño
Es una clase proporcionada por Selenium WebDriver para la implementación del POM
Para encontrar elementos web, se utiliza la clase By
Para encontrar elementos web, se utiliza la anotación @FindBy
Cada objeto de clase de página debe inicializarse individualmente
Todos los elementos de la página se inicializan mediante el método estático initElements()
No proporciona inicialización retardada
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.