.NET

30 jan, 2018

Testando aplicações Web com Selenium WebDriver, .NET Core 2.0, .NET Standard 2.0 e xUnit

Publicidade

Considerando o desenvolvimento de software em ambientes corporativos nos dias atuais, nota-se sem grande esforço, que boa parte das atividades estão ligadas à implementação de aplicações Web voltadas aos mais diversos fins. Inúmeras são as plataformas utilizadas para atingir tais objetivos, com a maioria dos recursos empregados girando em torno de tecnologias como HTML, CSS e JavaScript.

Outro aspecto que deve ser ressaltado diz respeito ao uso de metodologias ágeis. Adotadas por organizações interessadas em melhorar seus processos de desenvolvimento, abordagens como Scrum vêm crescendo bastante em popularidade dentro da área de TI. Dentre as razões para isto estão fatores, como:

  • Maior comprometimento dos envolvidos em um projeto;
  • Equipes condicionadas para agir de maneira autônoma, inclusive atuando de forma auto-gerenciável;
  • Uma maior produtividade e mais qualidade na implementação de um projeto;
  • Flexibilidade diante de mudanças nos requisitos (algo quase sempre inevitável);
  • Entregas mais rápidas e com uma maior frequência.

No que se refere à questão da qualidade, a utilização de frameworks para a automação de testes representa uma prática bastante comum dentro das metodologias ágeis. Suportadas pelas principais IDEs do mercado, estas ferramentas fornecem um rápido feedback na validação do funcionamento de partes de um projeto. Esta característica acaba por se revelar como um instrumento de grande valia, sobretudo por gerar análises que indicam se uma atividade produziu os resultados esperados ou não.

Cada uma das alternativas existentes para a codificação de testes costuma cobrir pontos bem delimitados. Para a validação de interfaces Web existe o Selenium WebDriver, um framework compatível com plataformas, como .NET, Java, Python, Ruby e PHP.

No caso específico do .NET, é importante destacar que o Selenium WebDriver pode ser combinado a soluções como MS Test, xUnit, NUnit, SpecFlow, Moq, Fluent Assertions e NSubstitute. É também compatível com o .NET Full e o .NET Core, já que suas versões mais recentes foram implementadas a partir do .NET Standard – abordei inclusive esta novidade em um artigo recente sobre Selenium:

Utilizando o Selenium WebDriver com .NET Core 2.0 e .NET Standard 2.0.

O objetivo deste artigo é detalhar os procedimentos necessários para o uso do Selenium WebDriver em projetos de testes baseados no .NET Core 2.0. Serão utilizados ainda o .NET Standard 2.0 e o framework xUnit na implementação das diversas estruturas aqui descritas, a fim de viabilizar a realização de validações com o Mozilla Firefox e o Google Chrome.

Instalação dos drivers necessários

O Selenium suporta os principais browsers da atualidade. Para a condução de testes baseados com o mesmo, deverão ser utilizados drivers específicos para cada navegador e disponibilizados na seção Download do site oficial:

Caso pretenda efetuar o download destes drivers, acesse o seguinte endereço:

Não há uma regra específica envolvendo a localização dos drivers do Selenium para Firefox (geckodriver.exe) e Chrome (chromedriver.exe) no Windows. A imagem a seguir mostra que foram criadas pastas para armazenar cada um destes drivers:

Uma visão geral dos projetos a serem implementados

Neste artigo, serão implementados testes automatizados de uma aplicação Web de conversão de distâncias. Isto acontecerá por meio do preenchimento de um valor em milhas num campo texto e o posterior acionamento de um botão para disparar a conversão, com a exibição em um label contendo a distância equivalente em quilômetros:

Por mais que este projeto (ConversorDistancias) tenha sido construído com o ASP.NET Core 2.0, a plataforma de desenvolvimento escolhida não afeta a forma como o Selenium WebDriver trabalhará. Aplicações implementadas em Java, PHP ou JavaScript poderiam ser verificadas sem maiores dificuldades, já que testes empregando o WebDriver focam na simulação da interação de usuários com elementos típicos de uma página HTML.

Os projetos detalhados neste artigo fazem parte de uma Solution chamada ExemploSelenium:

  • Selenium.Utils: Class Library baseada no .NET Standard 2.0 e na qual constarão funcionalidades que visam simplificar o uso do Selenium em projetos de testes;
  • ConversorDistancias.Testes: serão definidos aqui testes demonstrando a integração do Selenium WebDriver com o Mozilla Firefox e o Google Chrome, tendo como alvo a aplicação de conversão de distâncias aqui descrita. Este projeto também utilizará o .NET Core 2.0 e o framework xUnit.

A seguir está o template utilizado no Visual Studio 2017 para a criação da biblioteca Selenium.Utils:

Já na próxima imagem, é possível observar o template usado na geração do projeto ConversorDistancias.Testes:

Será necessário também adicionar o package Selenium.WebDriver a estes dois projetos (Selenium.Utils e ConversorDistancias.Testes):

Observação: para a implementação das estruturas descritas neste artigo, foi utilizado o Microsoft Visual Studio Enterprise 2017 Update 15.5.3.

Implementando o projeto Selenium.Utils

Farão parte da biblioteca Selenium.Utils:

  • O enum Browser, no qual estarão indicados os browsers previstos para uso com o Selenium neste artigo (Firefox e Chrome);
  • A classe WebDriverFactory, que retornará instâncias específicas para interação com cada tipo de browser acessado durante os testes;
  • O tipo WebDriverExtensions, com métodos que simplificarão a manipulação de controles HTML.

A listagem a seguir traz a definição do enum Browser:

namespace Selenium.Utils
{
    public enum Browser
    {
        Firefox,
        Chrome
    }
}

A classe estática WebDriverFactory constitui uma implementação do pattern Factory, sendo utilizada na geração de instâncias cujas classes se baseiam na interface IWebDriver (namespace OpenQA.Selenium):

  • O método CreateWebDriver recebe como parâmetros o browser a ser utilizado (com base no enum Browser) e o caminho do driver para acesso a um navegador, devolvendo como resultado uma instância do tipo IWebDriver;
  • As classes FirefoxDriver (namespace OpenQA.Selenium.Firefox) e ChromeDriver (namespace OpenQA.Selenium.Chrome) correspondem a implementações de IWebDriver, representando o meio a partir do qual acontecerá a integração com o Mozilla Firefox e o Google Chrome;
  • O construtor de FirefoxDriver receberá uma instância do tipo FirefoxDriverService (namespace OpenQA.Selenium.Firefox), a qual foi gerada por meio de uma chamada ao método CreateDefaultService (com a passagem do caminho do driver do Firefox como parâmetro a esta operação). Já o construtor de ChromeDriver receberá como parâmetro de entrada o caminho do driver para uso do Google Chrome em conjunto com o Selenium.
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Chrome;

namespace Selenium.Utils
{
    public static class WebDriverFactory
    {
        public static IWebDriver CreateWebDriver(
            Browser browser, string pathDriver)
        {
            IWebDriver webDriver = null;

            switch (browser)
            {
                case Browser.Firefox:
                    FirefoxDriverService service =
                        FirefoxDriverService.CreateDefaultService(pathDriver);

                    webDriver = new FirefoxDriver(service);

                    break;
                case Browser.Chrome:
                    webDriver = new ChromeDriver(pathDriver);

                    break;
            }

            return webDriver;
        }
    }
}

Já na próxima listagem está a implementação do tipo estático WebDriverExtensions, com Extensions Methods que poderão ser utilizados a partir de referências de IWebDriver:

  • Por serem extensões, o primeiro parâmetro em todos os métodos da classe WebDriverExtensions (uma instância baseada na interface IWebDriver) foi marcado com a palavra-chave this;
  • Serão informados à operação LoadPage uma instância do tipo TimeSpan (namespace System) contendo um tempo de espera, além da URL da página que passará por testes. A primeira ação será configurar o timeout de carregamento da página (acessando na sequência os métodos Manage e Timeouts e, por fim, a propriedade PageLoad); em seguida, serão invocadas as operações Navigate e GoToUrl para carregar a página que passará por testes;
  • O método GetText permite capturar o valor associado à propriedade Text de um elemento HTML. Esta função tem como parâmetro de entrada uma instância do tipo By (namespace OpenQA.Selenium), a qual corresponde à identificação de um controle a ser localizado em uma página HTML. A operação FindElement do objeto IWebDriver receberá como parâmetro a instância da classe By, retornando uma referência baseada na interface IWebElement (namespace OpenQA.Selenium). Esta última é uma representação de um elemento HTML, permitindo o acesso a propriedades de um controle (como a leitura do conteúdo associado a Text, no caso específico de GetText);
  • A operação SetText permitirá o preenchimento da propriedade Text de um controle HTML, contando com uma implementação bastante similar àquela observada em GetText. A única diferença está na chamada ao método SendKeys de IWebElement com a passagem de um valor representado pelo parâmetro text, a fim de simular a digitação em um campo de texto;
  • Quanto ao método Submit, esta funcionalidade possibilitará que se simule o clique de um botão (invocando para isto uma operação também chamada Submit de IWebElement).
using System;
using OpenQA.Selenium;

namespace Selenium.Utils
{
    public static class WebDriverExtensions
    {
        public static void LoadPage(this IWebDriver webDriver,
            TimeSpan timeToWait, string url)
        {
            webDriver.Manage().Timeouts().PageLoad = timeToWait;
            webDriver.Navigate().GoToUrl(url);
        }

        public static string GetText(this IWebDriver webDriver, By by)
        {
            IWebElement webElement = webDriver.FindElement(by);
            return webElement.Text;
        }

        public static void SetText(this IWebDriver webDriver,
            By by, string text)
        {	
            IWebElement webElement = webDriver.FindElement(by);
            webElement.SendKeys(text);
        }

        public static void Submit(this IWebDriver webDriver, By by)
        {
            IWebElement webElement = webDriver.FindElement(by);
            webElement.Submit();
        }
    }
}

Implementando o projeto ConversorDistancias.Testes

No projeto ConversorDistancias.Testes foram definidos:

  • O arquivo appsettings.json, com as configurações necessárias para a execução dos testes que fazem uso do Selenium;
  • A classe TelaConversaoDistancias, com chamadas ao Selenium WebDriver para a condução dos testes automatizados da página de conversão de distâncias;
  • O tipo TestesConversaoDistancias, contendo a implementação de diversos casos de testes que fazem uso da classe TelaConversaoDistancias e do framework xUnit.

Importante destacar que este projeto fará uso da biblioteca Selenium.Utils:

O package Microsoft.Extensions.Configuration.Json também será adicionado a ConversorDistancias.Testes, a fim de possibilitar o acesso às configurações que compõem o arquivo appsettings.json:

Na listagem a seguir está o conteúdo do arquivo appsettings.json, no qual foram indicados os caminho dos drivers do Selenium e a URL de acesso à interface Web que passará por testes:

{
  "Selenium": {
    "CaminhoDriverFirefox": "C:\\Selenium\\FirefoxDriver\\",
    "CaminhoDriverChrome": "C:\\Selenium\\ChromeDriver\\",
    "UrlTelaConversaoDistancias": "http://localhost:51107/"
  }
}

Já a próxima listagem traz a implementação da classe TelaConversaoDistancias:

  • Este tipo é a implementação de um padrão de projeto conhecido como Page Object. Tal pattern foi proposto por Martin Fowler e tem por objetivo encapsular chamadas envolvendo a manipulação de elementos HTML, de forma a simplificar com isto, a escrita de testes;
  • O construtor de TelaConversaoDistancias receberá uma instância de IConfiguration (namespace Microsoft.Extensions.Configuration), a fim de possibilitar o acesso às configurações que estão no arquivo appsettings.json. Quanto ao parâmetro baseado no enum Browser (definido em Selenium.Utils), o valor correspondente será utilizado para se obter o caminho do driver de integração com um navegador e repassado então ao método CreateWebDriver de WebDriverFactory (com a intenção de gerar com isto uma instância do tipo IWebDriver);
  • A referência baseada em IWebDriver será utilizada então nos demais métodos de TelaConversaoDistancias (CarregarPagina, PreencherDistanciaMilhas, ProcessarConversao, ObterDistanciaKm e Fechar);
  • O método CarregarPagina acionará a operação LoadPage de WebDriverExtensions (classe esta também definida em Selenium.Utils), informando à mesma a URL e um timeout de cinco segundos para o carregamento da página a ser testada;
  • Já a operação PreencherDistanciaMilhas acionará o método SetText de WebDriverExtensions com o intuito de simular o preenchimento do campo que conterá o valor de uma distância em milhas;
  • O método ProcessarConversao efetuará o envio da distância para conversão, acionando para isto, a operação Submit de WebDriverExtensions. A chamada ao método Until da instância do tipo WebDriverWait (namespace OpenQA.Selenium.Support.UI) fará com que se aguarde por 10 segundos o aparecimento de uma resposta em um label cujo ID é DistanciaKm; caso isto não aconteça, um erro é então gerado, de maneira a indicar que o teste em questão não obteve sucesso;
  • A operação ObterDistanciaKm faz uso do método Get de WebDriverExtensions, obtendo com isto o valor associado ao label DistanciaKm. Este resultado é então convertido, servindo de base para a execução de diferentes casos de teste na classe TestesConversaoDistancias;
  • Já o método Fechar, acionará o método Quit da instância do tipo IWebDriver. Este procedimento forçará o fechamento de janelas do browser que tenham sido criadas durante a condução de um teste. A atribuição de null ao atributo _driver contribuirá para liberar mais rapidamente recursos alocados durante a utilização de um objeto baseado em IWebDriver.
using System;
using Microsoft.Extensions.Configuration;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Selenium.Utils;

namespace ConversorDistancias.Testes
{
    public class TelaConversaoDistancias
    {
        private IConfiguration _configuration;
        private Browser _browser;
        private IWebDriver _driver;

        public TelaConversaoDistancias(
            IConfiguration configuration, Browser browser)
        {
            _configuration = configuration;
            _browser = browser;

            string caminhoDriver = null;
            if (browser == Browser.Firefox)
            {
                caminhoDriver =
                    _configuration.GetSection("Selenium:CaminhoDriverFirefox").Value;
            }
            else if (browser == Browser.Chrome)
            {
                caminhoDriver =
                    _configuration.GetSection("Selenium:CaminhoDriverChrome").Value;
            }

            _driver = WebDriverFactory.CreateWebDriver(
                browser, caminhoDriver);
        }
        public void CarregarPagina()
        {
            _driver.LoadPage(
                TimeSpan.FromSeconds(5),
                _configuration.GetSection("Selenium:UrlTelaConversaoDistancias").Value);
        }

        public void PreencherDistanciaMilhas(double valor)
        {
            _driver.SetText(
                By.Name("DistanciaMilhas"),
                valor.ToString());
        }

        public void ProcessarConversao()
        {
            _driver.Submit(By.Id("btnConverter"));

            WebDriverWait wait = new WebDriverWait(
                _driver, TimeSpan.FromSeconds(10));
            wait.Until((d) => d.FindElement(By.Id("DistanciaKm")) != null);
        }

        public double ObterDistanciaKm()
        {
            return Convert.ToDouble(
                _driver.GetText(By.Id("DistanciaKm")));
        }

        public void Fechar()
        {
            _driver.Quit();
            _driver = null;
        }
    }
}

Os diferentes cenários de testes a serem considerados para validação estão listados na tabela a seguir:

A classe TestesConversaoDistancias traduz estes diferentes cenários em estruturas de código para a execução de testes com o Firefox e o Chrome:

  • No construtor de TestesConversaoDistancias será gerada uma instância de IConfiguration (namespace Microsoft.Extensions.Configuration), a qual será utilizada na leitura das configurações que se encontram no arquivo appsettings.json;
  • A operação TestarConversaoDistancia foi marcada com o atributo Theory (namespace Xunit), o que indica que a mesma representa um método de testes para o xUnit. Além disso, foram associados diversos atributos InlineData (namespace Xunit) a TestarConversaoDistancia, com os valores de cada cenário indicados nesta estrutura correspondendo aos diferentes parâmetros de entrada de TestarConversaoDistancia;
  • Uma instância da classe TelaConversaoDistancias será gerada na operação TestarConversaoDistancia, com as diferentes chamadas de métodos desta referência equivalendo à interação de um usuário com a página de conversão de distâncias;
  • O resultado do cálculo de conversão será validado através de uma chamada ao método Equal da classe Assert (namespace Xunit), a fim de determinar se a validação em questão foi bem-sucedida.
using System.IO;
using Microsoft.Extensions.Configuration;
using Xunit;
using Selenium.Utils;

namespace ConversorDistancias.Testes
{
    public class TestesConversaoDistancias
    {
        private IConfiguration _configuration;

        public TestesConversaoDistancias()
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(quot;appsettings.json");
            _configuration = builder.Build();
        }

        [Theory]
        [InlineData(Browser.Firefox, 100, 160.9)]
        [InlineData(Browser.Firefox, 230.05, 370.1505)]
        [InlineData(Browser.Firefox, 250.5, 403.0545)]
        [InlineData(Browser.Chrome, 100, 160.9)]
        [InlineData(Browser.Chrome, 230.05, 370.1505)]
        [InlineData(Browser.Chrome, 250.5, 403.0545)]
        public void TestarConversaoDistancia(
            Browser browser, double valorMilhas, double valorKm)
        {
            TelaConversaoDistancias tela =
                new TelaConversaoDistancias(_configuration, browser);

            tela.CarregarPagina();
            tela.PreencherDistanciaMilhas(valorMilhas);
            tela.ProcessarConversao();
            double resultado = tela.ObterDistanciaKm();
            tela.Fechar();

            Assert.Equal(valorKm, resultado);
        }
    }
}

Testes

Para executar os testes declarados na classe TestesConversaoDistancias, será necessário acessar no Visual Studio 2017 o menu Test > Run > All Tests:

Na janela Test Explorer, aparecerá então o resultado do processamento dos diferentes casos de teste:

As janelas abertas durante os testes com o Firefox indicarão que este browser está sendo controlado automaticamente:

Conclusão

Uma das grandes vantagens do Selenium WebDriver está na capacidade de emular a interação de usuários humanos com páginas Web, facilitando assim a implementação de testes automatizados de interfaces. Outro ponto que merece destaque, é o fato deste framework suportar os principais browsers do mercado, algo que contribui para uma validação mais eficaz de funcionalidades por considerar os cenários de utilização mais comuns.

Informações adicionais sobre o uso de frameworks de testes na plataforma .NET podem ser encontradas no seguinte artigo:

Espero que este artigo tenha sido útil.

Até uma próxima oportunidade!

Referências