Jak znaleźć istniejący element HTML z python-selenium na stronie jupyterhub?

Dec 10 2020

Mam następującą konstrukcję na stronie HTML i chcę wybrać lielement (z python-selenium):

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

Używam następującej ścieżki XP:

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

Ale element nie wydaje się być znaleziony.

Kompletny, minimalny działający przykładowy kod:

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

Środowisko:

  • MacOS Mojave (10.14.6)
  • python 3.8.6
  • selen 3.8.0
  • geckodriver 0.26.0

Uzupełnienie

Próbowałem zarejestrować kroki za pomocą dodatku „Selenium IDE” do przeglądarki Firefox, który zawiera następujące kroki dla języka 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()

co oczywiście też nie działa. Z tymi liniami kodu pojawia się błąd

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

Odpowiedzi

4 DebanjanB Dec 18 2020 at 02:37

Byłeś wystarczająco blisko. W rzeczywistości cały Twój program miał tylko jeden następujący problem:

  • xpath_runall = "//li[@data-command='notebook:run-all-below']"Nie identyfikuje widoczny element z tekstu jako Run zaznaczonej komórki i Wszystkie poniższe jednoznacznie jako pierwszy dopasowany element jest ukryty elementem.

Dodatkowe uwagi

Kilka dodatkowych optymalizacji:

  • Element określony jako xpath = "//button[@title='Save the notebook contents and create checkpoint']"jest elementem klikalnym . Więc zamiast EC, jak presence_of_element_located()możesz użyćelement_to_be_clickable()

  • Gdy element jest zwrócony przez KE , jak element_to_be_clickable()można wywołać click()na tej samej linii.

  • Ścieżka xpath do identyfikacji elementu z tekstem jako Uruchom wybraną komórkę i Wszystkie poniżej wyglądałaby następująco:

    //li[@data-command='notebook:run-all-below']//div[@class='lm-Menu-itemLabel p-Menu-itemLabel' and text()='Run Selected Cell and All Below']
    
  • Ponieważ aplikacja jest zbudowana za pomocą JavaScript , musisz użyć ActionChains .


Rozwiązanie

Twoje zoptymalizowane rozwiązanie będzie:

  • Blok kodu:

    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")
    
  • Wyjście konsoli:

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

To zadziałało dla mnie. Znajduję pozycję menu najwyższego poziomu przy użyciu pełnej ścieżki xpath, a następnie ją klikam. Czekam chwilę, aby upewnić się, że pojawiło się menu podręczne, a następnie używając przesunięcia w stosunku do pierwotnego elementu menu, który wcześniej ustaliłem, przesuwam mysz do tego przesunięcia i klikam to, co wiem, że jest poprawne pod- pozycja w menu. W poniższym kodzie najpierw daję sobie szansę wybrania komórki:

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

Aktualizacja: bardziej optymalne rozwiązanie

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

Wygląda na to, że istnieją dwa elementy li o podobnych atrybutach. Musisz zidentyfikować właściwy element do kliknięcia. Użyj poniższego, xpathaby kliknąć właściwy element.

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

Wyjście konsoli:

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