Creació d'un simple POM amb Python - Behave

Creació d’un simple POM amb Python – Behave

Continuant amb la sèrie que vam començar fa un parell de setmanes sobre com fer un POM simple amb Java, avui vull deixar-vos una altra forma simple de generar un POM amb Python en Behave, per al meu gust un gran framework al qual se li pot treure moltíssim rendiment.

Com sempre vull esmentar que són petits manuals perquè les persones que estan començant en aquest apassionant món sàpiguen i entenguin com generar els seus propis POM i per què la necessitat dels mateixos, que no és una altra que tenir tot organitzat de la millor forma possible, per d’entendre, mantenir i reutilitzar el codi, amb una base estable de proves.

Com vam veure l’altre dia les capes bàsiques que formen un POM, són les següents:

  1. features, (cucumber scenarios & steps), parlarem més detalladament en un altre post (com a principal objectiu és documentar i provar funcionalitats per separat), a més també tindrà els steps comuns o genèrics i el fitxer environment, el qual contindrà el mètode before_scenario i after_scenario
  2. pages, una per cada pàgina de la web
  3. web_source, estaran els mètodes comuns i la instància al chormedriver
  4. drivers, estaran els diferents drivers per a poder treballar amb el nostre navegador

Creación de un simple POM con PYTHON – BEHAVE

NOTA: per als que van veure el meu post anterior aquest diagrama difereix una mica de l’altre, però al final l’essència és la mateixa

­­Documentació:

Instal·lació:

Nota: provat sobre la versió 3.8.5 de python

  • pip3 install behave
  • pip3 install behave2cucumber
  • pip3 install cucumber-tag-expressions
  • pip3 install behave-html-formatter
  • pip3 install behave-jenkins
  • pip3 install pylint
  • pip3 install allure-behave
  • npm install -g allure-commandline –save-dev

Amb tot això ja instal·lat i sense major dilació som-hi, i en honor al gran “Martín Berasategui” li donarem “garrot”:

Nota: com sempre dic és un entorn simple per a poder començar a fer proves, sempre obert a millores i modificacions oportunes necessàries.

web_source:

web_factory.py: Contindrà el mètode amb path al driver a utilitzar, si volguéssim posar per exemple geckodriver, únicament hauríem d’afegir la condicional i igualar-la a Firefox, per exemple on dins tindríem la nostra ruta a aquest.

from selenium import webdriver

from web_source.web import Web

def get_web(browser):
    if browser == "chrome":
        return Web(webdriver.Chrome("drivers/chromedriver.exe"))

web.py: la clase Web, tindrà tots els mètodes necessaris del WebDriver a utilitzar en la nostra implementació.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class Web(object):
    __TIMEOUT = 10

    def __init__(self, web_driver):
        super(Web, self).__init__()
        self._web_driver_wait = WebDriverWait(web_driver, Web.__TIMEOUT)
        self._web_driver = web_driver

    def open(self, url):
        self._web_driver.get(url)

    def maximize(self):
        self._web_driver.maximize_window()

    def title(self):
        return self._web_driver.title

    def curent_url(self):
        return self._web_driver.current_url

    def get_text_xpath(self, xpath):
        return self._web_driver_wait.until(EC.presence_of_element_located((By.XPATH, xpath))).text

    def find_by_xpath(self, xpath):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.XPATH, xpath)))

    def finds_by_xpath(self, xpath):
        return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.XPATH, xpath)))

    def find_by_xpath_displayed(self, xpath):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.XPATH, xpath))).is_displayed()

    def find_by_name(self, name):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.NAME, name)))

    def finds_by_name(self, name):
        return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.NAME, name)))

    def find_by_id(self, id):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.ID, id)))

    def find_by_id_displayed(self, id_value):
        return self._web_driver_wait.until(EC.presence_of_element_located((By.ID, id_value))).is_displayed()

    def finds_by_id(self, id):
        return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.ID, id)))

    def find_by_class_name(self, classname):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.CLASS_NAME, classname)))

    def finds_by_class_nam(self, classname):
        return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.CLASS_NAME, classname)))

    def find_by_css_selector(self, cssselector):
        return self._web_driver_wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, cssselector)))

    def finds_by_css_selector(self, cssselector):
        return self._web_driver_wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, cssselector)))

    def switch_frame(self,frame):
        return self._web_driver.switch_to.frame(frame)

    def close(self):
        self._web_driver.quit()

page_object:

  • dashboard_orange_hrm.py: la classe DashboardOrangeHrm, conté mètodes necessaris de la pàgina que volem provar.
from page_object.orangehrm.locators_orange_hrm import LocatorsHomeOrangeHrm

class DashboardOrangeHrm(LocatorsHomeOrangeHrm):

    def __init__(self):
        self.locators = self.locators_dashboard

    def dashboard_header(self, context):
        text = context.web.get_text_xpath(self.locators["xpath_dashboard_header"])
        if text == self.locators["Dashboard_header_name"]:
            return True
        return False
  • home_orange_hrm.py: Aquesta classe HomeOrangeHrm al igual que la anterior ha de contindre els mètodes necessaris de la pàgina a testar.
from page_object.orangehrm.locators_orange_hrm import LocatorsHomeOrangeHrm

class HomeOrangeHrm(LocatorsHomeOrangeHrm):

    def __init__(self):
        self.locator = self.locators

    def simple_login_home(self, context, user, pwd):
        user_name = context.web.find_by_id_displayed(self.locators["id_username"])
        password = context.web.find_by_id_displayed(self.locators["id_password"])
        if user_name is True and password is True:
            context.web.find_by_id(self.locators["id_username"]).send_keys(user)
            context.web.find_by_id(self.locators["id_password"]).send_keys(pwd)
            return True
        return False

    def table_login_home(self, context, name, pwd):
        for row in context.table:
            context.temp_name = row['name']
            context.temp_password = row['pwd']
            user_name = context.web.find_by_id_displayed(self.locators["id_username"])
            password = context.web.find_by_id_displayed(self.locators["id_password"])
            if user_name is True and password is True:
                context.web.find_by_id(self.locators["id_username"]).send_keys(context.temp_name)
                context.web.find_by_id(self.locators["id_password"]).send_keys(context.temp_password)
                return True
            return False

    def example_login_home(self, context, user, pwd):
        context.temp_name = user
        context.temp_password = pwd
        user_name = context.web.find_by_id_displayed(self.locators["id_username"])
        password = context.web.find_by_id_displayed(self.locators["id_password"])
        if user_name is True and password is True:
            context.web.find_by_id(self.locators["id_username"]).send_keys(context.temp_name)
            context.web.find_by_id(self.locators["id_password"]).send_keys(context.temp_password)
            return True
        return False

    def invalid_login(self, context):
        message = context.web.get_text_xpath(self.locators["xpath_message_error"])
        if message == "Invalid credentials":
            return True
        return False

    def view_logo(self, context):
        logo = context.web.find_by_xpath_displayed(self.locators["xpath_logo"])
        return logo

    def submit_bottom(self, context):
        context.web.find_by_id(self.locators["id_btn_login"]).click()

  • locator_orange_hrm.py: hem triat aquesta forma de datafactory per emmagatzemar el locators, perquè ens ha semblat la opció més didàctica, hi ha moltes altres però aquesta també és una bona manera de tenir els locators controlats, per tant la classe locator es farà servir per a emmagatzemar els localitzadors que farem servir en la nostra implementació.
class LocatorsHomeOrangeHrm:
    locators = {
        "id_username": "txtUsername",
        "id_password": "txtPassword",
        "xpath_message_error": "//span[@id='spanMessage']",
        "xpath_logo": "//div[@id='divLogo']//img",
        "id_btn_login": "btnLogin",
        "credential_error": "Invalid credentials"
    }

    locators_dashboard = {
        "xpath_dashboard_header": "//h1[contains(text(),'Dashboard')]",
        "Dashboard_header_name": "Dashboard"
    }

features:

  • orange_hrm_home.feature: En aquest fitxer de tipus .feature, tindrem tots els escenaris necessaris per a les nostres proves, tindrem tants com necessitem, una bona recomanació és ordenar-los per pàgina i tipus de prova (ej. Casos negatius, casos positius) per funcionalitat.

Nota: com comentavem abans farem un post sobre gerking, però a continuació ja podeu observar dos tipus d’escenaris diferent, per una banda l’escenari normal (anomenat Scenario), i d’altra banda els escenaris outlines (anomenats Scenarios Outline), així mateix podeu anar veient algunes diferències entre tots dos

Feature: OrangeHRM outline

  @test
  Scenario: Login to OrangeHRM with valid parameters
    Given the user is on the search page "orangehrmlive"
    When Enter username "admin" and password "admin123"
    And press on login button
    Then User must succesfully login to the Dashboard page

  @test
  Scenario: Logo presence on OrangeHRM home page
    Given the user is on the search page "orangehrmlive"
    When the user verify the title page "OrangeHRM"
    Then verify that the logo present on page

  @test
  Scenario Outline: Login table Examples valid to OrangeHRM
    Given the user is on the search page "orangehrmlive"
    When insert username "<name>" and password "<pwd>"
    And press on login button
    Then User must succesfully login to the Dashboard page

    Examples:
      |name     | pwd          |
      |admin    | admin123|

  @test
  Scenario Outline: Login table Examples invalid to OrangeHRM
    Given the user is on the search page "orangehrmlive"
    When insert username "<name>" and password "<pwd>"
    And press on login button
    Then User must unsuccesfully login to the Dashboard page

    Examples:
      |name      | pwd             |
      |admin     | admin1       |
      |admin1   | admin123  |
      |admin     | admin         |
  • orange_hrm_dashboard.feature: Escenari de la prova que realitzem sobre la pàgina anomenada dashboard, que no és una altra més que el login d’entrada, com us imagineu d’un login bàsic com aquest es poden treure com a mínim 4 casos negatius i un positiu, els quals us els deixo perquè els penseu.

Nota: el positiu ja està en l’exemple

Feature: OrangeHRM Dashboard

  @test
  Scenario: Login table to OrangeHRM
    Given the user is on the search page "orangehrmlive"
    When put username "<name>" and password "<pwd>"
      |name | pwd     |
      |admin| admin123|
    And press on login button
    Then User must succesfully login to the Dashboard page

Steps:

  • orange_hrm_steps.py: Aquest fitxer contindrà cadascun dels steps definits en cadascun dels escenaris dels features, cada vegada que arribi a un step (Given, When, Then, And), buscarà si existeix el mateix i l’executarà en cas contrari fallés i ens mostrarà el step que haurem d’implementar.
from behave import when, then

from page_object.orangehrm.home_orange_hrm import HomeOrangeHrm
from page_object.orangehrm.dashboard_orange_hrm import DashboardOrangeHrm

global HOME

@when('the user verify the title page "{titlepage}"')
def title_page(context, titlepage):
    title = context.web.title()
    assert title == titlepage

@then('verify that the logo present on page')
def verify_logo(context):
    home = HomeOrangeHrm()
    result = home.view_logo(context)
    assert result is True

# Login
@when('Enter username "{user}" and password "{pwd}"')
def login(context, user, pwd):
    home = HomeOrangeHrm()
    result = home.simple_login_home(context, user, pwd)
    assert result is True

# login table with header
@when('put username "{name}" and password "{pwd}"')
def login_table(context, name, pwd):
    home = HomeOrangeHrm()
    result = home.table_login_home(context, name, pwd)
    assert result is True

# login outline valid
@when('insert username "{user}" and password "{pwd}"')
def login_outline(context, user, pwd):
    home = HomeOrangeHrm()
    result = home.example_login_home(context, user, pwd)
    assert result is True

# finish valid login
@then('User must succesfully login to the Dashboard page')
def dashboard(context):
    home = DashboardOrangeHrm()
    dashboard_head = home.dashboard_header(context)
    assert dashboard_head is True

# finish invalid login
@then('User must unsuccesfully login to the Dashboard page')
def login_error(context):
    home = HomeOrangeHrm()
    result = home.invalid_login(context)
    assert result is True
  • hook: Aquest fitxer contindrà els steps genèrics o comuns per totes les proves. D’aquesta manera evitarem repeticions de codi.
from behave import given, when

from page_object.orangehrm.home_orange_hrm import HomeOrangeHrm
from page_object.telefonica.google_telefonica import google_telefonica

@given('the user is on the search page "{page}"')
def user_on_search_page(context, page):
    if page == "orangehrmlive":
        context.web.open('http://opensource-demo.orangehrmlive.com/')
        context.web.maximize()
        bing.accept_cookies(context)

@when('Click on login button')
def submit_login_form(context):
    submit = HomeOrangeHrm()
    submit.submit_bottom(context)

Environment:

Tindrem en el mateix els mètodes “before_scenario” i “after_scenario”, els quals s’executen abans i després de cada escenari.

  • before_scenario: Bàsicament el que farem es passar el mètode get_web del web_factory anteriorment mencionat el browser a fer servir, com nosaltres únicament tenim per a aquest exemple configurat el Chrome, perquè li estem passant el valor de chrome, perquè així carregui el driver corresponent al Chrome.
  • After_scenario: En aquest el que fem es tancar la instància del browser
from web_source.web_factory import get_web

def before_scenario(context, test):
    web = get_web("chrome")
    context.web = web

def after_scenario(context, test):
    context.web.close()

Finalment i atès que behave ens diu que hem de tenir tot sobre el mateix directori feaures i nosaltres creiem que és millor tenir-lo separat, hem de generar el següent fitxer a l’alçada del hook.

All_steps:

Nota:en el mateix cal indicar-li la ruta als steps, d’aquesta manera podríem tenir-los dividits per directoris diferents, només afegint en aquest les rutes als mateixos. 

from features.steps.orange_hrm import *

I want be chamaleon, gràcies a tot l’equip.

Naila Ros
Fernando Manrique Villanueva
QA Tester