Recherche d'un moyen unifié d'attendre et d'obtenir les changements d'état de Selenium pour les éléments Python

introduction

Selenium fournit des ʻexpected_conditions` pour examiner les changements d'état des éléments. Mais,

De nombreux aspects sont difficiles à utiliser. Le but de cet article est d'essayer de s'en débarrasser.

Lecteur supposé

Il est destiné à ceux qui ont utilisé du sélénium pour le moment. Cependant, ce n'est pas pour les utilisateurs avancés. Pour ceux qui savent ce qu'est XPATH ou find_element ().

environnement

Python 3.8.3 selenium 3.141.0 geckodriver v0.26.0 Firefox 77.0.1 (64 bits)

Source du résultat

Pour le moment, seule la source du résultat est affichée sans explication. (Bien que ce soit Python, ce n'est pas snake_case mais camelCase, il semble donc qu'une pierre puisse être lancée) Je ne l'ai pas complètement testé car il y a des branches conditionnelles que je n'ai pas utilisées.

De plus, dans cet article, nous partirons du principe que le module présenté dans la source suivante est ʻimport`.

python


import logging

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import (UnexpectedAlertPresentException, NoAlertPresentException,
                                        ElementNotVisibleException, TimeoutException, NoSuchElementException)

logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())

class CheckState():

  def __init__(self, locator=(), element=None, state="enabled"):
    self.locator = locator
    self.element = element
    self.state = state

  def __call__(self, driver):
    try:
      if self.element is not None and self.locator == ():
        element = self.element
      elif self.element is not None and self.locator != ():
        element = self.element.find_element(*self.locator)
      elif self.locator != ():
        element = driver.find_element(*self.locator)
      else:
        return False

      if self.state == "enabled":
        return element if element.is_enabled() == True else False
      elif self.state == "disabled":
        return element if element.is_enabled() == False else False
      elif self.state == "selected":
        return element if element.is_selected() == True else False
      elif self.state == "unselected":
        return element if element.is_selected() == False else False
      elif self.state == "displayed":
        return element if element.is_displayed() == True else False
      elif self.state == "undisplayed":
        return element if element.is_displayed() == False else False
      elif self.state == "clickable":
        if element.is_enabled() == False:
          return False
        return element if element.is_displayed() == True else False
      else:
        return False
    except Exception as e:
      logger.debug(f"CheckState: {type(e)}, {e}, {self.locator}, {self.element}, {self.state}")
      return False


def findElement(driver, locator=(), element=None, state="enabled", must=True, wait=30, interval=0.5, ignore=False):
  try:
    if element is None and locator == ():
      raise ValueError

    driverWait = WebDriverWait(driver, wait, interval)
    return driverWait.until(CheckState(locator=locator, element=element, state=state))

  except TimeoutException:
    if must == True and ignore == False:
      logger.error(f"findElement: {locator}, {element}, {state}, {must}, {wait}, {interval}, {ignore}")
      raise ValueError
    return None
  except Exception as e:
    if ignore == True:
      return None
    logger.error(f"findElement: {type(e)}, {e}")
    raise e


def isDriver(driver):
  if isinstance(driver, webdriver.remote.webdriver.WebDriver):
    return True
  return False

Une fonction qui vérifie l'état actuel d'un élément

Par exemple element = driver.find_element(by, value) Si vous obtenez l'élément par

--ʻElement.is_enabled () : S'il est ʻenabled --ʻElement.is_displayed () : s'il est affiché à l'écran --ʻElement.is_selected () : s'il est sélectionné --ʻElement.get_attribute (name) : Récupère l'attribut ou la propriété --ʻElement.get_property (name) : Récupère la propriété --ʻElement.value_of_css_property (property_name) `: Récupère la valeur de la propriété CSS

Vous pouvez vérifier l'état avec. Dans cet article, je voudrais considérer le plus simple ʻelement.is_enabled () , ʻelement.is_displayed (), ʻelement.is_selected () `.

Fonction ʻExpected_conditions` pour vérifier les changements d'état

Dans ʻexpected_conditions`,

Les fonctions suivantes sont fournies pour vérifier le changement d'état de.

--ʻEC.element_to_be_selected (element) : Si ʻelement est ʻis_selected () == True --ʻEC.element_located_to_be_selected (locator): Si l'élément indiqué par locator est ʻis_selected () == True --ʻEC.element_selection_state_to_be (element, is_selected): Si ʻelement est ʻis_selected () == is_selected --ʻEC.element_located_selection_state_to_be (locator, is_selected) : ʻis_selected () == is_selected --ʻEC.visibility_of (element) : Si ʻelement est ʻis_displayed () == True --ʻEC.visibility_of_element_located (locator): Si l'élément indiqué par locator est ʻis_displayed () == True --Si ʻEC.invisibility_of_element (element): ʻelement est ʻis_displayed () == False --ʻEC.invisibility_of_element_located (locator) : Si l'élément indiqué par locator est ʻis_displayed () == False

L'argument ici est

Ce sera. D'autre part

La fonction à vérifier uniquement n'est pas préparée et peut être remplacée

Ce sera. Cependant, cliquable ne sert souvent pas le but car il regarde ʻelement.is_enabled () et element.is_displayed ()`.

Comment utiliser

En général, la fonction de ʻexpected_conditions` est

python


driver = webdriver.Firefox(executable_path=path, options=options, service_log_path="nul")
driver.get(url)

locator = (By.XPATH, "//div[@id='cat']")
element = WebDriverWait(driver, 30, 1).until(EC.visibility_of_element_located(locator))

À utiliser en combinaison avec WebDriverWait (). Jusqu'à () etc. Regardons cela de plus près.

Expected conditions

Le premier est ʻEC.visibility_of_element_located () `. Cela peut être fait comme suit

python


func = EC.visibility_of_element_located(locator)
element = func(driver)

func renvoie une fonction qui prend un argument (en supposant driver). Ensuite, lorsque vous passez «driver» à «func» et que vous l'exécutez, si l'élément indiqué par «locator» est «affiché», cet élément est renvoyé, et s'il n'est pas «affiché», «False» est renvoyé. En d'autres termes, func est une fonction comme find_element (locator) qui ne lève pas d'exception en cas d'échec.

De plus, find_element () peut être modifié comme suit:

python


relativeLocator = (By.XPATH, "./div[@class='meow']") #Chemin relatif
child = element.find_element(*relativeLocator)

Vous pouvez également obtenir l'élément (child) sous ʻelement. Quand j'essaye de faire quelque chose de similaire avec ʻEC.element_to_be_clickable, j'obtiens:

python


child = EC.visibility_of_element_located(relativeLocator)(element)

Il semble que d'autres fonctions ʻexpected_conditionspeuvent également obtenir des éléments de chemins relatifs. Cependant, quand je lis l'explication (trouvée), il semble que je suppose le chemin absolu dedriver`. Regarder la source sur GitHub semble bien, mais je suis un peu inquiet. Par conséquent, je voudrais fournir d'autres moyens pour traiter des chemins relatifs.

WebDriverWait

Revenons un peu en arrière et regardons WebDriverWait (). Until ().

WebDriverWait () prend ce qui suit comme arguments (un omis):

-- pilote: En supposant pilote --timeout: temps d'attente maximum --poll_frequency: Intervalle d'essai

Et wait.until () prend un argument comme décrit ci-dessous.

--method: Une fonction qui prend un pilote comme argument. Cette fonction renvoie False en cas d'échec et non-False` en cas de succès

Si vous écrivez comme suit,

python


timeout = 30
poll_frequency = 1
wait = WebDriverWait(driver, timeout, poll_frequency)
element = wait.until(method)

Le comportement de wait.until (method) est

  1. Le contenu de la variable «driver» (généralement supposée être une instance de «driver») est passé à «method».
  2. Continuez à courir toutes les 1 seconde pendant 30 secondes jusqu'à ce que method (driver) réussisse (renvoie autre chose que False).
  3. Si method (driver) réussit, sa valeur de retour est renvoyée.
  4. Si cela échoue après 30 secondes, une exception sera levée.

Ce sera.

combinaison

D'après l'explication ci-dessus, si vous écrivez comme suit,

python


locator = (By.XPATH, "//div[@id='cat']")
element = WebDriverWait(driver, 30).until(EC.visibility_of_element_located(locator))

Si l'élément indiqué par «locator» existe et est «affiché», cet élément est affecté à «élément». Si l'élément n'est pas trouvé ou devient "affiché" après 30 secondes, une exception sera levée. Comme vous l'avez peut-être remarqué, vous pouvez obtenir l'élément relatif de ʻelement` en procédant comme suit.

python


relativeLocator = (By.XPATH, "./div[@class='meow']") #Chemin relatif
child = WebDriverWait(element, 30).until(EC.visibility_of_element_located(relativeLocator))

Correspond à ʻenabled/disabled`

ʻExpected_conditions n'a pas de fonction correspondant à ʻenabled (disabled), mais il est facile d'en créer une qui la supporte. En supposant qu'il est appelé depuis WebDriverWait (). Jusqu'à ()

--Une fonction qui prend un pilote comme argument. Cette fonction renvoie False en cas d'échec et non-False` en cas de succès

Vous pouvez voir que vous devez créer la fonction. Cependant, s'il s'agit d'une fonction, vous ne pouvez passer driver que si vous utilisez une variable globale, donc vous allez créer Class. La façon la plus simple de le faire est la suivante.

python


class IsEnabled():

  def __init__(self, locator=(), state=True):
    self.locator = locator
    self.state = state

  def __call__(self, driver):
    try:
      if self.locator == ():
        return False
      element = driver.find_element(*self.locator)
      return element if element.is_enabled() == self.state else False

    except Exception as e:
      return False

Il peut être utilisé comme suit:

python


locator = (By.XPATH, "//div[@id='cat']")
element = WebDriverWait(driver, 30, 1).until(IsEnabled(locator))

Il existe deux types de ʻexpected_conditions, l'un qui prend locator et l'autre qui prend ʻelement. Cependant, celui que vous avez créé doit spécifier locator. Et il ne prend pas en charge les chemins relatifs. Je vais essayer de l'améliorer afin qu'il puisse gérer ces derniers.

python


class IsEnabled():

  def __init__(self, locator=(), element=None, state=True):
    self.locator = locator
    self.element = element
    self.state = state

  def __call__(self, driver):
    try:
      if self.element is not None and self.locator == ():
        element = self.element
      elif self.element is not None and self.locator != ():
        element = self.element.find_element(*self.locator)
      elif self.locator != ():
        element = driver.find_element(*self.locator)
      else:
        return False

      return element if element.is_enabled() == state else False

    except Exception as e:
      return False

En faisant cela, vous pouvez l'utiliser comme suit.

python


#Obtenez l'élément indiqué par le localisateur lorsqu'il est activé
element = WebDriverWait(driver, 30, 1).until(IsEnabled(locator=locator))

#Renvoie l'élément lorsque l'élément est activé
element = WebDriverWait(driver, 30, 1).until(IsEnabled(element=element))

#Obtenir lorsque l'élément relatif de l'élément devient activé
child = WebDriverWait(driver, 30, 1).until(IsEnabled(element=element, locator=relativeLocator))

Le «CheckState» affiché au début correspond à un autre état.

Préparez une fonction qui se transforme en find_element ()

Vous pouvez maintenant obtenir l'élément avec la même description, comme find_element (). De plus, vous pouvez l'obtenir en regardant le changement d'état. Cependant, il est difficile d'écrire ce qui suit à chaque fois, et cela peut être déroutant lorsqu'il est utilisé en combinaison avec find_element ().

python


element = WebDriverWait(driver, 30, 1).until(CheckState(element=element, state="clickable"))

Nous définissons donc une fonction wrapper qui change find_element (). Personnellement, il ne semble pas que j'obtienne l'élément du nom de la fonction.

Je pense que c'est aussi un problème que vous créez trop de fonctions de wrapper et que vous ne savez pas ce que vous utilisez.

python


def findElement(driver, locator=(), element=None, state="enabled", must=True, wait=30, interval=0.5, ignore=False):
  try:
    if element is None and locator == ():
      raise ValueError

    driverWait = WebDriverWait(driver, wait, interval)
    return driverWait.until(CheckState(locator=locator, element=element, state=state))

  except TimeoutException:
    if must == True and ignore == False:
      logger.error(f"findElement: {locator}, {element}, {state}, {must}, {wait}, {interval}, {ignore}")
      raise ValueError
    return None
  except Exception as e:
    if ignore == True:
      return None
    logger.error(f"findElement: {type(e)}, {e}")
    raise e

Il est utilisé comme suit.

python


locator = (By.XPATH, "//div[@id='cat']")
element = findElement(driver, locator=locator):

finalement

Maintenant, je pense que vous pouvez l'écrire proprement. Cependant, en fonction du comportement du site, il est difficile de faire divers ajustements. Même si cela a fonctionné jusqu'à présent, il arrive souvent que le site soit lent et trébuche dans des endroits inattendus. Après tout, je ne peux pas abandonner time.sleep () Il y a une histoire dans laquelle je ne suis pas doué (´ ・ ω ・ `)

Recommended Posts

Recherche d'un moyen unifié d'attendre et d'obtenir les changements d'état de Selenium pour les éléments Python
Un moyen simple d'éviter plusieurs boucles for en Python
Un moyen standard de développer et de distribuer des packages en Python
Voulez-vous attendre un usage général avec Python Selenium?
Comment obtenir stacktrace en python
Obtenez un jeton pour conoha avec python
Attendez qu'une autre fenêtre s'ouvre dans Selenium
Une manière intelligente de chronométrer le traitement avec Python
Recherche d'un moyen efficace d'écrire un Dockerfile avec Python avec de la poésie
[Mac] Un moyen très simple d'exécuter des commandes système en Python et de générer les résultats
Comment échanger des éléments dans un tableau en Python et comment inverser un tableau.
Obtenez le nombre d'éléments spécifiques dans la liste python
Développer une bibliothèque pour obtenir la liste des collections Kindle en Python
Comment définir plusieurs variables dans une instruction Python for
Conseils pour coder courts et faciles à lire en Python
Obtenez de manière récursive la liste Excel dans un dossier spécifique avec python et écrivez-la dans Excel.
J'ai essayé "Comment obtenir une méthode décorée en Python"
Astuces utiles liées à la liste et aux instructions en Python
Comment obtenir la dernière (dernière) valeur d'une liste en Python
Comment obtenir une liste d'exceptions intégrées pour python
J'ai aussi essayé d'imiter la fonction monade et la monade d'état avec le générateur en Python
Introduction d'une bonne méthode de gestion des connexions DB en Python
Essayez simplement de recevoir un webhook avec ngrok et Python
Un moyen simple de visualiser le temps pris en Python et un moyen plus intelligent de l'améliorer
Comment déterminer l'existence d'un élément sélénium en Python
Comment comparer des listes et récupérer des éléments communs dans une liste
Que faire si vous obtenez moins zéro en Python
Comment obtenir une chaîne à partir d'un argument de ligne de commande en python
[Introduction à Python] Comment utiliser l'opérateur in dans l'instruction for?
J'ai essayé de faire un processus d'exécution périodique avec Selenium et Python
Conseils d'utilisation de Selenium et Headless Chrome dans un environnement CUI
Comment afficher les octets de la même manière en Java et Python
Affectations et modifications des objets Python
Sélénium et python pour ouvrir Google
[Python] Comment créer une liste de types de dictionnaire, ajouter / modifier / supprimer des éléments et extraire avec une instruction for
Python VBA pour obtenir une capture de la page WEB entière avec Selenium
Comment obtenir un nom de colonne et un nom d'index spécifiques avec Pandas DataFrame
Cliquez sur les liens Selenium afin d'obtenir les éléments des pages individuelles
Comment obtenir la valeur du magasin de paramètres dans lambda (en utilisant python)
Comment mettre un espace demi-largeur avant les lettres et les chiffres en Python.
Connectez-vous à postgreSQL depuis Python et utilisez des procédures stockées dans une boucle.
Implémentation du filtre à particules par Python et application au modèle d'espace d'états
Comment arrêter le programme jusqu'à une date et une heure spécifiques en python
Construisez un serveur léger en Python et écoutez les extensions HTTP de Scratch 2
sélénium: attendre l'élément avec ET / OU
Un moyen simple d'utiliser Wikipedia avec Python
Comment utiliser is et == en Python
J'ai essayé de faire un processus périodique avec CentOS7, Selenium, Python et Chrome
Étapes pour mettre dlib dans Python Tools pour Visual Studio et s'amuser
Conseils pour ceux qui ne savent pas comment utiliser is et == en Python
Comment compter le nombre d'éléments dans Django et sortir dans le modèle
Utilisez libsixel pour générer Sixel en Python et générer le graphe Matplotlib vers le terminal.
Pour ceux qui veulent apprendre Excel VBA et se lancer avec Python
J'ai recherché les compétences nécessaires pour devenir ingénieur web avec Python
Script Python pour obtenir une liste d'exemples d'entrée pour le concours AtCoder