Escrever os testes da interface do usuário
Nesta seção, ajude o Paulo e a Marina a escreverem os testes do Selenium que verificam os comportamentos da interface do usuário descritos por Marina.
Marina executa normalmente os testes no Chrome, no Firefox e no Microsoft Edge. Aqui, você fará o mesmo. O agente hospedado pela Microsoft que você usará está pré-configurado para funcionar com cada um desses navegadores.
Buscar o branch do GitHub
Nesta seção, você efetua fetch do branch selenium
do GitHub. Em seguida, você fará check-out ou alternará para esse branch. O conteúdo do branch ajudará você a acompanhar os testes que o Paulo e a Marina escreveram.
Esse branch contém o projeto do Space Game com o qual você trabalhou nos módulos anteriores. Ele também contém uma configuração do Azure Pipelines com a qual começar.
No Visual Studio Code, abra o terminal integrado.
Para baixar um branch chamado
selenium
no repositório da Microsoft, alterne para esse branch e execute os seguintes comandosgit fetch
egit checkout
:git fetch upstream selenium git checkout -B selenium upstream/selenium
Dica
Se você seguiu o teste manual da Amita na unidade anterior, talvez já tenha executado esses comandos. Se você já os executou na unidade anterior, ainda poderá executá-los novamente agora.
Lembre-se de que upstream refere-se ao repositório GitHub da Microsoft. A configuração do Git do seu projeto compreende o repositório remoto de upstream porque você configurou essa relação. Você a configurou ao criar o fork do projeto no repositório da Microsoft e a clonou localmente.
Em breve, você efetuará push desse branch para seu repositório do GitHub, conhecido como
origin
.Opcionalmente, no Visual Studio Code, abra o arquivo azure-pipelines.yml. Familiarize-se com a configuração inicial.
A configuração é semelhante àquelas que você criou nos módulos anteriores deste roteiro de aprendizagem. Ela compila apenas a configuração de Versão do aplicativo. Para resumir, ela também omite os gatilhos, as aprovações manuais e os testes que você configurou nos módulos anteriores.
Observação
Uma configuração mais robusta pode especificar os branches que participam do processo de build. Por exemplo, para ajudar a verificar a qualidade do código, você pode executar testes de unidade cada vez que efetuar push de uma alteração em qualquer branch. Você também pode implantar o aplicativo em um ambiente que executa testes mais exaustivos. Porém, realize essa implantação somente quando você tiver uma solicitação de pull, uma versão Release Candidate ou quando mesclar o código com o principal.
Para obter mais informações, confira Implement a code workflow in your build pipeline by using Git and GitHub e Gatilhos de pipeline de build.
Escrever o código de teste de unidade
Marina está ansiosa para aprender a escrever o código que controla o navegador da Web.
Ela e Paulo trabalharão em conjunto para escrever os testes do Selenium. Paulo já configurou um projeto NUnit vazio. Durante todo o processo, eles consultam a documentação do Selenium, alguns tutoriais online e as anotações que eles realizaram quando a Marina fez os testes manualmente. No final deste módulo, você encontrará mais recursos para ajudar você a realizar o processo.
Vamos examinar o processo que Paulo e Marina usaram para escrever os testes. Você pode acompanhar abrindo HomePageTest.cs no diretório Tailspin.SpaceGame.Web.UITests no Visual Studio Code.
Definir a classe HomePageTest
Paulo: 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 nossa classe de HomePageTest
. Nessa classe, colocaremos todos os nossos testes relacionados à home page.
Paulo adiciona esse código ao HomePageTest.cs:
public class HomePageTest
{
}
Paulo: Precisamos marcar essa classe como public
para que ela esteja disponível para a estrutura NUnit.
Adicionar a variável de membro IWebDriver
Paulo: depois, precisamos de uma variável de membro IWebDriver
. O 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.
Marina: Já ouvi falar de interfaces em programação. Você pode me contar mais?
Paulo: Imagine uma interface como uma especificação ou um blueprint 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 criam uma ou mais classes concretas que implementam essa interface. O Selenium fornece as classes concretas que precisamos.
Este diagrama mostra a interface IWebDriver
e algumas das classes que implementam essa interface:
O diagrama mostra três dos métodos que o IWebDriver
fornece: Navigate
, FindElement
e Close
.
Nas três classes mostradas aqui, ChromeDriver
, FirefoxDriver
e EdgeDriver
, cada uma implementa o IWebDriver
e os métodos dele. Há outras classes, como SafariDriver
, que também implementam o IWebDriver
. Cada classe de driver pode controlar o navegador da Web que ela representa.
Paulo adiciona uma variável de membro chamada driver
à classe HomePageTest
, da seguinte maneira:
public class HomePageTest
{
private IWebDriver driver;
}
Definir os acessórios de teste
Paulo: Queremos executar todo o conjunto de testes no Chrome, no Firefox e no Edge. No NUnit, podemos usar os acessórios de teste para executar todo o conjunto de testes várias vezes, uma vez para cada navegador no qual desejamos testar.
No NUnit, você usa o atributo TestFixture
para definir os seus acessórios de teste. O Paulo adiciona esses três acessórios de teste à classe HomePageTest
:
[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
private IWebDriver driver;
}
Paulo: em seguida, precisamos definir um construtor para nossa classe de teste. O construtor é chamado quando o NUnit cria uma instância dessa classe. Como argumento, o construtor usa a cadeia de caracteres que anexamos aos nossos acessórios de teste. Veja abaixo a aparência do código:
[TestFixture("Chrome")]
[TestFixture("Firefox")]
[TestFixture("Edge")]
public class HomePageTest
{
private string browser;
private IWebDriver driver;
public HomePageTest(string browser)
{
this.browser = browser;
}
}
Paulo: adicionamos a variável de membro browser
para que possamos usar o nome atual do navegador no nosso código de configuração. Após realizar isso, vamos escrever o código de configuração.
Definir o método Setup
Paulo: em seguida, precisamos atribuir nossa variável de membro IWebDriver
a uma instância de classe que implementa essa interface no navegador no qual estamos testando. As classes ChromeDriver
, FirefoxDriver
e EdgeDriver
implementam essa interface no Chrome, no Firefox e no Microsoft Edge, respectivamente.
Vamos criar um método chamado Setup
, que define a variável driver
. Usamos o atributo OneTimeSetUp
para dizer ao NUnit para executar esse método uma vez por acessório de teste.
[OneTimeSetUp]
public void Setup()
{
}
No método Setup
, podemos usar uma instrução switch
para atribuir a variável de membro driver
à 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 de cada classe de driver usa um caminho opcional para o software do driver que o Selenium precisa controlar no navegador da Web. Posteriormente, discutiremos a função das variáveis de ambiente mostradas aqui.
Neste exemplo, o construtor EdgeDriver
também requer opções adicionais para especificar que desejamos usar a versão Chromium do Edge.
Definir os métodos auxiliares
Paulo: Sei que precisaremos repetir duas ações em todos os testes:
- Localizar elementos na página, como os links que clicamos e as janelas modais que esperamos que sejam exibidas
- Clicar em elementos na página, como os links que revelam as janelas modais e o botão que fecha cada janela modal
Vamos escrever dois métodos auxiliares, um para cada ação. Vamos começar 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 é normalmente uma resposta a algum outro evento, como o carregamento da página ou a inserção de informações pelo usuário. O Selenium fornece a classe WebDriverWait
, que permite que você aguarde até que uma condição seja verdadeira. Se a condição não for verdadeira no período determinado, o WebDriverWait
vai gerar uma exceção ou um erro. Podemos usar a classe WebDriverWait
para aguardar até que um determinado elemento seja exibido e esteja pronto para receber a entrada de usuário.
Para localizar um elemento na página, use a classe By
. A classe By
fornece métodos que permitem encontrar um elemento por nome, por nome de classe CSS, por marca HTML ou, no nosso caso, pelo atributo id
.
O Paulo e a Marina codificam o método auxiliar FindElement
. É semelhante a 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;
});
}
Escrever o método auxiliar ClickElement
Paulo: em seguida, vamos escrever um método auxiliar que clica em links. O Selenium fornece algumas maneiras de escrever esse método. Uma delas é a interface IJavaScriptExecutor
. Com ela, podemos clicar em links de modo programático usando o JavaScript. Essa abordagem funciona bem, pois ela pode clicar em links sem primeiro rolá-los até que eles sejam exibidos.
ChromeDriver
, FirefoxDriver
e EdgeDriver
, cada um implementa IJavaScriptExecutor
. Precisamos converter o driver para essa interface e chamar ExecuteScript
para executar o método JavaScript click()
no objeto HTML subjacente.
O Paulo e a Marina codificam o método auxiliar ClickElement
. É semelhante a 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);
}
Marina: Gosto da ideia de adicionar esses métodos auxiliares. Eles parecem genéricos o suficiente para serem usados em quase todos os testes. Podemos adicionar mais métodos auxiliares posteriormente, conforme precisarmos deles.
Definir o método de teste
Paulo: agora estamos prontos para definir o método de teste. Com base nos testes manuais que executamos anteriormente, vamos chamar esse método de ClickLinkById_ShouldDisplayModalById
. É uma prática recomendada dar aos métodos de teste nomes descritivos que definem precisamente o que o teste realiza. Aqui, queremos clicar em um link definido pelo respectivo atributo id
. Então, queremos verificar se a janela modal apropriada é exibida, usando também o atributo id
.
O Paulo adiciona o código inicial do método de teste:
public void ClickLinkById_ShouldDisplayModalById(string linkId, string modalId)
{
}
Paulo: Antes de adicionarmos mais código, vamos definir o que esse teste deve fazer.
Marina: Consigo fazer essa parte. Queremos:
- Localizar o link pelo atributo
id
e clicar nele. - Localizar a janela modal resultante.
- Fechar a janela.
- Verifique se a janela modal foi exibida com êxito.
Paulo: Ótimo. Também precisaremos lidar com algumas outras coisas. Por exemplo, precisaremos ignorar o teste se o driver não puder ser carregado e precisaremos fechar a janela modal somente se ela tiver sido exibida com êxito.
Depois de encherem as canecas de café, Paulo e Marina adicionam um código ao 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);
}
Marina: Até agora, a codificação está dando certo. Mas como podemos conectar esse teste aos atributos id
que coletamos anteriormente?
Paulo: Ótima pergunta. Faremos isso em seguida.
Definir dados de caso de teste
Paulo: No NUnit, você pode fornecer dados aos seus testes de algumas maneiras. Nesse caso, usamos o atributo TestCase
. Esse atributo usa argumentos que depois são passados para o método de teste quando ele é executado. Podemos ter vários atributos TestCase
e cada um testar um recurso diferente do nosso aplicativo. Cada atributo TestCase
produz um caso de teste que é incluso no relatório exibido no final de uma execução de pipeline.
O Paulo adiciona esses atributos TestCase
ao método de teste. Esses atributos descrevem o botão Baixar jogo, uma das telas do jogo e o melhor jogador no placar de líderes. Cada atributo especifica dois atributos id
: um para o link a ser clicado e um 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)
{
...
Paulo: Para cada atributo TestCase
, o primeiro parâmetro é o atributo id
no qual o link deve clicar. O segundo parâmetro é o atributo id
da janela modal que esperamos que seja exibida. Veja como esses parâmetros correspondem aos dois argumentos de cadeia de caracteres no método de teste.
Marina: Entendi. Com alguma prática, acho que consigo adicionar meus próprios testes. Quando poderemos ver esses testes em execução no nosso pipeline?
Paulo: Antes de enviarmos por push as alterações pelo pipeline, primeiro verificaremos se o código é compilado e executado localmente. Vamos fazer commit e enviar as alterações por push para o GitHub e vê-las passar pelo pipeline somente depois de verificarmos se tudo está funcionando. Vamos executar os testes localmente agora.