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

Nota: esta página reuniu conteúdos de várias fontes, incluindo o Selenium wiki

Visão geral

Dentro da interface de usuário (UI) do seu aplicativo web, existem áreas com as quais seus testes interagem. O Page Object modela apenas essas áreas como objetos dentro do código de teste. Isso reduz a quantidade de código duplicado e significa que, se a UI mudar, a correção precisará ser aplicada apenas em um lugar.

Page Object é um padrão de design (Design Pattern) que se tornou popular na automação de testes para melhorar a manutenção de testes e reduzir a duplicação de código. Page Object é uma classe orientada a objetos que serve como interface para uma página do seu AUT (Aplicativo Sob Teste). Os testes usam então os métodos desta classe de Page Object sempre que precisam interagir com a UI dessa página. A vantagem é que, se a UI da página mudar, os próprios testes não precisam mudar, apenas o código dentro do Page Object precisa mudar. Subsequentemente, todas as mudanças para suportar essa nova UI estão localizadas em um lugar.

Vantagens

  • Existe uma separação bem definida entre o código do teste e o código da página especifica.
  • Existe um repositório único para os serviços ou operações que a página oferece, em vez de ter esses serviços espalhados pelos testes.

Em ambos os casos, isso permite que quaisquer modificações necessárias devido a mudanças na UI sejam feitas em um lugar somente. Informações úteis sobre esta técnica podem ser encontradas em vários blogs, pois este ‘padrão de design de teste (test design pattern)’ está se tornando amplamente utilizado. Encorajamos os leitores que desejam saber mais a pesquisar na internet por blogs sobre este assunto. Muitos já escreveram sobre este padrão de design e podem fornecer dicas úteis além do escopo deste guia do usuário. Para começar, vamos ilustrar Page Object com um exemplo simples.

Exemplos

Primeiro, considere um exemplo, típico da automação de testes, que não usa um objeto de página:

/***
 * Testes da funcionalidade de login
 */
public class Login {

  public void testLogin() {
    // preencha os 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();

    // verifique se 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"));
  }
}

Existem dois problemas com essa abordagem.

  • Não há separação entre o método de teste e os localizadores do aplicativo em teste (IDs neste exemplo); ambos estão entrelaçados em um único método. Se a UI do aplicativo em teste muda seus identificadores, layout ou como um login é inserido e processado, o próprio teste deve mudar.
  • Os localizadores ID estariam espalhados em vários testes, em todos os testes que tivessem que usar esta página de login.

Aplicando as técnicas de Page Object, este exemplo poderia ser reescrito da seguinte forma no exemplo para uma página de login.

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());
    }
  }

  /**
    * Faz login como um usuário válido
    *
    * @param userName
    * @param password
    * @return pbjeto da Pagina Inicial
    */
  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 para uma página inicial poderia parecer assim.

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;

/**
 * Page Object encapsula a Página Inicial
 */
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());
    }
  }

  /**
    * Obtém a mensagem (tag h1)
    *
    * @return String da mensagem de texto
    */
  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 que oferecem os serviços representados pela Página inicial do usuário logado. Estes métodos por sua vez podem retornar mais Page Object, por exemplo, clicar no botão "Compor email" pode retornar um objeto da classe ComposeMail */
}

Então agora, o teste de login usaria esses dois objetos de página da seguinte maneira.

/***
 * Testes da funcionalidade de login
 */
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 o Page Object pode ser projetado, mas existem algumas regras básicas para obter a manutenibilidade desejada do seu código de teste.

Afirmações em Page Objects

Os Page Objects em si nunca devem fazer verificações ou afirmações. Isso faz parte do seu teste e sempre deve estar dentro do código do teste, nunca em um objeto de página. O objeto de página conterá a representação da página e os 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 isso é para verificar se a página e possivelmente elementos críticos na página, foram carregados corretamente. Essa verificação deve ser feita ao instanciar o objeto de página. Nos exemplos acima, tanto os construtores SignInPage quanto HomePage verificam se a página esperada está disponível e pronta para solicitações do teste.

Objetos Componentes de Página (Page Component Object)

Page Object não precisa necessariamente representar todas as partes de uma página. Isso foi notado por Martin Fowler nos primeiros dias, enquanto cunhava o termo “objetos de painel (panel objects)”.

Os mesmos princípios usados para objetos de página podem ser usados para criar “Objetos Componente de Página”, como foi chamado mais tarde, que representam partes discretas da página e podem ser incluídos em Page Object. Esses objetos de componente podem fornecer referências aos elementos dentro dessas partes discretas e métodos para aproveitar a funcionalidade ou comportamento fornecidos por eles.

Por exemplo, uma página de Produtos tem vários produtos.

<!-- Página de Produtos -->
<div class="header_container">
    <span class="title">Products</span>
</div>

<div class="inventory_list">
    <div class="inventory_item">
    </div>
    <div class="inventory_item">
    </div>
    <div class="inventory_item">
    </div>
    <div class="inventory_item">
    </div>
    <div class="inventory_item">
    </div>
    <div class="inventory_item">
    </div>
</div>

Cada produto é um componente da página de Produtos.

<!-- Inventory Item -->
<div class="inventory_item">
    <div class="inventory_item_name">Backpack</div>
    <div class="pricebar">
        <div class="inventory_item_price">$29.99</div>
        <button id="add-to-cart-backpack">Add to cart</button>
    </div>
</div>

A página de Produtos “TEM-UMA (HAS-A)” lista de produtos. This object relationship is called Composition. Essa relação de objeto é chamada de Composição. Em termos mais simples, algo é composto de outra coisa.

public abstract class BasePage {
    protected WebDriver driver;

    public BasePage(WebDriver driver) {
        this.driver = driver;
    }
}

// Page Object
public class ProductsPage extends BasePage {
    public ProductsPage(WebDriver driver) {
        super(driver);
        // Sem afirmações, lança uma exceção se o elemento não for carregado
        new WebDriverWait(driver, Duration.ofSeconds(3))
            .until(d -> d.findElement(By.className("header_container")));
    }

    // Retornar uma lista de produtos é um serviço da página
    public List<Product> getProducts() {
        return driver.findElements(By.className("inventory_item"))
            .stream()
            .map(e -> new Product(e)) // Mapeia WebElement para um componente do produto
            .toList();
    }

    // Retorna um produto específico usando uma função booleana (predicado)
    // Este é o padrão de estratégia comportamental do GoF
    public Product getProduct(Predicate<Product> condition) {
        return getProducts()
            .stream()
            .filter(condition) // Filtra por nome de produto ou preço
            .findFirst()
            .orElseThrow();
    }
}

O objeto do componente Produto é usado dentro do objeto de página Produtos.

public abstract class BaseComponent {
    protected WebElement root;

    public BaseComponent(WebElement root) {
        this.root = root;
    }
}

// Objeto Componente da Página (Page Component Object)
public class Product extends BaseComponent {
    // O elemento raiz contém todo o componente
    public Product(WebElement root) {
        super(root); // inventory_item
    }

    public String getName() {
        // A localização de um elemento começa na raiz do componente
        return root.findElement(By.className("inventory_item_name")).getText();
    }

    public BigDecimal getPrice() {
        return new BigDecimal(
                root.findElement(By.className("inventory_item_price"))
                    .getText()
                    .replace("$", "")
            ).setScale(2, RoundingMode.UNNECESSARY); // Higienização e formatação
    }

    public void addToCart() {
        root.findElement(By.id("add-to-cart-backpack")).click();
    }
}

Agora, o teste dos produtos usaria o Page Objecto e o Page Component Obeject da seguinte maneira.

public class ProductsTest {
    @Test
    public void testProductInventory() {
        var productsPage = new ProductsPage(driver); // page object
        var products = productsPage.getProducts();
        assertEquals(6, products.size()); // esperado, atual
    }
    
    @Test
    public void testProductPrices() {
        var productsPage = new ProductsPage(driver);

        // Passa uma expressão lambda (predicado) para filtrar a lista de produtos
        // O predicado ou "estratégia" é o comportamento passado como parâmetro
        var backpack = productsPage.getProduct(p -> p.getName().equals("Backpack")); // page component object
        var bikeLight = productsPage.getProduct(p -> p.getName().equals("Bike Light"));

        assertEquals(new BigDecimal("29.99"), backpack.getPrice());
        assertEquals(new BigDecimal("9.99"), bikeLight.getPrice());
    }
}

A página e o componente são representados por seus próprios objetos. Ambos os objetos têm apenas métodos para os serviços que oferecem, o que corresponde à aplicação do mundo real na programação orientada a objetos.

Você pode até aninhar objetos de componentes dentro de outros objetos de componentes para páginas mais complexas. Se uma página na AUT tiver vários componentes, ou componentes comuns usados em todo o site (por exemplo, uma barra de navegação), então isso pode melhorar a manutenibilidade e reduzir a duplicação de código.

Outros Padrões de Projeto (Design Patterns) Usados em Testes

Existem outros padrões de projeto que também podem ser usados em testes. Discutir todos esses está além do escopo deste guia do usuário. Aqui, apenas queremos introduzir os conceitos para tornar o leitor ciente de algumas das coisas que podem ser feitas. Como foi mencionado anteriormente, muitos escreveram sobre este tópico e encorajamos o leitor a procurar blogs sobre esses tópicos.

Notas de Implementação

Page Objects podem ser pensados como se estivessem voltados para duas direções simultaneamente. Voltado para o desenvolvedor de um teste, eles representam os serviços oferecidos por uma página específica. Virado para longe do desenvolvedor, eles devem ser a única coisa que tem um conhecimento profundo da estrutura do HTML de uma página (ou parte de uma página). É mais simples pensar nos métodos de um Page Object como oferecendo os “serviços” que uma página oferece, em vez de expor os detalhes e a mecânica da página. Como exemplo, pense na caixa de entrada de qualquer sistema de email baseado na web. Entre os serviços que oferece estão a capacidade de compor um novo e-mail, escolher ler um único e-mail e listar as linhas de assunto dos e-mails na caixa de entrada. Como esses são implementados não deve importar para o teste.

Porque estamos encorajando o desenvolvedor de um teste a tentar pensar sobre os serviços com os quais estão interagindo em vez da implementação, os Page Objects raramente devem expor a instância subjacente do WebDriver. Para facilitar isso, os métodos no Page Object devem retornar outros Page Objects. Isso significa que podemos efetivamente modelar a jornada do usuário em nosso aplicativo. Também significa que se a maneira como as páginas se relacionam entre si mudar (como quando a página de login pede ao usuário para alterar sua senha na primeira vez que eles entram em um serviço quando antes não fazia isso), simplesmente mudando a assinatura do método apropriado fará com que os testes falhem em compilação. Colocando de outra forma; podemos dizer quais testes falhariam sem precisar executá-los quando mudamos a relação entre as páginas e refletimos isso nos PageObjects.

Uma consequência dessa abordagem é que pode ser necessário modelar (por exemplo) tanto um login bem-sucedido quanto um mal-sucedido; ou um clique poderia ter um resultado diferente dependendo do estado do aplicativo. Quando isso acontece, é comum ter vários métodos no PageObject:

public class LoginPage {
    public HomePage loginAs(String username, String password) {
        // ... mágica inteligente acontece aqui
    }
    
    public LoginPage loginAsExpectingError(String username, String password) {
        //  ... falha no login aqui, talvez porque o nome de usuário e/ou a senha estão incorretos
    }
    
    public String getErrorMessage() {
        // Para que possamos verificar se o erro correto é mostrado
    }
}

O código apresentado acima mostra um ponto importante: os testes, não os Page Objects, devem ser responsáveis por fazer asserções sobre o estado de uma página. Por exemplo:

public void testMessagesAreReadOrUnread() {
    Inbox inbox = new Inbox(driver);
    inbox.assertMessageWithSubjectIsUnread("I like cheese");
    inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu");
}

could be re-written as:

public void testMessagesAreReadOrUnread() {
    Inbox inbox = new Inbox(driver);
    assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese"));
    assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu"));
}

Claro, como em toda diretriz, existem exceções, e uma que é comumente vista com Page Objects é verificar se o WebDriver está na página correta quando instanciamos o Page Object. Isso é feito no exemplo abaixo.

Finalmente, um Page Object não precisa representar uma página inteira. Pode representar uma seção que aparece com frequência dentro de um site ou página, como a navegação do site. O princípio essencial é que há apenas um lugar em sua suíte de testes com conhecimento da estrutura do HTML de uma determinada (parte de uma) página.

Resumo

  • Os métodos públicos representam os serviços que a página oferece
  • Tente não expor as entranhas da página
  • Geralmente não faça asserções
  • Métodos retornam outros PageObjects *Não precisa representar uma página inteira
  • Resultados diferentes para a mesma ação são modelados como métodos diferentes

Example

public class LoginPage {
    private final WebDriver driver;

    public LoginPage(WebDriver driver) {
        this.driver = driver;

        // Verifica se estamos na página correta.
        if (!"Login".equals(driver.getTitle())) {
            // Alternativamente, poderíamos navegar para a página de login, talvez fazendo logout primeiro
            throw new IllegalStateException("This is not the login page");
        }
    }

    // A página de login contém vários elementos HTML que serão representados como WebElements.
    // Os localizadores para esses elementos devem ser definidos apenas uma vez.
        By usernameLocator = By.id("username");
        By passwordLocator = By.id("passwd");
        By loginButtonLocator = By.id("login");

    // A página de login permite que o usuário digite seu nome de usuário no campo de nome de usuário
    public LoginPage typeUsername(String username) {
        // Este é o único lugar que "sabe" como entrar com um nome de usuário
        driver.findElement(usernameLocator).sendKeys(username);

        // Retorna o objeto de página atual, já que esta ação não navega para uma página representada por outro Page Object
        return this;	
    }
Este é o único lugar que "sabe" como entrar com uma senha
    // A página de login permite que o usuário digite sua senha no campo de senha
    public LoginPage typePassword(String password) {
        // Este é o único lugar que "sabe" como entrar com uma senha
        driver.findElement(passwordLocator).sendKeys(password);

        // Retorna o objeto de página atual, já que esta ação não navega para uma página representada por outro Page Object
        return this;	
    }

    // A página de login permite que o usuário envie o formulário de login
    public HomePage submitLogin() {
        // Este é o único lugar que envia o formulário de login e espera que o destino seja a página inicial.
        // Um método separado deve ser criado para a instância de clicar em login enquanto espera uma falha de login.
        driver.findElement(loginButtonLocator).submit();

        // Retorna um novo objeto de página representando o destino. Caso a página de login vá para algum outro lugar (por exemplo, um aviso legal),
        // então a alteração da assinatura do método para este método significará que todos os testes que dependem deste comportamento não serão compilados.
        return new HomePage(driver);	
    }

    // A página de login permite que o usuário envie o formulário de login sabendo que um nome de usuário inválido e/ou senha foram inseridos
    public LoginPage submitLoginExpectingFailure() {
        // Este é o único lugar que envia o formulário de login e espera que o destino seja a página de login devido à falha no login.
        driver.findElement(loginButtonLocator).submit();

        // Retorna um novo objeto de página representando o destino. Caso o usuário seja navegado para a página inicial depois de enviar um login com credenciais
        // que se espera falhar no login, o script falhará quando tentar instanciar o PageObject LoginPage.
        return new LoginPage(driver);	
    }

    // Conceitualmente, a página de login oferece ao usuário o serviço de ser capaz de "entrar"
    // no aplicativo usando um nome de usuário e senha. 
    public HomePage loginAs(String username, String password) {
        // Os métodos PageObject que inserem nome de usuário, senha e enviam login já foram definidos e não devem ser repetidos aqui.
        typeUsername(username);
        typePassword(password);
        return submitLogin();
    }
}

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.

    • If you choose pytest as your test runner, this can be easily done by yielding your driver in a global fixture. This way each test gets its own driver instance, and you can ensure that drivers always quit after a test is finished (pass or fail).

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(driver);
gsp.setSearchString().clickSearchButton();

A classe de objeto da página do Google com este comportamento fluente pode ser assim:

public abstract class BasePage {
    protected WebDriver driver;

    public BasePage(WebDriver driver) {
        this.driver = driver;
    }
}

public class GoogleSearchPage extends BasePage {
    public GoogleSearchPage(WebDriver driver) {
        super(driver);
        // Generally do not assert within pages or components.
        // Effectively throws an exception if the lambda condition is not met.
        new WebDriverWait(driver, Duration.ofSeconds(3)).until(d -> d.findElement(By.id("logo")));
    }

    public GoogleSearchPage setSearchString(String sstr) {
        driver.findElement(By.id("gbqfq")).sendKeys(sstr);
        return this;
    }

    public void clickSearchButton() {
        driver.findElement(By.id("gbqfb")).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();