如何正确等待python-selenium中的框架可用?

时间:2017-12-14 11:10:17

标签: python selenium selenium-webdriver

我需要测试一个相当复杂的网页设置,包含嵌套框架。

在实际问题中,selenium代码正在加载包含框架的新网页内容,我想切换到该框架。为了避免任何明确的等待,我尝试了以下代码片段:

self.driver.switch_to_default_content()
WebDriverWait(self.driver, 300).\
        until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame1')))
WebDriverWait(self.driver, 300).\
        until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame2')))

但是,此代码段始终失败并导致以下错误:

  ...
  File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/wait.py", line 71, in until
    value = method(self._driver)
  File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/expected_conditions.py", line 247, in __call__
    self.frame_locator))
  File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/expected_conditions.py", line 402, in _find_element
    raise e
WebDriverException: Message: TypeError: can't access dead object

但是,如果我另外使用睡眠:

time.sleep(30)
self.driver.switch_to_default_content()
WebDriverWait(self.driver, 300).\
        until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame1')))
WebDriverWait(self.driver, 300).\
        until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame2')))

selenium能够找到框架内的框架并切换到它。看起来在错误的情况下,selenium切换到'frame1'而'frame2'尚未加载,但'frame2'被加载到'frame1'的某个其他实例中,或者selenium无法识别(可能是bug?)。所以现在selenium在某个'frame1'里面,并且由于某些原因没有意识到'frame2'已被加载。

我能解决这个问题的唯一方法(不使用长时间睡眠)就是使用这段丑陋的代码:

    mustend = time.time() + 300
    while time.time() < mustend:
        try:
            self.driver.switch_to_default_content()
            self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
            self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))               
            break
        except WebDriverException as e:
            self.log("Sleeping 1 sec")
            time.sleep(1)
    if time.time() > mustend:
        raise TimeoutException

所以每当我得到一个WebDriverException(死对象)时,我会转到顶层框架并尝试逐帧切换到内框。

我可以尝试其他方法吗?

其他信息

  • iframe是嵌套的,即'frame2'位于'frame1'。

2 个答案:

答案 0 :(得分:3)

更好的方法是制作自己的expected_condition。 例如:

class nested_frames_to_be_available_and_switch:
    def __init__(self, *args):
        """
        :param args: locators tuple of nested frames (BY.ID, "ID1"), (BY.ID, "ID2"), ...
        """
        self.locators = args

    def __call__(self, driver):
        try:
            for locator in self.locators:
                driver.switch_to.frame(driver.find_element(*locator))
        except WebDriverException:
            driver.switch_to_default_content()
            return False
        return True

WebDriverWait(driver, 300).until(nested_frames_to_be_available_and_switch((By.ID, 'frame1'), (By.ID, 'frame1')))

但也许没有必要......告诉我需要看看你的HTML DOM。

答案 1 :(得分:1)

如果不查看以下内容,有点难以预测错误 WebDriverException: Message: TypeError: can't access dead object 的实际原因:

  • 完整的 HTML DOM
  • Frameset
  • 的存在
  • Frames
  • 的存在
  • Frame Loading
  • 的顺序
  • JavaScript 标签
  • AJAX Calls <iframe> 的存在

此时值得一提的是,最初 Selenium 始终关注 default_content 。以下是使用嵌套 frames framesets 的几种方法:

  • 如果 frame1 frame2 处于同一级别且 Top Level Browsing Context 我们应该:

    self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
    self.driver.switch_to_default_content()
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
    
  • 如果 frame2 嵌套在 frame1 中,则从 frame1 切换到 frame2 我们应该:

    self.driver.switch_to_default_content()
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
    //code
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
    
  • 如果 frame2 frame3 frame1 之内,则需要切换从 frame2 frame3 我们应该:

    self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame3"))
    
  • 如果 frame2 frame3 位于 frameset23 frame1 然后,要从 frame2 切换到 frame3 ,我们应该:

    self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
    #ignore presence of frameset23
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
    #ignore presence of frameset23
    self.driver.switch_to.frame(self.driver.find_element_by_id("frame3"))
    

使用适当的WebDriverWait更好的方法:

在处理 frames framesets 时,我们应始终使用以下内容诱导 WebDriverWait expected_conditions

  • Frames 之间切换:

    frame_to_be_available_and_switch_to_it
    
  • WebElements内的 Frames 进行互动:

    element_to_be_clickable