Comment lire les fichiers de configuration dans les tests avec Selenium en Python

Salut habr. À la veille du début du cours Python QA Engineer, nous avons préparé une autre traduction intéressante pour vous.










Le didacticiel de cet article vous aidera à tester vos interfaces Web. Nous allons créer une solution de test d'interface Web simple et robuste à l'aide de Python , pytest et Selenium WebDriver . Nous examinerons des stratégies pour créer de bons tests et des modèles pour écrire de bons tests automatisés. Bien entendu, le projet de test développé peut servir de bonne base pour créer vos propres cas de test.



Quel navigateur?



Le test de recherche DuckDuckGo de l'un des chapitres précédents fonctionne très bien ... mais uniquement dans Chrome. Jetons browserun autre regard sur le luminaire :



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


Le type de pilote et le délai d'expiration sont codés en dur. Pour une preuve de concept, cela peut être bon, mais les tests de production doivent pouvoir être configurés au moment de l'exécution. Les tests d'interfaces Web doivent fonctionner dans n'importe quel navigateur. Les valeurs de délai d'expiration par défaut doivent être ajustées au cas où certains environnements fonctionneraient plus lentement que d'autres. Les données sensibles telles que les noms d'utilisateur et les mots de passe ne doivent également jamais apparaître dans le code source. Comment travaillez-vous avec de telles données de test ?



Toutes ces valeurs sont des données de configuration pour le système de test automatisé. Ce sont des valeurs discrètes qui affectent systématiquement le fonctionnement de l'automatisation. Les données de configuration doivent arriver à l'entrée à chaque essai. Tout ce qui concerne la configuration des tests et de l'environnement doit être traité comme des données de configuration afin que le code d'automatisation puisse être réutilisé.



Sources d'entrée



Dans un système de test automatisé, il existe plusieurs façons de lire les données d'entrée:



  • Arguments de ligne de commande;
  • Variables d'environnement;
  • Propriétés du système;
  • Fichiers de configuration;
  • Demandes d'API.


Malheureusement, la plupart des frameworks de test ne prennent pas en charge la lecture de données à partir d'arguments de ligne de commande. Les variables d'environnement et les propriétés système sont difficiles à gérer et potentiellement dangereuses à gérer. Les API de service sont un excellent moyen de consommer des entrées, en particulier d'obtenir des secrets (comme des mots de passe) à partir d'un service de gestion de clés comme AWS KMS ou Azure Key Vault . Cependant, payer pour une telle fonctionnalité peut être inacceptable et écrire soi-même n'est pas judicieux. Dans ce cas, les fichiers de configuration sont la meilleure option.



Un fichier de configuration est un fichier normal contenant des données de configuration. Les tests automatisés peuvent le lire lors de l'exécution des tests et utiliser les valeurs d'entrée pour piloter les tests. Par exemple, le fichier de configuration peut spécifier le type de navigateur utilisé comme appareil de navigateur dans notre exemple de projet. En règle générale, les fichiers de configuration sont dans un format standard tel que JSON, YAML ou INI. Ils doivent également être plats pour pouvoir être facilement distingués des autres fichiers.



Notre fichier de configuration



Écrivons un fichier de configuration pour notre projet de test. Nous utiliserons le format JSON car il est facile à utiliser, populaire et possède une hiérarchie claire. De plus, le module json est une bibliothèque standard Python qui convertit facilement les fichiers JSON en dictionnaires. Créez un nouveau fichier nommé tests/config.jsonet ajoutez le code suivant:



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


JSON utilise des paires clé-valeur. Comme nous l'avons dit, il existe deux valeurs de configuration dans notre projet: la sélection du navigateur et le délai d'expiration. Ici, "browser" est une chaîne et "wait_time" est un entier.



Lire un fichier de configuration avec pytest



Les fixtures sont le meilleur moyen de lire les fichiers de configuration en utilisant pytest. Ils peuvent être utilisés pour lire les fichiers de configuration avant de commencer les tests, puis insérer des valeurs dans des tests ou même d'autres fixtures. Ajoutez le luminaire suivant à 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


L'appareil configlit et analyse le fichier tests/config.jsondans un dictionnaire à l'aide du module json. Les chemins d'accès aux fichiers codés en dur sont une pratique assez courante. En fait, de nombreux outils et systèmes d'automatisation vérifieront la présence de fichiers dans plusieurs répertoires ou les modèles de dénomination. La portée du projecteur est définie sur "session", donc le projecteur s'exécutera une fois par session de test. Il n'est pas nécessaire de lire le même fichier de configuration à chaque fois dans un nouveau test - c'est inefficace!



Une entrée de configuration est nécessaire lors de l'initialisation du WebDriver. Mettez à jour l'appareil browsercomme suit:



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


L'appareil browseraura désormais une dépendance d' appareil config. Même s'il est configlancé une fois par session de test, le navigateur sera toujours appelé avant chaque test. Maintenant, j'ai browserun chaînage if-elsepour déterminer quel type de WebDriver utiliser. Pour l'instant, seul Chrome est pris en charge, mais nous ajouterons bientôt quelques types supplémentaires. Si le navigateur n'est pas détecté, une exception sera levée. Le délai d'expiration implicite prendra également sa valeur dans le fichier de configuration.



Puisqu'il browserrenvoie toujours une instance WebDriver, les tests qui l'utilisent n'ont pas besoin d'être refactorisés! Lançons des tests pour nous assurer que le fichier de configuration fonctionne:



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


Ajout de nouveaux navigateurs



Maintenant que notre projet a un fichier de configuration, nous pouvons l'utiliser pour changer de navigateur. Lançons le test sur Mozilla Firefox au lieu de Google Chrome. Pour ce faire, téléchargez et installez le dernier Firefox , puis téléchargez le dernier geckodriver (pilote Firefox). Assurez-vous qu'il se trouve geckodriverégalement dans le chemin du système.



Mettez à jour le code browserde l'appareil pour qu'il fonctionne avec 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()


Ensuite, ajoutez une option au fichier de configuration «firefox»:



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


Maintenant, redémarrez le test et vous verrez une fenêtre Firefox au lieu de Chrome!







Validation



Malgré le fait que le fichier de configuration fonctionne, il y a un défaut important dans la logique de son traitement: les données ne sont pas vérifiées avant de lancer les tests. L'appareil browserlèvera une exception si le navigateur n'est pas sélectionné correctement, mais cela se produira pour chaque test. Ce sera beaucoup plus efficace si une exception de ce type est lancée une fois par session de test. De plus, le test échouera si les clés "browser" ou "wait_time" sont manquantes dans le fichier de configuration . Corrigeons ça.



Ajoutez un nouvel appareil pour valider la sélection du navigateur:



@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']


L'appareil config_browserdépend de l' appareil de configuration. De plus, comme config, il a scope = "session". Nous obtiendrons une exception s'il n'y a pas de clé "navigateur" dans le fichier de configuration ou si le navigateur sélectionné n'est pas pris en charge. Enfin, il renvoie le navigateur sélectionné afin que les tests et autres appareils puissent accéder en toute sécurité à cette valeur.



Vient ensuite le montage suivant pour la validation du délai d'expiration:



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


Si un délai d'expiration est spécifié dans le fichier de configuration, l'appareil config_wait_timele renverra. Sinon, il retournera 10 secondes par défaut.



Mettez à browsernouveau à jour l'appareil pour utiliser les nouveaux appareils de validation:



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


L'écriture de fonctions d'appareil distinctes pour chaque valeur de données de configuration les rend simples, claires et spécifiques. Ils vous permettent également de déclarer uniquement les valeurs nécessaires pour envoyer des requêtes.



Exécutez le test et assurez-vous que tout fonctionne:



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


Et c'est cool! Cependant, pour rendre la validation plus réaliste, vous devez être délicat. Changeons la valeur de "browser" en "safari" - un navigateur non pris en charge.



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


Hou la la! L'erreur indiquait clairement pourquoi elle était apparue. Maintenant, que se passe-t-il si nous supprimons la sélection du navigateur du fichier de configuration?



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


Excellent! Un autre message d'erreur utile. Pour le dernier test, ajoutons une sélection de navigateur, mais supprimons le délai:



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


Le test doit s'exécuter car le délai d'expiration est facultatif. Eh bien, les changements que nous avons apportés ont été bénéfiques! N'oubliez pas que vous devez parfois également tester vos tests .



Examen final



Il y a deux autres petites choses que nous pouvons faire pour rendre le code de test plus propre. Tout d'abord, déplaçons nos montages Web dans un fichier conftest.pyafin que tous les tests puissent les utiliser, pas seulement les tests dans tests / test_web.py. Deuxièmement, tirons quelques valeurs littérales dans des variables de module.



Créez un nouveau fichier nommé tests/conftest.pyavec le code suivant:



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


Le contenu complet tests/test_web.pydevrait maintenant être plus simple et plus propre:



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


Eh bien, c'est déjà le style Python!



Et après?



Ainsi, l'exemple de code de notre projet de test est terminé. Vous pouvez l'utiliser comme base pour créer de nouveaux tests. Vous pouvez également trouver le dernier exemple du projet sur GitHub . Cependant, le fait que nous ayons fini d'écrire le code ne signifie pas que nous avons terminé la formation. Dans les prochains articles, nous parlerons de la manière de faire passer l'automatisation des tests Python au niveau supérieur!






All Articles