3

I have a rather complex webpage setup I need to test, containing nested frames.

In the actual problem the selenium code is loading new webpage contents containing a frame, which I want to switch to. In order to avoid any explicit waits, I tried the following code snippet:

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

However, this snippet always fails and results in the following error:

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

However, if I use a sleep in addition:

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 is able to find the frame inside the frame and switch to it. It looks like in the error case selenium switches to 'frame1' while 'frame2' is not yet loaded, but 'frame2' gets loaded in some other instance of 'frame1', or not recognized by selenium (maybe a bug?). So now selenium is inside some 'frame1' and for some reasons does not realize that the 'frame2' has been loaded.

The only way I can fix this (without using a long sleep) is by using this ugly piece of code:

    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

So whenever I get a WebDriverException (dead object), I go to the top-level frame and try to switch to the inner frame - frame by frame.

Is there any other approach I can try?

Additional information

  • The iframes are nested, i.e. 'frame2' is inside 'frame1'.
undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
Alex
  • 41,580
  • 88
  • 260
  • 469

2 Answers2

3

Better approach is to make your own expected_condition. For example:

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

But maybe there is no need for that.. To tell so I need to see your html DOM.

suit
  • 569
  • 2
  • 13
1

This error message...

WebDriverException: Message: TypeError: can't access dead object

...implies that there was an error while switching between <iframes>.

Some more information in terms of:

  • The relevant HTML
  • Presence of Frameset
  • Presence of Frames
  • Hierarchy of Nested Frames
  • Sequence of Frame Loading
  • Presence of JavaScript and AJAX Calls within the respective <iframe> tags

would have helped us to analyze the issue in a better way. However, at this point it is worth to mention that initially Selenium always gets the focus on the default_content. Here are a few approaches to work with nested <frames> and <framesets>:

  • If both frame1 and frame2 are at same level i.e. under the Top Level Browsing Context you need to:

    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"))
    
  • If frame2 is nested within frame1, to switch from frame1 to frame2 you need to:

    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"))
    
  • If frame2 and frame3 is within frame1 then, to switch from frame2 to frame3 you need to:

    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"))
    
  • If frame2 and frame3 is within a frameset23 which is within frame1 then, to switch from frame2 to frame3 you need to:

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

Better approach with proper WebDriverWait

While dealing with and you need to induce WebDriverWait inconjunction with expected_conditions:

frame_to_be_available_and_switch_to_it()

As an example to switch from Top Level Browsing Context to an <iframe> an effective line of code will be:

  • Using frame ID:

    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID,"frameID")))
    
  • Using frame NAME:

    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.NAME,"frameNAME")))
    
  • Using frame CLASS_NAME:

    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.frame_CLASS_NAME,"u_0_f")))
    
  • Using frame CSS_SELECTOR:

    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR,"frame_CSS_SELECTOR")))
    
  • Using frame XPATH:

    WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.XPATH,"frame_XPATH")))
    

tl; dr

Ways to deal with #document under iframe

undetected Selenium
  • 183,867
  • 41
  • 278
  • 352
  • Tried it all including `WebDriverWait`. With that I get the same error (i.e. see question where I use `WebDriverWait`...) – Alex Dec 14 '17 at 15:23
  • DebanjanB: Do you know of some documentation/book which explains in depth all of the HTML DOM, Framesets, Frame loading, interaction with javascript, AJAX calls etc etc etc? – Alex Jul 05 '18 at 09:51