Cómo leer archivos de configuración en pruebas con Selenium en Python

Hola habr. En vísperas del inicio del curso Python QA Engineer, hemos preparado otra traducción interesante para ti.










El tutorial de este artículo le ayudará a probar sus interfaces web. Crearemos una solución de prueba de interfaz web simple y robusta utilizando Python , pytest y Selenium WebDriver . Veremos estrategias para crear buenas pruebas y patrones para escribir buenas pruebas automatizadas. Por supuesto, el proyecto de prueba desarrollado puede servir como una buena base para crear sus propios casos de prueba.



¿Qué navegador?



La prueba de búsqueda DuckDuckGo de uno de los capítulos anteriores funciona bien ... pero solo en Chrome. Echemos browserotro vistazo al accesorio :



@pytest.fixture
def browser():
  driver = Chrome()
  driver.implicitly_wait(10)
  yield driver
  driver.quit()


El tipo de controlador y el tiempo de espera están codificados. Para una prueba de concepto, esto puede ser bueno, pero las pruebas de producción deben poder configurarse en tiempo de ejecución. Las pruebas de interfaces web deberían funcionar en cualquier navegador. Los valores de tiempo de espera predeterminados deben ajustarse en caso de que algunos entornos funcionen más lentamente que otros. Los datos confidenciales como nombres de usuario y contraseñas tampoco deberían aparecer nunca en el código fuente. ¿Cómo trabaja con estos datos de prueba ?



Todos estos valores son datos de configuración para el sistema de prueba automatizado. Son valores discretos que afectan sistemáticamente el funcionamiento de la automatización. Los datos de configuración deben llegar a la entrada con cada ejecución de prueba. Todo lo relacionado con la configuración del entorno y la prueba debe tratarse como datos de configuración para que el código de automatización pueda reutilizarse.



Fuentes de insumos



En un sistema de prueba automatizado, hay varias formas de leer los datos de entrada:



  • Argumentos de la línea de comandos;
  • Variables de entorno;
  • Propiedades del sistema;
  • Archivos de configuración;
  • Solicitudes de API.


Desafortunadamente, la mayoría de los marcos de prueba no admiten la lectura de datos de los argumentos de la línea de comandos. Las variables de entorno y las propiedades del sistema son difíciles de administrar y potencialmente peligrosas de manejar. Las API de servicios son una excelente manera de consumir entradas, especialmente obtener secretos (como contraseñas) de un servicio de administración de claves como AWS KMS o Azure Key Vault . Sin embargo, pagar por dicha funcionalidad puede ser inaceptable y escribir usted mismo no es razonable. En este caso, los archivos de configuración son la mejor opción.



Un archivo de configuración es un archivo normal que contiene datos de configuración. Las pruebas automatizadas pueden leerlo cuando se ejecutan las pruebas y utilizar los valores de entrada para impulsar las pruebas. Por ejemplo, el archivo de configuración podría especificar el tipo de navegador utilizado como accesorio del navegador en nuestro proyecto de muestra. Normalmente, los archivos de configuración tienen un formato estándar, como JSON, YAML o INI. También deben ser planos para que se puedan distinguir fácilmente de otros archivos.



Nuestro archivo de configuración



Escribamos un archivo de configuración para nuestro proyecto de prueba. Usaremos el formato JSON porque es fácil de usar, popular y tiene una jerarquía clara. Además, el módulo json es una biblioteca estándar de Python que convierte archivos JSON en diccionarios con facilidad. Cree un nuevo archivo con el nombre tests/config.jsony agregue el siguiente código:



{
  "browser": "chrome",
  "wait_time": 10
}


JSON usa pares clave-valor. Como dijimos, hay dos valores de configuración en nuestro proyecto: selección del navegador y tiempo de espera. Aquí "browser" es una cadena y "wait_time" es un número entero.



Leyendo un archivo de configuración con pytest



Los accesorios son la mejor manera de leer archivos de configuración usando pytest. Se pueden usar para leer archivos de configuración antes de iniciar las pruebas y luego insertar valores en las pruebas o incluso en otros dispositivos. Agregue el siguiente accesorio 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


El dispositivo configlee y analiza el archivo tests/config.jsonen un diccionario usando el módulo json. Las rutas de archivo codificadas de forma rígida son una práctica bastante común. De hecho, muchas herramientas y sistemas de automatización buscarán archivos en varios directorios o con patrones de nombres. El alcance del dispositivo se establece en "sesión", por lo que el dispositivo se ejecutará una vez por sesión de prueba. No es necesario leer el mismo archivo de configuración cada vez en una nueva prueba, ¡esto es ineficiente!



Se necesita una entrada de configuración al inicializar WebDriver. Actualice el dispositivo de la browsersiguiente manera:



@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()


El dispositivo browserahora tendrá una dependencia de dispositivo config. Incluso si se configinicia una vez por sesión de prueba, se seguirá llamando al navegador antes de cada prueba. Ahora browsertengo un encadenamiento if-elsepara determinar qué tipo de WebDriver usar. Por ahora, solo se admite Chrome, pero pronto agregaremos algunos tipos más. Si no se detecta el navegador, se lanzará una excepción. El tiempo de espera implícito también tomará su valor del archivo de configuración.



Dado browserque todavía devuelve una instancia de WebDriver, las pruebas que lo utilizan no necesitan ser refactorizadas. Hagamos pruebas para asegurarnos de que el archivo de configuración funcione:



$ 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 ===========================


Agregar nuevos navegadores



Ahora que nuestro proyecto tiene un archivo de configuración, podemos usarlo para cambiar el navegador. Ejecutemos la prueba en Mozilla Firefox en lugar de Google Chrome. Para hacer esto, descargue e instale la última versión de Firefox y luego descargue la última versión de geckodriver (controlador de Firefox). Asegúrese de que también esté geckodriveren la ruta del sistema.



Actualice el código del dispositivo browserpara que funcione con 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()


Luego agregue una opción al archivo de configuración «firefox»:



{
  "browser": "firefox",
  "wait_time": 10
}


¡Ahora reinicie la prueba y verá una ventana de Firefox en lugar de Chrome!







Validación



A pesar de que el archivo de configuración funciona, existe una falla significativa en la lógica de su procesamiento: los datos no se verifican antes de ejecutar las pruebas. El dispositivo browserlanzará una excepción si el navegador no se selecciona correctamente, pero sucederá para cada prueba. Será mucho más eficaz si se lanza una excepción de este tipo una vez por sesión de prueba. Además, la prueba fallará si faltan las claves de "navegador" o "tiempo de espera" en el archivo de configuración . Arreglemos esto.



Agregue un nuevo dispositivo para validar la selección del 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']


El dispositivo config_browserdepende del dispositivo de configuración. Además, como config, tiene scope = "session". Obtendremos una excepción si no hay una clave de "navegador" en el archivo de configuración o si el navegador seleccionado no es compatible. Finalmente, devuelve el navegador seleccionado para que las pruebas y otros accesorios puedan acceder de forma segura a este valor.



El siguiente es el siguiente accesorio para la validación del tiempo de espera:



@pytest.fixture(scope='session')
def config_wait_time(config):
  return config['wait_time'] if 'wait_time' in config else 10


Si se especifica un tiempo de espera en el archivo de configuración, el dispositivo config_wait_timelo devolverá. De lo contrario, regresará 10 segundos por defecto.



Actualice el dispositivo browsernuevamente para usar los nuevos dispositivos de validación:



@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()


Escribir funciones de dispositivo separadas para cada valor de datos de configuración las hace simples, claras y específicas. También le permiten declarar solo los valores necesarios para enviar solicitudes.



Ejecute la prueba y asegúrese de que todo funcione:



$ 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 ===========================


¡Y eso es genial! Sin embargo, debe ser complicado para que la validación sea más realista. Cambiemos el valor de "navegador" a "safari" , un navegador no compatible.



$ 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 ============================


¡Guauu! El error indicó claramente por qué apareció. Ahora, ¿qué sucede si eliminamos la selección del navegador del archivo de configuración?



$ 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! Otro mensaje de error útil. Para la última prueba, agreguemos una selección de navegador, pero eliminemos el tiempo de espera:



$ 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 ===========================


La prueba debe ejecutarse porque el tiempo de espera es opcional. Bueno, ¡los cambios que hicimos han sido beneficiosos! Recuerde que a veces también necesita probar sus pruebas .



Examen final



Hay dos pequeñas cosas más que podemos hacer para que el código de prueba sea más limpio. Primero, muevamos nuestros accesorios web a un archivo conftest.pypara que todas las pruebas puedan usarlos, no solo las pruebas en tests / test_web.py. En segundo lugar, introduzcamos algunos valores literales en las variables del módulo.



Cree un nuevo archivo nombrado tests/conftest.pycon el siguiente 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()


El contenido completo tests/test_web.pyahora debería ser más simple y limpio:



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


Bueno, ¡esto ya es estilo Python!



¿Que sigue?



Entonces, el código de muestra para nuestro proyecto de prueba está completo. Puede usarlo como base para crear nuevas pruebas. También puede encontrar el ejemplo final del proyecto en GitHub . Sin embargo, el hecho de que hayamos terminado de escribir el código no significa que hayamos terminado el entrenamiento. En artículos futuros, hablaremos sobre cómo llevar la automatización de pruebas de Python al siguiente nivel.






All Articles