Localizando Elementos em Documentos HTML (Selenium e Locators do Playwright)
Depois de concluirmos esta aula, nós:
- Saberemos como podemos encontrar elementos dentro de documentos HTML.
- Entenderemos como podemos implementar interfaces de usuário (UI) testáveis.
- Saberemos como podemos escrever testes End-to-End mais rápidos e mais robustos.
1. Introdução ao Sistema Sob Teste (SUT)
Durante esta aula, escreveremos testes End-to-End que localizam elementos dentro de um documento HTML. O código-fonte do nosso documento HTML de exemplo é o seguinte:
|
|
A seguir, vamos descobrir como podemos encontrar elementos em documentos HTML.
2. Localizando Elementos em Documentos HTML
No Selenium WebDriver, a interface SearchContext declara dois métodos fundamentais que podemos utilizar quando queremos encontrar elementos em documentos HTML:
- O método
findElement(by: By)retorna o primeiro elemento (WebElement) que corresponde ao critério de busca informado. - O método
findElements(by: By)retorna uma lista de elementos (List<WebElement>) que correspondem ao critério de busca informado.
Ambos os métodos recebem um objeto By como parâmetro. Esse objeto especifica o critério de busca utilizado para localizar o elemento ou elementos desejados. Podemos criar esses objetos invocando os métodos fábrica estáticos da classe By (e, de forma análoga no Playwright, utilizamos os métodos page.locator(...), page.getByRole(...) ou page.getByText(...)).
Antes de utilizarmos esses métodos, precisamos compreender dois conceitos essenciais:
- Busca relativa ao contexto atual: Esses métodos retornam elementos encontrados a partir do contexto atual. Isso significa que, se invocarmos esses métodos em um objeto
WebDriver(ouPageno Playwright), nosso código buscará os elementos em todo o documento HTML. Por outro lado, se invocarmos esses métodos em um objetoWebElement(ou encadearmos em umLocatorpai), nosso código buscará os elementos apenas entre os filhos daquele elemento HTML atual. - Como verificar que um elemento NÃO existe na página: Se quisermos garantir que um elemento não está presente no documento HTML, não devemos usar o método
findElement(), pois ele lança umaNoSuchElementExceptioncaso nenhum elemento seja encontrado! Em vez disso, devemos invocar o métodofindElements(By...)e verificar se a lista retornada está vazia (assertThat(browser.findElements(...)).isEmpty(), ouassertThat(locator).hasCount(0)no Playwright).
Vamos ver agora, passo a passo, como localizar cada elemento do nosso documento HTML utilizando os diferentes critérios de busca. Começaremos buscando um elemento pelo nome da sua tag HTML.
2.1. Localizando um Elemento pelo Nome da Tag (By.tagName)
Se quisermos encontrar um elemento que possui um nome de tag específico (como <blockquote>), podemos criar o critério de busca invocando o método estático By.tagName(), passando o nome da tag HTML como parâmetro (no Playwright: page.locator("blockquote")).
Depois de escrevermos o teste End-to-End correspondente, nossa classe de teste fica assim:
|
|
Vamos avançar e descobrir como podemos encontrar um elemento que possui um id específico.
2.2. Localizando um Elemento pelo seu ID (By.id)
Se quisermos encontrar um elemento que possui um atributo id específico (como <h1 id="page-header">), criamos o critério de busca invocando o método estático By.id(), passando o id do elemento desejado como parâmetro (no Playwright: page.locator("#page-header")).
Depois de escrevermos esse teste End-to-End, a parte relevante da nossa classe de teste fica assim:
|
|
Vamos ver agora como encontrar um elemento utilizando o nome de uma classe CSS como critério de busca.
2.3. Localizando um Elemento pelo Nome da Classe (By.className)
Se quisermos encontrar um elemento que possui uma classe CSS específica (como <p class="text-block">), criamos o critério de busca invocando o método estático By.className(), passando o nome da classe como parâmetro (no Playwright: page.locator(".text-block")).
Depois de escrevermos esse teste End-to-End, nossa classe de teste fica assim:
|
|
A seguir, veremos como utilizar o atributo name do elemento como critério de busca.
2.4. Localizando um Elemento pelo Atributo name (By.name)
Se quisermos encontrar um elemento cujo atributo name possui um valor específico (como <input name="text-input" value="Random text"/>), criamos o critério de busca invocando o método estático By.name(), passando o valor do atributo name como parâmetro (no Playwright: page.locator("[name='text-input']")).
Para lermos o valor preenchido dentro de um <input>, invocamos getAttribute("value") no WebElement retornado:
|
|
Vamos descobrir agora como podemos localizar um elemento de link (<a>) que possui um texto de link específico.
2.5. Localizando um Link pelo Texto Exato (By.linkText)
Se quisermos encontrar um elemento <a> que possui um texto visível exato (como <a href="http://www.google.com">Go to Google</a>), criamos o critério de busca invocando o método estático By.linkText(), passando o texto esperado do link como parâmetro (no Playwright: page.getByRole(AriaRole.LINK, Page.GetByRoleOptions().setName("Go to Google"))).
Depois de escrevermos esse teste End-to-End, nossa classe de teste fica assim:
|
|
A seguir, veremos como encontrar um link utilizando apenas uma parte do seu texto como critério de busca.
2.6. Localizando um Link por Texto Parcial (By.partialLinkText)
Se quisermos encontrar um elemento <a> cujo texto visível contenha uma substring específica (como "Test With Spring" dentro de "Go to Test With Spring Course"), criamos o critério de busca invocando o método estático By.partialLinkText():
|
|
Vamos avançar e ver como podemos localizar um elemento utilizando um seletor CSS.
2.7. Localizando um Elemento com Seletor CSS (By.cssSelector)
Embora os métodos básicos fornecidos pela classe By sejam adequados para a maioria das situações, às vezes precisamos de flexibilidade extra. Quando esse for o caso, podemos especificar nosso critério de busca utilizando um seletor CSS através do método estático By.cssSelector().
Veja como localizamos o elemento com a classe .text-block usando By.cssSelector(".text-block"):
|
|
Por fim, vamos ver como especificar nosso critério de busca utilizando uma expressão XPath.
2.8. Localizando um Elemento com Expressão XPath (By.xpath)
Se os métodos básicos da classe By não forem suficientes e não pudermos (ou não quisermos) utilizar um seletor CSS, também podemos especificar nosso critério de busca utilizando uma expressão XPath através do método estático By.xpath().
Veja como localizamos o elemento <blockquote> utilizando a expressão XPath /html/body/blockquote:
|
|
Agora que já sabemos encontrar elementos com todos os métodos da classe By, vamos conhecer as boas práticas que nos ajudam a construir interfaces de usuário testáveis e escrever testes End-to-End melhores.
3. Boas Práticas para Interfaces Testáveis e Seletores Resilientes
Existem dois princípios fundamentais que nos ajudam a implementar interfaces de usuário testáveis e escrever testes End-to-End mais rápidos e robustos:
Princípio 1: Atribua IDs, classes, nomes (ou data-testid) claros aos elementos HTML
Devemos atribuir atributos id, class e name (ou atributos dedicados de teste como data-testid) significativos aos elementos HTML da nossa aplicação. Isso nos permite localizar esses elementos utilizando critérios de busca que têm muito menos chance de quebrar quando fizermos alterações visuais ou de layout na nossa interface de usuário.
Princípio 2: Priorize id, name ou class em vez de XPath ou Textos de Link
Quando localizamos elementos em documentos HTML, devemos preferir utilizar o id, name ou class semântica do elemento pelos seguintes quatro motivos:
- Performance: Utilizar o
idounamedo elemento é a maneira mais rápida (em termos de desempenho de busca no DOM) de encontrar elementos em documentos HTML. - Resiliência a mudanças de layout: Testes End-to-End que utilizam
id,classsemântica ounamedo elemento têm muito menos probabilidade de quebrar quando refatoramos a estrutura visual da página. - Internacionalização (i18n) e Testes A/B: Se a nossa interface de usuário precisar suportar múltiplos idiomas ou se fizermos muitos testes A/B de copywriting, não devemos usar o texto do link (
linkText/partialLinkText) como critério de busca, porque manter esses seletores sincronizados dá um trabalho enorme. - Fragilidade de Seletores CSS complexos e XPath estrutural: Seletores que dependem da hierarquia do DOM via CSS ou expressões XPath (como
/html/body/div[2]/blockquote) são extremamente frágeis e quebrarão nossos testes End-to-End na primeira alteração de layout. Devemos deixá-los apenas como último recurso quando não houver outra opção.
Resumo
Esta aula nos ensinou quatro pontos fundamentais:
- Podemos encontrar elementos em documentos HTML utilizando os métodos
findElement()efindElements()declarados pela interfaceSearchContext(oulocator()no Playwright). - Podemos especificar o critério de busca utilizando os métodos fábrica estáticos fornecidos pela classe
By(id,name,className,tagName,linkText,partialLinkText,cssSelectorexpath). - Devemos atribuir
ids,classes enames claros aos nossos elementos HTML na camada de view, pois isso torna nossa interface de usuário intrinsecamente testável. - Sempre que possível, devemos priorizar a busca por
id,nameouclasssemântica para manter nossos testes End-to-End rápidos e imunes a refatorações visuais.