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 - Design patterns and development strategies
(previously located: https://github.com/SeleniumHQ/selenium/wiki/Bot-Style-Tests)
Overview
Over time, projects tend to accumulate large numbers of tests. As the total number of tests increases,
it becomes harder to make changes to the codebase — a single “simple”
change may cause numerous tests to fail, even though the application still
works properly. Sometimes these problems are unavoidable, but when they do
occur you want to be up and running again as quickly as possible. The following
design patterns and strategies have been used before with WebDriver to help make
tests easier to write and maintain. They may help you too.
DomainDrivenDesign: Express your tests in the language of the end-user of the app.
PageObjects: A simple abstraction of the UI of your web app.
LoadableComponent: Modeling PageObjects as components.
BotStyleTests: Using a command-based approach to automating tests, rather than the object-based approach that PageObjects encourage
Loadable Component
What Is It?
The LoadableComponent is a base class that aims to make writing PageObjects
less painful. It does this by providing a standard way of ensuring that
pages are loaded and providing hooks to make debugging the failure of a
page to load easier. You can use it to help reduce the amount of boilerplate
code in your tests, which in turn make maintaining your tests less tiresome.
There is currently an implementation in Java that ships as part of Selenium 2,
but the approach used is simple enough to be implemented in any language.
Simple Usage
As an example of a UI that we’d like to model, take a look at
the new issue page. From
the point of view of a test author, this offers the service of being able to
file a new issue. A basic Page Object would look like:
package com.example.webdriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
public class EditIssue {
private final WebDriver driver;
public EditIssue(WebDriver driver) {
this.driver = driver;
}
public void setSummary(String summary) {
WebElement field = driver.findElement(By.name("summary"));
clearAndType(field, summary);
}
public void enterDescription(String description) {
WebElement field = driver.findElement(By.name("comment"));
clearAndType(field, description);
}
public IssueList submit() {
driver.findElement(By.id("submit")).click();
return new IssueList(driver);
}
private void clearAndType(WebElement field, String text) {
field.clear();
field.sendKeys(text);
}
}
In order to turn this into a LoadableComponent, all we need to do is to set that as the base type:
public class EditIssue extends LoadableComponent<EditIssue> {
// rest of class ignored for now
}
This signature looks a little unusual, but it all means is that this class
represents a LoadableComponent that loads the EditIssue page.
By extending this base class, we need to implement two new methods:
@Override
protected void load() {
driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
}
@Override
protected void isLoaded() throws Error {
String url = driver.getCurrentUrl();
assertTrue("Not on the issue entry page: " + url, url.endsWith("/new"));
}
The load
method is used to navigate to the page, whilst the isLoaded
method
is used to determine whether we are on the right page. Although the
method looks like it should return a boolean, instead it performs a
series of assertions using JUnit’s Assert class. There can be as few
or as many assertions as you like. By using these assertions it’s
possible to give users of the class clear information that can be
used to debug tests.
With a little rework, our PageObject looks like:
package com.example.webdriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import static junit.framework.Assert.assertTrue;
public class EditIssue extends LoadableComponent<EditIssue> {
private final WebDriver driver;
// By default the PageFactory will locate elements with the same name or id
// as the field. Since the summary element has a name attribute of "summary"
// we don't need any additional annotations.
private WebElement summary;
// Same with the submit element, which has the ID "submit"
private WebElement submit;
// But we'd prefer a different name in our code than "comment", so we use the
// FindBy annotation to tell the PageFactory how to locate the element.
@FindBy(name = "comment") private WebElement description;
public EditIssue(WebDriver driver) {
this.driver = driver;
// This call sets the WebElement fields.
PageFactory.initElements(driver, this);
}
@Override
protected void load() {
driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
}
@Override
protected void isLoaded() throws Error {
String url = driver.getCurrentUrl();
assertTrue("Not on the issue entry page: " + url, url.endsWith("/new"));
}
public void setSummary(String issueSummary) {
clearAndType(summary, issueSummary);
}
public void enterDescription(String issueDescription) {
clearAndType(description, issueDescription);
}
public IssueList submit() {
submit.click();
return new IssueList(driver);
}
private void clearAndType(WebElement field, String text) {
field.clear();
field.sendKeys(text);
}
}
That doesn’t seem to have bought us much, right? One thing it has done is
encapsulate the information about how to navigate to the page into the page
itself, meaning that this information’s not scattered through the code base.
It also means that we can do this in our tests:
EditIssue page = new EditIssue(driver).get();
This call will cause the driver to navigate to the page if that’s necessary.
Nested Components
LoadableComponents start to become more useful when they are used in conjunction
with other LoadableComponents. Using our example, we could view the “edit issue”
page as a component within a project’s website (after all, we access it via a
tab on that site). You also need to be logged in to file an issue. We could
model this as a tree of nested components:
+ ProjectPage
+---+ SecuredPage
+---+ EditIssue
What would this look like in code? For a start, each logical component
would have its own class. The “load” method in each of them would “get”
the parent. The end result, in addition to the EditIssue class above is:
ProjectPage.java:
package com.example.webdriver;
import org.openqa.selenium.WebDriver;
import static org.junit.Assert.assertTrue;
public class ProjectPage extends LoadableComponent<ProjectPage> {
private final WebDriver driver;
private final String projectName;
public ProjectPage(WebDriver driver, String projectName) {
this.driver = driver;
this.projectName = projectName;
}
@Override
protected void load() {
driver.get("http://" + projectName + ".googlecode.com/");
}
@Override
protected void isLoaded() throws Error {
String url = driver.getCurrentUrl();
assertTrue(url.contains(projectName));
}
}
and SecuredPage.java:
package com.example.webdriver;
import org.openqa.selenium.By;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import static org.junit.Assert.fail;
public class SecuredPage extends LoadableComponent<SecuredPage> {
private final WebDriver driver;
private final LoadableComponent<?> parent;
private final String username;
private final String password;
public SecuredPage(WebDriver driver, LoadableComponent<?> parent, String username, String password) {
this.driver = driver;
this.parent = parent;
this.username = username;
this.password = password;
}
@Override
protected void load() {
parent.get();
String originalUrl = driver.getCurrentUrl();
// Sign in
driver.get("https://www.google.com/accounts/ServiceLogin?service=code");
driver.findElement(By.name("Email")).sendKeys(username);
WebElement passwordField = driver.findElement(By.name("Passwd"));
passwordField.sendKeys(password);
passwordField.submit();
// Now return to the original URL
driver.get(originalUrl);
}
@Override
protected void isLoaded() throws Error {
// If you're signed in, you have the option of picking a different login.
// Let's check for the presence of that.
try {
WebElement div = driver.findElement(By.id("multilogin-dropdown"));
} catch (NoSuchElementException e) {
fail("Cannot locate user name link");
}
}
}
The “load” method in EditIssue now looks like:
@Override
protected void load() {
securedPage.get();
driver.get("https://github.com/SeleniumHQ/selenium/issues/new");
}
This shows that the components are all “nested” within each other. A call to get()
in EditIssue will cause all its dependencies to load too. The example usage:
public class FooTest {
private EditIssue editIssue;
@Before
public void prepareComponents() {
WebDriver driver = new FirefoxDriver();
ProjectPage project = new ProjectPage(driver, "selenium");
SecuredPage securedPage = new SecuredPage(driver, project, "example", "top secret");
editIssue = new EditIssue(driver, securedPage);
}
@Test
public void demonstrateNestedLoadableComponents() {
editIssue.get();
editIssue.setSummary("Summary");
editIssue.enterDescription("This is an example");
}
}
If you’re using a library such as Guiceberry in your tests,
the preamble of setting up the PageObjects can be omitted leading to nice, clear, readable tests.
Bot Pattern
(previously located: https://github.com/SeleniumHQ/selenium/wiki/Bot-Style-Tests)
Although PageObjects are a useful way of reducing duplication in your tests, it’s not always a pattern that teams feel comfortable following. An alternative approach is to follow a more “command-like” style of testing.
A “bot” is an action-oriented abstraction over the raw Selenium APIs. This means that if you find that commands aren’t doing the Right Thing for your app, it’s easy to change them. As an example:
public class ActionBot {
private final WebDriver driver;
public ActionBot(WebDriver driver) {
this.driver = driver;
}
public void click(By locator) {
driver.findElement(locator).click();
}
public void submit(By locator) {
driver.findElement(locator).submit();
}
/**
* Type something into an input field. WebDriver doesn't normally clear these
* before typing, so this method does that first. It also sends a return key
* to move the focus out of the element.
*/
public void type(By locator, String text) {
WebElement element = driver.findElement(locator);
element.clear();
element.sendKeys(text + "\n");
}
}
Once these abstractions have been built and duplication in your tests identified, it’s possible to layer PageObjects on top of bots.
2 - Sobre automação de testes
Primeiro, comece perguntando a si mesmo se você realmente precisa ou não de um navegador.
As probabilidades são de que, em algum ponto, se você estiver trabalhando em um aplicativo da web complexo,
você precisará abrir um navegador e realmente testá-lo.
No entanto, os testes funcionais do usuário final, como os testes Selenium, são caros para executar.
Além disso, eles normalmente exigem infraestrutura substancial
para ser executado de forma eficaz.
É uma boa regra sempre se perguntar se o que você deseja testar
pode ser feito usando abordagens de teste mais leves, como testes de unidade
ou com uma abordagem de nível inferior.
Depois de determinar que está no negócio de teste de navegador da web,
e você tem seu ambiente Selenium pronto para começar a escrever testes,
você geralmente executará alguma combinação de três etapas:
- Configurar os dados
- Executar um conjunto discreto de ações
- Avaliar os resultados
Você deve manter essas etapas o mais curtas possível;
uma ou duas operações devem ser suficientes na maioria das vezes.
A automação do navegador tem a reputação de ser “instável”,
mas, na realidade, é porque os usuários freqüentemente exigem muito dele.
Em capítulos posteriores, retornaremos às técnicas que você pode usar
para mitigar problemas aparentemente intermitentes nos testes,
em particular sobre como superar as condições de corrida
entre o navegador e o WebDriver.
Mantendo seus testes curtos
e usando o navegador da web apenas quando você não tiver absolutamente nenhuma alternativa,
você pode ter muitos testes com instabilidade mínima.
Uma vantagem distinta dos testes do Selenium
é sua capacidade inerente de testar todos os componentes do aplicativo,
de back-end para front-end, da perspectiva do usuário.
Em outras palavras, embora os testes funcionais possam ser caros para executar,
eles também abrangem grandes partes críticas para os negócios de uma só vez.
Requerimentos de teste
Como mencionado antes, os testes do Selenium podem ser caros para serem executados.
Até que ponto depende do navegador em que você está executando os testes,
mas historicamente o comportamento dos navegadores tem variado tanto que muitas vezes
foi uma meta declarada testar cruzado contra vários navegadores.
Selenium permite que você execute as mesmas instruções em vários navegadores
em vários sistemas operacionais,
mas a enumeração de todos os navegadores possíveis,
suas diferentes versões e os muitos sistemas operacionais em que são executados
rapidamente se tornará uma tarefa não trivial.
Larry escreveu um site que permite aos usuários solicitarem seus
unicórnios personalizados.
O fluxo de trabalho geral (o que chamaremos de “caminho feliz”) é algo
como isso:
- Criar uma conta
- Configurar o unicórnio
- Adicionar ao carrinho de compras
- Verificar e pagar
- Dar feedback sobre o unicórnio
Seria tentador escrever um grande roteiro do Selenium
para realizar todas essas operações - muitos tentarão.
Resista à tentação!
Isso resultará em um teste que
a) leva muito tempo,
b) estará sujeito a alguns problemas comuns em torno de problemas de tempo de renderização de página, e
c) se falhar,
não lhe dará um método conciso e “superficial” para diagnosticar o que deu errado.
A estratégia preferida para testar este cenário seria
dividi-lo em uma série de testes independentes e rápidos,
cada um dos quais tem uma “razão” de existir.
Vamos fingir que você deseja testar a segunda etapa:
Configure o unicórnio.
Ele executará as seguintes ações:
- Criar uma conta
- Configurar o unicórnio
Observe que estamos pulando o restante dessas etapas,
vamos testar o resto do fluxo de trabalho em outros casos de teste pequenos e discretos
depois de terminarmos com este.
Para começar, você precisa criar uma conta.
Aqui você tem algumas escolhas a fazer:
- Deseja usar uma conta existente?
- Você deseja criar uma nova conta?
- Existem propriedades especiais de tal usuário que precisam ser
levadas em consideração antes do início da configuração?
Independentemente de como você responde a esta pergunta,
a solução é torná-la parte da etapa de “configurar os dados” do teste.
Se Larry expôs uma API que permite a você (ou qualquer pessoa)
criar e atualizar contas de usuário,
certifique-se de usar isso para responder a esta pergunta.
Se possível, você deseja iniciar o navegador somente depois de ter um usuário “em mãos”,
cujas credenciais você pode usar para fazer login.
Se cada teste para cada fluxo de trabalho começar com a criação de uma conta de usuário,
muitos segundos serão adicionados à execução de cada teste.
Chamar uma API e falar com um banco de dados são operações rápidas,
“sem cabeçalho” que não requerem o processo caro de
abrir um navegador, navegar para as páginas certas,
clicando e aguardando o envio dos formulários, etc.
Idealmente, você pode abordar esta fase de configuração em uma linha de código,
que será executado antes que qualquer navegador seja iniciado:
// Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
// mas eles não têm informações de pagamento configuradas, nem têm
// privilégios administrativos. No momento em que o usuário é criado, seu endereço
// de e-mail e senha são gerados aleatoriamente - você nem precisa
// conhecê-los.
User user = UserFactory.createCommonUser(); //Este método está definido em algum outro lugar.
// Faça login como este usuário.
// O login neste site leva você à sua página pessoal "Minha conta", e então
// o objeto AccountPage é retornado pelo método loginAs, permitindo que você
// execute ações da AccountPage.
AccountPage accountPage = loginAs(user.getEmail(), user.getPassword());
# Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
# mas eles não têm informações de pagamento configuradas, nem têm
# privilégios administrativos. No momento em que o usuário é criado, seu endereço
# de e-mail e senha são gerados aleatoriamente - você nem precisa
# conhecê-los.
user = user_factory.create_common_user() #This method is defined elsewhere.
# Faça login como este usuário.
# O login neste site leva você à sua página pessoal "Minha conta", e então
# o objeto AccountPage é retornado pelo método loginAs, permitindo que você
# execute ações da AccountPage.
account_page = login_as(user.get_email(), user.get_password())
// Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
// mas eles não têm informações de pagamento configuradas, nem têm
// privilégios administrativos. No momento em que o usuário é criado, seu endereço
// de e-mail e senha são gerados aleatoriamente - você nem precisa
// conhecê-los.
User user = UserFactory.CreateCommonUser(); //This method is defined elsewhere.
// Faça login como este usuário.
// O login neste site leva você à sua página pessoal "Minha conta", e então
// o objeto AccountPage é retornado pelo método loginAs, permitindo que você
// execute ações da AccountPage.
AccountPage accountPage = LoginAs(user.Email, user.Password);
# Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
# mas eles não têm informações de pagamento configuradas, nem têm
# privilégios administrativos. No momento em que o usuário é criado, seu endereço
# de e-mail e senha são gerados aleatoriamente - você nem precisa
# conhecê-los.
user = UserFactory.create_common_user #This method is defined elsewhere.
# Faça login como este usuário.
# O login neste site leva você à sua página pessoal "Minha conta", e então
# o objeto AccountPage é retornado pelo método loginAs, permitindo que você
# execute ações da AccountPage.
account_page = login_as(user.email, user.password)
// Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
// mas eles não têm informações de pagamento configuradas, nem têm
// privilégios administrativos. No momento em que o usuário é criado, seu endereço
// de e-mail e senha são gerados aleatoriamente - você nem precisa
// conhecê-los.
var user = userFactory.createCommonUser(); //This method is defined elsewhere.
// Faça login como este usuário.
// O login neste site leva você à sua página pessoal "Minha conta", e então
// o objeto AccountPage é retornado pelo método loginAs, permitindo que você
// execute ações da AccountPage.
var accountPage = loginAs(user.email, user.password);
// Crie um usuário que tenha permissões somente leitura - eles podem configurar um unicórnio,
// mas eles não têm informações de pagamento configuradas, nem têm
// privilégios administrativos. No momento em que o usuário é criado, seu endereço
// de e-mail e senha são gerados aleatoriamente - você nem precisa
// conhecê-los.
val user = UserFactory.createCommonUser() //This method is defined elsewhere.
// Faça login como este usuário.
// O login neste site leva você à sua página pessoal "Minha conta", e então
// o objeto AccountPage é retornado pelo método loginAs, permitindo que você
// execute ações da AccountPage.
val accountPage = loginAs(user.getEmail(), user.getPassword())
Como você pode imaginar, a UserFactory
pode ser estendida
para fornecer métodos como createAdminUser ()
e createUserWithPayment ()
.
A questão é que essas duas linhas de código não o distraem do objetivo final deste teste:
configurando um unicórnio.
Os detalhes do modelo de objeto de página
será discutido em capítulos posteriores, mas vamos apresentar o conceito aqui:
Seus testes devem ser compostos de ações,
realizadas do ponto de vista do usuário,
dentro do contexto das páginas do site.
Essas páginas são armazenadas como objetos,
que conterão informações específicas sobre como a página da web é composta
e como as ações são realizadas -
muito pouco disso deve preocupar você como testador.
Que tipo de unicórnio você quer?
Você pode querer rosa, mas não necessariamente.
Roxo tem sido bastante popular ultimamente.
Ela precisa de óculos escuros? Tatuagens de estrelas?
Essas escolhas, embora difíceis, são sua principal preocupação como testador -
você precisa garantir que seu centro de atendimento de pedidos
envia o unicórnio certo para a pessoa certa,
e isso começa com essas escolhas.
Observe que em nenhum lugar desse parágrafo falamos sobre botões,
campos, menus suspensos, botões de opção ou formulários da web.
Nem deveriam seus testes!
Você deseja escrever seu código como o usuário tentando resolver seu problema.
Aqui está uma maneira de fazer isso (continuando do exemplo anterior):
// O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
// Isso armazena apenas os valores; não preenche formulários da web nem interage
// com o navegador de qualquer forma.
Unicorn sparkles = new Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS);
// Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
// lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
// nos leva lá.
AddUnicornPage addUnicornPage = accountPage.addUnicorn();
// Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
// o método createUnicorn(). Este método pegará os atributos do Sparkles,
// preencher o formulário e clicar em enviar.
UnicornConfirmationPage unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles);
# O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
# Isso armazena apenas os valores; não preenche formulários da web nem interage
# com o navegador de qualquer forma.
sparkles = Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)
# Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
# lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
# nos leva lá.
add_unicorn_page = account_page.add_unicorn()
# Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
# o método createUnicorn(). Este método pegará os atributos do Sparkles,
# preencher o formulário e clicar em enviar.
unicorn_confirmation_page = add_unicorn_page.create_unicorn(sparkles)
// O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
// Isso armazena apenas os valores; não preenche formulários da web nem interage
// com o navegador de qualquer forma.
Unicorn sparkles = new Unicorn("Sparkles", UnicornColors.Purple, UnicornAccessories.Sunglasses, UnicornAdornments.StarTattoos);
// Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
// lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
// nos leva lá.
AddUnicornPage addUnicornPage = accountPage.AddUnicorn();
// Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
// o método createUnicorn(). Este método pegará os atributos do Sparkles,
// preencher o formulário e clicar em enviar.
UnicornConfirmationPage unicornConfirmationPage = addUnicornPage.CreateUnicorn(sparkles);
# O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
# Isso armazena apenas os valores; não preenche formulários da web nem interage
# com o navegador de qualquer forma.
sparkles = Unicorn.new('Sparkles', UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)
# Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
# lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
# nos leva lá.
add_unicorn_page = account_page.add_unicorn
# Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
# o método createUnicorn(). Este método pegará os atributos do Sparkles,
# preencher o formulário e clicar em enviar.
unicorn_confirmation_page = add_unicorn_page.create_unicorn(sparkles)
// O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
// Isso armazena apenas os valores; não preenche formulários da web nem interage
// com o navegador de qualquer forma.
var sparkles = new Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS);
// Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
// lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
// nos leva lá.
var addUnicornPage = accountPage.addUnicorn();
// Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
// o método createUnicorn(). Este método pegará os atributos do Sparkles,
// preencher o formulário e clicar em enviar.
var unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles);
// O Unicórnio é um objeto de nível superior - ele possui atributos, que são definidos aqui.
// Isso armazena apenas os valores; não preenche formulários da web nem interage
// com o navegador de qualquer forma.
val sparkles = Unicorn("Sparkles", UnicornColors.PURPLE, UnicornAccessories.SUNGLASSES, UnicornAdornments.STAR_TATTOOS)
// Uma vez que já estamos "na" página da conta, temos que usá-la para chegar ao
// lugar real onde você configura os unicórnios. Chamar o método "Add Unicorn"
// nos leva lá.
val addUnicornPage = accountPage.addUnicorn()
// Agora que estamos na AddUnicornPage, passaremos o objeto "sparkles" para
// o método createUnicorn(). Este método pegará os atributos do Sparkles,
// preencher o formulário e clicar em enviar.
unicornConfirmationPage = addUnicornPage.createUnicorn(sparkles)
Agora que você configurou seu unicórnio,
você precisa passar para a etapa 3: certifique-se de que realmente funcionou.
// O método exists() de UnicornConfirmationPage pegará o objeto
// Sparkles - uma especificação dos atributos que você deseja ver e compará-los
// com os campos na página
Assert.assertTrue("Sparkles should have been created, with all attributes intact", unicornConfirmationPage.exists(sparkles));
# O método exists() de UnicornConfirmationPage pegará o objeto
# Sparkles - uma especificação dos atributos que você deseja ver e compará-los
# com os campos na página
assert unicorn_confirmation_page.exists(sparkles), "Sparkles should have been created, with all attributes intact"
// O método exists() de UnicornConfirmationPage pegará o objeto
// Sparkles - uma especificação dos atributos que você deseja ver e compará-los
// com os campos na página
Assert.True(unicornConfirmationPage.Exists(sparkles), "Sparkles should have been created, with all attributes intact");
# O método exists() de UnicornConfirmationPage pegará o objeto
# Sparkles - uma especificação dos atributos que você deseja ver e compará-los
# com os campos na página
expect(unicorn_confirmation_page.exists?(sparkles)).to be, 'Sparkles should have been created, with all attributes intact'
// O método exists() de UnicornConfirmationPage pegará o objeto
// Sparkles - uma especificação dos atributos que você deseja ver e compará-los
// com os campos na página
assert(unicornConfirmationPage.exists(sparkles), "Sparkles should have been created, with all attributes intact");
// O método exists() de UnicornConfirmationPage pegará o objeto
// Sparkles - uma especificação dos atributos que você deseja ver e compará-los
// com os campos na página
assertTrue("Sparkles should have been created, with all attributes intact", unicornConfirmationPage.exists(sparkles))
Observe que o testador ainda não fez nada além de falar sobre unicórnios neste código–
sem botões, sem localizadores, sem controles do navegador.
Este método de modelagem do aplicativo
permite que você mantenha esses comandos de nível de teste no lugar e imutáveis,
mesmo se Larry decidir na próxima semana que não gosta mais de Ruby-on-Rails
e decidir reimplementar todo o site
em Haskell com um front-end Fortran.
Seus objetos de página exigirão alguma pequena manutenção para
estar conformidade com o redesenho do site,
mas esses testes permanecerão os mesmos.
Pegando esse design básico,
você desejará continuar seus fluxos de trabalho com o menor número possível de etapas voltadas para o navegador.
Seu próximo fluxo de trabalho envolverá adicionar um unicórnio ao carrinho de compras.
Provavelmente, você desejará muitas iterações deste teste para ter certeza de que o carrinho está mantendo o estado adequado:
Existe mais de um unicórnio no carrinho antes de você começar?
Quantos cabem no carrinho de compras?
Se você criar mais de um com o mesmo nome e / ou recursos, ele falhará?
Manterá apenas o existente ou acrescentará outro?
Cada vez que você passa pelo fluxo de trabalho,
você deseja evitar ter que criar uma conta,
fazer login como o usuário e configurar o unicórnio.
Idealmente, você será capaz de criar uma conta
e pré-configurar um unicórnio por meio da API ou banco de dados.
Em seguida, tudo que você precisa fazer é fazer login como o usuário, localizar Sparkles,
e adicioná-lo ao carrinho.
Automatizar ou não automatizar?
A automação é sempre vantajosa? Quando se deve decidir automatizar os casos de teste?
Nem sempre é vantajoso automatizar casos de teste. Tem vezes que
o teste manual pode ser mais apropriado.
Por exemplo, se a interface do aplicativo mudará consideravelmente em um futuro próximo,
então qualquer automação pode precisar ser reescrita de qualquer maneira.
Além disso, às vezes simplesmente não há tempo suficiente para construir automação de testes.
A curto prazo, o teste manual pode ser mais eficaz.
Se um aplicativo tem um prazo muito curto, atualmente não há
automação de teste disponível, e é imperativo que o teste seja feito dentro
nesse período, o teste manual é a melhor solução.
3 - Tipos de teste
Teste de aceitação
Este tipo de teste é feito para determinar se um recurso ou sistema
atende às expectativas e requisitos do cliente.
Este tipo de teste geralmente envolve
cooperação ou feedback do cliente, sendo uma atividade de validação que
responde a pergunta:
Estamos construindo o produto certo?.
Para aplicações web, a automação desse teste pode ser feita
diretamente com o Selenium, simulando o comportamento esperado do usuário.
Esta simulação pode ser feita por gravação / reprodução ou por meio dos
diferentes idiomas suportados, conforme explicado nesta documentação.
Observação: o teste de aceitação é um subtipo de teste funcional,
ao qual algumas pessoas também podem se referir.
Teste funcional
Este tipo de teste é feito para determinar se um
recurso ou sistema funciona corretamente sem problemas. Verifica
o sistema em diferentes níveis para garantir que todos os cenários
são cobertos e que o sistema faz o que está
suposto fazer. É uma atividade de verificação que
responde a pergunta:
Estamos construindo o produto corretamente?.
Isso geralmente inclui: os testes funcionam sem erros
(404, exceções …), de forma utilizável (redirecionamentos corretos),
de forma acessível e atendendo às suas especificações
(consulte teste de aceitação acima).
Para aplicativos da web, a automação desse teste pode ser
feito diretamente com o Selenium, simulando os retornos esperados.
Esta simulação pode ser feita por gravação / reprodução ou por meio de
os diferentes idiomas suportados, conforme explicado nesta documentação.
Como o próprio nome indica, testes de desempenho são feitos
para medir o desempenho de um aplicativo.
Existem dois subtipos principais para testes de desempenho:
Teste de carga
O teste de carga é feito para verificar o quão bem o
aplicativo funciona sob diferentes cargas definidas
(geralmente um determinado número de usuários conectados ao mesmo tempo).
Teste de estresse
O teste de estresse é feito para verificar o quão bem
a aplicação funciona sob estresse (ou acima da carga máxima suportada).
Geralmente, os testes de estresse são feitos executando alguns
testes escritos com Selenium simulando diferentes usuários
utilizando uma função específica no aplicativo da web e
recuperando algumas medições significativas.
Isso geralmente é feito por outras ferramentas que recuperam as métricas.
Uma dessas ferramentas é a JMeter.
Para um aplicativo da web, os detalhes a serem medidos incluem
taxa de transferência, latência, perda de dados, tempos de carregamento de componentes individuais …
Nota 1: todos os navegadores têm uma guia de desempenho em seus
seção de ferramentas para desenvolvedores (acessível pressionando F12)
Nota 2: é um subtipo de teste não funcional
já que isso geralmente é medido por sistema e não por função / recurso.
Teste regressivo
Esse teste geralmente é feito após uma alteração, correção ou adição de recurso.
Para garantir que a mudança não quebrou nenhumas das
funcionalidades, alguns testes já executados são executados novamente.
O conjunto de testes re-executados pode ser total ou parcial
e pode incluir vários tipos diferentes, dependendo
da equipe de aplicação e desenvolvimento.
Desenvolvimento orientado a testes (TDD)
Em vez de um tipo de teste per se, o TDD é uma metodologia iterativa de desenvolvimento na qual os testes conduzem o design de um recurso.
Cada ciclo começa criando um conjunto de testes de unidade no qual
o recurso deve eventualmente ser aprovado (eles devem falhar na primeira execução).
Depois disso, ocorre o desenvolvimento para fazer os testes passarem.
Os testes são executados novamente, iniciando outro ciclo
e esse processo continua até que todos os testes sejam aprovados.
Visa acelerar o desenvolvimento de um aplicativo
com base no fato de que os defeitos custam menos quanto mais cedo são encontrados.
Desenvolvimento orientado a comportamento (BDD)
BDD também é uma metodologia de desenvolvimento iterativa
com base no TDD acima, em que o objetivo é envolver
todas as partes no desenvolvimento de um aplicativo.
Cada ciclo começa criando algumas especificações
(que deve falhar). Em seguida, crie a os testes de unidade com falha
(que também devem falhar) e, em seguida, faça o desenvolvimento.
Este ciclo é repetido até que todos os tipos de testes sejam aprovados.
Para fazer isso, uma linguagem de especificação é
usada. Deve ser compreensível por todas as partes e ser
simples, padronizada e explícita.
A maioria das ferramentas usa Gherkin como esse idioma.
O objetivo é ser capaz de detectar ainda mais erros
do que TDD, visando potenciais erros de aceitação
também e tornar a comunicação entre as partes mais fácil.
Um conjunto de ferramentas está atualmente disponível
para escrever as especificações e combiná-las com funções de código,
como Cucumber ou SpecFlow.
Um conjunto de ferramentas é construído em cima do Selenium para tornar este processo
ainda mais rápido, transformando diretamente as especificações BDD em
código executável.
Alguns deles são JBehave, Capybara e Robot Framework.
4 - 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.
4.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.
4.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
4.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.4 - Simulação de serviços externos
Eliminar as dependências de serviços externos melhorará muito
a velocidade e estabilidade de seus testes.
4.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
4.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.
4.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.
4.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.
4.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();
}
}
}
4.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();
5 - Piores práticas
Temas a evitar quando automatizar navegadores com Selenium.
5.1 - Captchas
CAPTCHA, abreviação de Completely Automated Public Turing test
to tell Computers and Humans Apart,
foi projetado explicitamente para impedir a automação, portanto, não tente!
Existem duas estratégias principais para contornar as verificações CAPTCHA:
- Desative CAPTCHAs em seu ambiente de teste
- Adicione um hook para permitir que os testes ignorem o CAPTCHA
5.2 - Downloads de arquivo
Embora seja possível iniciar um download
clicando em um link com um navegador sob o controle do Selenium,
a API não expõe o progresso do download,
tornando-o menos do que ideal para testar arquivos baixados.
Isso ocorre porque o download de arquivos não é considerado um aspecto importante
de emular a interação do usuário com a plataforma da web.
Em vez disso, encontre o link usando Selenium
(e todos os cookies necessários)
e passe este cookie para uma biblioteca de solicitação HTTP como
libcurl.
O driver HtmlUnit pode baixar
anexos acessando-os como fluxos de entrada, implementando o
AttachmentHandler.
O AttachmentHandler pode ser adicionado ao WebClient HtmlUnit.
5.3 - Códigos de respostas HTTP
Para algumas configurações de navegador no Selenium RC,
Selenium atuou como um proxy entre o navegador
e o site sendo automatizado.
Isso significa que todo o tráfego do navegador que passou pelo Selenium
poderia ser capturado ou manipulado.
O método captureNetworkTraffic()
pretendia capturar todo o tráfego de rede entre o navegador
e o site sendo automatizado,
incluindo códigos de resposta HTTP.
Selenium WebDriver é uma abordagem completamente diferente
para a automação do navegador,
preferindo agir mais como um usuário.
Isso é representado na maneira como você escreve testes com o WebDriver.
Em testes funcionais automatizados,
verificar o código de status
não é um detalhe particularmente importante da falha de um teste;
as etapas que o precederam são mais importantes.
O navegador sempre representará o código de status HTTP,
imagine, por exemplo, uma página de erro 404 ou 500.
Uma maneira simples de “falhar rapidamente” quando você encontrar uma dessas páginas de erro
é verificar o título da página ou o conteúdo de um ponto confiável
(por exemplo, a tag <h1>
) após cada carregamento de página.
Se você estiver usando o modelo de objeto de página,
você pode incluir esta verificação em seu construtor de classe
ou ponto semelhante onde o carregamento da página é esperado.
Ocasionalmente, o código HTTP pode até ser representado
na página de erro do navegador
e você pode usar o WebDriver para ler isso
e melhorar sua saída de depuração.
Verificar se a própria página da web está alinhada
com a prática ideal do WebDriver
de representar a visão do usuário do site.
Se você insiste, uma solução avançada para capturar códigos de status HTTP
é replicar o comportamento do Selenium RC usando um proxy.
A API WebDriver fornece a capacidade de definir um proxy para o navegador,
e há uma série de proxies que irão
permitir que você manipule de forma programática
o conteúdo das solicitações enviadas e recebidas do servidor da web.
Usar um proxy permite que você decida como deseja responder
para códigos de resposta de redirecionamento.
Além disso, nem todo navegador
torna os códigos de resposta disponíveis para WebDriver,
então optar por usar um proxy
permite que você tenha uma solução que funciona para todos os navegadores.
5.4 - Login via Gmail, email e Facebook
Por vários motivos, fazer login em sites como Gmail e Facebook
usando do WebDriver não é recomendado.
Além de ser contra os termos de uso desses sites
(onde você corre o risco de ter a conta encerrada),
é lento e não confiável.
A prática ideal é usar as APIs que os provedores de e-mail oferecem,
ou no caso do Facebook, o serviço de ferramentas para desenvolvedores
que expõe uma API para criar contas de teste, amigos e assim por diante.
Embora usar uma API possa parecer um pouco trabalhoso,
você será recompensado em velocidade, confiabilidade e estabilidade.
A API também não deve mudar,
enquanto as páginas da web e os localizadores de HTML mudam frequentemente
e exigem que você atualize sua estrutura de teste.
Login em sites de terceiros usando WebDriver
em qualquer ponto do seu teste aumenta o risco
de seu teste falhar porque torna o teste mais longo.
Uma regra geral é que testes mais longos
são mais frágeis e não confiáveis.
Implementações WebDriver que estão
em conformidade com W3C
também anotam o objeto navigator
com uma propriedade WebDriver
para que os ataques de negação de serviço possam ser mitigados.
5.5 - Dependência entre testes
Uma ideia comum e um equívoco sobre o teste automatizado é sobre uma
ordem de testes específica. Seus testes devem ser executados em qualquer ordem,
e não depender da conclusão de outros testes para ter sucesso.
5.6 - Teste de performance/desempenho
Teste de desempenho usando Selenium e WebDriver
geralmente não é recomendado.
Não porque é incapaz,
mas porque não é otimizado para o trabalho
e é improvável que você obtenha bons resultados.
Pode parecer ideal para teste de desempenho
no contexto do usuário, mas um conjunto de testes WebDriver
estão sujeitos a muitos pontos de fragilidade externa e interna
que estão além do seu controle;
por exemplo, velocidade de inicialização do navegador,
velocidade dos servidores HTTP,
resposta de servidores de terceiros que hospedam JavaScript ou CSS,
e a penalidade de instrumentação
da própria implementação do WebDriver.
A variação nesses pontos causará variação em seus resultados.
É difícil separar a diferença
entre o desempenho do seu site
e o desempenho de recursos externos,
e também é difícil dizer qual é a penalidade de desempenho
para usar WebDriver no navegador,
especialmente se você estiver injetando scripts.
A outra atração potencial é “economizar tempo” -
execução de testes funcionais e de desempenho ao mesmo tempo.
No entanto, os testes funcionais e de desempenho têm objetivos opostos.
Para testar a funcionalidade, um testador pode precisar ser paciente
e aguarde o carregamento,
mas isso irá turvar os resultados do teste de desempenho e vice-versa.
Para melhorar o desempenho do seu site,
você precisará ser capaz de analisar o desempenho geral
independente das diferenças de ambiente,
identificar práticas de código ruins,
repartição do desempenho de recursos individuais
(ou seja, CSS ou JavaScript),
para saber o que melhorar.
Existem ferramentas de teste de desempenho disponíveis
que podem fazer este trabalho,
que fornecem relatórios e análises,
e podem até fazer sugestões de melhorias.
Pacotes de exemplo (código aberto) a serem usados são: JMeter
5.7 - Navegação por links
Usar o WebDriver para navegar por links
não é uma prática recomendada. Não porque não pode ser feito,
mas porque WebDriver definitivamente não é a ferramenta ideal para isso.
O WebDriver precisa de tempo para inicializar,
e pode levar vários segundos, até um minuto
dependendo de como seu teste é escrito,
apenas para chegar à página e atravessar o DOM.
Em vez de usar o WebDriver para isso,
você poderia economizar muito tempo
executando um comando curl,
ou usando uma biblioteca como BeautifulSoup
uma vez que esses métodos não dependem
em criar um navegador e navegar para uma página.
Você está economizando muito tempo por não usar o WebDriver para essa tarefa.
5.8 - Autenticação de Dois Fatores (2FA)
A autenticação de dois fatores, conhecida como 2FA, é um mecanismo de autorização
onde a senha de uso único (OTP) é gerada usando aplicativos móveis “Autenticadores”,
como “Google Authenticator”, “Microsoft Authenticator”
etc., ou por SMS, e-mail para autenticação. Automatizar isso perfeitamente
e consistentemente é um grande desafio no Selenium. Existem algumas maneiras
para automatizar este processo. Mas essa será outra camada em cima de nossos
testes Selenium e não protegidos também. Portanto, você pode evitar a automação do 2FA.
Existem algumas opções para contornar as verificações 2FA:
- Desative 2FA para determinados usuários no ambiente de teste, para que você possa
usar essas credenciais de usuário na automação.
- Desative 2FA em seu ambiente de teste.
- Desative 2FA se você fizer o login de determinados IPs. Dessa forma, podemos configurar nosso
teste os IPs da máquina para evitar isso.