Comment faire en sorte que Selenium Web Driver attende qu'un élément soit accessible, et pas seulement présent?
J'écris des tests pour une application web. Certaines commandes ouvrent des boîtes de dialogue contenant des contrôles visibles, mais indisponibles pendant quelques instants. (Ils sont grisés, mais WebDriver les voit toujours comme visibles).
Comment puis-je dire à Selenium d'attendre que l'élément soit réellement accessible, et pas seulement visible?
try:
print "about to look for element"
element = WebDriverWait(driver, 10).until(lambda driver : driver.find_element_by_id("createFolderCreateBtn"))
print "still looking?"
finally: print 'yowp'
Voici le code que j'ai essayé, mais il "voit" le bouton avant qu'il ne soit utilisable et se charge pratiquement au-delà de la supposée "attente".
Notez que je peux insérer 10 secondes de sommeil dans le code au lieu de cela et le code fonctionnera correctement, mais c'est moche, peu fiable et inefficace. Mais cela prouve que le problème est simplement que la commande "click" est en avance sur la disponibilité des contrôles.
print time.time()
try:
print "about to look for element"
def find(driver):
e = driver.find_element_by_id("createFolderCreateBtn")
if (e.get_attribute("disabled")=='true'):
return False
return e
element = WebDriverWait(driver, 10).until(find)
print "still looking?"
finally: print 'yowp'
print "ok, left the loop"
print time.time()
Voici ce que nous avons fini avec. (Merci à lukeis et RossPatterson.) Notez que nous devions trouver tous les éléments par identifiant, puis filtrer par "désactivé". J'aurais préféré un seul modèle de recherche, mais que pouvez-vous faire?
Je suppose que la chronologie des événements va comme ceci:
- il n'y a pas d'éléments nécessaires sur la page.
- l'élément nécessaire apparaît, mais est désactivé:
<input type="button" id="createFolderCreateBtn" disabled="disabled" />
- l'élément nécessaire devient activé:
<input type="button" id="createFolderCreateBtn" />
Actuellement, vous recherchez élément par identifiant et vous en trouvez un à l'étape 2, qui est antérieure à celle dont vous avez besoin. Ce que vous devez faire, c'est de le rechercher par xpath:
//input[@id="createFolderCreateBtn" and not(@disabled)]
Voici la différence:
from lxml import etree
html = """
<input type="button" id="createFolderCreateBtn" disabled="disabled" />
<input type="button" id="createFolderCreateBtn" />
"""
tree = etree.fromstring(html, parser=etree.HTMLParser())
tree.xpath('//input[@id="createFolderCreateBtn"]')
# returns both elements:
# [<Element input at 102a73680>, <Element input at 102a73578>]
tree.xpath('//input[@id="createFolderCreateBtn" and not(@disabled)]')
# returns single element:
# [<Element input at 102a73578>]
Pour terminer, voici votre code fixe:
try:
print "about to look for element"
element_xpath = '//input[@id="createFolderCreateBtn" and not(@disabled)]'
element = WebDriverWait(driver, 10).until(
lambda driver : driver.find_element_by_xpath(element_xpath)
)
print "still looking?"
finally:
print 'yowp'
METTRE À JOUR:
Repast le même avec le webdriver réel.
Voici le code de la page example.html
:
<input type="button" id="createFolderCreateBtn" disabled="disabled" />
<input type="button" id="createFolderCreateBtn" />
Voici la session ipython:
In [1]: from Selenium.webdriver import Firefox
In [2]: browser = Firefox()
In [3]: browser.get('file:///tmp/example.html')
In [4]: browser.find_elements_by_xpath('//input[@id="createFolderCreateBtn"]')
Out[4]:
[<Selenium.webdriver.remote.webelement.WebElement at 0x103f75110>,
<Selenium.webdriver.remote.webelement.WebElement at 0x103f75150>]
In [5]: browser.find_elements_by_xpath('//input[@id="createFolderCreateBtn" and not(@disabled)]')
Out[5]:
[<Selenium.webdriver.remote.webelement.WebElement at 0x103f75290>]
MISE À JOUR 2:
Cela fonctionne aussi avec ceci:
<input type="button" id="createFolderCreateBtn" disabled />
Il y a déjà quelques bonnes réponses publiées ici, mais j'ai pensé ajouter ma solution. Attente explicite, etc. sont d'excellentes fonctions pour les tests avec Selenium. Cependant, l'attente explicite remplit simplement la fonction d'une Thread.Sleep()
que vous ne pouvez définir qu'une seule fois. La fonction ci-dessous correspond à ce que j’avais l'habitude de "raser" quelques minutes. Il attend que l'élément soit "accessible".
//ALTERNATIVE FOR THREAD.SLEEP
public static class Wait
{
//public static void wait(this IWebDriver driver, List<IWebElement> IWebElementLIst)
public static void wait(this IWebDriver driver, By bylocator)
{
bool elementPresent = IsPresent.isPresent(driver, bylocator);
while (elementPresent != true)
{
Thread.Sleep(1000);
elementPresent = IsPresent.isPresent(driver, bylocator);
}
}
}
C'est en C #, mais l'adapter ne serait pas si difficile. J'espère que cela t'aides.
Je pense que quelque chose dans ce sens devrait également fonctionner:
from Selenium import webdriver
from Selenium.webdriver.common.by import By
from Selenium.webdriver.support.ui import WebDriverWait
from Selenium.webdriver.support import expected_conditions
browser = webdriver.Firefox()
wait = WebDriverWait(browser, 30)
wait.until(expected_conditions.presence_of_element_located((By.XPATH, "//*[@id='createFolderCreateBrn' and not(@disabled)]")))