Escrever os testes da interface do usuário
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.
No Visual Studio Code, abra o terminal integrado.
Para baixar uma ramificação nomeada
selenium
do repositório da Microsoft, alterne para essa ramificação e execute os seguintesgit fetch
comandosgit 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
.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:
O diagrama mostra três dos métodos que IWebDriver
fornece: Navigate
, FindElement
, e Close
.
As três classes mostradas aqui, , , e , ChromeDriver
FirefoxDriver
cada implemento IWebDriver
e EdgeDriver
seus 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 ChromeDriver
classes , , e implementam essa interface para Chrome, Firefox e Edge, FirefoxDriver
EdgeDriver
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_ShouldDisplayModalById
de . É 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:
- Localize o link por seu
id
atributo e, em seguida, clique no link. - Localize o modal resultante.
- Feche o modal.
- 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.