How to find_element_by_link_text while having: NoSuchElement Exception?

前端 未结 4 1149
独厮守ぢ
独厮守ぢ 2021-01-14 22:31

This question has been asked over and over again - and in-spite of trying all the hacks I still can\'t seem to figure out what\'s wrong.

I tried increasing t

4条回答
  •  萌比男神i
    2021-01-14 22:43

    NoSuchElementException is thrown when the element could not be found.

    If you encounter this exception, please check the followings:

    • Check your selector used in your find_by...
    • Element may not yet be on the screen at the time of the find operation.

    If webpage is still loading, check for selenium.webdriver.support.wait.WebDriverWait() and write a wait wrapper to wait for an element to appear.

    Troubleshooting and code samples

    You can add breakpoint just before your failing line pdb.set_trace() (don't forget to import pdb), then run your test and once your debugger stops, then do the following tests.

    1. You could try:

      driver.find_element_by_xpath(u'//a[text()="Foo text"]')
      

      instead. This is more reliable test, so if this would work, use it instead.

    2. If above won't help, please check if your page has been loaded properly via:

      (Pdb) driver.execute_script("return document.readyState")
      'complete'
      

      Sometimes when the page is not loaded, you're actually fetching the elements from the old page. But even though, readyState could still indicate the state of the old page (especially when using click()). Here is how this is explained in this blog:

      Since Selenium webdriver has become more advanced, clicks are much more like "real" clicks, which has the benefit of making our tests more realistic, but it also means it's hard for Selenium to be able to track the impact that a click has on the browsers' internals -- it might try to poll the browser for its page-loaded status immediately after clicking, but that's open to a race condition where the browser was multitasking, hasn't quite got round to dealing with the click yet, and it gives you the .readyState of the old page.

    3. If you think this is happening because the page wasn't loaded properly, the "recommended" (however still ugly) solution is an explicit wait:

      from selenium.webdriver.common.by import By
      from selenium.webdriver.support.ui import WebDriverWait 
      from selenium.webdriver.support import expected_conditions
      
      old_value = browser.find_element_by_id('thing-on-old-page').text
      browser.find_element_by_link_text('my link').click()
      WebDriverWait(browser, 3).until(
          expected_conditions.text_to_be_present_in_element(
              (By.ID, 'thing-on-new-page'),
              'expected new text'
          )
      )
      

      The naive attempt would be something like this:

      def wait_for(condition_function):
          start_time = time.time()
          while time.time() < start_time + 3:
              if condition_function():
                  return True
              else:
                  time.sleep(0.1)
          raise Exception(
              'Timeout waiting for {}'.format(condition_function.__name__)
          )
      
      
      def click_through_to_new_page(link_text):
          browser.find_element_by_link_text('my link').click()
      
          def page_has_loaded():
              page_state = browser.execute_script(
                  'return document.readyState;'
              ) 
              return page_state == 'complete'
      
          wait_for(page_has_loaded)
      

      Another, better one would be (credits to @ThomasMarks):

      def click_through_to_new_page(link_text):
          link = browser.find_element_by_link_text('my link')
          link.click()
      
          def link_has_gone_stale():
              try:
                  # poll the link with an arbitrary call
                  link.find_elements_by_id('doesnt-matter') 
                  return False
              except StaleElementReferenceException:
                  return True
      
          wait_for(link_has_gone_stale)
      

      And the final example includes comparing page ids as below (which could be bulletproof):

      class wait_for_page_load(object):
      
          def __init__(self, browser):
              self.browser = browser
      
          def __enter__(self):
              self.old_page = self.browser.find_element_by_tag_name('html')
      
          def page_has_loaded(self):
              new_page = self.browser.find_element_by_tag_name('html')
              return new_page.id != self.old_page.id
      
          def __exit__(self, *_):
              wait_for(self.page_has_loaded)
      

      And now we can do:

      with wait_for_page_load(browser):
          browser.find_element_by_link_text('my link').click()
      

      Above code samples are from Harry's blog.

    4. Here is the version proposed by Tommy Beadle (by using staleness approach):

      import contextlib
      from selenium.webdriver import Remote
      from selenium.webdriver.support.ui import WebDriverWait
      from selenium.webdriver.support.expected_conditions import staleness_of
      
      class MyRemote(Remote):
          @contextlib.contextmanager
          def wait_for_page_load(self, timeout=30):
              old_page = self.find_element_by_tag_name('html')
              yield
              WebDriverWait(self, timeout).until(staleness_of(old_page))
      
    5. If you think it isn't about page load, double check if your element isn't in iframe or different window. If so, you've to switch to it first. To check list of available windows, run: driver.window_handles.

提交回复
热议问题