Los mejores consejos para los expertos en selenio
Por Tony Yoshicedo
5 de noviembre.
8 min leer
Una vez que haya estado usando Selenium durante un tiempo y se sienta cómodo escribiendo casos de prueba, puede concentrarse en las técnicas y los principios de diseño para llevar la automatización de las pruebas de IU al siguiente nivel.
Antes de que comencemos
Este artículo asume que ha estado usando Selenium y se siente cómodo escribiendo casos de prueba. Ya pasó por la prueba de inspeccionar DOM para crear sus propios XPath. Tal vez esté utilizando el modelo de objetos de página. A estas alturas probablemente sea bastante bueno buscando soluciones en Internet. (Si desea ayuda en ese departamento, le recomiendo encarecidamente este artículo de mi colega con algunos trucos geniales de Selenium.)
Lo que sigue se desglosa en técnicas y patrones de diseño. Es posible que ya sepa todo sobre algunos de estos. Está bien, simplemente pase a las secciones que le interesen. Usaré Java aquí, pero las técnicas y prácticas deberían ser de aplicación general.
Técnicas
Mejores localizadores
Los localizadores defectuosos provocan falsas fallas en las pruebas. Pierden tiempo y distraen de la función prevista de la prueba. Los malos localizadores suelen depender de una calidad inestable de la página web, ya sea un valor dinámico o la posición del elemento en la página. Cuando esas cualidades cambian invariablemente, el localizador se rompe. Los buenos localizadores simplemente funcionan. Identifican correctamente su elemento previsto y permiten que la prueba haga su trabajo.
La clave aquí es identificar las cualidades del elemento que son estables, luego elegir el subconjunto mínimo de estas cualidades que identifican de manera única ese elemento para reducir su exposición al cambio. Todos sabemos que los XPath absolutos son malos, pero es bueno entender exactamente por qué.
/ html / body / div [25] / div [2] / div / span / span [2] / div / h2 / p
Tome una subsección de este XPath: div [25] / div [2] . Esto representa las siguientes cualidades:
- El nodo está en algún lugar de un div
- Ese div es el segundo div
- Ese div está directamente dentro de otro div
- Ese div es el 25o div.
En esta pequeña subsección tenemos al menos 4 cualidades que se utilizan para identificar un elemento. Si alguno de ellos cambia, nuestro localizador se rompe. No tenemos ninguna razón para creer que no cambiarán porque ninguno de ellos describe realmente el elemento. Mirando este XPath, ni siquiera pudimos adivinar el propósito del elemento.
Es menos probable que se rompan los localizadores que reflejan el propósito del elemento. Si bien la posición o apariencia del elemento puede cambiar, su función no debería. Idealmente, puede adivinar qué hace el elemento mirando su localizador.
// p [contiene (@clase, "contenido")] [contiene (., "Orientación para el éxito")]
Con lo anterior, está claro que el elemento representa contenido que describe "Orientación para el éxito". Observe que el atributo de clase, aunque generalmente se usa para controlar la apariencia, también describe la función del elemento.
A menudo, las cualidades únicas y estables de un elemento no se encuentran en el propio elemento. En su lugar, tendrá que ir a algún pariente del elemento cuya función está bien descrita en el DOM. Tome el ejemplo anterior en el elemento "Orientación para el éxito". Si bien este localizador es bueno, la copia de texto a menudo puede cambiar, o puede que no sea una opción si el sitio admite varios idiomas. Al buscar en el DOM, podríamos encontrar que el div padre tiene una identificación descriptiva. En ese caso, podríamos preferir un localizador como:
// div [@ id = ”success_guide”] // p
Esperas explícitas
La tentación aquí es establecer una espera y una esperanza implícitas que manejarán la mayoría de los problemas. El problema es que este enfoque amplio trata todas las situaciones de la misma manera sin siquiera abordar la mayoría de los problemas asociados con la espera. ¿Qué sucede si el elemento se vuelve presente después de unos segundos, pero aún no está listo para hacer clic? ¿Qué pasa si el elemento está presente, pero está oculto por una superposición? La solución es utilizar bien elaborado esperas explícitas.
Las condiciones de espera explícitas toman la forma de:
WebDriverWait wait = new WebDriverWait (webdriver, timeOutInSeconds) wait.until (/ * alguna condición es verdadera * /)
Las esperas explícitas son poderosas porque son descriptivas. Le permiten indicar las condiciones necesarias para poder proceder. Un ejemplo común de esto es cuando la prueba necesita hacer clic en un elemento. Es insuficiente que el elemento esté presente; debe estar visible y habilitado. Las esperas explícitas le permiten describir estos requisitos directamente en la prueba. Podemos estar seguros de que se han cumplido las condiciones antes de que la prueba intente continuar, lo que hace que sus pruebas sean más estables:
WebDriverWait wait = nuevo WebDriverWait (webdriver, timeOutInSeconds) wait.until (ExpectedConditions.elementToBeClickable (elemento))
Las esperas descriptivas explícitas también le permiten escribir pruebas que se centran en los aspectos de comportamiento de la aplicación. Debido a que el comportamiento de la aplicación no cambia con frecuencia, esto le permite crear pruebas más estables. Dado el ejemplo anterior, es necesario que un elemento esté visible y que se habilite para hacer clic en él, pero tampoco debe quedar oculto por otro elemento. Si hay una gran superposición de "carga" que cubre su elemento, el clic fallará. Podemos crear una espera explícita que describa este comportamiento de carga y espera a que se cumplan las condiciones necesarias:
WebDriverWait wait = nuevo WebDriverWait (webdriver, timeOutInSeconds) wait.until (ExpectedConditions.invisibilityOf (loadingOverlay))
Condiciones esperadas
Puede que hayas notado la Condiciones esperadas métodos de utilidad que se utilizan en los ejemplos anteriores. Esta clase contiene una gran cantidad de condiciones útiles para usar al escribir sus pruebas de Selenium. Si aún no lo ha hecho, vale la pena tomarse un momento para repasar la API completa. Usará comúnmente ExpectedConditions.elementToBeClickable (..) o ExpectedConditions.invisibilityOf (..), pero también puede encontrar usos para alertIsPresent (), jsReturnsValue (…) o titleContains (..). Incluso puede encadenar las condiciones usando Condiciones esperadas y (..) o Condiciones esperadas o (..).
Ejecutando JavaScript
Los WebDrivers brindan la capacidad de ejecutar JavaScript dentro del contexto del navegador. Esta es una característica simple con una versatilidad increíble. Esto se puede usar para tareas comunes, como forzar a una página a desplazarse a un elemento, como con:
driver.executeScript ("argumentos [0] .scrollIntoView (falso)", elemento)
También se puede utilizar para aprovechar las bibliotecas de JavaScript utilizadas en una aplicación como JQuery o React. Por ejemplo, puede verificar si el texto en un editor enriquecido se ha cambiado llamando a:
driver.executeScript ("return EDITOR.instances.editor.checkDirty ()")
La función executeScript abre toda la API de la biblioteca para su prueba. Estas API a menudo proporcionan información útil sobre el estado de la aplicación que, de otro modo, sería imposible o inestable consultar con WebElements.
El uso de API de biblioteca acopla su prueba a una implementación de biblioteca. Las bibliotecas a menudo se pueden intercambiar durante el desarrollo de una aplicación, por lo que se requiere precaución al usar executeScript de esta manera. Debería considerar abstraer estas llamadas específicas de la biblioteca detrás de una interfaz más abstracta para ayudar a reducir la exposición de sus pruebas a la inestabilidad, como con el patrón Bot (ver más abajo).
Diseño
El patrón de bot
La patrón de bot abstrae las llamadas a la API de Selenium en acciones. Las acciones se pueden utilizar a lo largo de sus pruebas para hacerlas más legibles y concisas.
Ya hemos visto algunos ejemplos en los que esto sería útil en este artículo. Debido a que es necesario que se pueda hacer clic en un elemento antes de intentar hacer clic en él, es posible que siempre queramos esperar a que se pueda hacer clic en el elemento antes de cada clic:
void test () {/ * código de prueba * / WebDriverWait wait = new WebDriverWait (controlador, 5); wait.until (ExpectedConditions.elementToBeClickable (elemento)); element.click (); wait.until (ExpectedConditions.elementToBeClickable (element2)); element2.click (); }
En lugar de escribir la condición de espera cada vez que la prueba hace clic en un elemento, el código se puede abstraer en su propio método:
Bot de clase pública {public void waitAndClick (elemento WebElement, tiempo de espera largo) {Espera de WebDriverWait = new WebDriverWait (controlador, tiempo de espera); wait.until (ExpectedConditions.elementToBeClickable (elemento)); element.click (); }}
Entonces nuestro código se convierte en:
prueba void () {/ * código de prueba * / bot.waitAndClick (elemento, 5); bot.waitAndClick (element2, 5); }
El bot también se puede ampliar para crear implementaciones específicas de la biblioteca. Si la aplicación alguna vez comienza a usar una biblioteca diferente, todo el código de prueba puede permanecer igual y solo es necesario actualizar el Bot:
Bot de clase pública {controlador WebDriver privado; richEditorBot richEditor privado; Bot público (controlador WebDriver, RichEditorBot richEditor) {this.driver = controlador; this.richEditor = richEditor; } public boolean isEditorDirty () {richEditor.isEditorDirty (); }} public class RichEditorBot () {public boolean isEditorDirty () {return ((JavascriptExecutor) driver) .executeScript (“return EDITOR.instances.editor.checkDirty ()”); }} void test () {/ * código de prueba * / bot.isEditorDirty (); }
Un bot de ejemplo está disponible como parte de la biblioteca WebDriverExtensions, así como una implementación específica de la biblioteca:
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/Bot.java
- https://github.com/webdriverextensions/webdriverextensions/blob/master/src/main/java/com/github/webdriverextensions/vaadin/VaadinBot.java
El patrón de bot y el modelo de objeto de página se pueden usar juntos. En sus pruebas, la abstracción de nivel superior es el objeto de página que representa los elementos funcionales de cada componente. Los Objetos de página contienen un Bot para usar en las funciones de Objeto de página, lo que hace que sus implementaciones sean más simples y fáciles de entender:
Public class LoginComponent {bot bot privado; @FindBy (id = “login”) private WebElement loginButton; Public LoginComponent (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public void clickLogin () {bot.waitAndClick (loginButton, 5); }}
Fábrica de WebDriver
Instanciar y configurar una instancia de WebDriver como ChromeDriver o FirefoxDriver directamente en el código de prueba significa que la prueba ahora tiene dos preocupaciones: construir un WebDriver específico y probar una aplicación. Una fábrica de WebDriver separa estas preocupaciones al mover toda la creación de instancias y la configuración de WebDriver fuera de la prueba.
Esto se puede lograr de varias formas, pero el concepto es simple: crear una fábrica que proporcione un WebDriver completamente configurado. En su código de prueba, obtenga el WebDriver de fábrica en lugar de construirlo directamente. Ahora, cualquier inquietud relacionada con WebDriver se puede manejar en un solo lugar. Cualquier cambio puede ocurrir en ese lugar y cada prueba obtendrá el WebDriver actualizado.
El proyecto Web Driver Factory utiliza este concepto para administrar la vida útil de WebDrivers en múltiples pruebas. Esta compleja tarea se abstrae en la fábrica, lo que permite que las pruebas solo soliciten un WebDriver con las opciones proporcionadas:
La fábrica de WebDriver facilita la reutilización de una única prueba en varios navegadores. Toda la configuración se puede manejar a través de un archivo externo. La prueba solo solicita a la fábrica de WebDriver una instancia de WebDriver y la fábrica maneja los detalles. Un ejemplo de esto se utiliza para admitir pruebas de redes paralelas en el marco TestNG:
Ampliación del modelo de objetos de página
El modelo Page Object proporciona una capa de abstracción que presenta las funciones de los componentes de una aplicación mientras oculta los detalles de cómo Selenium interactúa con estos componentes. Este es un patrón de diseño poderoso que hace que el código sea reutilizable y más fácil de entender. Sin embargo, puede haber mucha sobrecarga al crear una clase para cada página y componente. Existe el texto estándar para cada clase, luego componentes compartidos entre clases, como inicializar la instancia y pasar el objeto WebDriver o Bot. Esta sobrecarga se puede reducir ampliando el modelo de objeto de página.
Si está utilizando el patrón de Bot junto con su modelo de Objeto de página, entonces cada Objeto de página necesitará una instancia del Bot. Esto podría verse así:
Public class LoginComponent {bot privado bot; @FindBy (id = “login”) private WebElement loginButton; Public LoginComponent (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } public void clickLogin () {bot.waitAndClick (loginButton, 5); }}
En lugar de incluir el código del Bot en cada constructor, este código podría trasladarse a otra clase que amplíe cada componente. Esto permite que el código del componente individual se centre en los detalles del componente y no en el código de inicialización o en pasar el Bot:
Componente de clase pública {bot bot privado; Componente público (Bot bot) {PageFactory.initElements (bot.getDriver (), this); this.bot = bot; } bot público getBot () {bot de retorno; }} la clase pública LoginComponent extiende el componente {@FindBy (id = “login”) private WebElement loginButton; Public LoginComponent (Bot bot) {super (bot); } public void clickLogin () {getBot (). waitAndClick (loginButton, 5); }}
De manera similar, es común que un componente verifique que se está instanciando en el momento adecuado, para facilitar la depuración cuando los componentes se usan incorrectamente. Es posible que deseemos verificar que el título sea correcto:
La página de inicio de sesión de clase pública extiende el componente {página de inicio de sesión pública (bot bot) {super (bot); bot.waitForTitleContains ("Inicie sesión"); }}
En lugar de incluir esta llamada al Bot en cada clase, podemos mover esta comprobación a una versión especializada de Component que se extienden otras páginas. Esto proporciona un pequeño beneficio que se suma al crear muchos objetos de página:
La clase pública TitlePage extiende el Componente {Public LoginPage (Bot bot, String title) {super (bot); bot.waitForTitleContains (título); }} la clase pública LoginPage extiende TitlePage {Public LoginPage (Bot bot) {super (bot, "Por favor, inicie sesión"); }}
Otras bibliotecas proporcionan clases auxiliares exactamente para este propósito. La biblioteca de Selenium Java incluye el objeto LoadableComponent que abstrae la funcionalidad y verifica la carga de una página:
WebDriverExtensions va aún más lejos al abstraer gran parte del código alrededor de los objetos de página en anotaciones, creando componentes más simples y fáciles de leer:
Siempre hay más para aprender
Este artículo solo ha abordado algunas técnicas útiles y patrones de diseño. Se han escrito libros sobre el tema. Escribir buenas pruebas significa escribir un buen software y escribir un buen software es una tarea compleja.
He cubierto parte de la información que he aprendido a lo largo de los años para crear mejores pruebas de selenio. Parasoft Selenic aprovecha nuestra experiencia para simplificar aún más la tarea. Con las herramientas y el conocimiento adecuados, podemos mejorar el proceso y crear pruebas estables, legibles y fáciles de mantener.