Comment trouver un élément HTML existant avec python-sélénium dans une page jupyterhub?

Dec 10 2020

J'ai la construction suivante dans une page HTML et je veux sélectionner l' liélément (avec python-sélénium):

<li class="p-Menu-item p-mod-disabled" data-type="command" data-command="notebook:run-all-below">
    <div class="p-Menu-itemIcon"></div>
    <div class="p-Menu-itemLabel" style="">Run Selected Cell and All Below</div>
    <div class="p-Menu-itemShortcut" style=""></div>
    <div class="p-Menu-itemSubmenuIcon"></div>
</li>

J'utilise le xpath suivant:

//li[@data-command='notebook:run-all-below']

Mais l'élément ne semble pas être trouvé.

Exemple de code de travail complet et minimal:

import time
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 as EC

driver = webdriver.Firefox()
driver.get("https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab/tree/demo")

# Wait for the page to be loaded
xpath = "//button[@title='Save the notebook contents and create checkpoint']"
element = WebDriverWait(driver, 600).until(
    EC.presence_of_element_located((By.XPATH, xpath))
)
time.sleep(10)
print("Page loaded")

# Find and click on menu "Run"
xpath_run = "//div[text()='Run']"
element = WebDriverWait(driver, 60).until(
    EC.element_to_be_clickable((By.XPATH, xpath_run))
)
element.click()
print("Clicked on 'Run'")

# Find and click on menu entry "Run Selected Cell and All Below"
xpath_runall = "//li[@data-command='notebook:run-all-below']"
element = WebDriverWait(driver, 600).until(
    EC.element_to_be_clickable((By.XPATH, xpath_runall))
)
print("Found element 'Run Selected Cell and All Below'")
element.click()
print("Clicked on 'Run Selected Cell and All Below'")

driver.close()

Environnement:

  • MacOS Mojave (10.14.6)
  • python 3.8.6
  • sélénium 3.8.0
  • geckodriver 0.26.0

Addenda

J'ai essayé d'enregistrer les étapes avec le module complémentaire Firefox "Selenium IDE" qui donne les étapes suivantes pour python:

sdriver.get("https://hub.gke2.mybinder.org/user/jupyterlab-jupyterlab-demo-y0bp97e4/lab/tree/demo")
driver.set_window_size(1650, 916)
driver.execute_script("window.scrollTo(0,0)")
driver.find_element(By.CSS_SELECTOR, ".lm-mod-active > .lm-MenuBar-itemLabel").click()

ce qui, bien sûr, ne fonctionne pas non plus. Avec ces lignes de code, j'obtiens une erreur

selenium.common.exceptions.NoSuchElementException: Message: Unable to locate element: .lm-mod-active > .lm-MenuBar-itemLabel

Réponses

4 DebanjanB Dec 18 2020 at 02:37

Vous étiez assez proche. En fait, l'ensemble de votre programme n'avait qu'un seul problème, comme suit:

  • Le xpath_runall = "//li[@data-command='notebook:run-all-below']"n'identifie pas l'élément visible avec du texte comme Exécuter la cellule sélectionnée et Tout en dessous de manière unique car le premier élément correspondant est un élément masqué .

Considérations supplémentaires

Quelques optimisations supplémentaires:

  • L'élément identifié comme xpath = "//button[@title='Save the notebook contents and create checkpoint']"est un élément cliquable . Donc au lieu de EC comme presence_of_element_located()vous pouvez utiliserelement_to_be_clickable()

  • Une fois que l'élément est renvoyé via EC , element_to_be_clickable()vous pouvez appeler le click()sur la même ligne.

  • Le xpath pour identifier l'élément avec du texte comme Exécuter la cellule sélectionnée et Tout ci - dessous serait:

    //li[@data-command='notebook:run-all-below']//div[@class='lm-Menu-itemLabel p-Menu-itemLabel' and text()='Run Selected Cell and All Below']
    
  • Comme l' application est construite via JavaScript, vous devez utiliser ActionChains .


Solution

Votre solution optimisée sera:

  • Bloc de code:

    from selenium import webdriver
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.action_chains import ActionChains
    
    driver = webdriver.Firefox(executable_path=r'C:\WebDrivers\geckodriver.exe')
    driver.get("https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab/tree/demo")
    WebDriverWait(driver, 60).until(EC.element_to_be_clickable((By.XPATH, "//button[@title='Save the notebook contents and create checkpoint']")))
    print("Page loaded")
    WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//div[text()='Run']"))).click()
    print("Clicked on Run")
    element = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "//li[@data-command='notebook:run-all-below']//div[@class='lm-Menu-itemLabel p-Menu-itemLabel' and text()='Run Selected Cell and All Below']")))
    ActionChains(driver).move_to_element(element).click(element).perform()
    print("Clicked on Run Selected Cell and All Below")
    
  • Sortie de la console:

    Page loaded
    Clicked on Run
    Clicked on Run Selected Cell and All Below
    
3 Booboo Dec 17 2020 at 23:21

Cela a fonctionné pour moi. Je trouve l'élément de menu de niveau supérieur en utilisant xpath complet, puis je clique dessus. J'attends un peu de temps pour m'assurer que le menu contextuel est apparu, puis en utilisant un décalage de l'élément de menu d'origine que j'ai prédéterminé, je déplace la souris vers ce décalage et clique sur ce que je sais être le sous-menu correct. élément du menu. Dans le code ci-dessous, je me donne d'abord une chance de sélectionner une cellule:

driver.implicitly_wait(300) # wait up to 300 seconds before calls to find elements time out
driver.get('https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab/tree/demo')
driver.execute_script("scroll(0, 0);")
elem = driver.find_element_by_xpath('//div[text()="Run"]')
elem.click() # click on top-level menu item
time.sleep(.2) # wait for sub-menu to appear
action = webdriver.common.action_chains.ActionChains(driver)
action.move_to_element_with_offset(elem, 224, 182)
# click on sub-menu item:
action.click()
action.perform()

Mise à jour: une solution plus optimale

driver.implicitly_wait(300) # wait up to 300 seconds before calls to find elements time out
driver.get('https://mybinder.org/v2/gh/jupyterlab/jupyterlab-demo/master?urlpath=lab/tree/demo')
driver.execute_script("scroll(0, 0);")
elem = driver.find_element_by_xpath('//div[text()="Run"]')
elem.click()
driver.implicitly_wait(.2)
elem2 = driver.find_element_by_xpath('//*[contains(text(),"Run Selected Cell and All Below")]')
driver.execute_script("arguments[0].click();", elem2) # sub-menu, however, stays open
# to close the sub-menu menu:
elem.click()
1 KunduK Dec 10 2020 at 19:58

Il semble qu'il existe deux éléments li avec des attributs similaires. Vous devez identifier le bon élément sur lequel xpathcliquer. Utilisez ce qui suit pour cliquer sur le bon élément.

xpath_runall = "//ul[@class='lm-Menu-content p-Menu-content']//li[@data-command='notebook:run-all-below']"
element = WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.XPATH, xpath_runall))
)
elementText=element.text
print("Found element '{}'".format(elementText))
element.click()
print("Clicked on '{}'".format(elementText))

Sortie de la console:

Page loaded
Clicked on 'Run'
Found element 'Run Selected Cell and All Below'
Clicked on 'Run Selected Cell and All Below'