This is the multi-page printable view of this section.
Click here to print.
Return to the regular view of this page.
Diretrizes e recomendações
Guias e recomendações ao preparar soluções de testes com o projecto Selenium.
Uma nota sobre “Melhores práticas”: evitamos intencionalmente a frase “Melhores
Práticas” nesta documentação. Nenhuma abordagem funciona para todas as situações.
Preferimos a ideia de “Diretrizes e Recomendações”. Nós encorajamos
que você leia e decida cuidadosamente quais abordagens
funcionarão para você em seu ambiente específico.
O teste funcional é difícil de acertar por muitos motivos.
Como se o estado, a complexidade e as dependências do aplicativo não tornassem o teste suficientemente difícil,
lidar com navegadores (especialmente com incompatibilidades entre navegadores)
torna a escrita de bons testes um desafio.
Selenium fornece ferramentas para facilitar a interação funcional do usuário,
mas não o ajuda a escrever suítes de teste bem arquitetadas.
Neste capítulo, oferecemos conselhos, diretrizes e recomendações
sobre como abordar a automação funcional de páginas da web.
Este capítulo registra os padrões de design de software populares
entre muitos dos usuários do Selenium
que tiveram sucesso ao longo dos anos.
1 - Modelos de objetos de página
Objeto de página é um padrão de design que se tornou popular na automação de teste para
melhorar a manutenção de teste e reduzir a duplicação de código. Um objeto de página é uma
classe orientada a objetos que serve como uma interface para uma página de seu AUT.
Os testes então usam os métodos desta classe de objeto de página sempre que precisam
interagir com a interface do usuário dessa página. O benefício é que, se a IU mudar para
a página, os próprios testes não precisam ser alterados, apenas o código dentro do
o objeto da página precisa ser alterado. Posteriormente, todas as alterações para oferecer suporte a essa nova IU
estão localizados em um só lugar.
O padrão de design do objeto de página oferece as seguintes vantagens:
- Há uma separação clara entre o código de teste e o código específico da página, como
localizadores (ou seu uso se você estiver usando um mapa de interface do usuário) e layout.
- Existe um único repositório para os serviços ou operações oferecidos pela página
em vez de ter esses serviços espalhados pelos testes.
Em ambos os casos, isso permite qualquer modificação necessária devido a mudanças na IU
ser feito em um só lugar. Informações úteis sobre esta técnica podem ser encontradas em
vários blogs, já que esse ‘padrão de design de teste’ está se tornando amplamente usado. Nós
incentivamos o leitor que deseja saber mais a pesquisar blogs na internet
nesse assunto. Muitos escreveram sobre este padrão de design e podem fornecer
dicas úteis que vão além do escopo deste guia do usuário. Para começar, no entanto,
vamos ilustrar objetos de página com um exemplo simples.
Primeiro, considere um exemplo, típico de automação de teste, que não usa um
objeto de página:
/***
* Tests login feature
*/
public class Login {
public void testLogin() {
// preenche dados de login na página de entrada
driver.findElement(By.name("user_name")).sendKeys("userName");
driver.findElement(By.name("password")).sendKeys("my supersecret password");
driver.findElement(By.name("sign-in")).click();
// verifica que a tag h1 é "Hello userName" após o login
driver.findElement(By.tagName("h1")).isDisplayed();
assertThat(driver.findElement(By.tagName("h1")).getText(), is("Hello userName"));
}
}
Há dois problemas com esta abordagem.
- Não há separação entre o método de teste e os localizadores AUT (IDs neste exemplo);
ambos estão interligados em um único método. Se a IU da aplicação muda
seus identificadores, layout ou como um login é inserido e processado, o próprio teste
deve mudar.
- Os localizadores do ID estariam espalhados em vários testes, em todos os testes que precisassem
usar esta página de login.
Aplicando as técnicas de objeto de página, este exemplo poderia ser reescrito assim
no exemplo a seguir de um objeto de página para uma página de Sign-in.
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
/**
* Page Object encapsula a página de login.
*/
public class SignInPage {
protected WebDriver driver;
// <input name="user_name" type="text" value="">
private By usernameBy = By.name("user_name");
// <input name="password" type="password" value="">
private By passwordBy = By.name("password");
// <input name="sign_in" type="submit" value="SignIn">
private By signinBy = By.name("sign_in");
public SignInPage(WebDriver driver){
this.driver = driver;
if (!driver.getTitle().equals("Sign In Page")) {
throw new IllegalStateException("This is not Sign In Page," +
" current page is: " + driver.getCurrentUrl());
}
}
/**
* Login como um usuário válido
*
* @param userName
* @param password
* @return HomePage object
*/
public HomePage loginValidUser(String userName, String password) {
driver.findElement(usernameBy).sendKeys(userName);
driver.findElement(passwordBy).sendKeys(password);
driver.findElement(signinBy).click();
return new HomePage(driver);
}
}
e o objeto de página de uma página inicial pode ter a seguinte aparência.
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
/**
* Page Object encapsula a Home Page
*/
public class HomePage {
protected WebDriver driver;
// <h1>Hello userName</h1>
private By messageBy = By.tagName("h1");
public HomePage(WebDriver driver){
this.driver = driver;
if (!driver.getTitle().equals("Home Page of logged in user")) {
throw new IllegalStateException("This is not Home Page of logged in user," +
" current page is: " + driver.getCurrentUrl());
}
}
/**
* Get message (h1 tag)
*
* @return String message text
*/
public String getMessageText() {
return driver.findElement(messageBy).getText();
}
public HomePage manageProfile() {
// Encapsulamento da página para gerenciar a funcionalidade do perfil
return new HomePage(driver);
}
/* Mais métodos fornecendo o serviços representados pela Home Page
do usuário logado. Esses métodos por sua vez podem retornar mais Page Objects
por exemplo clicar no botão Compor Email poderia retornar um objeto ComposeMail */
}
Portanto, agora, o teste de login usaria esses dois objetos de página da seguinte maneira.
/***
* Tests login feature
*/
public class TestLogin {
@Test
public void testLogin() {
SignInPage signInPage = new SignInPage(driver);
HomePage homePage = signInPage.loginValidUser("userName", "password");
assertThat(homePage.getMessageText(), is("Hello userName"));
}
}
Há muita flexibilidade em como os objetos de página podem ser projetados, mas
existem algumas regras básicas para obter a manutenção desejada de seu
código de teste.
Os próprios objetos de página nunca devem fazer verificações ou afirmações. Isto é
parte do seu teste e deve estar sempre dentro do código do teste, nunca em um objeto de página.
O objeto da página conterá a representação da página, e o
serviços que a página fornece por meio de métodos, mas nenhum código relacionado ao que está sendo
testado deve estar dentro do objeto de página.
Há uma única verificação que pode e deve estar dentro do objeto de página e que é para verificar se a página
e, possivelmente, elementos críticos em a página, foram carregados corretamente.
Esta verificação deve ser feita enquanto instanciar o objeto de página.
Nos exemplos acima, ambos SignInPage e os construtores da HomePage verificam se a página
esperada está disponível e pronta para solicitações do teste.
Um objeto de página não precisa necessariamente representar todas as partes da página em si.
Os mesmos princípios usados para objetos de página podem ser usados para
criar “Objetos de Componente de Página” que representam pedaços discretos da
página e podem ser incluídos em objetos de página. Esses objetos de componentes podem
fornecer referências aos elementos dentro desses blocos discretos, e
métodos para utilizar a funcionalidade fornecida por eles. Você também pode
aninhar objetos de componentes dentro de outros objetos de componentes para páginas mais complexas.
Se uma página na aplicação tem vários componentes, ou
componentes usados em todo o site (por exemplo, uma barra de navegação), então
pode melhorar a manutenção e reduzir a duplicação de código.
Existem outros padrões de design que também podem ser usados em testes. Alguns usam um
Page Factory para instanciar seus objetos de página. Discutir tudo isso é
além do escopo deste guia do usuário. Aqui, queremos apenas apresentar o
conceitos para tornar o leitor ciente de algumas coisas que podem ser feitas. Como
foi mencionado anteriormente, muitos escreveram sobre este tópico e nós encorajamos o
leitor para pesquisar blogs sobre esses tópicos.
2 - Linguagem específica de domínio (DSL)
Uma linguagem específica de domínio (DSL) é um sistema que fornece ao usuário
um meio expressivo de resolver um problema. Ele permite a um usuário
interagir com o sistema em seus termos - não apenas na linguagem do programador.
Seus usuários, em geral, não se importam com a aparência do seu site. Eles não
preocupam-se com a decoração, animações ou gráficos. Eles
deseja usar seu sistema para empurrar seus novos funcionários através do
processo com dificuldade mínima; eles querem reservar uma viagem para o Alasca;
eles querem configurar e comprar unicórnios com desconto. Seu trabalho como
testador deve chegar o mais perto possível de “capturar” essa mentalidade.
Com isso em mente, começamos a “modelar” o aplicativo que você está
trabalhando, de modo que os scripts de teste (o único proxy de pré-lançamento do usuário) “fala a linguagem” e representa o usuário.
Com Selenium, DSL é geralmente representado por métodos, escritos para fazer
a API simples e legível - eles permitem um relatório entre o
desenvolvedores e as partes interessadas (usuários, proprietários de produtos, negócios
especialistas em inteligência, etc.).
Benefícios
- Legível: As partes interessadas da empresa podem entendê-lo.
- Gravável: Fácil de escrever, evita duplicações desnecessárias.
- Extensível: Funcionalidade pode (razoavelmente) ser adicionada
sem quebrar contratos e funcionalidades existentes.
- Manutenção: Deixando os detalhes de implementação fora do teste
casos, você está bem isolado contra alterações no AUT *.
Java
Aqui está um exemplo de um método DSL razoável em Java.
Por questão de brevidade, ele assume que o objeto driver
é pré-definido
e está disponível para o método.
/**
* Recebe um username e password, prrenche os campos, e clica em "login".
* @return Uma instância de AccountPage
*/
public AccountPage loginAsUser(String username, String password) {
WebElement loginField = driver.findElement(By.id("loginField"));
loginField.clear();
loginField.sendKeys(username);
// Preenche o campo password. O localizador que estamos usando é "By.id", e devemos
// definí-lo em algum outro lugar dentro da Classe.
WebElement passwordField = driver.findElement(By.id("password"));
passwordField.clear();
passwordField.sendKeys(password);
// Clica o botão de login, que possui o id "submit".
driver.findElement(By.id("submit")).click();
// Cria e retorna uma nova instância de AccountPage (via o Selenium
// PageFactory embutido).
return PageFactory.newInstance(AccountPage.class);
}
Este método abstrai completamente os conceitos de campos de entrada,
botões, cliques e até páginas do seu código de teste. Usando este
abordagem, tudo o que o testador precisa fazer é chamar esse método. Isto dá
uma vantagem de manutenção: se os campos de login mudaram, você
teria apenas que alterar esse método - não seus testes.
public void loginTest() {
loginAsUser("cbrown", "cl0wn3");
// Agora que estamos logados, fazemos alguma outra coisa--como usamos uma DSL para suportar
// nossos testadores, é apenas escolher um dos métodos disponíveis.
do.something();
do.somethingElse();
Assert.assertTrue("Algo deveria ter sido feito!", something.wasDone());
// Note que ainda não nos referimos a nenhum botão ou web control nesse
// script...
}
Vale a pena repetir: um de seus principais objetivos deve ser escrever um
API que permite que seus testes resolvam o problema em questão, e NÃO
o problema da IU. A IU é uma preocupação secundária para o seu
usuários - eles não se importam com a interface do usuário, eles apenas querem fazer seu trabalho
feito. Seus scripts de teste devem ser lidos como uma lista de itens sujos que o usuário deseja FAZER e as coisas que deseja SABER. Os testes
não devem se preocupar com COMO a interface do usuário exige que você vá
sobre isso.
*AUT: Application under test
3 - Gerando estado da aplicação
Selenium não deve ser usado para preparar um caso de teste. Tudo as
ações repetitivas e preparações para um caso de teste devem ser feitas por meio de outros
métodos. Por exemplo, a maioria das IUs da web tem autenticação (por exemplo, um formulário de login). Eliminar o login via navegador da web antes de cada teste irá
melhorar a velocidade e estabilidade do teste. Um método deve ser
criado para obter acesso à AUT* (por exemplo, usando uma API para fazer login e definir um
cookie). Além disso, a criação de métodos para pré-carregar dados para
o teste não deve ser feito usando Selenium. Como dito anteriormente,
APIs existentes devem ser aproveitadas para criar dados para a AUT *.
*AUT: Application under test
4 - Simulação de serviços externos
Eliminar as dependências de serviços externos melhorará muito
a velocidade e estabilidade de seus testes.
5 - Relatórios melhorados
O Selenium não foi projetado para relatar sobre o status de casos de teste. Aproveitar
os recursos de relatórios integrados de frameworks de teste unitários é um bom começo.
A maioria dos frameworks de teste unitários podem gerar relatórios formatados em xUnit ou HTML.
Relatórios xUnit são populares para importar resultados para um servidor de integração contínua
(CI) como Jenkins, Travis, Bamboo, etc. Aqui estão alguns links
para obter mais informações sobre resultados de relatórios em vários idiomas.
NUnit 3 Console Runner
NUnit 3 Console Command Line
xUnit getting test results in TeamCity
xUnit getting test results in CruiseControl.NET
xUnit getting test results in Azure DevOps
6 - Evite compartilhamento de estado
Embora mencionado em vários lugares, vale a pena mencionar novamente. Garanta que
os testes são isolados uns dos outros.
-
Não compartilhe dados de teste. Imagine vários testes em que cada um consulta o banco de dados
para pedidos válidos antes de escolher um para executar uma ação. Caso dois testes
peguem a mesma ordem, provavelmente você obterá um comportamento inesperado.
-
Limpe dados desatualizados no aplicativo que podem ser obtidos por outro
teste, por exemplo registros de pedidos inválidos.
-
Crie uma nova instância do WebDriver por teste. Isso ajuda a garantir o isolamento do teste
e torna a paralelização mais simples.
7 - Tips on working with locators
When to use which locators and how best to manage them in your code.
Take a look at examples of the supported locator strategies.
No geral, se os IDs de HTML estiverem disponíveis, únicos e consistentemente
previsíveis, eles são o método preferido para localizar um elemento
uma página. Eles tendem a trabalhar muito rapidamente e dispensar muito processamento
que vem com travessias de DOM complicadas.
Se IDs exclusivos não estiverem disponíveis, um seletor CSS bem escrito é o
método preferido de localização de um elemento. XPath funciona bem como CSS
seletores, mas a sintaxe é complicada e frequentemente difícil de
depurar. Embora os seletores XPath sejam muito flexíveis, eles não são tipicamente testados em performance por fornecedores de navegadores e tendem a ser bastante lentos.
As estratégias de seleção baseadas em linkText e partialLinkText têm
desvantagens porque eles só funcionam em elementos de link. Além disso, eles
chamam seletores querySelectorAll internamente no WebDriver.
O nome da tag pode ser uma maneira perigosa de localizar elementos. tem
frequentemente, vários elementos da mesma tag presentes na página.
Isso é útil principalmente ao chamar o método _findElements(By) _ que
retorna uma coleção de elementos.
A recomendação é manter seus localizadores compactos e
legíveis quanto possível. Pedir ao WebDriver para percorrer a estrutura DOM
é uma operação cara, e quanto mais você pode restringir o escopo de
sua pesquisa, melhor.
8 - Independência de Testes
Escreva cada teste como sua própria unidade. Escreva os testes de uma forma que não seja
dependente de outros testes para concluir:
Digamos que existe um sistema de gerenciamento de conteúdo com o qual você pode criar
algum conteúdo personalizado que então aparece em seu site como um módulo após
publicação, e pode levar algum tempo para sincronizar entre o CMS e a aplicação.
Uma maneira errada de testar seu módulo é que o conteúdo seja criado e
publicado em um teste e, em seguida, verificar o módulo em outro teste. Este teste
não é viável, pois o conteúdo pode não estar disponível imediatamente para o
outro teste após a publicação.
Em vez disso, você pode criar um conteúdo stub que pode ser ligado e desligado
dentro do teste e use-o para validar o módulo. Contudo,
para a criação de conteúdo, você ainda pode ter um teste separado.
9 - Considere usar uma API fluente
Martin Fowler cunhou o termo “API Fluent”. Selenium já
implementa algo assim em sua classe FluentWait
, que é
pretende ser uma alternativa à classe padrão Wait
.
Você pode habilitar o padrão de design de API fluente em seu objeto de página
e, em seguida, consulte a página de pesquisa do Google com um snippet de código como este:
driver.get( "http://www.google.com/webhp?hl=en&tab=ww" );
GoogleSearchPage gsp = new GoogleSearchPage();
gsp.withFluent().setSearchString().clickSearchButton();
A classe de objeto da página do Google com este comportamento fluente
pode ser assim:
public class GoogleSearchPage extends LoadableComponent<GoogleSearchPage> {
private final WebDriver driver;
private GSPFluentInterface gspfi;
public class GSPFluentInterface {
private GoogleSearchPage gsp;
public GSPFluentInterface(GoogleSearchPage googleSearchPage) {
gsp = googleSearchPage;
}
public GSPFluentInterface clickSearchButton() {
gsp.searchButton.click();
return this;
}
public GSPFluentInterface setSearchString( String sstr ) {
clearAndType( gsp.searchField, sstr );
return this;
}
}
@FindBy(id = "gbqfq") private WebElement searchField;
@FindBy(id = "gbqfb") private WebElement searchButton;
public GoogleSearchPage(WebDriver driver) {
gspfi = new GSPFluentInterface( this );
this.get(); // Se load() falhar, chama isLoaded() até que a página termine de carregar
PageFactory.initElements(driver, this); // Inicializa WebElements na página
}
public GSPFluentInterface withFluent() {
return gspfi;
}
public void clickSearchButton() {
searchButton.click();
}
public void setSearchString( String sstr ) {
clearAndType( searchField, sstr );
}
@Override
protected void isLoaded() throws Error {
Assert.assertTrue("Google search page is not yet loaded.", isSearchFieldVisible() );
}
@Override
protected void load() {
if ( isSFieldPresent ) {
Wait<WebDriver> wait = new WebDriverWait( driver, Duration.ofSeconds(3) );
wait.until( visibilityOfElementLocated( By.id("gbqfq") ) ).click();
}
}
}
10 - Navegador novo por teste
Comece cada teste a partir de um estado limpo conhecido.
Idealmente, ligue uma nova máquina virtual para cada teste.
Se ligar uma nova máquina virtual não for prático,
pelo menos inicie um novo WebDriver para cada teste.
Most browser drivers like GeckoDriver and ChromeDriver will start with a clean
known state with a new user profile, by default.
WebDriver driver = new FirefoxDriver();