J'ai une configuration de test py.test
python-Selenium très complexe dans laquelle je crée un pilote Web Firefox dans un appareil py.test
. Voici une idée de ce que je fais:
'driver.py':
class Driver(object):
"""
Driver class with basic wrappers around the Selenium webdriver
and other convenience methods.
"""
def __init__(self, config, options):
"""Sets the driver and the config.
"""
self.remote = options.getoption("--remote")
self.headless = not options.getoption("--with-head")
if self.headless:
self.display = Display(visible=0, size=(13660, 7680))
self.display.start()
# Start the Selenium webdriver
self.webdriver = fixefox_module.get_driver()
'conftest.py':
@pytest.fixture
def basedriver(config, options):
driver = driver.Driver(config, options)
yield driver
print("Debug 1")
driver.webdriver.quit()
print("Debug 2")
Et lors de l'exécution du test, je ne peux voir que Debug 1
imprimé. L'ensemble du processus s'arrête à ce stade et ne semble pas continuer. L'ensemble du test au sélénium est bloqué au webdriver.quit)
.
Les tests, cependant, se sont bien déroulés ...
Quelles raisons pourraient être pour ce comportement?
Addenda:
La raison pour laquelle l'exécution se bloque semble être une fenêtre contextuelle demandant à l'utilisateur s'il souhaite quitter la page en raison de données non enregistrées. Cela signifie que la documentation de la méthode quit
est incorrecte. Il est dit:
Quits the driver and close every associated window.
Il s’agit d’un problème non trivial, pour lequel Sélénium agit vraiment comme un incohérent. Comme indiqué dans la documentation, la méthode quit
devrait simplement fermer la (les) fenêtre (s) du navigateur, mais ce n’est pas le cas. Au lieu de cela, vous obtenez une fenêtre contextuelle demandant à l'utilisateur s'il souhaite quitter la page:
Le problème, c’est que cette fenêtre n’apparaît qu’après que l’utilisateur a appelé
driver.quit()
Une façon de résoudre ce problème consiste à définir le profil suivant pour le pilote.
from Selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)
L’avertissement de fermeture est vrai par défaut dans Firefox comme vous pouvez le voir dans about:config
et vous pouvez les désactiver pour votre profil:
Et depuis,
La raison pour laquelle l'exécution se bloque semble être une fenêtre contextuelle qui demande à l'utilisateur S'il souhaite quitter la page en raison de données non sauvegardées.
Vous pouvez définir browser.tabs.warnOnClose
dans votre profil de configuration Firefox comme suit:
from Selenium import webdriver
profile = webdriver.FirefoxProfile()
profile.set_preference("browser.tabs.warnOnClose", False)
driver = webdriver.Firefox(firefox_profile = profile)
Vous pouvez regarder profile.DEFAULT_PREFERENCES
qui est le json à python/site-packages/Selenium/webdriver/firefox/webdriver_prefs.json
J'ai donc pu reproduire votre problème en utilisant l'exemple de fichier HTML ci-dessous.
<html>
<body>
Please enter a value for me: <input name="name" >
<script>
window.onbeforeunload = function(e) {
return 'Dialog text here.';
};
</script>
<h2>ask questions on exit</h2>
</body>
</html>
Ensuite, j'ai exécuté un exemple de script qui reproduit le blocage
from Selenium import webdriver
driver = webdriver.Firefox()
driver.get("http://localhost:8090/index.html")
driver.find_element_by_name("name").send_keys("Tarun")
driver.quit()
Cela pendre Selenium python indéfiniment. Ce qui n'est pas une bonne chose en tant que telle. Le problème est que window.onload
et window.onbeforeunload
sont difficiles à gérer pour Selenium en raison du cycle de vie du moment où ils se produisent. onload
se produit avant même que Selenium ait injecté son propre code pour supprimer alert
et confirm
à des fins de traitement. Je suis à peu près sûr que onbeforeunload
n'est pas non plus à la portée de Sélénium.
Donc, il y a plusieurs façons de se déplacer.
Changement d'application
Demandez aux développeurs de ne pas utiliser les événements onload
ou onbeforeunload
. Vont-ils écouter? Pas certain
Désactive beforeunload dans le profil
C'est ce que vous avez déjà posté dans votre réponse
from Selenium import webdriver
profile = webdriver.FirefoxProfile()
# other settings here
profile.set_preference("dom.disable_beforeunload", True)
driver = webdriver.Firefox(firefox_profile = profile)
Désactive les événements via le code
try:
driver.execute_script("window.onunload = null; window.onbeforeunload=null")
finally:
pass
driver.quit()
Cela ne fonctionnerait que si vous n'avez pas plusieurs onglets ouverts ou si l'onglet suppose de générer le popup est actif. Mais c’est un bon moyen générique de gérer cette situation
Ne pas laisser le sélénium pendre
Eh bien, la raison pour laquelle Selenium se bloque, c’est qu’il envoie une requête au pilote geckodriver, qui l’envoie ensuite au firefox; Mais le problème est que le pilote python Selenium ne définit aucun délai d’expiration pour cette connexion.
Pour résoudre le problème, il suffit d’ajouter deux lignes de code ci-dessous.
import socket
socket.setdefaulttimeout(10)
try:
driver.quit()
finally:
# Set to something higher you want
socket.setdefaulttimeout(60)
Mais le problème avec cette approche est que le pilote/navigateur ne sera toujours pas fermé. C'est là que vous avez besoin d'une approche encore plus robuste pour tuer le navigateur, comme indiqué dans la réponse ci-dessous.
En Python, comment vérifier si Selenium WebDriver a arrêté ou non?
Code du lien ci-dessus pour rendre la réponse complète
from Selenium import webdriver
import psutil
driver = webdriver.Firefox()
driver.get("http://tarunlalwani.com")
driver_process = psutil.Process(driver.service.process.pid)
if driver_process.is_running():
print ("driver is running")
firefox_process = driver_process.children()
if firefox_process:
firefox_process = firefox_process[0]
if firefox_process.is_running():
print("Firefox is still running, we can quit")
driver.quit()
else:
print("Firefox is dead, can't quit. Let's kill the driver")
firefox_process.kill()
else:
print("driver has died")
Pour les utilisateurs de ChromeDriver:
options = Options()
options.add_argument('no-sandbox')
driver.close()
driver.quit()
crédits à ... https://bugs.chromium.org/p/chromedriver/issues/detail?id=1135
D'après ce que j'ai compris, je vais essayer de répondre à deux questions fondamentales:
- Pourquoi l'échec de l'appel de la méthode
driver.webdriver.quit()
laisse-t-il le script en état de blocage/non réponse au lieu de générer une exception?- Pourquoi le test était-il toujours une réussite si le script n'a jamais terminé son cycle d'exécution?
Pour répondre à la première question, je vais essayer d’expliquer le Selenium Architecture , qui dissipera en grande partie nos doutes.
Alors, comment fonctionne Selenium Webdriver?
Chaque instruction ou commande que vous écrivez à l’aide de Selenium Client Library sera convertie en JSON Wire Protocol over http, qui sera à son tour transmise à nos pilotes de navigateur (chromedriver, geckodriver). Donc, fondamentalement, ces URL http générées (basées sur _ _ _ REST architecture) atteignent les pilotes du navigateur. Dans les pilotes de navigateur, il y a des serveurs http qui transmettent en interne les URL reçues à Real Browser (en tant que HTTP sur HTTP Server) pour lequel la réponse appropriée sera générée par le navigateur Web et renvoyée à Browser. Pilotes (comme HTTP sur HTTP Server) qui utilisera à son tour JSON Wire Protocol pour renvoyer la réponse à Selenium Client Library qui décidera finalement de la marche à suivre en fonction de la réponse atteint. Veuillez vous référer à l'image ci-jointe pour plus de précisions:
Revenant maintenant à la question où le script est en attente, nous pouvons simplement conclure que notre navigateur fonctionne toujours sur demande reçue. C’est pourquoi aucune réponse n’est renvoyée à Navigateur Pilote qui, à son tour, est parti Bibliothèque Selenium quit()
fonction en attente, c’est-à-dire en attente de l’achèvement du traitement de la demande.
Il existe donc diverses solutions de contournement que nous pouvons utiliser, dont l'une est déjà expliquée par Alex . Mais je crois qu’il existe un bien meilleur moyen de gérer de telles conditions, car Selenium du navigateur pourrait nous laisser en attente/gel pour d’autres cas aussi selon mon expérience personnelle, donc je préfère personnellement Approche de destruction du fil avec délai comme Selenium Object toujours s'exécute dans main() thread
. Nous pouvons allouer un temps spécifique au thread principal et tuer main() thread
si le délai de session expiré est atteint.
Passons maintenant à la deuxième question qui est:
Pourquoi le test était-il toujours une réussite si le script ne s'est jamais terminé, c'est un cycle d'exécution de ?
Eh bien, je n'ai pas beaucoup d'idée sur la façon dont fonctionne pytest
mais j'ai une idée de base sur le fonctionnement du moteur de test en fonction duquel je vais essayer de répondre à celui-ci.
Pour commencer, il est impossible qu'un scénario de test réussisse tant que le script complet n'est pas terminé. De nouveau, si vos scénarios de test réussissent, il pourrait y avoir très peu de scénarios possibles tels que:
[TestNG][4]
en Java: @AfterClass, @AfterTest, @AfterGroups, @AfterMethod, @AfterSuite), ce qui signifie que l'exécution de votre test est déjà terminée. Donc, cela pourrait être la raison pour laquelle les tests se révèlent être un succès.Je ne suis toujours pas sûr de la cause appropriée pour la deuxième raison. Je continuerai à chercher et à mettre à jour le message si je trouvais quelque chose.
@Alex : Pouvez-vous mettre à jour la question avec une meilleure compréhension, c'est-à-dire la conception de votre test actuelle que je peux explorer pour trouver une meilleure explication.
Le meilleur moyen de garantir que vous exécutez votre code de démontage dans pytest est de définir une fonction de finaliseur et de l’ajouter en tant que finaliseur à cet appareil. Cela garantit que même si quelque chose échoue avant la commande de rendement, vous obtenez toujours votre démontage.
Pour éviter qu'une fenêtre contextuelle ne raccroche votre démontage, investissez dans des commandes WebdriverWait.until qui expirent à tout moment. Une fenêtre contextuelle apparaît, le test ne peut pas continuer, le délai est écoulé, le démontage est appelé.