Base para automação de testes utilizando a linguagem Python com as tecnologias do Selenium WebDriver e UnitTest e com a estrutura organizacional CMT (Controller Model Test), uma adaptação do MVC.
Para exemplificar o funcionamento da base será automatizado a tela de login do site Netflix. para cobrir os seguintes cenários de teste:
- CT01 - Acessar tela de Boas Vinda
- CT02 - Acessar tela de Login
- CT03 - Senha Inválida
- CT04 - Usuário Inválido
- CT05 - Usuário Válido
Projeto
git clone https://github.com/andreinaoliveira/QA-Base-Automation.git
Dependencias
- Python 3
Módulos
Os módulos devem ser instalados no cmd com os comandos abaixos
pip install selenium
pip install webdriver-manager
Contem a função titleTest() recebendo testName. Quando a função é chamada imprime o nome do teste passado por parâmetro de forma mais amigável no terminal. Essa função é chamada para cada teste do unittest localizados na pasta test.
def titleTest(testName):
print(100 * '-')
print(testName.center(100))
print(100 * '-')
Exemplo da impressão:
----------------------------------------------------------------------------------------------------
CT01 - Acessar tela de Boas Vindas
----------------------------------------------------------------------------------------------------
Importa a biblioteca de loggin e formata a mensagem de log. Nesse arquivo é criado as funções debug(), info() e error(). Cada função recebe a mensagem que será enviada como log. Essas funções são chamadas em controller/webdriver.
import logging
log_format = '%(asctime)s :: %(name)s :: %(levelname)s :: %(module)s :: %(message)s'
logging.basicConfig(format=log_format, level=logging.INFO, filemode='w')
def degub(message):
logging.debug(message)
def info(message):
logging.info(message)
def error(message):
logging.error(message)
Em webdriver.py é criada a classe Element com os seguintes atribuitos e importações:
- driver: recebe o webdriver que será criado apenas no teste.
- name: nome do elemento ex.: Botão Sign In. O nome será enviado apenas nos log's.
- element: Ao ser encontrado, o elemento é salvo nesse atributo.
- as_Code_Type...: É a referência do elemento. É necessário atribuir valor a um dos itens para poder usar as funções da classe.
import os
import sys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from controller import log
class Element:
def __init__(self, driver, name):
self.driver = driver
self.name = name
self.element = None
self.as_1_ID = None
self.as_2_CLASS_NAME = None
self.as_3_NAME = None
self.as_4_TAG_NAME = None
self.as_5_LINK_TEXT = None
self.as_6_PARTIAL_LINK_TEXT = None
self.as_7_CSS_SELECTOR = None
self.as_8_XPATH = None
As funções da classe ao serem chamadas (find, click e set), executará as ações e retornará [True] ou [False] de acordo com o sucesso ou não da atividade. Portanto, além de executar a ação você poderá comparar o resultado, por exemplo, checar se retornou True, ou seja, checar se a ação foi executada com sucesso.
A função é chamada na função anterior, find(). Recebe um valor inteiro na variável code, o valor está no range de 1 a 8 e se refere ao tipo de referência do elemento (id, class etc.) o código é o mesmo do atributo as_Code_Type da classe. Exemplo, instanciando a classe como as_1_ID o code da função deve ser _ code(1) para buscar por ID. O ideal é que a função seja chamada apenas pelo find(), nunca diretamente.
def _code(self, code):
if code == 1:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, self.as_1_ID))
)
elif code == 2:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CLASS_NAME, self.as_2_CLASS_NAME))
)
elif code == 3:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.NAME, self.as_3_NAME))
)
elif code == 4:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, self.as_4_TAG_NAME))
)
elif code == 5:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.LINK_TEXT, self.as_5_LINK_TEXT))
)
elif code == 6:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.PARTIAL_LINK_TEXT, self.as_6_PARTIAL_LINK_TEXT))
)
elif code == 7:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, self.as_7_CSS_SELECTOR))
)
elif code == 8:
self.element = WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.XPATH, self.as_8_XPATH))
)
else:
log.error('Código informado em find() fora do range ou inválido.')
os.system("pause")
sys.exit()
- A Função tenta localizar o elemento e envia um log informando essa tentativa.
- Não encontrando, imprime o log de erro e retorna Falso.
- Encontrando, imprime log informando sucesso e retorna True.
def find(self, code):
try:
log.degub('Buscando ' + self.name)
self._code(code)
except Exception as e:
log.error('Erro ao identificar ' + self.name)
return False
else:
log.info(self.name + ' Identificado(a)')
return True
- Chama find() para atribuir valor ao atributo element da classe.
- Tenta clicar no elemento identificado.
- Não conseguindo, imprime o log de erro e retorna Falso
- Conseguindo, imprime log informando sucesso e retorna True
def click(self, code):
Element.find(self, code)
try:
self.element.click()
except Exception as e:
log.error('Erro ao clicar em ' + self.name)
return False
else:
log.info(self.name + ' Clicado(a)')
return True
- Chama find() para atribuir valor ao atributo element da classe.
- Tenta clicar no elemento identificado.
- Não conseguindo, imprime o log de erro e retorna Falso.
- Conseguindo, imprime log informando sucesso e retorna True.
def set(self, code, info):
Element.find(self, code)
try:
self.element.send_keys(info)
except Exception as e:
log.error('Erro ao escerver ' + self.name)
return False
else:
log.info(self.name + ' Inserido(a)')
return True
Modelo armazena todas as páginas de um sistema web em aquivos .py diferentes. Cada arquivo possui uma classe que se refere a página web em questão. O ideal é que os principais elementos da página sejam instanciados nessa classe herdando da classe Element de controller/webdriver. Para exemplificar, criamos o modelo da página de login da Netflix (login.py)
Como atribuito possuir:
- driver
As funções da página são divididas em:
- Check: Checa se está na página, checa se alguma mensagem de erro é apresentada etc.
- Click: realiza o clique em qualquer elemento da página.
- Set: Insere alguma informação na página.
- Instancia o elemento passando o driver e o nome do elemento.
- Atribuir o valor da referência.
- Retorna a função find que busca a referência informada.
def check_page_welcome(self):
e = Element(self.driver, '[Welcome] Page')
e.as_2_CLASS_NAME = 'our-story-card-title'
return e.find(2)
- Instancia o elemento passando o driver e o nome do elemento.
- Atribuir o valor da referência.
- Retorna a função click que tentará clicar na referência informada.
def click_signin_welcome(self):
e = Element(self.driver, '[Welcome] Sign In button')
e.as_5_LINK_TEXT = 'Sign In'
return e.click(5)
- Instancia o elemento passando o driver e o nome do elemento.
- Atribuir o valor da referência.
- Retorna a função set que tentará inserir uma informação na referência informada.
def set_email(self, email_or_number):
e = Element(self.driver, '[Login] Email')
e.as_1_ID = 'id_userLoginId'
return e.set(1, email_or_number)
Onde os testes de fato irão ocorrer. Após controller ser escrito suportando as instancias da página em model chega a hora de criar os casos de teste, para isso, será utilizado UnitTest. O teste será feiro com base no modelo login.py, portanto, o teste será chamado test_login.py
Como base para o teste, será importado:
- webdriver do próprio selenium
- format de controller
- página de model, no caso login
- unittest.
from selenium import webdriver
from controller import format
from model import P01_Login
import unittest
Por padrão o unitTest possui as funções setUp e tearDown, elas são chamadas para cada função de teste criada na classe test do unittest. setUp é chamada antes do teste e tearDown após o teste. Para o projeto, essas duas funções foram programadas para:
- setUp: Responsável por definir o driver e abrir o navegador na página inicial da Netflix.
- tearDown: Fechar a página web.
class test(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome('C:\Program Files (x86)\chromedriver.exe')
self.driver.get('https://www.netflix.com/br-en/')
# Adicionar aqui funções de teste, entre setUp e tearDown.
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
Considerando que as funções de teste foram escritas, ao final do teste o UnitTest informa quantos testes passaram e quantatos falharam indicando qual teste deu erro.
Objetivo
- Acessar o site da Netflix e checar se é carregada a tela de Boas Vindas.
Código
def test_CT01_AccessWelcome(self):
format.titleTest("CT01 - Acessar tela de Boas Vindas")
self.assertTrue(login.check_page_welcome(self.driver))
Objetivo
- Acessar o site da Netflix, clicar em "Sign In" e checar se é carregada a tela de Login.
Código
def test_CT02_AccessLogin(self):
format.titleTest("CT02 - Acessar tela de login")
login.click_signin_welcome(self.driver)
self.assertTrue(login.check_page_login(self.driver))
Objetivo
- Dado o acesso ao site da Netflix e clicado em "Sign In" preenchendo um e-mail válido e senha inválida no site, checar se a mensagem referente a senha errada é apresentada.
Código
def test_CT03_InvalidPassword(self):
format.titleTest("CT03 - Senha inválida")
login.click_signin_welcome(self.driver)
login.set_email(self.driver, 'teste@gmail.com')
login.set_password(self.driver, 'Teste@1234')
login.click_signin_login(self.driver)
self.assertTrue(login.check_error_passwordInvalid(self.driver))
Objetivo
- Dado o acesso ao site da Netflix e clicado em "Sign In" preenchendo e-mail e senha com dados inexistente no site, checar se a mensagem de que o usuário não existe é apresentada.
Código
def test_CT04_InvalidAccount(self):
format.titleTest("CT04 - Usuário inválido")
login.click_signin_welcome(self.driver)
login.set_email(self.driver, 'testeSelenium@gmail.com')
login.set_password(self.driver, 'Teste@1234')
login.click_signin_login(self.driver)
self.assertTrue(login.check_error_userInvalid(self.driver))
Objetivo
- Dado o acesso ao site da Netflix e clicado em "Sign In" preenchendo e-mail e senha com dados existentes no site e clicando em "Sign In", checar se a tela de Perfis é carregada.
Código
def test_CT05_ValidUser(self):
format.titleTest("CT05 - Usuário Válido")
login.click_signin_welcome(self.driver)
email = input('Informe um e-mail váido: ')
senha = input('Informe uma senha válida: ')
print()
login.set_email(self.driver, email)
login.set_password(self.driver, senha)
login.click_signin_login(self.driver)
self.assertTrue(login.check_page_profiles(self.driver))