
O tutorial deste artigo o ajudará a testar suas interfaces da web. Vamos criar uma solução de teste de interface web simples e robusta utilizando Python , pytest e selênio WebDriver . Veremos estratégias para construir bons testes e padrões para escrever bons testes automatizados. Obviamente, o projeto de teste desenvolvido pode servir como uma boa base para a criação de seus próprios casos de teste.
Qual navegador?
O teste de pesquisa DuckDuckGo de um dos capítulos anteriores funciona muito bem ... mas apenas no Chrome. Vamos dar uma
browser
outra olhada no acessório :
@pytest.fixture
def browser():
driver = Chrome()
driver.implicitly_wait(10)
yield driver
driver.quit()
O tipo de driver e o tempo limite são codificados. Para uma prova de conceito, isso pode ser bom, mas os testes de produção precisam ser configurados em tempo de execução. Os testes de interfaces da web devem funcionar em qualquer navegador. Os valores de tempo limite padrão devem ser ajustados no caso de alguns ambientes funcionarem mais lentamente do que outros. Dados confidenciais, como nomes de usuário e senhas, também nunca devem aparecer no código-fonte. Como você trabalha com esses dados de teste ?
Todos esses valores são dados de configuração para o sistema de teste automatizado. Eles são valores discretos que afetam sistematicamente como a automação funciona. Os dados de configuração devem chegar à entrada com cada execução de teste. Qualquer coisa relacionada à configuração de teste e ambiente deve ser tratada como dados de configuração para que o código de automação possa ser reutilizado.
Fontes de entrada
Em um sistema de teste automatizado, existem várias maneiras de ler os dados de entrada:
- Argumentos de linha de comando;
- Variáveis ambientais;
- Propriedades do sistema;
- Arquivos de configuração;
- Solicitações de API.
Infelizmente, a maioria das estruturas de teste não oferece suporte à leitura de dados de argumentos de linha de comando. Variáveis de ambiente e propriedades do sistema são difíceis de gerenciar e potencialmente perigosas de manusear. APIs de serviços são uma ótima maneira de consumir entrada, especialmente obtendo segredos (como senhas) de um serviço de gerenciamento de chaves como AWS KMS ou Azure Key Vault . No entanto, pagar por essa funcionalidade pode ser inaceitável e escrever você mesmo não é aconselhável. Nesse caso, os arquivos de configuração são a melhor opção.
Um arquivo de configuração é um arquivo normal que contém dados de configuração. O teste automatizado pode lê-lo quando os testes são executados e usar os valores de entrada para conduzir os testes. Por exemplo, o arquivo de configuração pode especificar o tipo de navegador usado como acessório do navegador em nosso projeto de amostra. Normalmente, os arquivos de configuração estão em um formato padrão, como JSON, YAML ou INI. Eles também devem ser planos para que possam ser facilmente diferenciados de outros arquivos.
Nosso arquivo de configuração
Vamos escrever um arquivo de configuração para nosso projeto de teste. Usaremos o formato JSON porque é fácil de usar, popular e tem uma hierarquia clara. Além disso, o módulo json é uma biblioteca padrão do Python que converte arquivos JSON em dicionários com facilidade. Crie um novo arquivo chamado
tests/config.json
e adicione o seguinte código:
{
"browser": "chrome",
"wait_time": 10
}
JSON usa pares de valores-chave. Como dissemos, existem dois valores de configuração em nosso projeto: seleção do navegador e tempo limite. Aqui, "navegador" é uma string e "wait_time" é um inteiro.
Lendo um arquivo de configuração com o pytest
Fixtures são a melhor maneira de ler arquivos de configuração usando o pytest. Eles podem ser usados para ler arquivos de configuração antes de iniciar os testes e, em seguida, inserir valores em testes ou mesmo em outros acessórios. Adicione o seguinte acessório a
tests/test_web.py
:
import json
@pytest.fixture(scope='session')
def config():
with open('tests/config.json') as config_file:
data = json.load(config_file)
return data
O fixture
config
lê e analisa o arquivo tests/config.json
em um dicionário usando o módulo json. Caminhos de arquivo embutidos em código são uma prática bastante comum. Na verdade, muitas ferramentas e sistemas de automação verificarão os arquivos em vários diretórios ou em relação a padrões de nomenclatura. O escopo do equipamento é definido como "sessão", então o equipamento será executado uma vez por sessão de teste. Não é necessário ler o mesmo arquivo de configuração todas as vezes em um novo teste - isso é ineficiente!
A entrada da configuração é necessária ao inicializar o WebDriver. Atualize o dispositivo da
browser
seguinte maneira:
@pytest.fixture
def browser(config):
if config['browser'] == 'chrome':
driver = Chrome()
else:
raise Exception(f'"{config["browser"]}" is not a supported browser')
driver.implicitly_wait(config['wait_time'])
yield driver
driver.quit()
O aparelho
browser
agora terá uma dependência de aparelho config
. Mesmo se for config
iniciado uma vez por sessão de teste, o navegador ainda será chamado antes de cada teste. Agora browser
tenho um encadeamento if-else
para determinar qual tipo de WebDriver usar. Por enquanto, apenas o Chrome é compatível, mas adicionaremos mais alguns tipos em breve. Se o navegador não for detectado, uma exceção será lançada. O tempo limite implícito também obterá seu valor do arquivo de configuração.
Como
browser
ele ainda retorna uma instância do WebDriver, os testes que o usam não precisam ser refatorados! Vamos executar testes para garantir que o arquivo de configuração funciona:
$ pipenv run python -m pytest tests/test_web.py
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item
tests/test_web.py . [100%]
=========================== 1 passed in 5.00 seconds ===========================
Adicionar novos navegadores
Agora que nosso projeto tem um arquivo de configuração, podemos usá-lo para alterar o navegador. Vamos executar o teste no Mozilla Firefox em vez do Google Chrome. Para fazer isso, baixe e instale o Firefox mais recente e, em seguida, baixe o geckodriver mais recente (driver do Firefox). Certifique-se de que
geckodriver
também esteja no caminho do sistema.
Atualize o código do fixture
browser
para funcionar com o Firefox:
from selenium.webdriver import Chrome, Firefox
@pytest.fixture
def browser(config):
if config['browser'] == 'chrome':
driver = Chrome()
elif config['browser'] == 'firefox':
driver = Firefox()
else:
raise Exception(f'"{config["browser"]}" is not a supported browser')
driver.implicitly_wait(config['wait_time'])
yield driver
driver.quit()
Em seguida, adicione uma opção ao arquivo de configuração
«firefox»
:
{
"browser": "firefox",
"wait_time": 10
}
Agora reinicie o teste e você verá uma janela do Firefox em vez do Chrome!

Validação
Apesar de o arquivo de configuração funcionar, existe uma desvantagem significativa na lógica de seu processamento: os dados não são verificados antes da execução dos testes. O fixture
browser
lançará uma exceção se o navegador não for selecionado corretamente, mas isso acontecerá para todos os testes. Será muito mais eficiente se uma exceção desse tipo for lançada uma vez por sessão de teste. Além disso, o teste falhará se as chaves "browser" ou "wait_time" estiverem faltando no arquivo de configuração . Vamos consertar isso.
Adicione um novo acessório para validar a seleção do navegador:
@pytest.fixture(scope='session')
def config_browser(config):
if 'browser' not in config:
raise Exception('The config file does not contain "browser"')
elif config['browser'] not in ['chrome', 'firefox']:
raise Exception(f'"{config["browser"]}" is not a supported browser')
return config['browser']
O equipamento
config_browser
depende do equipamento de configuração. Além disso, como config, tem scope = "session". Obteremos uma exceção se não houver uma chave "navegador" no arquivo de configuração ou se o navegador selecionado não for compatível. Finalmente, ele retorna o navegador selecionado para que os testes e outros acessórios possam acessar este valor com segurança.
A seguir está o seguinte dispositivo para validação de tempo limite:
@pytest.fixture(scope='session')
def config_wait_time(config):
return config['wait_time'] if 'wait_time' in config else 10
Se um tempo limite for especificado no arquivo de configuração, o aparelho
config_wait_time
irá retorná-lo. Caso contrário, ele retornará 10 segundos por padrão.
Atualize o acessório
browser
novamente para usar os novos acessórios de validação:
@pytest.fixture
def browser(config_browser, config_wait_time):
if config_browser == 'chrome':
driver = Chrome()
elif config_browser == 'firefox':
driver = Firefox()
else:
raise Exception(f'"{config_browser}" is not a supported browser')
driver.implicitly_wait(config_wait_time)
yield driver
driver.quit()
Escrever funções de acessórios separadas para cada valor de dados de configuração os torna simples, claros e específicos. Eles também permitem que você declare apenas os valores necessários para enviar solicitações.
Execute o teste e verifique se tudo funciona:
$ pipenv run python -m pytest tests/test_web.py
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item
tests/test_web.py . [100%]
=========================== 1 passed in 4.58 seconds ===========================
E isso é legal! No entanto, você precisa ser complicado para tornar a validação mais realista. Vamos alterar o valor de "navegador" para "safari" - um navegador sem suporte.
$ pipenv run python -m pytest tests/test_web.py
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item
tests/test_web.py E [100%]
==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________
config = {'browser': 'safari', 'wait_time': 10}
@pytest.fixture(scope='session')
def config_browser(config):
# Validate and return the browser choice from the config data
if 'browser' not in config:
raise Exception('The config file does not contain "browser"')
elif config['browser'] not in SUPPORTED_BROWSERS:
> raise Exception(f'"{config["browser"]}" is not a supported browser')
E Exception: "safari" is not a supported browser
tests/conftest.py:30: Exception
=========================== 1 error in 0.09 seconds ============================
Uau! O erro indicou claramente por que apareceu. Agora, o que acontece se removermos a seleção do navegador do arquivo de configuração?
$ pipenv run python -m pytest tests/test_web.py
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item
tests/test_web.py E [100%]
==================================== ERRORS ====================================
________________ ERROR at setup of test_basic_duckduckgo_search ________________
config = {'wait_time': 10}
@pytest.fixture(scope='session')
def config_browser(config):
# Validate and return the browser choice from the config data
if 'browser' not in config:
> raise Exception('The config file does not contain "browser"')
E Exception: The config file does not contain "browser"
tests/conftest.py:28: Exception
=========================== 1 error in 0.10 seconds ============================
Excelente! Outra mensagem de erro útil. Para o último teste, vamos adicionar uma seleção de navegador, mas remover o tempo limite:
$ pipenv run python -m pytest tests/test_web.py
============================= test session starts ==============================
platform darwin -- Python 3.7.3, pytest-4.5.0, py-1.8.0, pluggy-0.12.0
rootdir: /Users/andylpk247/Programming/automation-panda/python-webui-testing
collected 1 item
tests/test_web.py . [100%]
=========================== 1 passed in 4.64 seconds ===========================
O teste deve ser executado porque o tempo limite é opcional. Bem, as mudanças que fizemos foram benéficas! Lembre-se de que às vezes você também precisa testar seus testes .
Teste final
Existem mais duas pequenas coisas que podemos fazer para tornar o código de teste mais limpo. Primeiro, vamos mover nossos acessórios da web para um arquivo
conftest.py
para que todos os testes possam usá-los, não apenas os testes em tests / test_web.py. Em segundo lugar, vamos extrair alguns valores literais em variáveis de módulo.
Crie um novo arquivo chamado
tests/conftest.py
com o seguinte código:
import json
import pytest
from selenium.webdriver import Chrome, Firefox
CONFIG_PATH = 'tests/config.json'
DEFAULT_WAIT_TIME = 10
SUPPORTED_BROWSERS = ['chrome', 'firefox']
@pytest.fixture(scope='session')
def config():
# Read the JSON config file and returns it as a parsed dict
with open(CONFIG_PATH) as config_file:
data = json.load(config_file)
return data
@pytest.fixture(scope='session')
def config_browser(config):
# Validate and return the browser choice from the config data
if 'browser' not in config:
raise Exception('The config file does not contain "browser"')
elif config['browser'] not in SUPPORTED_BROWSERS:
raise Exception(f'"{config["browser"]}" is not a supported browser')
return config['browser']
@pytest.fixture(scope='session')
def config_wait_time(config):
# Validate and return the wait time from the config data
return config['wait_time'] if 'wait_time' in config else DEFAULT_WAIT_TIME
@pytest.fixture
def browser(config_browser, config_wait_time):
# Initialize WebDriver
if config_browser == 'chrome':
driver = Chrome()
elif config_browser == 'firefox':
driver = Firefox()
else:
raise Exception(f'"{config_browser}" is not a supported browser')
# Wait implicitly for elements to be ready before attempting interactions
driver.implicitly_wait(config_wait_time)
# Return the driver object at the end of setup
yield driver
# For cleanup, quit the driver
driver.quit()
O conteúdo completo
tests/test_web.py
agora deve ser mais simples e mais limpo:
import pytest
from pages.result import DuckDuckGoResultPage
from pages.search import DuckDuckGoSearchPage
def test_basic_duckduckgo_search(browser):
# Set up test case data
PHRASE = 'panda'
# Search for the phrase
search_page = DuckDuckGoSearchPage(browser)
search_page.load()
search_page.search(PHRASE)
# Verify that results appear
result_page = DuckDuckGoResultPage(browser)
assert result_page.link_div_count() > 0
assert result_page.phrase_result_count(PHRASE) > 0
assert result_page.search_input_value() == PHRASE
Bem, isso já é o estilo Python!
Qual é o próximo?
Portanto, o código de amostra para nosso projeto de teste está completo. Você pode usá-lo como base para a criação de novos testes. Você também pode encontrar o exemplo final do projeto no GitHub . No entanto, o fato de termos terminado de escrever o código não significa que terminamos o treinamento. Em artigos futuros, falaremos sobre como levar a automação de teste Python para o próximo nível!