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&amp;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();