Article image
Thiago Sampaio
Thiago Sampaio25/10/2021 18:42
Compartilhe

Introdução ao padrão de Objeto de Página (Page Object) para testes do Selenium

  • #BDD
  • #Selenium
  • #QA

Introdução

Como acontece com a maioria dos padrões de software, eles realmente fazem mais sentido quando se programa para uma situação em que percebe que precisa de ajuda. Aprender um padrão desde o início pode evitar que se cometa um erro de projeto, mas como na maioria das situações, cometer um erro é a melhor oportunidade de aprender.

O padrão de objeto de página, no entanto, faz todo o sentido se tiver um conjunto existente de testes funcionais do Selenium ou se estiver começando do zero.

Os exemplos de código são escritos em Java, mas os conceitos se aplicam a qualquer linguagem.

Problema

Quando se está escrevendo testes funcionais usando Selenium, uma grande parte do código consistirá em interações com a interface da web que se está testando por meio da API WebDriver. Depois de buscar elementos, verifica-se o estado do elemento por meio de várias asserções e seguirá para buscar o próximo elemento.

O exemplo abaixo é parte de um teste básico de Selenium:

List<WebElement> zipCodes = driver.findElements(By.id("zipCodes"));
for (WebElement zipCode : zipCodes) {
if (zipCode.getText().equals("12345")){
  zipCode.click();
  break;
}
}
WebElement city = driver.findElement(By.id("city"));
assertEquals("MyCityName", city.getText());

Este é um exemplo simples de teste que busca e itera sobre uma lista de zipcodes (CEPs) procurando 12345, clicando nele e buscando o elemento de cidade esperando que o nome da cidade seja "MyCityName".

Mesmo com um teste simples como este, a legibilidade é muito pobre. Existe uma grande quantidade de código WebDriver, que obscurece o propósito do teste, tornando-o lento e difícil de digerir.

Com qualquer interface, é comum que as mudanças menores e maiores na UI sejam implementadas com frequência. Isso pode ser um novo design, reestruturação de campos e botões, e isso provavelmente afetará o teste. Portanto, o teste falha e será necessário atualizar os seletores.

Agora, se tiver apenas um teste funcional para uma página, talvez isso não seja um grande problema. No entanto, se tiver um conjunto completo de testes de regressão, isso é absolutamente importante.

Portanto, alguns dos problemas típicos para este tipo de teste de Selenium são:

  • Os casos de teste são difíceis de ler
  • Mudanças na UI quebram vários testes, muitas vezes em vários lugares
  • Duplicação de seletores dentro e entre os testes sem reutilização

Solução

Portanto, em vez de cada teste buscar elementos diretamente e ser frágil em relação às alterações da UI, o Padrão de Objeto de Página apresenta o que é basicamente uma camada de desacoplamento.

Cria-se um objeto que representa a UI que deseja testar, que pode ser uma página inteira ou uma parte significativa dela. A responsabilidade desse objeto é envolver os elementos HTML e encapsular as interações com a UI, o que significa que é para onde todas as chamadas para WebDriver irão. É aqui que se encontra a maioria dos WebElements. E este é o único lugar que será necessário modificar quando a UI muda.

Implementação

Começar com esta implementação é muito simples:

  1. Configuração básica do Selenium
  2. Analise seu programa sob teste
  3. Criar objetos de página
  4. Escreva testes

1. Configuração básica do Selenium

Quando se inicia um conjunto de testes Selenium, é recomendável criar uma classe para conter todo o código de gerenciamento do ciclo de vida do driver. Portanto, comece com uma classe como esta:

public class FunctionalTest {
protected static WebDriver driver;

@BeforeClass
public static void setUp(){
  driver = new FirefoxDriver();
  driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}

@After
public void cleanUp(){
  driver.manage().deleteAllCookies();
}

@AfterClass
public static void tearDown(){
  driver.close();
}
}

E agora torne esta a superclasse para qualquer classe de teste que criar.

2. Analise seu programa sob teste

Para este exemplo, foi criado um formulário de inscrição com campos de nome e endereço e uma página de recibo.

image

Neste exemplo, a análise é muito direta, existindo um objeto de página para cada uma dessas páginas. Mas, como mencionado acima, não há necessariamente um mapeamento um para um entre uma página HTML completa e um objeto de página. Um objeto de página deve representar uma parte contextual significativa da página.

3. Crie objetos de página

Abaixo existem dois objetos de página, um para o formulário de inscrição e outro para o recibo.

public class SignUpPage extends PageObject {

@FindBy(id="firstname")
private WebElement firstName;

@FindBy(id="lastname")
private WebElement lastName;

@FindBy(id="address")
private WebElement address;

@FindBy(id="zipcode")
private WebElement zipCode;

@FindBy(id="signup")
private WebElement submitButton;

public SignUpPage(WebDriver driver) {
  super(driver);
}

public void enterName(String firstName, String lastName){
  this.firstName.clear();
  this.firstName.sendKeys(firstName);

  this.lastName.clear();
  this.lastName.sendKeys(lastName);
}

public void enterAddress(String address, String zipCode){
  this.address.clear();
  this.address.sendKeys(address);

  this.zipCode.clear();
  this.zipCode.sendKeys(zipCode);
}

public ReceiptPage submit(){
  submitButton.click();
  return new ReceiptPage(driver);
}
}
public class ReceiptPage extends PageObject {

@FindBy(tagName = "h1")
private WebElement header;

public ReceiptPage(WebDriver driver) {
  super(driver);
}

public String confirmationHeader(){
  return header.getText();
}
}

Agora, isso realmente não funcionará, até que se inicialize os WebElements anotados. Portanto, há outra superclasse para conter esse pequeno, mas importante trecho de código. Portanto, a classe PageObject é assim:

public class PageObject {
protected WebDriver driver;

public PageObject(WebDriver driver){
  this.driver = driver;
  PageFactory.initElements(driver, this);
}
}

A PageFactory processa todos os WebElements anotados e localiza o elemento na página usando o seletor anotado. Você pode encontrar a documentação para PageFactory na página The SeleniumHQ GitHub.

4. Escreva testes

Agora se tem todas as peças para começar a escrever os casos de teste reais. Aqui está um caso de teste passando pelo cenário de sucesso de inscrição e confirmação:

public class SignUpFormTest extends FunctionalTest {

@Test
public void signUp(){
  driver.get("http://www.kimschiller.com/page-object-pattern-tutorial/index.html");

  SignUpPage signUpPage = new SignUpPage(driver);
  assertTrue(signUpPage.isInitialized());

  signUpPage.enterName("First", "Last");
  signUpPage.enterAddress("123 Street", "12345");

  ReceiptPage receiptPage = signUpPage.submit();
  assertTrue(receiptPage.isInitialized());

  assertEquals("Thank you", receiptPage.confirmationHeader());
}
}

Olhando para o caso de teste agora, se vê que o problema de baixa legibilidade foi resolvido. O teste lê muito bem com a intenção de cada método muito claro.

Como foi removida todas as chamadas WebDriver do teste, não será necessário alterar o teste se, por exemplo, o campo firstname mudar seu id. Apenas o objeto da página precisará ser alterado.

Melhores Práticas

Existem algumas práticas recomendadas no uso de objetos de página, que você deve fazer um esforço para seguir.

  • Um objeto de página não deve ter nenhuma declaração
  • Um objeto de página deve representar elementos significativos de uma página e não necessariamente uma página completa
  • Ao navegar, deve-se retornar um objeto de página para a próxima página

Dicas e Truques

É uma boa prática verificar se uma página está pronta antes de interagir com ela e, conforme mostrado no SignUpFormTest:

SignUpPage signUpPage = new SignUpPage(driver);
assertTrue(signUpPage.isInitialized());

É provável que se repita esse padrão com cada instanciação de um objeto de página, então este é o lugar onde pode-se introduzir uma exceção à regra de não ter asserções em objetos de página.

A ideia aqui é mover essa afirmação para o construtor do objeto de página. Então, a criação falhará se o objeto de página por algum motivo não ficar pronto a tempo.

Portanto, o construtor terá a seguinte aparência:

public SignUpPage(WebDriver driver) {
super(driver);
assertTrue(firstName.isDisplayed());
}

Conclusão

Se está apenas começando a usar o Selenium ou gerenciando um grande conjunto de testes de regressão do Selenium, provavelmente se beneficiará com a introdução do padrão de objeto de página em seu código. Como criador do WebDriver, Simon Stewart - coloca isso sem rodeios: "Se você tem APIs do WebDriver em seus métodos de teste, você está fazendo isso errado".

O Padrão de Objeto de Página oferece uma maneira de desacoplar os scripts de teste da interface da web que está testando, introduzindo uma série de objetos de página. E os objetos de página são responsáveis pela comunicação com as páginas da web que está testando. Quaisquer consultas DOM disparadas por meio da API WebDriver passam pelos objetos de página, porque apenas os objetos de página devem saber como encontrar elementos na página usando os métodos localizadores numerosos disponíveis com Selenium.

Ao usar objetos de página, os testes se tornam mais concisos e legíveis. Os localizadores de elementos são centralizados, facilitando a manutenção. As alterações na interface do usuário afetam apenas objetos de página e não scripts de teste. E, finalmente, este é um projeto orientado a objetos bom e sólido.

Referência

Artigo original em inglês: Getting Started with Page Object Pattern for Your Selenium Tests

Artigo em inglês: PageObject

Artigo em português: Modelos de objetos de página

Compartilhe
Comentários (3)
Juliana Gomes
Juliana Gomes - 12/10/2023 17:25

Conteúdo com muito qualidade, obrigada por compartilhar!

JC

John Cordeiro - 21/03/2023 09:50

Muito bom!!

KHALYANDRA VEIGA
KHALYANDRA VEIGA - 06/12/2022 21:16

Top! = )