Automatizar tareas dinámicas en la web es posible gracias a Selenium WebDriver, un componente de la suite de Selenium que nos permite desarrollar con código acciones automatizadas en una página web a través del propio navegador.
Antes de explicarte las funcionalidades de Selenium Webdriver, conoceremos qué es Selenium.
¿Qué es Selenium?
Selenium es una suite de herramientas ampliamente utilizada para realizar pruebas automatizadas a través del navegador web, lo que permite reproducir fielmente el comportamiento de una persona real interactuando directamente con una página web a través del navegador.
Para más información, puedes consultar la página web de Selenium
Funcionalidades y lenguajes soportados por Selenium WebDriver
Selenium WebDriver es el sucesor de Selenium RC.
Los lenguajes soportados por Selenium WebDriver son:
- Java
- Python
- CSharp
- Ruby
- JavaScript
- Kotlin
Vale, pero… ¿qué utilidades prácticas puede tener?
Más allá de la automatización de pruebas de interfaz de usuario que es donde se suele centrar su uso, puede utilizarse como una herramienta para extraer datos <mark>públicos</mark> (scrapping) o para automatizar tareas repetitivas reales.
Ejemplo práctico
Vamos a realizar una prueba de concepto para demostrar un ejemplo práctico de uso de Selenium WebDriver
Objetivo de la prueba
El objetivo de la prueba va a consistir en realizar una búsqueda en Google y acceder al primer resultado que nos ofrezca. Para ello, nuestra aplicación deberá:
- Abrir el navegador web (en este caso, Google Chrome)
- Navegar a <https://www.google.es>
- Aceptar el diálogo de cookies
- Escribir una cadena de texto en el campo de búsqueda (En este caso, «bravent«)
- Ejecutar la búsqueda
- Acceder al primer resultado de la búsqueda
Preparación del entorno
Nos hemos decantado por desarrollar una aplicación de consola en .NET 6 y vamos a utilizar el navegador web Google Chrome.
Lo primero que debemos hacer es descargar el driver específico para nuestro navegador, en nuestro caso el driver para la versión 98.0.4758 de Google Chrome para Windows. Se pueden encontrar los drivers aquí
Descargamos el driver y lo extraemos en la carpeta C:WebDriver
Modificamos la variable de entorno PATH para añadir el directorio C:WebDriver, permitiendo que el driver de Google Chrome sea accesible desde línea de comandos:
setx PATH «%PATH%;C:\WebDriver»
A partir de este momento, ya estamos listos para abrir nuestro Visual Studio
Creación de la solución y proyectos con Visual Studio
Abrimos Visual Studio y creamos un nuevo proyecto de consola. En nuestro caso, la solución se llamará “Selenium.WebDriver.Demo”, y el framework utilizado será .NET 6
Como nos gusta desarrollar de una forma elegante, el siguiente paso será el de crear 3 proyectos de librería:
- WebDriver.Demo.Shared: Donde incluiremos las interfaces que deberán cumplir cualquier driver que implementemos, ya sea Chrome, Firefox, etc
- WebDriver.Demo.Chrome: Donde implementaremos de forma genérica las funcionalidades para controlar Google Chrome
- WebDriver.Demo.GoogleSearch: Donde implementaremos los métodos específicos para cubrir los requisitos de nuestra prueba
Estos proyectos también utilizarán el framework de .NET 6
Las dependencias entre proyectos las estableceremos de la siguiente forma:
- WebDriver.Demo <- Selenium.WebDriver.Demo.GoogleSearch <- Selenium.WebDriver.Demo.Shared
- WebDriver.Demo <- Selenium.WebDriver.Demo.Chrome <- Selenium.WebDriver.Demo.Shared
Dependencias necesarias
Para cubrir las funcionalidades propuestas, vamos a tener que instalar los siguientes paquetes NuGet en los proyectos
Selenium.WebDriver.Demo.Chrome:
- nupkg Selenium.Support
Selenium.WebDriver.Demo.Shared:
- nupkg Selenium.WebDriver
Llega lo divertido… ¡empecemos a codificar!
En primer lugar, vamos a crear nuestra interfaz IWebBrowser dentro del proyecto Selenium.WebDriver.Demo.Shared, que indicará cuales son los métodos obligatorios que deberá cumplir cualquier implementación de Selenium.WebDriver:
- Click: Debe hacer click en un elemento de la web
- FindElementById: Debe buscar un elemento del DOM por Id
- FindElementByName: Debe buscar un elemento en el DOM por Name
- GoToUrl: Debe navegar hacia una URL especificada
- IsElementPresent: Debe indicar si un elemento existe en el DOM
- WriteTextOnInputBox: Debe escribir un texto indicado en un elemento del DOM específico
using OpenQA.Selenium; namespace Selenium.WebDriver.Demo.Shared { public interface IWebBrowser { void Click(IWebElement element); IWebElement FindElementById(string id); IWebElement FindElementByName(string name); void GotToUrl(string url); bool IsElementPresent(By by); void WriteTextOnInputBox(IWebElement element, string text); } }
El siguiente paso es implementar nuestra interfaz para el navegador Google Chrome. Para ello, crearemos una clase llamada Chrome dentro del proyecto Selenium.WebDriver.Demo.Chrome con la siguiente implementación:
using OpenQA.Selenium; using OpenQA.Selenium.Chrome; using OpenQA.Selenium.Support.UI; using Selenium.WebDriver.Demo.Shared; namespace Selenium.WebDriver.Demo.Chrome { public class Chrome : IWebBrowser { private readonly ChromeDriver _driver; private int _timeoutSeconds; public ChromeDriver Driver => _driver; public Chrome(int timeoutSeconds = 60) { _driver = InitializeDriver(new ChromeOptions(), timeoutSeconds); } public void Click(IWebElement element) { element.Click(); } public IWebElement FindElementById(string id) { ValidateDriver(); return new WebDriverWait(Driver, TimeSpan.FromSeconds(_timeoutSeconds)).Until(e => e.FindElement(By.Id(id))); } public IWebElement FindElementByName(string name) { ValidateDriver(); return new WebDriverWait(Driver, TimeSpan.FromSeconds(_timeoutSeconds)).Until(e => e.FindElement(By.Name(name))); } public void GotToUrl(string url) { ValidateDriver(); Driver.Navigate().GoToUrl(url); } public bool IsElementPresent(By by) { ValidateDriver(); return new WebDriverWait(Driver, TimeSpan.FromSeconds(_timeoutSeconds)).Until(e => e.FindElement(by).Displayed); } public void WriteTextOnInputBox(IWebElement element, string text) { element.SendKeys(text); } private void ValidateDriver() { if (Driver == null) { throw new Exception("Driver is null"); } } private ChromeDriver InitializeDriver(ChromeOptions options, int timeoutSeconds = 60) { _timeoutSeconds = timeoutSeconds; ChromeDriver driver = new ChromeDriver(options); SetDriverTimeouts(driver, timeoutSeconds); return driver; } private void SetDriverTimeouts(ChromeDriver driver, int timeoutSeconds) { driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(timeoutSeconds); } } }
Espera, ¡no tan rápido! ¿Qué es eso de WebDriverWait?
Algo a tener siempre en cuenta: los retardos en la carga
Cuando trabajamos con Selenium.WebDriver, nunca podemos dar por sentado que un determinado elemento se va a encontrar en el DOM en el momento en el que vamos a acceder a él.
La carga asíncrona de javascript, la aparición o desaparición de elementos, etc, están muy condicionados por las velocidades de carga de la red o las velocidades de renderizado del propio navegador.
Por esta razón, Selenium nos provee de la clase WebDriverWait, que nos permite esperar a que un determinado elemento aparezca en el DOM pudiendo establecer un timeout:
return new WebDriverWait(Driver, TimeSpan.FromSeconds(_timeoutSeconds)).Until(e => e.FindElement(By.Id(id)));
En este ejemplo, WebDriverWait devolverá el elemento (de tipo IWebElement) buscado por Id si ese elemento aparece en el DOM antes de que se produzca el timeout. En caso contrario devolverá una excepción.
Entendido, sigamos codificando…
En este punto ya hemos implementado correctamente nuestro WebDriver para Chrome, por lo que podemos hacer uso de él dentro de nuestro proyecto Selenium.WebDriver.Demo.GoogleSearch. Vamos a crear una clase llamada GoogleSearch con la siguiente implementación:
using OpenQA.Selenium; using Selenium.WebDriver.Demo.Shared; namespace Selenium.WebDriver.Demo.GoogleSearch { public class GoogleSearch { private readonly IWebBrowser _browser; private readonly string _baseUrl; public GoogleSearch(IWebBrowser browser) { _browser = browser; _baseUrl = "https://www.google.es"; } public void Load() { _browser.GotToUrl(_baseUrl); } public void AcceptCookies() { if (_browser.IsElementPresent(By.Id("L2AGLb"))) { _browser.Click(_browser.FindElementById("L2AGLb")); } } public void SearchFor(string textToSearch) { _browser.WriteTextOnInputBox(_browser.FindElementByName("q"), textToSearch); } public void ExecuteSearch() { _browser.WriteTextOnInputBox(_browser.FindElementByName("q"), Keys.Enter); } public void ClickOnFirstResult() { IWebElement results = _browser.FindElementById("rso"); results.FindElement(By.TagName("a")).Click(); } } }
Y ahora es cuando empieza a cobrar sentido nuestra interfaz IWebBrowser.
Si el día de mañana queremos ejecutar esta búsqueda con el navegador Firefox, tan solo tenemos que implementar los métodos de la interfaz en una clase que utilice el driver de Firefox, desacoplando de una forma muy elegante nuestro código.
¡Llega el momento más emocionante! Vamos a ejecutarlo
Vamos a modificar nuestra clase Program del proyecto de consola para ejecutar nuestro programa. El código en este punto ya es extremadamente simple:
using Selenium.WebDriver.Demo.Chrome;
using Selenium.WebDriver.Demo.GoogleSearch;
GoogleSearch google = new GoogleSearch(new Chrome(30));
google.Load();
google.AcceptCookies();
google.SearchFor(«bravent»);
google.ExecuteSearch();
google.ClickOnFirstResult();