Seminario web destacado: MISRA C++ 2023: todo lo que necesita saber | Vea ahora

Los mejores consejos para los expertos en selenio

Foto de cabeza de Tony, desarrollador senior de software en Parasoft
9 de noviembre.
10 min leer

Una vez que haya estado usando Selenium por un tiempo y se sienta cómodo escribiendo casos de prueba, podrá concentrarse en técnicas y principios de diseño para llevar la automatización de pruebas de UI al siguiente nivel. Consulte estas técnicas y prácticas preparadas para usuarios de Selenium.

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 XPath. Quizás esté utilizando el modelo de objetos de página. Probablemente ya seas bastante bueno buscando soluciones en Internet. Si desea ayuda en ese departamento, lo 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.

7 consejos sobre selenio para probadores expertos

1. Mejores localizadores de elementos web

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[contains(@class, “content”)][contains(.,”Guidance for Success”)]

Con lo anterior, queda claro que el elemento representa contenido que describe "Guía 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 elemento mismo. En su lugar, necesitarás ir a algún pariente del elemento cuya función esté bien descrita en el DOM. Tomemos el ejemplo anterior en el elemento "Guía para el éxito". Si bien este localizador es bueno, copiar texto a menudo puede cambiar o puede no ser una opción si el sitio admite varios idiomas. Al buscar en el DOM, podríamos encontrar que el div principal tiene una identificación descriptiva. En ese caso, podríamos preferir un localizador como:

//div[@id=”success_guide”]//p

2. 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(/* some condition is true */)

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 = new WebDriverWait(webdriver, timeOutInSeconds)

wait.until(ExpectedConditions.elementToBeClickable(element))

Las esperas explícitas descriptivas 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 sea visible y se pueda hacer clic en él, pero tampoco debe estar 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 espere a que se cumplan las condiciones necesarias:

WebDriverWait wait = new WebDriverWait(webdriver, timeOutInSeconds)

wait.until(ExpectedConditions.invisibilityOf(loadingOverlay))

3. 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 (..).

4. 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("arguments[0].scrollIntoView(false)", element)

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).

5. Dominar 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() {
    /* test code */
    WebDriverWait wait = new WebDriverWait(driver, 5);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    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:

public class Bot {
    public void waitAndClick(WebElement element, long timeout) {
    WebDriverWait wait = new WebDriverWait(driver, timeout);
    wait.until(ExpectedConditions.elementToBeClickable(element));
    element.click();
    }
}

Entonces nuestro código se convierte en:

void test() {
    /* test code */
    bot.waitAndClick(element, 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:

public class Bot {
    private WebDriver driver;
    private RichEditorBot richEditor;

    public Bot(WebDriver driver, RichEditorBot richEditor) {
        this.driver = driver;
        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() {
    /* test code */
    bot.isEditorDirty();
}

Un bot de ejemplo está disponible como parte de la biblioteca WebDriverExtensions, así como una implementación específica de la biblioteca:

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 {
    private Bot 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);
    }
}

6. Simplificación de la gestión de WebDriver

Crear una instancia 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:

    1. Construyendo un WebDriver específico.
    2. Probando una aplicación.

WebDriver Factory separa estas preocupaciones al sacar de la prueba todas las instancias y configuraciones de WebDriver. Esto se puede lograr de muchas maneras, 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 resolver en un solo lugar. Cualquier cambio puede ocurrir en ese único 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:

WebDriver Factory facilita la reutilización de una única prueba en varios navegadores. Todas las configuraciones se pueden 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. Se utiliza un ejemplo de esto para admitir pruebas de red paralela en el marco TestNG:

7. Ampliación del modelo de objetos de página

El modelo de objeto de página 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 muchos gastos generales al crear una clase para cada página y componente. Está el texto estándar para cada clase, luego los componentes compartidos entre clases, como la inicialización de la instancia y la transferencia del objeto WebDriver o Bot. Esta sobrecarga se puede reducir ampliando el modelo de objeto de página. Si está utilizando el patrón 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 {
    private Bot 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:

public class Component {
    private Bot bot;

    public Component(Bot bot) {
        PageFactory.initElements(bot.getDriver(), this);
        this.bot = bot;
    }

    public Bot getBot() {
        return bot;
    }
}

public class LoginComponent extends Component {
    @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:

public class LoginPage extends Component {
    public LoginPage(Bot bot) {
        super(bot);
        bot.waitForTitleContains(“Please login”);
    }
}

En lugar de incluir esta llamada al Bot en cada clase, podemos trasladar esta verificación a una versión especializada de Componente que se extienden otras páginas. Esto proporciona un pequeño beneficio que se suma al crear muchos objetos de página:

public class TitlePage extends Component {
    public LoginPage(Bot bot, String title) {
        super(bot);
        bot.waitForTitleContains(title);
    }
}

public class LoginPage extends TitlePage {
    public LoginPage(Bot bot) {
        super(bot, “Please login”);
    }
}

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:

Mejores prácticas para las pruebas de selenio

Las mejores prácticas garantizan que aproveche al máximo sus escenarios de prueba de Selenium, tanto de forma inmediata como a largo plazo. Las siguientes mejores prácticas aprovechan los consejos enumerados anteriormente para crear escenarios sólidos, multiplataforma y entre navegadores. Tener en cuenta estas prácticas ayudará a simplificar la transición de la creación de scripts menos óptimos a escenarios totalmente optimizados y resistentes al cambio.

Código de prueba mantenible

Mejore la eficiencia y la productividad escribiendo pruebas que sean fáciles de mantener. Pasará menos tiempo actualizando las pruebas y más tiempo centrándose en problemas reales cuando las aplicaciones web cambien. Muchas de las técnicas de este blog son útiles en parte porque mejoran la mantenibilidad.

Escribir escenarios de prueba que sean fáciles de entender con solo mirarlos, o código autodocumentado, los hace más fáciles de mantener. Los localizadores de elementos web que describen el elemento, las condiciones de espera explícitas que especifican las condiciones previas necesarias y los objetos de página que coinciden con las páginas reales hacen que el código sea más fácil de leer y comprender. Incluso el patrón del bot ayuda a que los scripts de prueba se autodocumenten al asignar acciones reales del usuario con nombres de funciones definidas en el bot.

También es menos probable que los localizadores de elementos web descriptivos y las esperas explícitas se rompan con el tiempo porque se centran en las características de la aplicación web en lugar de en los detalles de implementación. Por ejemplo, localizadores como los siguientes:

//p[contains(@class, “content”)][contains(.,”Guidance for Success”)]

hacen un trabajo mucho mejor al describir el elemento que aquellos que se basan en detalles estructurales no relacionados, como:

/html/body/div[25]/div[2]/div/span/span[2]/div/h2/p

Es menos probable que las características de una aplicación web cambien que la implementación subyacente de las características. En consecuencia, es menos probable que los scripts de prueba escritos según las funciones requieran actualizaciones.

Pruebas basadas en datos

Proporcionar una variedad de datos a sus escenarios de prueba ejercita mejor su aplicación y proporciona una cobertura de prueba más amplia.

Los marcos de prueba como JUnit y TestNG proporcionan conjuntos de funciones integradas para admitir pruebas basadas en datos. Cualquier escenario de prueba que ingrese datos, como completar un formulario o iniciar sesión como usuario, puede beneficiarse de las pruebas basadas en datos.

Al mejorar sus escenarios mediante pruebas basadas en datos, evite la tendencia a crear diferentes flujos lógicos a través de su escenario para diferentes datos. En su lugar, separe sus escenarios según el tipo de datos que esperan. Esto permite escenarios de prueba claramente definidos con pasos lineales y sencillos.

Pruebas entre navegadores

Si bien los detalles pueden ser diferentes entre los navegadores, la funcionalidad principal de la aplicación web generalmente permanece consistente, por lo que es menos probable que las pruebas que se centran en esta funcionalidad principal a través de localizadores de elementos web descriptivos y esperas explícitas se interrumpan cuando se ejecutan en diferentes navegadores.

El patrón de bot es un lugar donde se pueden manejar diferencias de bajo nivel entre navegadores. Por ejemplo, si diferentes navegadores requieren JavaScript diferente para realizar la misma acción, el bot puede servir como un lugar para abstraer esta diferencia. Ambas implementaciones de JavaScript se pueden incluir en una única función que describe la acción y maneja la bifurcación entre las dos implementaciones de JavaScript, según el navegador. Luego, el código del script de prueba puede llamar a la función en el bot sin tener que preocuparse por los detalles de implementación, lo que hace que el código del escenario de prueba sea más fácil de leer y mantiene fuera los detalles específicos del navegador.

De manera similar, el código objeto de la página se puede utilizar para abstraer las diferencias entre los navegadores en la interfaz de la aplicación web. Por ejemplo, si iniciar sesión requiere pasos adicionales en algunos navegadores, esa diferencia se puede manejar en una función de alto nivel como "doLogin". Los escenarios de prueba solo necesitan saber cómo iniciar sesión y ninguno de los detalles necesarios para iniciar sesión utilizando diferentes navegadores.

Ejecución de pruebas en paralelo

Para ejecutar sus escenarios en múltiples navegadores y entornos, querrá ejecutar pruebas en paralelo para ahorrar tiempo. Este suele ser un paso final después de asegurarse de que sus escenarios de prueba se ejecuten sin problemas en todos los navegadores y que estén basados ​​en datos cuando corresponda.
Selenium Grid combinado con funciones de marcos como JUnit y TestNG brindan soporte integrado para pruebas paralelas. Otros servicios como BrowserStack proporcionan pruebas paralelas, entornos prediseñados y funciones de diagnóstico para mantener las ejecuciones de pruebas paralelas más fácilmente.

Domine el selenio para realizar pruebas eficientes

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 Selénic 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 mantenibles.

Vea cómo puede crear pruebas confiables de interfaz de usuario web de Selenium con IA.