Automatización en Selenium: Page Object Model y Page Factory
Publicado: 2022-03-11Escribir pruebas automatizadas es más que un lujo para cualquier equipo de desarrollo de software ágil. Es una necesidad y es una herramienta esencial para encontrar errores rápidamente durante las primeras fases de los ciclos de desarrollo de software. Cuando hay una nueva característica que todavía está en fase de desarrollo, los desarrolladores pueden ejecutar pruebas automatizadas y ver cómo esos cambios afectan otras partes del sistema. Este artículo explicará cómo puede acelerar las pruebas automatizadas utilizando el modelo de objeto de página en Selenium.
A través de la automatización de pruebas, es posible reducir el costo de la corrección de errores y lograr una mejora general en el proceso de control de calidad (QA) del software. Con las pruebas adecuadas, los desarrolladores tienen la oportunidad de encontrar y resolver errores incluso antes de que llegue al control de calidad. La automatización de pruebas nos ayuda aún más a automatizar los casos de prueba y las características que retroceden constantemente. De esta manera, los QA tienen más tiempo para probar otras partes de la aplicación. Además, esto ayuda a garantizar la calidad del producto en los lanzamientos de producción. Como resultado, obtenemos productos que son efectivamente más estables y un proceso de control de calidad más eficiente.
Aunque escribir pruebas automatizadas puede parecer una tarea fácil para desarrolladores e ingenieros, aún existe la posibilidad de terminar con pruebas mal implementadas y el alto costo del mantenimiento del código en cualquier proceso ágil. Intentar entregar constantemente cambios o funciones en cualquier proyecto de desarrollo ágil puede resultar costoso cuando se trata de pruebas. Cambiar un elemento en una página web en la que se basan 20 pruebas requerirá que uno pase por estas 20 rutinas de prueba y actualice cada una para adaptarse a este cambio recién introducido. Esto no solo puede llevar mucho tiempo, sino que también puede ser un factor desmotivador cuando se trata de implementar pruebas automatizadas desde el principio.
Pero, ¿qué pasaría si pudiéramos hacer el cambio en un solo lugar y hacer que todas las rutinas de prueba relevantes lo usen? En este artículo, veremos las pruebas automatizadas en Selenium y cómo podemos usar los modelos de objetos de página para escribir rutinas de prueba mantenibles y reutilizables.
Modelo de objetos de página en Selenium
El modelo de objetos de página es un patrón de diseño de objetos en Selenium, donde las páginas web se representan como clases y los diversos elementos de la página se definen como variables en la clase. Todas las posibles interacciones del usuario se pueden implementar como métodos en la clase:
clickLoginButton(); setCredentials(user_name,user_password);
Dado que los métodos bien nombrados en las clases son fáciles de leer, esto funciona como una forma elegante de implementar rutinas de prueba que son legibles y más fáciles de mantener o actualizar en el futuro. Por ejemplo:
Para admitir el modelo de objeto de página, utilizamos Page Factory. Page Factory en Selenium es una extensión de Page Object y se puede usar de varias maneras. En este caso usaremos Page Factory para inicializar elementos web que están definidos en clases de página web u objetos de página.
Las clases de página web o los objetos de página que contienen elementos web deben inicializarse mediante Page Factory antes de poder utilizar las variables de elementos web. Esto se puede hacer simplemente mediante el uso de la función initElements en PageFactory:
LoginPage page = new LoginPage(driver); PageFactory.initElements(driver, page);
O, aún más simple:
LoginPage page = PageFactory.intElements(driver,LoginPage.class)
O, dentro del constructor de la clase de la página web:
public LoginPage(WebDriver driver) { this.driver = driver; PageFactory.initElements(driver, this); }
Page Factory inicializará cada variable WebElement con una referencia a un elemento correspondiente en la página web real en función de los "localizadores" configurados. Esto se hace mediante el uso de anotaciones @FindBy . Con esta anotación, podemos definir una estrategia para buscar el elemento, junto con la información necesaria para identificarlo:
@FindBy(how=How.NAME, using="username") private WebElement user_name;
Cada vez que se llama a un método en esta variable WebElement , el controlador primero lo encontrará en la página actual y luego simulará la interacción. En caso de que estemos trabajando con una página simple, sabemos que encontraremos el elemento en la página cada vez que lo busquemos, y también sabemos que eventualmente navegaremos fuera de esta página y no volveremos a ella, podemos almacenar en caché el campo buscado usando otra anotación simple:
@FindBy(how=How.NAME, using="username") @CacheLookup private WebElement user_name;
Esta definición completa de la variable WebElement se puede reemplazar con su forma mucho más concisa:
@FindBy(name="username") private WebElement user_name;
La anotación @FindBy admite un puñado de otras estrategias que facilitan un poco las cosas:
id, name, className, css, tagName, linkText, partialLinkText, xpath
@FindBy() private WebElement user_name; @FindBy(name="passsword") private WebElement user_password; @FindBy(className="h3") private WebElement label; @FindBy(css=”#content”) private WebElement text;
Una vez inicializadas, estas variables de WebElement se pueden usar para interactuar con los elementos correspondientes en la página. El siguiente código, por ejemplo:
user_password.sendKeys(password);
… envía la secuencia dada de pulsaciones de teclas al campo de contraseña en la página, y es equivalente a:
driver.findElement(By.name(“user_password”)).sendKeys(password);
Continuando, a menudo se encontrará con situaciones en las que necesita encontrar una lista de elementos en una página, y ahí es cuando @FindBys resulta útil:
@FindBys(@FindBy(css=”div[class='yt-lockup-tile yt-lockup-video']”))) private List<WebElement> videoElements;
El código anterior encontrará todos los elementos div que tienen dos nombres de clase "yt-lockup-tile" e "yt-lockup-video". Podemos simplificar esto aún más reemplazándolo con lo siguiente:
@FindBy(how=How.CSS,using="div[class='yt-lockup-tile yt-lockup-video']") private List<WebElement> videoElements;
Además, puede usar @FindAll con múltiples anotaciones @FindBy para buscar elementos que coincidan con cualquiera de los localizadores dados:
@FindAll({@FindBy(how=How.ID, using=”username”), @FindBy(className=”username-field”)}) private WebElement user_name;
Ahora que podemos representar páginas web como clases de Java y usar Page Factory para inicializar variables de WebElement fácilmente, es hora de que veamos cómo podemos escribir pruebas simples de Selenium usando el patrón Page Object y Page Factory.
Proyecto simple de automatización de pruebas de Selenium en Java
Para nuestro tutorial de modelo de objeto de página, automaticemos el registro del desarrollador en Toptal. Para hacer eso, necesitamos automatizar los siguientes pasos:
Visita www.toptal.com
Haga clic en el botón "Aplicar como desarrollador"
En la página del portal, primero verifique si está abierto
Haga clic en el botón "Unirse a Toptal"
Rellenar el formulario
Envíe el formulario haciendo clic en el botón "Unirse a Toptal"
Configuración de un proyecto
Descargar e instalar Java JDK
Descargue e instale InteliJ Idea
Crear un nuevo proyecto Maven
Vincule "Project SDK" a su JDK, por ejemplo: en Windows "C:\Program Files\Java\jdkxxx"
Configure el ID de grupo y el ID de artefacto:
<groupId>SeleniumTEST</groupId> <artifactId>Test</artifactId>
- Agregue dependencias Selenium y JUnit Maven en su archivo POM de proyecto
<dependencies> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <!-- Selenium --> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-firefox-driver</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-support</artifactId> <version>${selenium.version}</version> </dependency> <dependency> <groupId>org.seleniumhq.selenium</groupId> <artifactId>selenium-java</artifactId> <version>${selenium.version}</version> </dependency> </dependencies>
Reemplace la versión de Selenium y la versión de JUnit con los números de versión más recientes que se pueden encontrar buscando JUnit Maven en Google y en el sitio de Selenium.
En este punto, si la compilación automática está habilitada, las dependencias deberían comenzar a descargarse automáticamente. De lo contrario, simplemente active Complementos > instalar > instalar: instalar en el panel Maven Projects en el lado derecho de su IntelliJ Idea IDE.
Una vez que se haya iniciado el proyecto, podemos comenzar a crear nuestro paquete de prueba en "src/test/java". Nombre el paquete "com.toptal" y cree dos paquetes más debajo de él: "com.toptal.webpages" y "com.toptal.tests".

Mantendremos nuestras clases de Page Object/Page Factory en “com.toptal.webpages” y las rutinas de prueba en “com.toptal.tests”.
Ahora, podemos comenzar a crear nuestras clases de objetos de página.
Objeto de página de página de inicio
El primero que debemos implementar es para la página de inicio de Toptal (www.toptal.com). Cree una clase en "com.toptal.webpages" y asígnele el nombre "Página de inicio".
package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.How; import org.openqa.selenium.support.PageFactory; public class HomePage { private WebDriver driver; //Page URL private static String PAGE_URL="https://www.toptal.com"; //Locators //Apply as Developer Button @FindBy(how = How.LINK_TEXT, using = "APPLY AS A DEVELOPER") private WebElement developerApplyButton; //Constructor public HomePage(WebDriver driver){ this.driver=driver; driver.get(PAGE_URL); //Initialise Elements PageFactory.initElements(driver, this); } public void clickOnDeveloperApplyButton(){ developerApplyButton.click(); } }
Determinación de localizadores de elementos
En la página de inicio de Toptal estamos interesados en un elemento en particular, y ese es el botón "Solicitar como desarrollador". Podemos encontrar este elemento haciendo coincidir el texto, que es lo que estamos haciendo arriba. Al modelar páginas web como clases de objetos de página, encontrar e identificar elementos a menudo puede convertirse en una tarea. Con las herramientas de depuración de Google Chrome o Firefox, esto puede ser más fácil. Al hacer clic con el botón derecho en cualquier elemento de una página, puede activar la opción "Inspeccionar elemento" del menú contextual para obtener información detallada sobre el elemento.
Una forma común (y mi preferida) es encontrar elementos usando la extensión FireBug de Firefox, en combinación con el controlador web de Firefox en Selenium. Después de instalar y habilitar la extensión FireBug, puede hacer clic derecho en la página y seleccionar "Inspeccionar elemento con FireBug" para abrir FireBug. Desde la pestaña HTML de FireBug, puede copiar el XPath, la ruta CSS, el nombre de la etiqueta o el "Id" (si está disponible) de cualquier elemento de la página.
Al copiar el XPath del elemento en la captura de pantalla anterior, podemos crear un campo WebElement para él en nuestro Objeto de página de la siguiente manera:
@FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1") WebElement heading;
O para simplificar las cosas, podemos usar el nombre de la etiqueta "h1" aquí, siempre que identifique de forma única el elemento que nos interesa:
@FindBy(tagName = "h1") WebElement heading;
Objeto de página DeveloperPortalPage
A continuación, necesitamos un objeto de página que represente la página del portal del desarrollador, uno al que podamos acceder haciendo clic en el botón "Solicitar como desarrollador".
En esta página, tenemos dos elementos de interés. Para determinar si la página se ha cargado, queremos verificar la existencia del encabezado. Y también queremos un campo WebElement para el botón "Unirse a Toptal".
package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class DeveloperPortalPage { private WebDriver driver; @FindBy(xpath = "/html/body/div[1]/div/div/header/div/h1") private WebElement heading; @FindBy(linkText = "JOIN TOPTAL") private WebElement joinToptalButton; //Constructor public DeveloperPortalPage (WebDriver driver){ this.driver=driver; //Initialise Elements PageFactory.initElements(driver, this); } //We will use this boolean for assertion. To check if page is opened public boolean isPageOpened(){ return heading.getText().toString().contains("Developer portal"); } public void clikOnJoin(){ joinToptalButton.click(); } }
Objeto de página DeveloperApplyPage
Y finalmente, para nuestro tercer y último objeto de página para este proyecto, definimos uno que representa la página que contiene el formulario de solicitud del desarrollador. Dado que aquí tenemos que lidiar con varios campos de formulario, definimos una variable WebElement para cada campo de formulario. Encontramos cada campo por su "id" y definimos métodos de establecimiento especiales para cada campo que simulan pulsaciones de teclas para los campos correspondientes.
package com.toptal.webpages; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.PageFactory; public class DeveloperApplyPage { private WebDriver driver; @FindBy(tagName = "h1") WebElement heading; @FindBy() WebElement developer_email; @FindBy() WebElement developer_password; @FindBy() WebElement developer_password_confirmation; @FindBy() WebElement developer_full_name; @FindBy() WebElement developer_skype; @FindBy() WebElement join_toptal_button; //Constructor public DeveloperApplyPage(WebDriver driver){ this.driver=driver; //Initialise Elements PageFactory.initElements(driver, this); } public void setDeveloper_email(String email){ developer_email.clear(); developer_email.sendKeys(email); } public void setDeveloper_password(String password){ developer_password.clear(); developer_password.sendKeys(password); } public void setDeveloper_password_confirmation(String password_confirmation){ developer_password_confirmation.clear(); developer_password_confirmation.sendKeys(password_confirmation); } public void setDeveloper_full_name (String fullname){ developer_full_name.clear(); developer_full_name.sendKeys(fullname); } public void setDeveloper_skype (String skype){ developer_skype.clear(); developer_skype.sendKeys(skype); } public void clickOnJoin(){ join_toptal_button.click(); } public boolean isPageOpened(){ //Assertion return heading.getText().toString().contains("Apply to join our network as a developer"); } }
Escribir una prueba de selenio simple
Con las clases de objetos de página que representan nuestras páginas y las interacciones del usuario como sus métodos, ahora podemos escribir nuestra rutina de prueba simple como una serie de afirmaciones y llamadas a métodos simples.
package com.toptal.tests; import com.toptal.webpages.DeveloperApplyPage; import com.toptal.webpages.DeveloperPortalPage; import com.toptal.webpages.HomePage; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; import java.net.URL; import java.util.concurrent.TimeUnit; public class ApplyAsDeveloperTest { WebDriver driver; @Before public void setup(){ //use FF Driver driver = new FirefoxDriver(); driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); } @Test public void applyAsDeveloper() { //Create object of HomePage Class HomePage home = new HomePage(driver); home.clickOnDeveloperApplyButton(); //Create object of DeveloperPortalPage DeveloperPortalPage devportal= new DeveloperPortalPage(driver); //Check if page is opened Assert.assertTrue(devportal.isPageOpened()); //Click on Join Toptal devportal.clikOnJoin(); //Create object of DeveloperApplyPage DeveloperApplyPage applyPage =new DeveloperApplyPage(driver); //Check if page is opened Assert.assertTrue(applyPage.isPageOpened()); //Fill up data applyPage.setDeveloper_email("[email protected]"); applyPage.setDeveloper_full_name("Dejan Zivanovic Automated Test"); applyPage.setDeveloper_password("password123"); applyPage.setDeveloper_password_confirmation("password123"); applyPage.setDeveloper_skype("automated_test_skype"); //Click on join //applyPage.clickOnJoin(); } @After public void close(){ driver.close(); } }
Ejecución de la prueba
En este punto, la estructura de su proyecto debería verse así:
Si desea ejecutar la prueba, seleccione "ApplyAsDeveloperTest" del árbol, haga clic derecho sobre él y luego seleccione Ejecutar 'ApplyAsDeveloperTest' .
Una vez que se haya ejecutado la prueba, puede ver los resultados en la esquina inferior izquierda de su IDE:
Conclusión
Page Object y Page Factory facilitan el modelado de páginas web en Selenium y las prueban automáticamente y hacen que la vida tanto de los desarrolladores como de los QA sea mucho más sencilla. Cuando se hace correctamente, estas clases de objetos de página se pueden reutilizar en todo su conjunto de pruebas y le brindan la oportunidad de implementar pruebas automatizadas de Selenium para sus proyectos desde el principio, sin comprometer el desarrollo ágil. Al abstraer las interacciones de los usuarios en sus modelos de objetos de página y mantener sus rutinas de prueba livianas y simples, puede adaptar su conjunto de pruebas a los requisitos cambiantes con poco esfuerzo.
Espero haber logrado mostrarle cómo escribir un código de prueba agradable y limpio que sea fácil de mantener. Terminaré el artículo con mi cita favorita de control de calidad:
¡Piense dos veces, codifique una vez!