Escrever os testes da interface do usuário

Concluído

Nesta seção, você ajuda Andy e Amita a escrever testes Selenium que verificam os comportamentos da interface do usuário que Amita descreveu.

Amita normalmente executa testes no Chrome, Firefox e Microsoft Edge. Aqui, você faz o mesmo. O agente hospedado pela Microsoft que você usará está pré-configurado para funcionar com cada um desses navegadores.

Obter o ramo do GitHub

Nesta seção, você busca a selenium ramificação no GitHub. Em seguida, você faz check-out, ou muda para, essa ramificação. O conteúdo do ramo irá ajudá-lo a acompanhar os testes que Andy e Amita escrevem.

Esta ramificação contém o projeto Space Game com o qual você trabalhou nos módulos anteriores. Ele também contém uma configuração do Azure Pipelines para começar.

  1. No Visual Studio Code, abra o terminal integrado.

  2. Para baixar uma ramificação nomeada selenium do repositório da Microsoft, alterne para essa ramificação e execute os seguintes git fetch comandos git checkout :

    git fetch upstream selenium
    git checkout -B selenium upstream/selenium
    

    Gorjeta

    Se você acompanhou o teste manual da Amita na unidade anterior, talvez já tenha executado esses comandos. Se você já os executou na unidade anterior, você ainda pode executá-los novamente agora.

    Lembre-se de que upstream refere-se ao repositório Microsoft GitHub. A configuração do Git do seu projeto compreende o controle remoto upstream porque você configura essa relação. Você o configurou quando bifurcou o projeto do repositório da Microsoft e o clonou localmente.

    Em breve, irá enviar este ramo para o seu repositório do GitHub, conhecido como origin.

  3. Opcionalmente, no Visual Studio Code, abra o arquivo azure-pipelines.yml . Familiarize-se com a configuração inicial.

    A configuração é semelhante às que você criou nos módulos anteriores neste caminho de aprendizagem. O módulo cria apenas a configuração da Versão da aplicação. Por uma questão de brevidade, ele também omite os gatilhos, aprovações manuais e testes que você configurou nos módulos anteriores.

    Nota

    Uma configuração mais robusta pode especificar as ramificações que participam do processo de compilação. Por exemplo, para ajudar a verificar a qualidade do código, você pode executar testes de unidade sempre que enviar uma alteração em qualquer ramificação. Você também pode implantar o aplicativo em um ambiente que executa testes mais exaustivos. Mas você faz essa implantação somente quando tem uma solicitação pull, quando tem um release candidate ou quando mescla o código para main.

    Para obter mais informações, consulte Implementar um fluxo de trabalho de código em seu pipeline de compilação usando gatilhos de pipeline Git e GitHub e Build.

Escreva o código de teste de unidade

Amita está animada para aprender a escrever código que controla o navegador da web.

Ela e Andy trabalharão juntos para escrever os testes de Selenium. Andy já montou um projeto NUnit vazio. Ao longo do processo, eles se referem à documentação do Selenium, alguns tutoriais on-line e as anotações que fizeram quando Amita fez os testes manualmente. No final deste módulo, você encontrará mais recursos para ajudá-lo a passar pelo processo.

Vamos rever o processo que Andy e Amita usam para escrever seus testes. Você pode acompanhar abrindo HomePageTest.cs no diretório Tailspin.SpaceGame.Web.UITests no Visual Studio Code.

Definir a classe HomePageTest

Andy: A primeira coisa que precisamos fazer é definir nossa classe de teste. Podemos optar por seguir uma das várias convenções de nomenclatura. Vamos chamar a nossa turma HomePageTest. Nesta aula, colocaremos todos os nossos testes relacionados à página inicial.

Andy adiciona este código ao HomePageTest.cs:

public class HomePageTest
{
}

Andy: Precisamos marcar essa classe para que ela esteja disponível para public a estrutura NUnit.

Adicionar a variável de membro IWebDriver

Andy: Em seguida, precisamos de uma IWebDriver variável membro. IWebDriver é a interface de programação que você usa para iniciar um navegador da Web e interagir com o conteúdo da página da Web.

Amita: Já ouvi falar de interfaces em programação. Pode dizer-me mais?

Andy: Pense em uma interface como uma especificação ou plano de como um componente deve se comportar. Uma interface fornece os métodos, ou comportamentos, desse componente. Mas a interface não fornece nenhum dos detalhes subjacentes. Você ou outra pessoa criaria uma ou mais classes concretas que implementassem essa interface. O selénio fornece as classes concretas de que precisamos.

Este diagrama mostra a IWebDriver interface e algumas das classes que implementam essa interface:

Diagram of the IWebDriver interface, its methods, and concrete classes.

O diagrama mostra três dos métodos que IWebDriver fornece: Navigate, FindElement, e Close.

As três classes mostradas aqui, , , e , ChromeDriverFirefoxDrivercada implemento IWebDriver e EdgeDriverseus métodos. Existem outras classes, como SafariDriver, que também implementam IWebDriver. Cada classe de driver pode controlar o navegador da Web que representa.

Andy adiciona uma variável membro nomeada driver à HomePageTest classe, como este código:

public class HomePageTest
{
    private IWebDriver driver;
}

Definir os equipamentos de teste

Andy: Queremos executar todo o conjunto de testes no Chrome, Firefox e Edge. No NUnit, podemos usar dispositivos de teste para executar todo o conjunto de testes várias vezes, uma vez para cada navegador em que queremos testar.

No NUnit, você usa o TestFixture atributo para definir seus equipamentos de teste. Andy adiciona estes três equipamentos de teste à HomePageTest classe:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private IWebDriver driver;
}

Andy: Em seguida, precisamos definir um construtor para nossa classe de teste. O construtor é chamado quando NUnit cria uma instância dessa classe. Como argumento, o construtor usa a cadeia de caracteres que anexamos aos nossos equipamentos de teste. Veja como é o código:

[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
    private string browser;
    private IWebDriver driver;

    public HomePageTest(string browser)
    {
        this.browser = browser;
    }
}

Andy: Adicionamos a browser variável member para que possamos usar o nome atual do navegador em nosso código de configuração. Vamos escrever o código de configuração a seguir.

Definir o método Setup

Andy: Em seguida, precisamos atribuir nossa IWebDriver variável membro a uma instância de classe que implemente essa interface para o navegador em que estamos testando. As ChromeDriverclasses , , e implementam essa interface para Chrome, Firefox e Edge, FirefoxDriverEdgeDriver respectivamente.

Vamos criar um método chamado Setup que define a driver variável. Usamos o OneTimeSetUp atributo para dizer ao NUnit para executar esse método uma vez por dispositivo de teste.

[OneTimeSetUp]
public void Setup()
{
}

Setup No método, podemos usar uma switch instrução para atribuir a driver variável member à implementação concreta apropriada com base no nome do navegador. Vamos adicionar esse código agora.

// Create the driver for the current browser.
switch(browser)
{
    case "Chrome":
    driver = new ChromeDriver(
        Environment.GetEnvironmentVariable("ChromeWebDriver")
    );
    break;
    case "Firefox":
    driver = new FirefoxDriver(
        Environment.GetEnvironmentVariable("GeckoWebDriver")
    );
    break;
    case "Edge":
    driver = new EdgeDriver(
        Environment.GetEnvironmentVariable("EdgeWebDriver"),
        new EdgeOptions
        {
            UseChromium = true
        }
    );
    break;
    default:
    throw new ArgumentException($"'{browser}': Unknown browser");
}

O construtor para cada classe de driver toma um caminho opcional para o software de driver que o Selenium precisa para controlar o navegador da web. Mais tarde, discutiremos o papel das variáveis de ambiente mostradas aqui.

Neste exemplo, o EdgeDriver construtor também requer opções adicionais para especificar que queremos usar a versão do Chromium do Edge.

Definir os métodos auxiliares

Andy: Eu sei que precisaremos repetir duas ações ao longo dos testes:

  • Encontrar elementos na página, como os links em que clicamos e as janelas modais que esperamos que apareçam
  • Clicar em elementos na página, como os links que revelam as janelas modais e o botão que fecha cada modal

Vamos escrever dois métodos auxiliares, um para cada ação. Começaremos com o método que localiza um elemento na página.

Escrever o método auxiliar FindElement

Quando você localiza um elemento na página, ele geralmente é em resposta a algum outro evento, como o carregamento da página ou a inserção de informações pelo usuário. O selênio fornece a WebDriverWait classe, que permite que você espere que uma condição seja verdadeira. Se a condição não for verdadeira dentro do período de tempo determinado, WebDriverWait lança uma exceção ou erro. Podemos usar a classe para aguardar que um determinado elemento seja exibido e estar pronto para receber a WebDriverWait entrada do usuário.

Para localizar um elemento na página, use a By classe. A By classe fornece métodos que permitem que você encontre um elemento por seu nome, por seu nome de classe CSS, por sua marca HTML ou, no nosso caso, por seu id atributo.

Andy e Amita codificam o FindElement método auxiliar. Parece com este código:

private IWebElement FindElement(By locator, IWebElement parent = null, int timeoutSeconds = 10)
{
    // WebDriverWait enables us to wait for the specified condition to be true
    // within a given time period.
    return new WebDriverWait(driver, TimeSpan.FromSeconds(timeoutSeconds))
        .Until(c => {
            IWebElement element = null;
            // If a parent was provided, find its child element.
            if (parent != null)
            {
                element = parent.FindElement(locator);
            }
            // Otherwise, locate the element from the root of the DOM.
            else
            {
                element = driver.FindElement(locator);
            }
            // Return true after the element is displayed and is able to receive user input.
            return (element != null && element.Displayed && element.Enabled) ? element : null;
        });
}

Escreva o método auxiliar ClickElement

Andy: Em seguida, vamos escrever um método auxiliar que clica em links. O selênio fornece algumas maneiras de escrever esse método. Um deles é a IJavaScriptExecutor interface. Com ele, podemos clicar programaticamente em links usando JavaScript. Essa abordagem funciona bem porque pode clicar em links sem primeiro rolá-los para a exibição.

ChromeDriver, FirefoxDriver, e EdgeDriver cada implemento IJavaScriptExecutor. Precisamos converter o driver para essa interface e, em seguida, chamar ExecuteScript para executar o método JavaScript click() no objeto HTML subjacente.

Andy e Amita codificam o ClickElement método auxiliar. Parece com este código:

private void ClickElement(IWebElement element)
{
    // We expect the driver to implement IJavaScriptExecutor.
    // IJavaScriptExecutor enables us to execute JavaScript code during the tests.
    IJavaScriptExecutor js = driver as IJavaScriptExecutor;

    // Through JavaScript, run the click() method on the underlying HTML object.
    js.ExecuteScript("arguments[0].click();", element);
}

Amita: Eu gosto da ideia de adicionar esses métodos auxiliares. Eles parecem gerais o suficiente para usar em quase qualquer teste. Podemos adicionar mais métodos auxiliares mais tarde, conforme precisarmos deles.

Definir o método de teste

Andy: Agora, estamos prontos para definir o método de teste. Com base nos testes manuais que executamos anteriormente, vamos chamar esse método ClickLinkById_ShouldDisplayModalByIdde . É uma boa prática dar aos métodos de teste nomes descritivos que definam precisamente o que o teste realiza. Aqui, queremos clicar em um link definido por seu id atributo. Em seguida, queremos verificar se a janela modal adequada aparece, também usando seu id atributo.

Andy adiciona código inicial para o método de teste:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}

Andy: Antes de adicionarmos mais código, vamos definir o que esse teste deve fazer.

Amita: Eu posso lidar com essa parte. Queremos:

  1. Localize o link por seu id atributo e, em seguida, clique no link.
  2. Localize o modal resultante.
  3. Feche o modal.
  4. Verifique se o modal foi exibido com êxito.

Andy: Ótimo. Também precisaremos lidar com algumas outras coisas. Por exemplo, precisamos ignorar o teste se o driver não puder ser carregado, e precisamos fechar o modal somente se o modal foi exibido com sucesso.

Depois de reabastecer suas canecas de café, Andy e Amita adicionam código ao seu método de teste. Eles usam os métodos auxiliares que escreveram para localizar elementos da página e clicar em links e botões. Eis o resultado:

public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
    // Skip the test if the driver could not be loaded.
    // This happens when the underlying browser is not installed.
    if (driver == null)
    {
        Assert.Ignore();
        return;
    }

    // Locate the link by its ID and then click the link.
    ClickElement(FindElement(By.Id(linkId)));

    // Locate the resulting modal.
    IWebElement modal = FindElement(By.Id(modalId));

    // Record whether the modal was successfully displayed.
    bool modalWasDisplayed = (modal != null && modal.Displayed);

    // Close the modal if it was displayed.
    if (modalWasDisplayed)
    {
        // Click the close button that's part of the modal.
        ClickElement(FindElement(By.ClassName("close"), modal));

        // Wait for the modal to close and for the main page to again be clickable.
        FindElement(By.TagName("body"));
    }

    // Assert that the modal was displayed successfully.
    // If it wasn't, this test will be recorded as failed.
    Assert.That(modalWasDisplayed, Is.True);
}

Amita: A codificação parece ótima até agora. Mas como conectamos esse teste aos id atributos que coletamos anteriormente?

Andy: Ótima pergunta. Vamos lidar com isso a seguir.

Definir dados de caso de teste

Andy: No NUnit, você pode fornecer dados para seus testes de algumas maneiras. Aqui, usamos o TestCase atributo. Esse atributo usa argumentos que ele posteriormente passa de volta para o método de teste quando ele é executado. Podemos ter vários TestCase atributos que testam cada um um recurso diferente do nosso aplicativo. Cada TestCase atributo produz um caso de teste incluído no relatório que aparece no final de uma execução de pipeline.

Andy adiciona esses TestCase atributos ao método de teste. Esses atributos descrevem o botão Baixar jogo, uma das telas do jogo e o melhor jogador na tabela de classificação. Cada atributo especifica dois id atributos: um para o link clicar e outro para a janela modal correspondente.

// Download game
[TestCase("download-btn", "pretend-modal")]
// Screen image
[TestCase("screen-01", "screen-modal")]
// // Top player on the leaderboard
[TestCase("profile-1", "profile-modal-1")]
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{

...

Andy: Para cada TestCase atributo, o primeiro parâmetro é o atributo para o id link clicar. O segundo parâmetro é o id atributo para a janela modal que esperamos que apareça. Você pode ver como esses parâmetros correspondem aos argumentos de duas cadeias de caracteres em nosso método de teste.

Amita: Eu vejo isso. Com alguma prática, acho que posso adicionar os meus próprios testes. Quando podemos ver esses testes em execução em nosso pipeline?

Andy: Antes de enviarmos as alterações pelo pipeline, vamos primeiro verificar se o código é compilado e executado localmente. Confirmaremos e enviaremos alterações para o GitHub e as veremos passar pelo pipeline somente depois de verificarmos se tudo funciona. Vamos executar os testes localmente agora.