Wie finde ich ein vorhandenes HTML-Element mit Python-Selen in einer Jupyterhub-Seite?

Dec 10 2020

Ich habe das folgende Konstrukt in einer HTML-Seite und möchte das liElement (mit Python-Selen) auswählen :

<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>

Ich benutze den folgenden xpath:

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

Aber das Element scheint nicht gefunden zu sein.

Vollständiger, minimal funktionierender Beispielcode:

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()

Umgebung:

  • MacOS Mojave (10.14.6)
  • Python 3.8.6
  • Selen 3.8.0
  • Geckodriver 0.26.0

Nachtrag

Ich habe versucht, die Schritte mit dem Firefox-Add-On "Selenium IDE" aufzuzeichnen, das die folgenden Schritte für Python enthält:

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()

was natürlich auch nicht funktioniert. Mit diesen Codezeilen bekomme ich eine Fehlermeldung

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

Antworten

4 DebanjanB Dec 18 2020 at 02:37

Du warst nah genug. Tatsächlich hatte Ihr gesamtes Programm nur ein einziges Problem:

  • Das xpath_runall = "//li[@data-command='notebook:run-all-below']"sichtbare Element mit Text wird nicht eindeutig als Ausgewählte Zelle ausführen und Alle unten identifiziert, da das erste übereinstimmende Element ein verstecktes Element ist.

Weitere Überlegungen

Weitere Optimierungen:

  • Das als identifizierbare Element xpath = "//button[@title='Save the notebook contents and create checkpoint']"ist ein anklickbares Element. Also anstelle von EC, wie presence_of_element_located()Sie verwenden könnenelement_to_be_clickable()

  • Sobald das Element durch zurückgegeben EC als element_to_be_clickable()Sie können die aufrufen click()auf der gleichen Linie.

  • Der x-Pfad zum Identifizieren des Elements mit Text als " Ausgewählte Zelle ausführen" und "Alle unten" lautet :

    //li[@data-command='notebook:run-all-below']//div[@class='lm-Menu-itemLabel p-Menu-itemLabel' and text()='Run Selected Cell and All Below']
    
  • Da die Anwendung über JavaScript erstellt wird , müssen Sie ActionChains verwenden .


Lösung

Ihre optimierte Lösung lautet:

  • Codeblock:

    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")
    
  • Konsolenausgabe:

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

Das hat bei mir funktioniert. Ich finde den Menüpunkt der obersten Ebene mit vollem xpath und klicke dann darauf. Ich warte eine Weile, um sicherzustellen, dass das Popup-Menü angezeigt wird, und bewege dann mit einem Versatz zum ursprünglichen Menüpunkt, den ich zuvor festgelegt habe, die Maus auf diesen Versatz und klicke auf das, was ich als das richtige Untermenü kenne Menüpunkt. Im folgenden Code gebe ich mir zuerst die Möglichkeit, eine Zelle auszuwählen:

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()

Update: Eine optimalere Lösung

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

Es scheint, dass es zwei li-Elemente mit ähnlichen Attributen gibt. Sie müssen das richtige Element zum Klicken identifizieren. Verwenden Sie Folgendes, xpathum auf das richtige Element zu klicken.

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))

Konsolenausgabe:

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