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
browser
un 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.json
et 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
config
lit et analyse le fichier tests/config.json
dans 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
browser
comme 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
browser
aura désormais une dépendance d' appareil config
. Même s'il est config
lancé une fois par session de test, le navigateur sera toujours appelé avant chaque test. Maintenant, j'ai browser
un chaînage if-else
pour 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
browser
renvoie 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
browser
de 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
browser
lè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_browser
dé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_time
le renverra. Sinon, il retournera 10 secondes par défaut.
Mettez à
browser
nouveau à 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.py
afin 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.py
avec 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.py
devrait 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!