Vanilton Pinheiro
Version 1.36
Version 1.25
Vanilton Pinheiro
Analista de Sistemas
O que trás vocês até aqui?
# O que vamos precisar
Introdução Automação de Teste
Entendendo a Estrutura de Teste com Unittest
Conhecendo o Playwright
Funções, Eventos
Localizadores
Inspetor e Gerador de Teste e Relatórios
# O que veremos
# 1 Introdução Automação de Teste
Automação de teste é o uso de software para controlar a execução do teste de software, a comparação dos resultados esperados com os resultados reais, a configuração das pré-condições de teste e outras funções de controle e relatório de teste.
# 1 Introdução Automação de Teste
Fonte: https://pt.wikipedia.org/wiki/Automa%C3%A7%C3%A3o_de_teste
# 1 Introdução Automação de Teste
# 1 Introdução Automação de Teste
# 1 Introdução Automação de Teste
# 1 Introdução Automação de Teste
Lento
Rápido
$$$
$
# de testes
# 1 Introdução Automação de Teste
# 2 Entendendo a Estrutura de Teste com Unittest
O teste unitário consiste em verificar o comportamento das menores unidades em sua aplicação.
Tecnicamente, isso seria uma classe ou até mesmo um método de classe em línguas orientadas a objetos, e seria um procedimento ou função em línguas processuais e funcionais.
# 2 Entendendo a Estrutura de Teste com Unittest
Cada teste deve lidar apenas com:
Um objective — Um cenário a ser testado (um “dado”).
Uma action — Um método para testar (um “quando”).
Um assert — Uma chamada para um método de verificação (um “então”).
# 2 Entendendo a Estrutura de Teste com Unittest
# 2 Entendendo a Estrutura de Teste com Unittest
Unittest o foi originalmente inspirada no JUnit e tem uma semelhança às principais estruturas de teste de unidade em outras linguagens. Ele suporta automação de teste, compartilhamento de código de configuração e desligamento para testes, agregação de testes em coleções e independência dos testes da estrutura de relatórios.
# 2 Entendendo a Estrutura de Teste com Unittest
Na IDE PyCharm:
# 2 Entendendo a Estrutura de Teste com Unittest
Um caso de teste é um método dentro dessa classe com nome iniciado de test. Ex: test_cadastro_xpto()
# 2 Entendendo a Estrutura de Teste com Unittest
No corpo de um método de teste basicamente o que se faz é:
Criar um objeto da classe que contem o método a ser testado (se o método a ser testado for um método de instância)
Invocar o método em teste com os parâmetros desejados e armazenar o valor de retorno
Utilizar uma das asserções disponíveis no framework para comparar o resultado obtido com o resultado esperado.
# 2 Entendendo a Estrutura de Teste com Unittest
Para a criação de um caso de teste e necessário:
Identificar o método a ser testado
Compreender a especificação do método: o que recebe de entrada e qual a saída produzida em função da entrada escolhida.
Comparar a saída produzida (obtida) com aquela que deveria ser gerada conforme a especificação.
# 2 Entendendo a Estrutura de Teste com Unittest
É uma boa prática de escrita de casos de teste unitário, separar cada caso de teste em um método de teste.
Da mesma forma, deve-se agrupar em uma classe de teste, os casos de teste referentes a determinada classe e/ou método em teste, visando facilitar a localização dos testes.
# 2 Entendendo a Estrutura de Teste com Unittest
Clonar / baixar a branch main em https://github.com/Vanilton18/xpto-exemplos-teste
Verifique o comportamento do método check_valid_identifier na Classe Validators no pacote apps
Criar a classe de Teste no pacote test_apps e implemente os cenários de acordo com a especificação do método.
# 2 Entendendo a Estrutura de Teste com Unittest
Verifique o comportamento do método check_password na Classe Validators no pacote apps
Criar a classe de Teste no pacote test_apps e implemente os cenários de acordo com a especificação do método.
# 3 Conhecendo o Playwright
O Playwright foi criado especificamente para acomodar as necessidades de testes de ponta a ponta, famoso e2e.
# 3 Conhecendo o Playwright
É uma biblioteca Node para automatizar os navegadores Chromium, WebKit e Firefox com uma única API. Ele permite a automação da Web entre navegadores de modo confiável e rápido.
# 3 Conhecendo o Playwright
Playwright fornece automação entre navegadores por meio de uma única API.
O Playwright inicia navegadores sem periféricos (headless) por padrão.
A abordagem usada pelo Playwright será familiar para os usuários de outras estruturas de teste do navegador, como Selenium WebDriver.
# 3 Conhecendo o Playwright
08/2022
06/2023
Atualizado em: 10/06/2023
# 3 Conhecendo o Playwright
>=3.7
# 3 Conhecendo o Playwright
# 3 Conhecendo o Playwright
# 3 Conhecendo o Playwright
pip install --upgrade pip
pip install playwright
playwright install
Vamos criar um projeto Python puro e instalar as dependências:
# 3 Conhecendo o Playwright
mkdir projeto-playwright # Criar pasta do projeto
python -m venv venv # Criar ambiente virtual
pip install --upgrade pip # Atualizar gerenciador de pacotes
pip install playwright # Instalar biblioteca da ferramenta
playwright install chromium # Instalar navegador Chromium (chromium, firefox, webkit)
playwright install # Instalar todos navegadores
Vamos criar um projeto Python e instalar as dependências:
# 3 Conhecendo o Playwright
playwright --help # Opções do executor Playwright
playwright --version # Versão instalada
Comandos:
open [options] [url] # abrir página no navegador especificado via -b, --browser
codegen [options] [url] # abrir página e gerar código para ações do usuário
cr [options] [url] # abrir página no Chromium
ff [options] [url] # abrir página no Firefox
wk [options] [url] # abrir página no WebKit
screenshot [options] <url> <filename> # Capturar tela
pdf [options] <url> <filename> # Salvar página como pdf
show-trace [options] [trace...] # Mostrar visualizador de rastreamento
# 3 Conhecendo o Playwright
#Sigla de navegadores
# cr -> chromium, ff -> firefox, wk -> webkit
# Exemplos de uso via CLI
playwright --help # Opções do executor Playwright
playwright open -b cr https://vanilton.net # Abrir navegador chrome em um site
playwright open --help # Opções do comando open
playwright pdf --timeout 50000 https://google.com meu_pdf.pdf
# Gerar PDF da página
playwright screenshot --timeout 30000 http://vanilton.net ss_full.png
# Gerar imagem PNG da página com um determinado tamanho de tela
playwright screenshot --timeout 30000 --viewport-size "720, 1280" https://vanilton.net ss_portrait.png
# Gravar passos de teste
playwright codegen https://vanilton.net --save-trace trace.zip
# Abrir um trace gravado
playwright show-trace trace.zip
# 3 Conhecendo o Playwright
1. Navegar
3. Capturar
2. Interagir
4. Esperar
# 3 Conhecendo o Playwright
# Acessar a url
page.goto(url, **kwargs)
# Atualizar a página (F5)
page.reload(**kwargs)
# Voltar uma página no histórico
page.go_back(**kwargs)
# Avançar uma página no histórico
page.go_forward(**kwargs)
# 3 Conhecendo o Playwright
from playwright.sync_api import sync_playwright
browser = sync_playwright().start().chromium.launch()
page = browser.new_page()
page.goto("https://google.com")
print("Acessado -> " + page.title())
page.goto("http://vanilton.net")
print("Acessado -> " + page.title())
page.reload()
print("Atualizando página")
page.go_back()
print("Retornando ao google")
page.go_forward()
print("Avançando ao vanilton.net")
print("Acessado -> " + page.title())
Experimente executar o código abaixo:
# 3 Conhecendo o Playwright
# Preencher
page.locator(CSS).fill("Vanilton Pinheiro")
# Clicar
page.locator(XPATH).click()
# Selecionar um elemento
page.select_option('select#colors', 'blue')
# Selecionar múltiplos elementos
page.select_option('select#colors', ['red', 'green', 'blue'])
# 3 Conhecendo o Playwright
from playwright.sync_api import sync_playwright
browser = sync_playwright().start().chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://google.com")
# Preencher campo de pesquisa
page.locator('[name="q"]').fill("Vanilton Pinheiro")
# Capturar informações da página
print(page.title())
print(page.inner_html('html'))
print("valor? -> " + page.locator('[name="q"]').input_value())
print("oculto? -> " + page.locator('[name="q"]').is_hidden().__str__())
print("visível? -> " + page.locator('[name="q"]').is_visible().__str__())
print("disponível? -> " + page.locator('[name="q"]').is_enabled().__str__())
print("Valor Atributo Aria Label? -> " + page.locator('[name="q"]').get_attribute("aria-label"))
# Fechar página
page.close()
Experimentem executar o código abaixo:
# 4 Localizadores
Toda e qualquer ferramenta que realiza automatização, seja uma página web, um aplicativo mobile ou desktop, necessita de uma estratégia para identificar o elemento ao qual se deseja interagir, eis o que chamamos de localizador.
# 4 Localizadores
# 4 Localizadores
page.locator(selector, **kwargs)
O Playwright suporta muitos seletores diferentes, como Text, CSS, XPath e muitos mais.
Argumentos opcionais
# 4 Localizadores
locator = page.locator("text=Submit")
locator.hover()
locator.click()
Toda vez que o localizador é usado para alguma ação, o elemento DOM atualizado é localizado na página. Portanto, no trecho abaixo, o elemento DOM subjacente será localizado duas vezes, antes de cada ação
# 4 Localizadores
O Playwright a partir da versão 1.27 adotou localizadores padrões, todos retornando um objeto Locator da API Playwrigth.
Atualizado em: 13/06/2023
# 4 Localizadores
#para localizar por atributos de acessibilidade explícitos e implícitos.
page.get_by_role()
#para localizar por conteúdo de texto.
page.get_by_text()
#para localizar um controle de formulário pelo texto do rótulo associado.
page.get_by_label()
#para localizar uma entrada por espaço reservado.
page.get_by_placeholder()
#para localizar um elemento, geralmente imagem, por sua alternativa de texto.
page.get_by_alt_text()
#para localizar um elemento por seu atributo de título.
page.get_by_title()
#para localizar um elemento com base em seu atributo data-testid
page.get_by_test_id()
Atualizado em: 13/06/2023
# 4 Localizadores
O localizador get_by_role tem sua utilização sugerida para elementos que possuem interação como botões, checkboxes e radios e etc.
Atualizado em: 15/06/2023
# 4 Localizadores
locator = page.get_by_role("button", name="Nome")
locator.click()
page.get_by_role("checkbox", name="Moto").check()
page.get_by_role("radio", name="M").check()
page.get_by_role("checkbox", name=re.compile("moto", re.IGNORECASE)).check()
Atualizado em: 15/06/2023
# 4 Localizadores
# Corresponde <span>
page.get_by_text("meu querido")
# Corresponde <div>
page.get_by_text("Olá meu querido")
# Corresponde segunda <div>
page.get_by_text("Olá", exact=True)
# Corresponde entre <div>s
page.get_by_text(re.compile("Olá"))
# Corresponde segunda <div>
page.get_by_text(re.compile("^olá$", re.IGNORECASE))
Localizadores get_by_text sugere-se utilização para elementos que não possuem interação como div, span, p, h1 e etc.
Você pode localizar por substring de texto, string exata ou uma expressão regular:
Atualizado em: 15/06/2023
<div>Olá
<span>meu querido
</span>
</div>
<div>Olá</div>
# 4 Localizadores
page.get_by_title("message-area")
Localizadores get_by_title identificam elementos por seu atributo de título.
<form id="form-login">
<label for="username">First name:</label><br>
<input type="text" id="username" name="username" maxlength="30" required=""><br>
<label for="password">Password:</label><br>
<input type="password" id="password" required=""><br>
<input type="submit" class="btn-primary" onclick="Enviar();" value="Autenticar"><br>
<span id="message" title="message-area">Credenciais inválidas. Tente novamente.</span>
</form>
Atualizado em: 30/06/2023
Se deve encontrar uma correspondência exata: com distinção entre maiúsculas e minúsculas e string inteira.
page.get_by_title("parametro", exact=True).fill("xpto")
# 4 Localizadores
# 4 Localizadores
page.get_by_label("First name:").fill("juju")
Localizadores get_by_label permitem identificar elementos de entrada pelo texto do elemento <label> ou aria-labelledby associado, ou pelo atributo aria-label.
<form id="form-login">
<label for="username">First name:</label><br>
<input type="text" id="username" name="username" maxlength="30" required=""><br>
<label for="password">Password:</label><br>
<input type="password" id="password" required=""><br>
<input type="submit" class="btn-primary" onclick="Enviar();" value="Autenticar"><br>
<span id="message" title="message-area">Credenciais inválidas. Tente novamente.</span>
</form>
Se deseja encontrar uma correspondência exata: com distinção entre maiúsculas e minúsculas e string inteira.
Atualizado em: 30/06/2023
page.get_by_label("parametro", exact=True).fill("xpto")
# 4 Localizadores
page.get_by_placeholder("admin").fill("jose")
Localizadores get_by_placeholder permitem identificar elementos de entrada pelo texto do elemento <label> ou aria-labelledby associado, ou pelo atributo aria-label.
<form id="form-login">
<label for="username">First name:</label><br>
<input type="text" id="username" name="username" maxlength="30" required="" placeholder="admin"><br>
<label for="password">Password:</label><br>
<input type="password" id="password" required=""><br>
<input type="submit" class="btn-primary" onclick="Enviar();" value="Autenticar"><br>
<span id="message" title="message-area">Credenciais inválidas. Tente novamente.</span>
</form>
Atualizado em: 30/06/2023
Se deseja encontrar uma correspondência exata: com distinção entre maiúsculas e minúsculas e string inteira.
page.get_by_placeholder("admin", exact=True).fill("xpto")
# 4 Localizadores
Todas as imagens devem ter um atributo alt que descreva a imagem. Você pode localizar uma imagem com base na alternativa de texto usando page.get_by_alt_text().
page.get_by_alt_text("triângulo equilátero")
<img src="equilatero.png" alt="triângulo equilátero">
# 4 Localizadores
# 4 Localizadores
# 4 Localizadores
page.locator("text=Atualizar").click()
page.locator("text=atualiz").click()
page.locator("text='Atualizar Página'").click()
page.locator("text='atualizar página'").click()
page.locator("text='Download'").click()
page.locator("input", has_text="Autenticar")
<html>
<body>
<h2>Log in</h2>
<div>
<form>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname">
<input type="submit" class="btn-primary"
onclick="Enviar();" value="Enviar" />
</form>
<button>Atualizar Página</button>
<button>Download Relatório A</button>
</div>
</body>
</html>
A correspondência padrão não diferencia maiúsculas de minúsculas e procura uma substring
Modo exato 'valor' entre aspas, implica correspondência com distinção entre maiúsculas e minúsculas
Elemento contendo outro elemento por texto
# 4 Localizadores
Fonte: https://regex101.com/
page.locator("text=/s*Relatório [A-Z]/i").inner_text()
Modo JavaScript Regex
# 4 Localizadores
# Clica no primeiro button que encontrar
page.locator("button").click()
# Clica no primeiro button com visibilidade
page.locator("button:visible").click()
page.locator("button >> visible=true").click()
<button style='display: none'>Invisible</button>
<button>Visible</button>
# 4 Localizadores
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/web-test/display/")
page.locator('.circle').click()
print(page.locator('#circle').get_attribute("display"))
3. Implemente clicar no botão Atualizar Página
# 4 Localizadores
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/web-test/display/")
page.locator("section", has=page.locator(".circle >> visible=true")).click()
print(page.locator('#circle').get_attribute("display"))
Ainda seguindo o exemplo anterior, os localizadores suportam uma opção para selecionar apenas elementos que tenham um descendente que corresponda a outro localizador.
Observe que o localizador interno é correspondido a partir do externo, não da raiz do documento.
# 4 Localizadores
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/web-test/display/")
print(page.locator("section:has(div.circle)").inner_text())
print(page.locator('#circle').get_attribute("display"))
Seguindo o exemplo anterior, a pseudoclasse :has() é uma pseudoclasse CSS experimental. Ele retorna um elemento se algum dos seletores passados como parâmetros relativos ao :scope do elemento fornecido corresponder a pelo menos um elemento.
Implemente a localização da seção de Opções
# 4 Localizadores
# Listando seções
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/web-test/display/")
sections = page.locator("section")
print(sections.all_inner_texts())
print(sections.locator("h4", has_text="Op").inner_text())
print(page.locator('#circle').get_attribute("display"))
Aumentando localizadores existentes
Implemente pegar a seção pelo seu índice
# 4 Localizadores
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/web-test/display/")
# A lista separada por vírgulas de seletores CSS corresponderá a todos os
# elementos que podem ser selecionados por um dos seletores dessa lista.
page.locator('button:has-text("Show Hide Elements"), button:has-text("Exibir Elementos Ocultos")').click()
print(page.locator('#show-element').inner_text())
page.locator('button:has-text("Show Hide Elements"), button:has-text("Exibir Elementos Ocultos")').click()
print(page.locator('#show-element').inner_text())
Selecionando elementos que correspondem a uma das condições (OR)
Onde esta solução pode ser interessante?
# 4 Localizadores
XPath pode ser usado para navegar pelos elementos e atributos em um documento XML.
XPath usa expressões para selecionar nós ou conjuntos de nós em um documento XML. Essas expressões se parecem muito com as utilizadas em sistemas de arquivos de computador.
# 4 Localizadores
XPath significa XML Path Language
XPath usa a sintaxe "path like" para identificar e navegar em nós em um documento
XML XPath contém mais de 200 funções integradas
XPath é um elemento importante no padrão XSLT
XPath é uma recomendação do W3C
Arquivos HTML seguem o padrão XML, logo...
# 4 Localizadores
Os seletores XPath são equivalentes a chamar Document.evaluate. Exemplo: xpath=//html/body.
O seletor começando com // ou .. é considerado um seletor xpath. Por exemplo, o Playwright converte '//html/body' para 'xpath=//html/body'.
XPATH não acessa Shadow DOM
# 4 Localizadores
Inspect Chrome
Utilize CTRL + F para ativar
Clique com o direito do mouse para copiar caminhos xpath
# 4 Localizadores
/ Seleciona do nó raiz
// Seleciona os nós no documento que correspondem à seleção, não importa onde eles estejam
. Seleciona o nó atual
.. Seleciona o pai do nó atual
@ Seleciona atributos
* Corresponde a qualquer nó de elemento
# 4 Localizadores
<html>
<head>
<meta charset="utf-8">
<title>Exemplo de Visualização Elementos</title>
</head>
<body>
<h3>Clique em um elemento para ocultar</h3>
<section>
<h4>Formas</h4>
<div class="circle" style="display: none" id="circle"></div>
<div class="circle" id="circle-clone"></div>
<div class="rounded" id="rounded"></div>
<div class="square" id="square"></div>
</section>
<section>
<h4>Opções</h4>
<button style="display: none">Atualizar Página</button>
<button id='refresh-page' onclick="AtualizarPagina()">Atualizar Página</button>
<button id="show-element">Exibir Elementos Ocultos</button>
</section>
</body>
</html>
/html # Selecionar o elemento html a partir da raiz da árvore DOM
//h4 # Selecionar todos elementos h4 a partir da raiz da árvore DOM de modo descendente
//h4/.. # Selecionar todos elementos acima de um h4
//@style # Selecionar todos elementos com um atributo style
# 4 Localizadores
/html # Selecionar o elemento html a partir da raiz da árvore DOM
//h4 # Selecionar todos elementos h4 a partir da raiz da árvore DOM de modo descendente
//h4/.. # Selecionar todos elementos acima de um h4
//@style # Selecionar todos elementos com um atributo style
# 4 Localizadores
<html>
<head>
<meta charset="utf-8">
<title>Exemplo de Visualização Elementos</title>
</head>
<body>
<h3>Clique em um elemento para ocultar</h3>
<section>
<h4>Formas</h4>
<div class="circle" style="display: none" id="circle"></div>
<div class="circle" id="circle-clone"></div>
<div class="rounded" id="rounded"></div>
<div class="square" id="square"></div>
</section>
<section>
<h4>Opções</h4>
<button style="display: none">Atualizar Página</button>
<button id='refresh-page' onclick="AtualizarPagina()">Atualizar Página</button>
<button id="show-element">Exibir Elementos Ocultos</button>
</section>
</body>
</html>
# Os predicados [] são usados para localizar um nó específico ou um nó que contém um valor específico.
//section[1] # Selecionar o primeiro elemento section
//section[last()] # Selecionar a última section
//section[last()-1] # Selecionar a penúltina section
//div[@class="rounded"] # Selecionar uma div com classe de nome rounded
//section/*[position()>3] # Selecionar todos elementos filhos de section a partir do terceiro nó
//*[@onclick] # Selecionar todos elementos com um atributo onclick
# 4 Localizadores
# Os predicados [] são usados para localizar um nó específico ou um nó que contém um valor específico.
//section[1] # Selecionar o primeiro elemento section
//section[last()] # Selecionar a última section
//section[last()-1] # Selecionar a penúltina section
//div[@class="rounded"] # Selecionar uma div com classe de nome rounded
//section/*[position()>3] # Selecionar todos elementos filhos de section a partir do terceiro nó
//*[@onclick] # Selecionar todos elementos com um atributo onclick
# 4 Localizadores
<html>
<head>
<meta charset="utf-8">
<title>Exemplo de Visualização Elementos</title>
</head>
<body>
<h3>Clique em um elemento para ocultar</h3>
<section>
<h4>Formas</h4>
<div class="circle" style="display: none" id="circle"></div>
<div class="circle" id="circle-clone"></div>
<div class="rounded" id="rounded"></div>
<div class="square" id="square"></div>
</section>
<section>
<h4>Opções</h4>
<button style="display: none">Atualizar Página</button>
<button id='refresh-page' onclick="AtualizarPagina()">Atualizar Página</button>
<button id="show-element">Exibir Elementos Ocultos</button>
</section>
</body>
</html>
# Funções de predicados
//button[contains(.,'Exibir')] # Retorna um ou mais botões contendo o texto "Exibir".
//button[contains(.,'Exibir')] | //button[contains(.,'Hide')] # Selecionando vários caminhos
//button[text()='Show Hide Elements'] # Seleciona um ou mais botões com o nome exato 'Show Hide Elements'
//button[@id and @onclick]
# 4 Localizadores
# Funções de predicados
//button[contains(.,'Exibir')] # Retorna um ou mais botões contendo o texto "Exibir".
//button[contains(.,'Exibir')] | //button[contains(.,'Hide')] # Selecionando vários caminhos
//button[text()='Show Hide Elements'] # Seleciona um ou mais botões com o nome exato 'Show Hide Elements'
//button[@id and @onclick]
# 4 Localizadores
<html>
<head>
<meta charset="utf-8">
<title>Exemplo de Visualização Elementos</title>
</head>
<body>
<h3>Clique em um elemento para ocultar</h3>
<section>
<h4>Formas</h4>
<div class="circle" style="display: none" id="circle"></div>
<div class="circle" id="circle-clone"></div>
<div class="rounded" id="rounded"></div>
<div class="square" id="square"></div>
</section>
<section>
<h4>Opções</h4>
<button style="display: none">Atualizar Página</button>
<button id='refresh-page' onclick="AtualizarPagina()">Atualizar Página</button>
<button id="show-element">Exibir Elementos Ocultos</button>
</section>
</body>
</html>
# Um axe representa um relacionamento com o nó atual e é usado para localizar nós relativos a esse nó na árvore.
//h4/following-sibling::div # Seleciona todos os irmãos após o nó atual
//button//preceding-sibling::h4 # Seleciona todos os irmãos antes do nó atual
//h4/ancestor::section # Seleciona todos os ancestrais (pai, avô, etc.) do nó atual
# 4 Localizadores
# Pegar texto dos elementos filhos
//body//child::*
# Pegar texto do nó principal sem os filhos
//body/text()
# Pegar textos começando com um valor
//body[starts-with(.,'Resultado')]
# Funcao text() not working (pegando o texto do nó pai)
//body/text()
# 4 Localizadores
# 4 Localizadores
Um aspecto importante dos componentes da Web é o encapsulamento — ser capaz de manter a estrutura, o estilo e o comportamento da marcação ocultos e separados de outros códigos na página para que as diferentes partes não entrem em conflito e o código possa ser mantido limpo e organizado.
# 4 Localizadores
O mecanismos de localização por CSS e Text acessam o Shadow DOM por padrão, seguindo os seguintes passos:
# 4 Localizadores
page.locator(":light(localizador)")
Existe a possibilidade de ignorar o acesso ao Shadow DOM, para isso:
# 4 Localizadores
# 4 Localizadores
:right-of(inner > selector) #Retorna elementos que estão a direita de acordo com o seletor
:left-of(inner > selector) #Retorna elementos que estão a esquerda de acordo com o seletor
:above(inner > selector) #Retorna elementos que estão acima de acordo com o seletor
:below(inner > selector) #Retorna elementos que estão abaixo de acordo com o seletor
:near(inner > selector) #Retorna elementos que estão próximos (dentro de 50 pixels CSS)
Os seletores de layout dependem do layout da página e podem produzir resultados inesperados. Por exemplo, um elemento diferente pode ser correspondido quando o layout muda em um pixel.
# 4 Localizadores
# 4 Localizadores
Você pode restringir a consulta à n-ésima correspondência usando o seletor nth ou utilizar nth-match
# nth índice inicia de 0
# Click no primeiro botão
page.locator("button >> nth=0").click()
# Click último botão
page.locator("button >> nth=-1").click()
#:nth-match índice inicia de 1
# Preenche o segundo input da tela
page.locator(":nth-match(input, 2)").fill("xpto")
# 4 Localizadores
# 4 Localizadores
Seletores podem ser combinados com a chave >>, por exemplo: 'seletor1 >> seletor2 >> seletor3'. Quando os seletores são encadeados, o próximo é consultado em relação ao resultado do anterior.
# Seleção encadeada
css=article >> css=.bar > .baz >> css=span[attr=value]
Equivale a:
document
.querySelector('article')
.querySelector('.bar > .baz')
.querySelector('span[attr=value]')
# Seleção com filtro de texto a partir do elemento raiz
css=article >> text=Hello
# 4 Localizadores
O Playwright suporta atalhos para selecionar elementos usando certos atributos (não sendo o sugerido pela documentação). Atualmente, apenas os seguintes atributos são suportados:
# Preenche um input com id "username"
page.locator('id=username').fill('value')
# Clica em um elemento com data-test-id "submit"
page.locator('data-test-id=submit').click()
# 4 Localizadores
page.locator('data-test-id:light=submit').click()
css=[data-test="login"]:enable.
# 5 Funções e Eventos
# Clica no elemento identificado
page.locator(seletor).click()
# Input texto, data, time (limpando campo)
page.locator(seletor).fill("value")
# Combobox de Seleção
page.select_option(seletor, value='mul')
page.locator(seletor).select_option(value='blue')
page.locator(seletor).select_option(label='/')
page.locator(seletor).select_option(index=2)
Toda interação ocorrerá após a identificação do elemento (localizador), as principais que necessitamos são:
# 5 Funções e Eventos
# Digita caracter por caracter (na posição do cursor sem limpar)
page.locator('#area').type('Hello World!')
# Exemplo digitando uma letra a cada segundo
page.locator('#area').type('Hello World!', delay=1000)
Type - este método emitirá todos os eventos de teclado necessários, com todos os eventos keydown, keyup,
keypress no lugar. Você pode até especificar o atraso opcional entre as teclas pressionadas para
simular o comportamento real do usuário.
# 5 Funções e Eventos
# 5 Funções e Eventos
# Retorna o texto do innerHtml do elemento encontrado pelo seletor
page.locator(seletor).inner_text()
''' Retorna o texto do innerHtml do primeiro elemento encontrado pelo
seletor (caso venha uma lista)'''
page.locator(seletor).first.inner_text()
# Retorna o texto do value do input encontrado pelo seletor
page.locator(seletor).input_value()
# Retorna a lista de innerHtml dos textos dos elementos encontrados pelo seletor
page.locator(seletor).all_inner_texts().__str__()
# Retorna o título da página
page.title()
# Quantidade de elementos retornados pelo seletor
page.locator(seletor).count()
# Retorna o valor de um atributo de acordo com o seletor
page.locator(seletor).get_attribute("placeholder")
Toda captura ocorrerá após a identificação do elemento (localizador), as principais que necessitamos são:
# 5 Funções e Eventos
# 5 Funções e Eventos
# Exemplo utilizando o objeto 'location'para obter informações do endereço da página atual
page.evaluate("window.location")
# Buscando o valor do input do primeiro input da tela
page.evaluate("document.getElementsByTagName('input')[0].value")
#Se o resultado for uma promessa ou se a função for assíncrona,
#a avaliação aguardará automaticamente até que seja resolvida:
status = page.evaluate("""async () => {
response = await fetch(location.href)
return response.status
}""")
# Exemplo utilizando argumento
page.evaluate('num => num * 5', 2)
# Utilizando objeto
print(page.evaluate('object => object.foo[0]', {'foo': [1,2,3]}))
A API page.evaluate(expression, **kwargs) pode executar uma função JavaScript no contexto da página da web e trazer os resultados de volta ao ambiente Playwright. Exemplos como: window e document podem ser usados no evaluate.
# 5 Funções e Eventos
page.evaluate(expression, **kwargs) recebem um único argumento opcional. Este argumento pode ser uma mistura de valores Serializable e instâncias JSHandle ou ElementHandle
Nota: Como qualquer elemento DOM na página também é um objeto JavaScript, qualquer ElementHandle também é um JSHandle.
# 5 Funções e Eventos
Experimente executar o código abaixo:
page.goto("http://vanilton.net/web-test/promise/")
# Cria o objeto window
object_handle = page.evaluate_handle('window')
print(object_handle.json_value())
# Passando por parâmetro um handle e recuperando seu innerHTML
body_handle = page.evaluate_handle('document.body')
body_text = page.evaluate("body => body.innerHTML", body_handle)
print(body_text)
# Encerra a referência ao elemento
body_handle.dispose()
object_handle.dispose()
Implemente o retorno da largura da tela pelo objeto screen dentro de window
# 5 Funções e Eventos
ElementHandle representa um elemento DOM na página. ElementHandles podem ser criados com o método page.query_selector(selector, **kwargs).
Nota: O uso de ElementHandle é desencorajado, use objetos Locator e asserções web-first.
Handle aponta para um elemento DOM específico na página. Se esse elemento altera o texto ou é usado para renderizar um componente totalmente diferente, handle ainda está apontando para esse mesmo elemento DOM. Isso pode levar a comportamentos inesperados.
handle = page.query_selector("text=Submit")
handle.hover()
handle.click()
# 5 Funções e Eventos
Experimente executar o código abaixo:
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.goto("http://vanilton.net/blog")
href_element = page.query_selector("a")
print(href_element.inner_text())
# 5 Funções e Eventos
# Realizar a ação diretamente no teclado virtual (onde estiver o foco)
page.keyboard.press('Meta+a', delay=1000)
# Realizar o Enter do teclado no seletor
page.locator('#submit').press('Enter')
# Realizar o atalho Control + seta para direita do teclado
page.locator('#name').press('Control+ArrowRight')
# Digitar uma tecla $ com o teclado
page.locator('#value').press('$')
# Selecionando o valor do input (Meta no MacOs) com delay de 1s
page.locator('input >> nth=0').press('Meta+a', delay=1000)
Keyboard fornece uma API para gerenciar um teclado virtual. A API de alto nível é keyboard.type(texto, **kwargs), que recebe caracteres brutos e gera eventos keydown, keypress/input e keyup adequados em sua página.
# 5 Funções e Eventos
F1-F12
Digit0-Digit9
KeyA-KeyZ
Backquote
Minus
Equal
Backslash
Backspace
Tab
Delete
Escape
ArrowDown
End
Enter
Home
Insert
PageDown e
PageUp
ArrowRight
ArrowUp
, etc.
Outros exemplos de Keys
Shift
Control
Alt
Meta
ShiftLeft.
Atalhos de Modificação
# 5 Funções e Eventos
page.keyboard.type("Hello World!")
page.keyboard.press("ArrowLeft")
page.keyboard.down("Shift") # down (segura a tecla)
for i in range(6):
page.keyboard.press("ArrowLeft")
page.keyboard.up("Shift") # up (solta a tecla)
page.keyboard.press("Backspace")
Fazendo a inserção desses comandos ao final, qual o valor que estará contido no input?
# 5 Funções e Eventos
Acesse: https://keycode.info
# 5 Funções e Eventos
page = sync_playwright().start().chromium.launch(headless=False).new_page()
#with page.expect_request("**/*teste*.png") as first:
with page.expect_request("**teste_agil-520x389.png") as first:
page.goto("http://vanilton.net/blog")
print(first.is_done())
print(first.value.headers)
print(first.value.url)
print(first.value.response().status)
O Playwright permite ouvir vários tipos de eventos que acontecem na página da web, como solicitações de rede, criação de páginas filhas, trabalhadores dedicados etc. Existem várias maneiras de se inscrever nesses eventos:
# 5 Funções e Eventos
def print_request_sent(request):
print("Request sent: " + request.url)
def print_request_finished(request):
print("Request finished: " + request.url)
def print_request_response(request):
print("Request response: " + request.url)
# Execute essa função e observe o console da IDE
def test_wait_add_remove_event_listener():
page = sync_playwright().start().chromium.launch(headless=False).new_page()
page.on("request", print_request_sent)
page.on("response", print_request_response)
page.on("requestfinished", print_request_finished)
#page.remove_listener("requestfinished", print_request_finished)
page.goto("https://ge.globo.com")
page.goto("https://wikipedia.org")
Vamos verificar como acompanhar requests realizadas no navegador, para isso execute o exemplo abaixo:
# 5 Funções e Eventos
page.goto("http://vanilton.net/web-test/iframe/")
frame_element = page.frame_locator('iframe')
frame_element.locator('text=Produtos').click()
print(frame_element.locator('css=title').inner_text())
Uma página pode ter um ou mais objetos Frame anexados a ela. Cada página tem um quadro principal e as interações no nível da página (como clique) devem operar no quadro principal.
Implemente escolher um dos posts que será carregado no iframe e exiba o título da página.
# 5 Funções e Eventos
# Marca o checkbox
page.locator('#agree').check()
# Verifica o estado de marcado
assert page.locator('#agree').is_checked() is True
# Desmarca um checkbox marcado <label>.
page.locator('#subscribe-label').uncheck()
# Seleciona um elemento radio button
page.locator('text=XL').check()
Essa é a maneira mais fácil de marcar e desmarcar uma caixa de seleção ou um botão de opção. Este método pode ser usado com input[type=checkbox], input[type=radio], [role=checkbox] ou rótulo associado com checkbox ou botão de rádio.
# 5 Funções e Eventos
# Identificar o checkbox ou radio contendo XL
element = page.get_by_label('XL')
# Verifica o estado de marcado
assert element.is_checked() is True
# Desmarca um checkbox marcado <label>.
element.uncheck()
# Marcar o elemento
element.check()
Utilizando o localizador padrão get_by_label
Caso existam label com valores que conflitem e deseje identificar o valor exato utilize:
page.get_by_label("parametro", exact=True)
# 5 Funções e Eventos
# 5 Funções e Eventos
# Print da tela de acordo com o tamanho da tela
page.screenshot(path='caminho_da_imagem')
# Print de toda a tela (fullscreen)
page.screenshot(path='caminho_da_imagem', full_page=True)
# Base64 da imagem completa
# Experimente colar a string gerada em: https://codebeautify.org/base64-to-image-converter
screenshot_bytes = page.screenshot(full_page=True)
print(base64.b64encode(screenshot_bytes).decode())
# Print de um elemento específico
page.locator('#xpto').screenshot(path='caminho/imagem.png')
A API de capturas de tela aceita muitos parâmetros para formato de imagem, área de clipe, qualidade, etc. Verifique-os.
# 5 Funções e Eventos
Acesse: http://lojafake.vanilton.net
# 5 Funções e Eventos
# Marca os elementos identificados
page.locator("input").highlight()
Realce o(s) elemento(s) correspondente(s) na tela.
Acesse: https://vanilton.net/web-test/calculatorSoapPHP/ e teste com o código abaixo:
Útil para depuração.
# 5 Funções e Eventos
to be continue...
# 5 Funções e Eventos
Arrastando e soltando elementos
page.drag_and_drop(source, target, **kwargs)
page.goto("http://vanilton.net/web-test/drag-drop/")
page.drag_and_drop("id=drag1", 'id=div2')
print(page.locator("[id='content1']").inner_text())
page.drag_and_drop("id=drag1", "id=div1")
print(page.locator("[id='content2']").inner_text())
Funciona utilizando a api HTML 5 Nativa
# 5 Funções e Eventos
Continuando o exercício da lojafake, agora experimentem realizar o drag drop de um filme para a área de assistidos no site da loja utilizando a função drag and drop.
# 5 Funções e Eventos
Podemos realizar o arraste e soltar utilizando a função move e hover e com apoio do bouding_box()
page.hover()
page.mouse.down()
page.locator().bounding_box()
page.mouse.up()
Explorem os comportamentos das funções e implemente o arraste de um filme para a área de Filmes Vistos
# 5 Funções e Eventos
# Inicia o rastreamento antes de criar ou navegar em páginas
context.tracing.start(screenshots=True, snapshots=True, sources=True)
page.goto("https://playwright.dev")
# Encerra o rastreamento e exporta em .zip
context.tracing.stop(path = "trace.zip")
O Trace Viewer é uma ferramenta GUI que ajuda a explorar os passos gravados após a execução do script.
Métodos para visualização do trace
Local
Nuvem
# 5 Funções e Eventos
# 5 Funções e Eventos
#timeout em milissegundos
# Essa configuração alterará o tempo máximo de navegação padrão para os seguintes métodos e atalhos relacionados:
page.set_default_navigation_timeout(timeout)
# Essa configuração alterará o tempo máximo padrão para todos os métodos que aceitam a opção de timeout.
page.set_default_timeout(timeout)
# Alterar o tamanho da página (também pode ser utilizado no contexto)
page.set_viewport_size({"width": 640, "height": 480})
# 5 Funções e Eventos
O Playwright realiza uma série de verificações de ação nos elementos antes de fazer ações para garantir que essas ações se comportem conforme o esperado. Ele espera automaticamente que todas as verificações relevantes sejam aprovadas e só então executa a ação solicitada. Se as verificações necessárias não passarem dentro do tempo limite determinado, a ação falhará com TimeoutError.
# 5 Funções e Eventos
Action | Attached | Visible | Stable | Receives Events | Enabled | Editable |
---|---|---|---|---|---|---|
check | Yes | Yes | Yes | Yes | Yes | - |
click | Yes | Yes | Yes | Yes | Yes | - |
dblclick | Yes | Yes | Yes | Yes | Yes | - |
setChecked | Yes | Yes | Yes | Yes | Yes | - |
tap | Yes | Yes | Yes | Yes | Yes | - |
uncheck | Yes | Yes | Yes | Yes | Yes | - |
hover | Yes | Yes | Yes | Yes | - | - |
scrollIntoViewIfNeeded | Yes | - | Yes | - | - | - |
screenshot | Yes | Yes | Yes | - | - | - |
fill | Yes | Yes | - | - | Yes | Yes |
selectText | Yes | Yes | - | - | - | - |
dispatchEvent | Yes | - | - | - | - | - |
focus | Yes | - | - | - | - | - |
getAttribute | Yes | - | - | - | - | - |
innerText | Yes | - | - | - | - | - |
innerHTML | Yes | - | - | - | - | - |
press | Yes | - | - | - | - | - |
setInputFiles | Yes | - | - | - | - | - |
selectOption | Yes | Yes | - | - | Yes | - |
textContent | Yes | - | - | - | - | - |
type | Yes | - | - | - | - | - |
O elemento é considerado anexado quando conectado a um Documento ou a um ShadowRoot.
visibility:hidden
display:none
O elemento é considerado estável quando manteve a mesma caixa delimitadora por pelo menos dois quadros de animação consecutivos.
O elemento é considerado habilitado a menos que seja um <button>, <select>, <input> ou <textarea> com uma propriedade desabilitada.
O elemento é considerado editável quando habilitado e não possui conjunto de propriedades readonly.
O elemento é considerado receptor de eventos de ponteiro quando é o alvo de acerto do evento de ponteiro no ponto de ação.
# 5 Funções e Eventos
browser = sync_playwright().start().chromium.launch(timeout=60000)
# 5 Funções e Eventos
page.wait_for_timeout(timeout)
time.sleep(5)
page.wait_for_url(url, **kwargs)
page.wait_for_selector(selector, **kwargs)
page.wait_for_load_state(**kwargs)
page.wait_for_event(event, **kwargs)
# 5 Funções e Eventos
# 5 Funções e Eventos
# 5 Funções e Eventos
Nunca reiniciar o Browser
Sempre cria uma instância do Browser
Browser Context
# 5 Funções e Eventos
# 3 Conhecendo o Playwright
Browser
Context
Context
Page
Page
Page
Page
browser.close()
context.close()
page.close()
Anônimo
Anônimo
# 5 Funções e Eventos
browser = sync_playwright().start().chromium.launch(headless=False)
context = browser.new_context(
geolocation={'longitude': 12.492348, 'latitude': 41.890221},
permissions=['geolocation'],
record_video_dir='video/',
record_video_size={'width': 800, 'height': 600},
timezone_id="Europe/Rome",
locale="it-IT"
)
page = context.new_page()
page.goto("https://maps.google.com")
page.locator("button[aria-label='Mostra la tua posizione']").click()
# 5 Funções e Eventos
# 5 Funções e Eventos
# Select one file
page.locator('input#upload').set_input_files('myfile.pdf')
# Select multiple files
page.locator('input#upload').set_input_files(['file1.txt', 'file2.txt'])
# Remove all the selected files
page.locator('input#upload').set_input_files([])
# Upload buffer from memory
page.locator("input#upload").set_input_files(
files=[
{"name": "test.txt", "mimeType": "text/plain", "buffer": b"this is a test"}
],
)
Você pode selecionar arquivos de entrada para upload usando o método page.set_input_files(selector, files, **kwargs)
# 5 Funções e Eventos
# 5 Funções e Eventos
# 6 Inspetor, Gerador de Teste e Relatório
playwright codegen lojafake.vanilton.net
# 6 Inspetor, Gerador de Teste e Relatório
playwright codegen --save-storage=auth.json
Por vezes é necessário capturar tokens ou outros valores em storage do navegador que podem ser reaproveitados em outros testes, para isso podemos executar o comando abaixo:
Experimente acessar https://vanilton.net/web-test/crud-local-storage/ cadastrar duas pessoas e fechar o navegador.
# 6 Inspetor, Gerador de Teste e Relatório
playwright open --load-storage=auth.json https://vanilton.net/web-test/crud-local-storage/
Agora vamos restaurar o storage salvo no último exercício
Importante saber que são restaurados apenas dados de localStorage e cookies
# 6 Inspetor, Gerador de Teste e Relatório
page.pause()
Executando a função pause contido no objeto page será chamado o inspetor de geração de código e será pausado o seu script, podendo assim iniciar uma gravação e reproduzir novos passos para geração de código.
# 6 Inspetor, Gerador de Teste e Relatório
# Retorna um elemento
playwright.$('body')
# Retorna todas incidências
playwright.$$('body')
# Retorna a localização elemento com o texto
playwright.inspect('text=Log in')
# Procurando um link contendo um texto
playwright.locator('a', { hasText: 'O que é' });
# Procuranto por localizadores padrões
playwright.getByRole('button', 'name=Aceitar')
Depois de definir um ponto de interrupção em seu teste, você poderá executá-lo com PWDEBUG=console.
# 6 Inspetor, Gerador de Teste e Relatório
playwright codegen https://vanilton.net -o meu_script.py --target python --save-trace trace_vanilton.zip
É possível gerar o código gravado e salvá-lo em um script de saída pré-determinado, definindo também a linguagem que será gerado o código fonte e por fim incluir o rastreio de todo o teste por meio do save trace.
Atualizado em: 29/06/2023
# 6 Inspetor, Gerador de Teste e Relatório
# Vamos criar um script runner para criar o report das nossas classes de teste unittest
import unittest
from HTMLTestRunner.runner import HTMLTestRunner
from pythonModule import ClassTest
test1 = unittest.TestLoader().loadTestsFromTestCase(ClassTest1)
test2 = unittest.TestLoader().loadTestsFromTestCase(ClassTest2)
suite = unittest.TestSuite([test1, test2])
runner = HTMLTestRunner(log=True, verbosity=2, output='report', title='Test report', report_name='report',
open_in_browser=True, description="Testes da Aplicação XPTO", tested_by="Vanilton Pinheiro",
add_traceback=True, style=style)
runner.run(suite)
pip install HTMLTestRunner-rv
# 6 Inspetor, Gerador de Teste e Relatório
Revisão de conceitos básicos de Playwright
Testes de fluxo de navegação complexos
Estruturação de testes reutilizáveis
Geração de relatórios de teste avançados
Integração com sistemas de build e CI/CD
Otimização de testes para desempenho e velocidade
# O que veremos no Avançado
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
Teste
HTTP Request
Web Socket (TCP)
Abrir o navegador
Clicar no elemento
Comandos
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Instalando com plugin Pytest
pip install pytest-playwright
# Atualizando versão instalada para mais atual
pip install -U pytest-playwright
# Instalando navegadores
playwright install
# Avançado 1 - Revisão de conceitos básicos de Playwright
Aqui o modo de instalação sem o Plugin Pytest
Nota: Desinstalação Incluída na versão 1.35.0
# Remove os navegadores instalados por esta instalação
playwright uninstall
# Remova todos os navegadores Playwright já instalados
playwright uninstall --all
# Avançado 1 - Revisão de conceitos básicos de Playwright
Nota: Desinstalação Incluída na versão 1.35.0
# Avançado 1 - Revisão de conceitos básicos de Playwright
O Playwright está em constante atualização, para verificar o que há de novo acesse: https://playwright.dev/docs/release-notes
Ou se desejar acesse o github oficial (repositório da versão cliente em python) https://github.com/microsoft/playwright-python
>>> from playwright.sync_api import sync_playwright
>>> playwright = sync_playwright().start()
>>> browser = playwright.chromium.launch(headless=False)
>>> page = browser.new_page()
>>> page.goto("https://vanilton.net")
>>> site_title = page.title()
>>> page.screenshot(path=f'{site_title}.png')
>>> browser.close()
>>> playwright.stop()
Vamos rever as principais funcionalidade Playwright para executar seus testes, para isso podemos utilizar o modo interativo, logo:
1. Abra o terminal python e em seguida:
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
# Avançado 1 - Revisão de conceitos básicos de Playwright
Expectativas
Realidade
# Avançado 1 - Revisão de conceitos básicos de Playwright
As expectativas são as verificações realizadas pelo Playwright em torno do que é retornado (realidade) pelo software em teste, logo um resultado positivo ou negativo para a expectativa.
# Avançado 1 - Revisão de conceitos básicos de Playwright
from playwright.sync_api import sync_playwright, expect
..
..
expect(locator).to_be_visible()
# Avançado 1 - Revisão de conceitos básicos de Playwright
As expectativas possuem um valor de espera padrão definido em 5 segundos, para alterar temos duas opções:
# 1 - Definindo globalmente o tempo de espera para o objeto expect
from playwright.sync_api import sync_playwright, expect
expect.set_options(timeout=10_000)
# 2 - Definindo no uso individual da expect
expect(page.get_by_text("Name")).to_be_visible(timeout=10_000)
# Avançado 1 - Revisão de conceitos básicos de Playwright
from playwright.sync_api import sync_playwright, expect
..
..
expect(locator).to_be_hidden()
# Avançado 1 - Revisão de conceitos básicos de Playwright
Ainda na aplicação todos faça:
from playwright.sync_api import sync_playwright, expect
import re
..
..
expect(page).to_have_url(re.compile(".*texto"))
# Avançado 1 - Revisão de conceitos básicos de Playwright
from playwright.sync_api import sync_playwright, expect
..
..
expect(locator).to_have_count(count)
# Avançado 1 - Revisão de conceitos básicos de Playwright
Agora vamos verificar uma imagem pelo seu atributo src e id, para isso:
from playwright.sync_api import sync_playwright, expect
..
..
expect(locator).to_have_attribute("src", "equilatero.png")
expect(locator).to_have_id("seu_id")
# Avançado 1 - Revisão de conceitos básicos de Playwright
Agora vamos verificar campos editáveis, vazio, com e sem foco, possuindo um determinado css, estando vazio ou preenchido e habilitado para clique.
from playwright.sync_api import sync_playwright, expect
..
..
locator = page.get_by_role("textbox")
expect(locator).to_be_editable()
expect(locator).not_to_be_editable()
expect(locator).to_be_focused()
expect(locator).not_to_be_focused()
expect(locator).to_have_css("display", "flex")
expect(locator).not_to_have_css(name, value)
expect(locator).to_be_empty()
expect(locator).not_to_be_empty()
expect(locator).to_be_enabled()
expect(locator).not_to_be_enabled()
Para mudar o padrão de ID utilizado:
playwright.selectors.set_test_id_attribute("seu_id")
# Avançado 1 - Revisão de conceitos básicos de Playwright
Também é possível realizar várias concatenações de checagem por meio do and_ ou or_ sempre direcionado ao mesmo elemento buscado.
expect(page.get_by_test_id("story").and_(page.locator("[name='story']"))).to_be_visible()
expect(page.get_by_test_id("story").or_(page.locator("[name='story2']"))).to_be_visible()
# Avançado 2 - Fluxo de teste complexo
É uma plataforma de comércio eletrônico de código aberto.
Saleor é o e-commerce nº 1 no GitHub e a estratégia mais preparada para o futuro para qualquer tecnologia de visualização de marca, extensibilidade e abertura como vitais para experiências revolucionárias do cliente.
# Avançado 2 - Fluxo de teste complexo
Email address: admin@example.com
Password: admin
Após autenticar crie as seguintes expects :
Deve estar visível mensagem de boas vindas "Hello there, admin@example.com".
Verificar a presença dos menus "Produtos", "Orders", "Discounts" com os textos informados e ignorando case sensitive, utilize expect(locator).to_have_text(..)
# Avançado 2 - Fluxo de teste complexo
Criar um localizador para identificar a lista de atividades
Deve ser possível iterar nos itens da lista
Criar uma lista de Ordens extraída da lista de atividades, considerar apenas o números das ordens, pode-se utilizar regex
Abrir uma página nova e na tela de pesquisa de ordens, consultar os números das ordens contidas na lista
Por fim somar todos os valores das ordens pesquisadas
# Avançado 2 - Fluxo de teste complexo
Extra: Utilize o context.storage_state para salvar o storage do login e utilizá-lo posteriormente.
# Avançado 2 - Fluxo de teste complexo
Shipping Zone | Descrição |
---|---|
Envio Nacional | Essa opção inclui todas as regiões dentro do país em que a empresa está localizada. Por exemplo, se a empresa está nos Estados Unidos, seria o envio para todos os estados dentro dos EUA. |
Envio Internacional | Essa opção abrange todos os países para os quais a empresa está disposta a enviar seus produtos. Pode ser dividida em regiões geográficas, como América do Norte, Europa, Ásia, etc. |
Envio Regional | Nessa opção, a empresa pode definir zonas de envio com base em áreas geográficas específicas dentro de um país. Por exemplo, regiões, estados ou províncias específicos. |
Envio por Continente | Essa opção agrupa os países por continente. Por exemplo, América do Norte, América do Sul, Europa, África, Ásia, Oceania. |
Envio Local | Essa opção é adequada para empresas que têm uma loja física ou depósito em uma determinada área e desejam oferecer opções de retirada na loja ou entrega local. |
Envio Internacional Selecionado: | Nessa opção, a empresa pode selecionar manualmente os países ou regiões específicos para os quais está disposta a enviar, em vez de incluir todos os países. |
Envio Global | Essa opção é adequada para empresas que desejam oferecer envio para qualquer país ao redor do mundo, sem restrições. |
# Avançado 2 - Fluxo de teste complexo
Canal de venda | Descrição |
---|---|
Lojas físicas | Essas são as lojas tradicionais, onde os clientes podem visitar pessoalmente, ver e tocar os produtos antes de fazer uma compra. Podem ser lojas de varejo, shoppings, boutiques, supermercados, entre outros. |
Comércio eletrônico (e-commerce) | Os canais de venda online têm se tornado cada vez mais populares. As lojas virtuais permitem que os clientes façam compras pela internet, escolhendo produtos, adicionando ao carrinho e efetuando o pagamento online. Exemplos incluem sites de varejistas, marketplaces, como Amazon e eBay, e lojas próprias de marcas. |
Marketplaces | São plataformas que reúnem diversos vendedores em um único local, proporcionando aos consumidores uma ampla variedade de produtos de diferentes marcas e categorias. Alguns exemplos famosos são o Amazon Marketplace, o Mercado Livre e o Etsy. |
Vendas diretas | Esse modelo envolve a venda de produtos diretamente aos consumidores, sem intermediários. Pode ser realizado através de representantes de vendas independentes (consultores, revendedores), que fazem demonstrações e vendas em domicílio, por exemplo. |
Vendas por telefone | Algumas empresas têm serviços de atendimento ao cliente por telefone, onde os clientes podem fazer pedidos diretamente com um representante de vendas. |
Redes sociais | As redes sociais têm se tornado um canal de vendas popular, permitindo que as empresas exibam seus produtos, interajam com os clientes e efetuem vendas diretamente através dessas plataformas, como Facebook, Instagram, Pinterest e TikTok. |
Canais de TV | Alguns produtos são vendidos através de programas de televendas, onde os apresentadores demonstram e promovem os produtos ao vivo e os telespectadores podem fazer pedidos por telefone ou online. |
Venda por catálogo | Esse modelo envolve o uso de catálogos impressos ou digitais, onde os clientes podem ver os produtos, fazer pedidos e aguardar a entrega. |
# Avançado 2 - Fluxo de teste complexo
Tipo de Produto | Categoria | Coleções | Categorias:Produtos |
---|---|---|---|
Eletrônicos | Smartphones Laptops TVs Câmeras digitais Fones de ouvido |
Coleção "Smart Tech": incluindo smartphones, smartwatches e dispositivos domésticos inteligentes. Coleção "Gaming Gear": com laptops, teclados, mouses e fones de ouvido voltados para jogos. Coleção "Home Entertainment": com TVs, alto-falantes e sistemas de som para uma experiência de entretenimento em casa aprimorada. |
Smartphones: iPhone 13, Samsung Galaxy S21, Google Pixel 6. Laptops: MacBook Pro, Dell XPS 15, Lenovo ThinkPad. TVs: LG OLED CX, Sony Bravia A80J, Samsung QN90A. Câmeras digitais: Canon EOS R5, Sony Alpha A7 III, Nikon Z7. Fones de ouvido: Apple AirPods Pro, Sony WH-1000XM4, Jabra Elite 85t. |
Vestuário e Moda | Camisetas Calças Vestidos Sapatos Acessórios (bolsas, cintos, chapéus) |
Coleção "Tendências de Verão": com roupas leves, estampas florais e cores vibrantes para a estação. Coleção "Roupas de Trabalho": focada em trajes elegantes e profissionais para o ambiente corporativo. Coleção "Streetwear Urbano": com peças modernas e despojadas, inspiradas na cultura das ruas. |
Camisetas: Nike Dri-FIT, Adidas Originals Trefoil, H&M Basic Tee. Calças: Levi's 501, Lululemon ABC Jogger, Zara Slim-Fit Chinos. Vestidos: Reformation midi dress, Zara floral sundress, ASOS wrap dress. Sapatos: Nike Air Max 90, Converse Chuck Taylor All Star, Doc Martens 1460. Acessórios (bolsas, cintos, chapéus): Louis Vuitton Speedy Bag, Gucci GG Belt, Brixton Fedora Hat. |
Alimentos | Frutas e vegetais frescos Carnes e aves Produtos lácteos Cereais e grãos Snacks e petiscos |
Coleção "Sabores Internacionais": oferecendo uma variedade de alimentos e ingredientes étnicos de diferentes partes do mundo. Coleção "Opções Saudáveis": com produtos orgânicos, vegetarianos, veganos e sem glúten para uma alimentação mais saudável. Coleção "Snacks Gourmet": trazendo petiscos exclusivos e gourmet, perfeitos para momentos especiais. |
Frutas e vegetais frescos: maçãs, bananas, cenouras. Carnes e aves: frango, carne bovina, peixe salmão. Produtos lácteos: leite, queijo cheddar, iogurte grego. Cereais e grãos: arroz integral, aveia em flocos, macarrão de trigo integral. Snacks e petiscos: batatas fritas, chocolate, castanhas. |
Móveis | Sofás Camas Mesas de jantar Armários Estantes |
Coleção "Estilo Minimalista": com móveis de linhas simples, cores neutras e designs clean. Coleção "Estilo Vintage": apresentando móveis retrô, peças antigas restauradas e acabamentos vintage. Coleção "Mobília Modular": oferecendo móveis versáteis e funcionais que se adaptam a diferentes espaços e necessidades. |
Sofás: sofá de couro, sofá-cama, sofá modular. Camas: cama king size, cama de plataforma, cama com gavetas de armazenamento. Mesas de jantar: mesa de jantar retangular, mesa de jantar redonda, mesa de jantar de madeira maciça. Armários: armário de quarto, armário de cozinha, armário de banheiro. Estantes: estante de madeira, estante de metal, estante modular. |
Beleza e Cuidados Pessoais | Maquiagem Perfumes Produtos para cuidados com a pele Produtos para cuidados com o cabelo Produtos de higiene pessoal |
Coleção "Cuidados com a Pele Natural": com produtos feitos com ingredientes naturais e orgânicos. Coleção "Maquiagem Glamourosa": com tons vibrantes, brilhos e produtos de alta qualidade para looks glamourosos. Coleção "Cuidados Masculinos": voltada para produtos de barbear, cuidados com a barba e cuidados com o cabelo masculino. |
Maquiagem: base líquida, batom matte, paleta de sombras. Perfumes: Chanel No. 5, Dior Sauvage, Jo Malone English Pear & Freesia. Produtos para cuidados com a pele: limpador facial, creme hidratante, máscara facial. Produtos para cuidados com o cabelo: shampoo, condicionador, óleo capilar. Produtos de higiene pessoal: sabonete líquido, desodorante, creme dental. |
Automóveis | Carros de passeio SUVs Motocicletas Caminhões Veículos elétricos |
Coleção "Carros Esportivos": apresentando modelos de alta performance, design arrojado e tecnologia avançada. Coleção "Carros Elétricos": com veículos elétricos e híbridos, focados na sustentabilidade e eficiência energética. Coleção "Aventura Todo-Terreno": incluindo veículos off-road, SUVs robustos e caminhões para aventuras off-road. |
Carros de passeio: Toyota Corolla, Honda Civic, Volkswagen Golf. SUVs: Ford Explorer, Nissan Rogue, Jeep Grand Cherokee. Motocicletas: Yamaha YZF-R6, Harley-Davidson Street Glide, Honda CBR500R. Caminhões: Ford F-150, Chevrolet Silverado, Ram 1500. Veículos elétricos: Tesla Model 3, Nissan Leaf, Chevrolet Bolt EV. |
Esportes e Lazer | Bicicletas Equipamentos de ginástica Artigos esportivos (bolas, tacos, raquetes) Jogos de tabuleiro Livros e filmes |
Coleção "Fitness em Casa": com equipamentos de ginástica compactos e acessórios para treinos em casa. Coleção "Esportes Aquáticos": oferecendo equipamentos para mergulho, surf, stand-up paddle e outros esportes aquáticos. Coleção "Clássicos dos Jogos": trazendo jogos de tabuleiro tradicionais, como xadrez, damas e gamão. |
Bicicletas: Mountain Bike: Trek Fuel EX, Specialized Stumpjumper, Giant Anthem. Equipamentos de ginástica: Esteira NordicTrack Commercial 1750, ProForm Pro 2000, Sole F80. Artigo esportivo: Bola de Futebol: Nike Premier League Strike, Adidas Tango, Select Numero 10. Jogos de tabuleiro: xadrez, damas, gamão, Carcassonne, Settlers of Catan Livros e filmes: Biografias de atletas famosos, livros sobre técnicas e estratégias esportivas. |
Casa e Decoração: | Utensílios de cozinha Decoração de parede Cortinas e persianas Tapetes e carpetes Iluminação |
Coleção "Estilo Escandinavo": com móveis e acessórios de decoração minimalistas, funcionais e aconchegantes. Coleção "Decoração Boho": apresentando elementos étnicos, cores vibrantes e texturas naturais para uma atmosfera boêmia. Coleção "Tecnologia Inteligente para Casa": com dispositivos domésticos conectados, como lâmpadas inteligentes, termostatos e sistemas de segurança. |
Utensílios de cozinha: Conjunto de panelas antiaderentes Decoração de parede: Relógios de parede elegantes Cortinas e persianas: Cortinas de linho, Persianas de madeira, Cortinas blackout Tapetes e carpetes: Tapete de lã felpudo, Tapete persa vintage, Tapete geométrico moderno Iluminação: Lustre de cristal, Luminária de teto embutida, Lâmpada de mesa com cúpula |
# Avançado 2 - Fluxo de teste complexo
#Clone Saleor's repo
git clone https://github.com/saleor/saleor-platform.git --recursive --jobs 3
cd saleor-platform
docker-compose build
##Apply database migrations:
docker-compose run --rm api python3 manage.py migrate
docker-compose run --rm api python3 manage.py collectstatic --noinput
Optionally
# Finally, create yourself an admin account:
docker-compose run --rm api python3 manage.py createsuperuser
docker-compose up -d
# Avançado 2 - Fluxo de teste complexo
Garanta que as portas abaixo estão livres:
1025
8025
8000
9000
5432
6379
5775 e 78
6831 e 32
14268
9411
# Avançado 2 - Fluxo de teste complexo
# Avançado 2 - Fluxo de teste complexo
Repositório | Tipo de Produto | Panda | Girafa | Quero-Quero | Bicho Preguiça | Urso |
---|---|---|---|---|---|---|
Para tornar os testes menos verbosos e otimizar o tempo de implementação o Playwright se integrou ao Pytest que fornece recursos para facilitar nossa vida, para tanto precisamos conhecer o funcionamento do Pytest inicialmente, então vamos lá!
# Avançado 3 - Estruturação de testes reutilizáveis
import pytest
class Music:
def __init__(self, nome):
self.nome = nome
def __eq__(self, outra):
return self.nome == outra.nome
@pytest.fixture
def minha_musica():
return Music("Black")
@pytest.fixture
def playlist(minha_musica):
return [Music("Numb"), minha_musica]
def test_minha_musica_na_playlist(playlist):
assert Music('Numb') in playlist
Definindo de modo simplório é tudo aquilo necessário para realizar o teste. Elas são utilizadas pelo Pytest como exemplo abaixo:
O que acontece nessa Fixture?
Qual resultado desse teste?
# Avançado 3 - Estruturação de testes reutilizáveis
"function"
): A fixture é executada uma vez para cada função de teste. Isso é útil quando você precisa configurar um estado específico para cada teste individualmente.Escopo de classe ("class"
): A fixture é executada uma vez para cada classe de teste. Isso é útil quando você precisa configurar um estado comum para todos os testes em uma classe.
Escopo de módulo ("module"
): A fixture é executada uma vez para cada módulo de teste. Isso é útil quando você precisa configurar um estado comum para todos os testes em um módulo.
Escopo de sessão ("session"
): A fixture é executada uma vez para toda a sessão de teste. Isso é útil quando você precisa configurar um estado comum para todos os testes em várias classes ou módulos.
# Avançado 3 - Estruturação de testes reutilizáveis
import pytest
# Configuração de sessão
@pytest.fixture(scope="session", autouse=True)
def setup_session():
print("Configuração de sessão")
# Opcionalmente, você pode retornar dados ou objetos úteis para os testes
yield
print("Desmontando sessão")
# Configuração de módulo
@pytest.fixture(scope="module", autouse=True)
def setup_module():
print("Configuração de módulo")
# Opcionalmente, você pode retornar dados ou objetos úteis para os testes
yield
print("Desmontando módulo")
# Configuração de classe
@pytest.fixture(scope="class", autouse=True)
def setup_class():
print("Configuração de classe")
# Opcionalmente, você pode retornar dados ou objetos úteis para os testes
yield
print("Desmontando classe")
# Configuração de função
@pytest.fixture(scope="function", autouse=True)
def setup_function():
print("Configuração de função")
# Opcionalmente, você pode retornar dados ou objetos úteis para os testes
yield
print("Desmontando função")
# Exemplo de teste
class TestExemplo:
def test1(self):
print("Executando o teste 1")
def test2(self):
print("Executando o teste 2")
# Execute os testes
pytest.main()
# Avançado 3 - Estruturação de testes reutilizáveis
O parâmetro autouse=True
garante que a fixture seja aplicada automaticamente a todos os testes sem a necessidade de chamá-la explicitamente.
Ao executar os testes, você verá que as configurações e desmontagens são realizadas de acordo com o escopo da fixture correspondente.
class Robo:
def __init__(self, nome, bateria=100):
self.nome = nome
self.bateria = bateria
def apresentar(self):
if self.bateria > 0:
self._consumir_bateria(5)
return f"Olá, eu sou o robô {self.nome}."
else:
return "Desculpe, estou sem energia para me apresentar."
def mover(self, direcao):
if self.bateria > 10:
self._consumir_bateria(10)
return f"Robô {self.nome} se moveu para {direcao}."
else:
return "Desculpe, estou sem energia para me mover."
def recarregar(self):
self.bateria = 100
return f"Robô {self.nome} recarregado."
def _consumir_bateria(self, quantidade):
self.bateria -= quantidade
# Avançado 3 - Estruturação de testes reutilizáveis
Neste exemplo, a classe Robo
tem um construtor que recebe o nome do robô e uma quantidade inicial de bateria (padrão é 100). A classe possui os seguintes métodos:
apresentar(): Retorna uma saudação se o robô tiver energia suficiente (consome 5 de bateria).
mover(direcao): Move o robô para uma direção específica se houver energia suficiente (consome 10 de bateria).
recarregar()
: Recarrega a bateria do robô para 100.
_consumir_bateria(quantidade)
é um método auxiliar privado que reduz a quantidade de bateria do robô quando chamado por outros métodos.def test_my_app_is_working(fixture_name):
# Test using fixture_name
# ...
O Pytest Plugin para Playwright pode usar esses fixtures como argumento para a função de teste, conforme exemplo abaixo:
# Avançado 3 - Estruturação de testes reutilizáveis
Esses acessórios são criados quando solicitados em uma função de teste e destruídos quando o teste termina.
context: Novo contexto do navegador para um teste
page: Nova página do navegador para um teste
# Avançado 3 - Estruturação de testes reutilizáveis
# Avançado 3 - Estruturação de testes reutilizáveis
# Avançado 3 - Estruturação de testes reutilizáveis
from playwright.sync_api import Page
def test_basic_test(page: Page):
# ...
As páginas são isoladas entre os testes devido ao Browser Context, que é equivalente a um novo perfil de navegador, onde cada teste obtém um novo ambiente, mesmo quando vários testes são executados em um único navegador.
# Avançado 3 - Estruturação de testes reutilizáveis
from playwright.sync_api import Page
def test_basic_test(page: Page):
# ...
# Avançado 3 - Estruturação de testes reutilizáveis
Grandes suítes de teste podem ser estruturadas para otimizar a facilidade de criação e manutenção. Modelos de objeto por página é uma dessas abordagens para estruturar seu conjunto de testes.
# Avançado 3 - Estruturação de testes reutilizáveis
A estrutura de pacotes deve ficar similar ao modelo abaixo
# Avançado 3 - Estruturação de testes reutilizáveis
pages
|__ __init__.py
|__ auth.py
|__ channel.py
tests
|__ test_auth.py
|__ test_channel.py
.gitignore
README.md
requirements.txt
# pages/search.py
class SearchPage:
def __init__(self, page):
self.page = page
def navigate(self):
self.page.goto("https://bing.com")
def search(self, text):
self.search_term_input = self.page.locator("[name='q']")
self.search_term_input.fill(text)
self.search_term_input.press("Enter")
# test_search.py
from pages.search import SearchPage
from playwright.sync_api import Page, expect
# No teste
class TestBing:
def test_search_bing(self, page: Page):
search_page = SearchPage(page)
search_page.navigate()
search_page.search("search query")
page.wait_for_selector('text=resultados')
expect(page.get_by_text("resultados").first).to_be_visible()
# Avançado 3 - Estruturação de testes reutilizáveis
# pages/search.py
class SearchPage:
def __init__(self, page):
self.page = page
@property
def page_title(self):
return self.page.title()
def navigate(self):
self.page.goto("https://bing.com")
self.page_title()
# Avançado 3 - Estruturação de testes reutilizáveis
Utilize propriedades (get e set) para mapear elementos específicos ou criar estruturas padrões para uma determinada página.
pytest <script/module> --browser webkit --headed
Para executar seus testes, use Pytest CLI.
--headed
: Executar os testes com interface gráfica (padrão: headless).
--browser
: Executar o teste no chromium
, firefox
, ou webkit
. Pode ser especificado várias vezes (padrão: todos os navegadores)
--browser-channel
Browser channel para se utilizar.
--slowmo
Executar o teste com atraso (milissegundos).
--device
Device to be emulated.
--output
Directory for artifacts produced by tests (default: test-results
).
--tracing
Se deve registrar o rastreio do teste após cada teste. on
, off
, or retain-on-failure
(padrão: off
).
--video
Se deve gravar o teste após cada teste. on
, off
, or retain-on-failure
(padrão: off
).
--screenshot
Se deve capturar automaticamente uma captura de tela após cada teste. on
, off
, ou only-on-failure
(padrão: off
).
# Avançado 3 - Estruturação de testes reutilizáveis
# test/conftest.py
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args, playwright):
playwright.selectors.set_test_id_attribute("data-test-id")
playwright.chromium.launch(headless=False)
return {
**browser_context_args,
"storage_state": "storage.json",
"viewport": {
"width": 1920,
"height": 1080,
}
}
# Avançado 3 - Estruturação de testes reutilizáveis
Uma prática comum ao criar essa estrutura de page objects é ter um módulo responsável por configurações genéricas do teste, para isso podemos criar o arquivo abaixo:
# test/conftest.py
import pytest
@pytest.fixture(scope="session")
def browser_context_args(browser_context_args, playwright):
iphone_12 = playwright.devices['iPhone 12 Pro']
return {
**browser_context_args,
**iphone_12,
}
# Emulando Device
pytest --device="iPhone 12 Pro"
# Executando teste em slowmotion (Retarda as operações em 1 segundo.)
pytest --slowmo 1000
# Executando teste no navegador presente na máquina
# Os canais disponíveis são chrome, msedge, chrome-betaou msedge-beta.msedge-dev
pytest --browser-channel chrome
# O base-url é usado para permitir que você defina o URL base da configuração
pytest --base-url http://localhost:8080
# Avançado 3 - Estruturação de testes reutilizáveis
playwright install msedge
# pytest.ini
[pytest]
addopts =
--browser chromium
--headed
--slowmo 1000
# Exeuctar
pytest
# Avançado 3 - Estruturação de testes reutilizáveis
O Pytest oferece para os testes com Playwright a possibilidade de configurar um arquivo com os parâmetros testados anteriormente
# Avançado 4 - Integração com sistemas de build e CI/CD
Integração Contínua ou CI é uma prática importante no desenvolvimento de software. CI é uma abordagem em que as alterações de código são integradas em um repositório compartilhado com frequência, geralmente várias vezes ao dia. Cada vez que uma alteração é feita, o sistema de CI automaticamente constrói e testa o código para verificar se ele ainda funciona corretamente.
# Avançado 4 - Integração com sistemas de build e CI/CD
Existem várias razões pelas quais executar testes em CI é benéfico, vou citar alguns:
# Avançado 4 - Integração com sistemas de build e CI/CD
É importante cuidar da qualidade do nosso código de teste, uma vez que ele bem mantido agiliza o desenvolvimento de testes e facilita a manutenção. Para isso vamos inserir a ferramenta de análise estática Flake8 no projeto conforme comando abaixo.
pip install flake8
# Avançado 4 - Integração com sistemas de build e CI/CD
O Flake8 ajuda a manter um código Python consistente e legível ao aplicar regras de estilo de código. Ele verifica se o código está em conformidade com as diretrizes definidas no PEP 8, como indentação correta, comprimento de linha, uso adequado de espaços em branco, nomenclatura de variáveis e funções, entre outros.
# Avançado 4 - Integração com sistemas de build e CI/CD
Para executar a análise do código execute o comando abaixo:
# Comando base
flake8
# Comando com opções log do flake com verbose, erros com statistics e traços de código
flake8 --verbose --statistics --show-source
# Avançado 4 - Integração com sistemas de build e CI/CD
Após instalar é importante configurar algumas validações que o mesmo deve realizar, ou não, no seu projeto de teste, para tanto crie um arquivo chamado .flake8 na raiz do projeto e insira o conteúdo abaixo:
[flake8]
#Ignorar um aviso ou erro específico para ignorar:
extend-ignore = E203
#Ignorar a verificação nessas pastas
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,venv
#Valor máximo de complexidade ciclomática permitido para um bloco de código.
max-complexity = 10
#Quantidade de caracteres por linha
max-line-length = 120
Lembre-se de que é recomendável manter a complexidade ciclomática baixa, geralmente abaixo de um limite específico, como 10 ou 15, para facilitar a leitura, a compreensão e a manutenção do código. Veja no link mais detalhes
# Baixar imagem com pytest-playwright versão 1.35
docker pull mcr.microsoft.com/playwright/python:v1.35.0-jammy
Para aplicar a abordagem de Integração contínua utilizando Playwright podemos utilizar como apoio a plataforma Docker que auxilia no provisionamento do ambiente de desenvolvimento e teste em plataformas que oferecem o recurso de CI como Gitlab e GitHub
# Avançado 4 - Integração com sistemas de build e CI/CD
# Linux
docker run -it -v $(pwd):/projeto mcr.microsoft.com/playwright/python:v1.36.0-jammy bash
# Windows
docker run -it -v .:/projeto mcr.microsoft.com/playwright/python:v1.36.0-jammy bash
1. Vamos acessar o container em modo interativo para testar:
# Avançado 4 - Integração com sistemas de build e CI/CD
2. Agora acesse a pasta projeto em /projeto e instale as bibliotecas python presentes no requirements.txt.
3. Execute o teste
# Dockerfile_dep
FROM mcr.microsoft.com/playwright/python:v1.36.0-jammy as playwright_base_image
LABEL authors="Vanilton Pinheiro"
ENV APP_PATH /usr/src/app
RUN mkdir $APP_PATH
WORKDIR $APP_PATH
COPY ./requirements.txt $APP_PATH
RUN python -m pip install --upgrade pip
RUN pip install -r requirements.txt
Para agilizar a execução da pipeline podemos manter uma imagem base com as bibliotecas do nosso projeto, segue o exemplo de um Dockerfile
# Avançado 4 - Integração com sistemas de build e CI/CD
# Construindo a imagem com dependências do projeto de teste
docker build -f Dockerfile_dep -t playwright_base_image .
# .dockerignore
venv
__pycache__
*.pyc
*.pyo
*.pyd
.Python
env
pip-log.txt
pip-delete-this-directory.txt
.tox
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.log
.git
.mypy_cache
.pytest_cache
.hypothesis
Ignorar pastas e arquivos serem adicionados na imagem construída
FROM playwright_base_image
LABEL authors="Vanilton Pinheiro"
COPY . $APP_PATH
Com a imagem base criada agora podemos utilizar a imagem com todas as bibliotecas pronta apenas para executar o código fonte, para isso é necessário apenas copiar o código de teste e fazer a execução do container
# Avançado 4 - Integração com sistemas de build e CI/CD
# Construindo a imagem para execução dos testes
docker build -t playwright_run_image .
# Executando os testes dentro de um container baseado na imagem criada
docker run playwright_run_image pytest
# Executando utilizando volume (sem build de nova imagem)
# Linux
docker run -v $(pwd):/usr/src/app playwright_base_image pytest
# Windows
docker run -v .:/usr/src/app playwright_base_image pytest
Lembre-se que todas essas imagens foram criadas localmente, logo para utilizá-las em um CI na nuvem é necessário fazer o push das imagens para um registry acessível pelo CI.
# Avançado 4 - Integração com sistemas de build e CI/CD
# Autenticar no registry do Docker Hub
docker login registry-1.docker.io
# Enviar imagem para o registry
docker image push <nome_imagem>:<tag>
# Avançado 4 - Integração com sistemas de build e CI/CD
Gitlab CE ou EE
Gitlab Runner
# Avançado 4 - Integração com sistemas de build e CI/CD
Caso deseje utilizar um Runner em sua infraestrutura para executar os testes, podemos configurar o mesmo, para isso:
# Avançado 4 - Integração com sistemas de build e CI/CD
# Avançado 4 - Integração com sistemas de build e CI/CD
# Avançado 4 - Integração com sistemas de build e CI/CD
# Avançado 4 - Integração com sistemas de build e CI/CD
docker volume create gitlab-runner-config
docker run -d --name gitlab-runner --restart always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v gitlab-runner-config:/etc/gitlab-runner \
gitlab/gitlab-runner:latest
docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest register
# Enter the GitLab instance URL (for example, https://gitlab.com/):
https://gitlab.com/
# Enter the registration token: (Gerar token linux)
token
# Enter an executor: docker+machine, kubernetes, custom, ssh, docker-autoscaler,
# shell, virtualbox, instance, docker, docker-windows, parallels:
docker
# Enter the default Docker image (for example, ruby:2.7):
ubuntu:20.04
1. Criar um volume
2. Iniciar container
3. Registrar o Runner
# Avançado 4 - Integração com sistemas de build e CI/CD
Com o projeto de teste funcionando agora podemos colocá-los no repositório do gitlab.com e executá-lo via pipeline por meio do uso de imagem docker utilizando Runners compartilhados pelo serviço do Gitlab.com
# Avançado 4 - Integração com sistemas de build e CI/CD
# .gitlab-ci.yml
stages:
- code_analysis
- test
flake8:
stage: code_analysis
image: registry.gitlab.com/pipeline-components/flake8:latest
script:
- flake8 --verbose --statistics --show-source
chrome:
stage: test
image: mcr.microsoft.com/playwright/python:v1.35.0-jammy
script:
- python -m pip install --upgrade pip
- pip install -r requirements.txt
- pytest -s -v --junitxml=test-results.xml
artifacts:
when: always
reports:
junit: test-results.xml
No GitHub a estratégia não se torna muito diferente do modelo criado no Gitlab, veja o modelo ao lado de Action criada para execução de pipeline no GitHub.
# Avançado 4 - Integração com sistemas de build e CI/CD
# .github/workflows/main.yml
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
playwright:
name: 'Playwright Tests'
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright/python:v1.35.0-jammy
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run your code analysis
run: flake8 --verbose --statistics --show-source
- name: Run your tests Chrome
run: pytest -s -v -r test --junitxml=test-results.xml
- name: Run your tests Webkit
run: pytest -s -v -r test --browser webkit --junitxml=test-results-webkit.xml
- name: Upload test results chrome
uses: actions/upload-artifact@v3
with:
name: Test Results
path: test-results.xml
- name: Upload test results webkit
uses: actions/upload-artifact@v3
with:
name: Test Results
path: test-results-webkit.xml
- name: Surface failing tests chrome
if: always()
uses: pmeier/pytest-results-action@main
with:
path: test-results.xml
summary: true
display-options: fEX
fail-on-empty: true
- name: Surface failing tests webkit
if: always()
uses: pmeier/pytest-results-action@main
with:
path: test-results-webkit.xml
summary: true
display-options: fEX
fail-on-empty: true
# Avançado 5 - Geração de relatórios de teste avançado
# Instalar a depdendência
pip install pytest-html
# Gerando com arquivos css e html separados
pytest --html=report.html
# Gerando todo conteúdo no arquivo html
pytest --html=report.html --self-contained-html
# Estilisando seu report
pytest --html=report.html --css=report_theme.css
# Avançado 5 - Geração de relatórios de teste avançado
# Rastreando, gravando e printando os passos quando havendo falha
pytest --tracing=retain-on-failure
# Gera o video do cenário
pytest --video=retain-on-failure --screenshot=only-on-failure
# Gera o print screen da tela
pytest --screenshot=only-on-failure
# Para alterar a pasta e caminho padrão (test-results)
pytest --output <pasta>
# Avançado 5 - Geração de relatórios de teste avançado
# Visualizando resultado do trace com falha
playwright show-trace test-results/**/trace.zip
# Avançado 5 - Geração de relatórios de teste avançado
# Avançado 5 - Geração de relatórios de teste avançado
Allure é uma ferramenta de relatório de teste leve e flexível em vários idiomas projetada para criar relatórios de teste sofisticados e claros.
# Avançado 5 - Geração de relatórios de teste avançado
O Allure funciona conforme imagem abaixo:
# Avançado 5 - Geração de relatórios de teste avançado
Para utilizá-lo vamos seguir os seguintes passos:
# Avançado 5 - Geração de relatórios de teste avançado
Server e UI Allure
# docker-compose.yml
version: '3'
services:
allure:
image: "frankescobar/allure-docker-service"
environment:
CHECK_RESULTS_EVERY_SECONDS: 1
KEEP_HISTORY: 1
ports:
- "5050:5050"
volumes:
- ${PWD}/allure-results:/app/allure-results
- ${PWD}/allure-reports:/app/default-reports
allure-ui:
image: "frankescobar/allure-docker-service-ui"
environment:
ALLURE_DOCKER_PUBLIC_API_URL: "http://localhost:5050"
ALLURE_DOCKER_PUBLIC_API_URL_PREFIX: ""
ports:
- "5252:5252"
# Avançado 5 - Geração de relatórios de teste avançado
Instalar e Publicar resultado de teste com Allure
# Biblioteca
pip install allure-pytest
# Gerando o resultado do teste
pytest --alluredir=allure-results
# Avançado 5 - Geração de relatórios de teste avançado
Publicar resultado de teste com Allure via API, pode-se utilizar o script abaixo ou algum presente neste endereço: https://github.com/fescobar/allure-docker-service/tree/master/allure-docker-api-usage
import os, requests, json, base64
# This directory is where you have all your results locally, generally named as `allure-results`
allure_results_directory = '/allure-results2'
# This url is where the Allure container is deployed. We are using localhost as example
allure_server = 'http://localhost:5050'
# Project ID according to existent projects in your Allure container -
# Check endpoint for project creation >> `[POST]/projects`
project_id = 'default'
# project_id = 'my-project-id'
# current_directory = os.path.dirname(os.path.realpath(__file__)) - Caso arquivo na raiz do projeto
current_directory = os.path.abspath(r"..")
results_directory = current_directory + allure_results_directory
print('RESULTS DIRECTORY PATH: ' + results_directory)
files = os.listdir(results_directory)
print('FILES:')
results = []
for file in files:
result = {}
file_path = results_directory + "/" + file
print(file_path)
if os.path.isfile(file_path):
try:
with open(file_path, "rb") as f:
content = f.read()
if content.strip():
b64_content = base64.b64encode(content)
result['file_name'] = file
result['content_base64'] = b64_content.decode('UTF-8')
results.append(result)
else:
print('Empty File skipped: ' + file_path)
finally :
f.close()
else:
print('Directory skipped: ' + file_path)
headers = {'Content-type': 'application/json'}
request_body = {
"results": results
}
json_request_body = json.dumps(request_body)
ssl_verification = True
print("------------------SEND-RESULTS------------------")
response = requests.post(allure_server + '/allure-docker-service/send-results?project_id=' + project_id, headers=headers, data=json_request_body, verify=ssl_verification)
print("STATUS CODE:")
print(response.status_code)
print("RESPONSE:")
json_response_body = json.loads(response.content)
json_prettier_response_body = json.dumps(json_response_body, indent=4, sort_keys=True)
print(json_prettier_response_body)
# Avançado 5 - Geração de relatórios de teste avançado
Acessando o resultado
http://localhost:5252/allure-docker-service-ui/projects/default/reports/latest
Configure as categorias via categories.json
# install dependency
pip install pytest-xdist
# use the --numprocesses flag
pytest --numprocesses auto
Utilizando o Pytest podemos paralelizar os testes, ou seja, executando vários ao mesmo tempo. Para isso instale a biblioteca abaixo:
# Avançado 6 - Otimização de testes para desempenho e velocidade
Dependendo do hardware e da natureza de seus testes, você pode definir numprocesses para qualquer valor entre 2 e o número de CPUs na máquina.
Deixando em auto ele irá buscar a quantidade de cores de sua máquina, criando um worker para cada.
from playwright.sync_api import Playwright, APIRequestContext, expect
from typing import Generator
@pytest.fixture(scope="session")
def api_request_context(
playwright: Playwright,
) -> Generator[APIRequestContext, None, None]:
request_context = playwright.request.new_context(
base_url="http://localhost:8000/"
)
yield request_context
request_context.dispose()
def test_request(api_request_context: APIRequestContext) -> None:
body = """
{'key': 'value'}
"""
response = api_request_context.post(f"resource/", data=body)
expect(response).to_be_ok()
print(response.json())
Para agilizar pré-condições é possível realizar requests via Playwright para preparar ambientes de teste.
# Avançado 6 - Otimização de testes para desempenho e velocidade
vanilton18@gmail.com