Frage

I am trying to search through all the html of websites that I reach using selenium webdriver. In selenium, when I have an iframe, I must switch to the iframe and then switch back to the main html to search for other iframes.

However, with nested iframes, this can be quite complicated. I must switch to an iframe, search it for iframes, then switch to one iframe found, search IT for iframes, then to go to another iframe I must switch to the main frame, then have my path saved to switch back to where I was before, etc.

Unfortunately, many pages I've found have iframes within iframes within iframes (and so on).

Is there a simple algorithm for this? Or a better way of doing it?

War es hilfreich?

Lösung 2

I was not able to find a website with several layers of nested frames to fully test this concept, but I was able to test it on a site with just one layer of nested frames. So, this might require a bit of debugging to deal with deeper nesting. Also, this code assumes that each of the iframes has a name attribute.

I believe that using a recursive function along these lines will solve the issue for you, and here's an example data structure to go along with it:

def frame_search(path):
    framedict = {}
    for child_frame in browser.find_elements_by_tag_name('frame'):
        child_frame_name = child_frame.get_attribute('name')
        framedict[child_frame_name] = {'framepath' : path, 'children' : {}}
        xpath = '//frame[@name="{}"]'.format(child_frame_name)
        browser.switch_to.frame(browser.find_element_by_xpath(xpath))
        framedict[child_frame_name]['children'] = frame_search(framedict[child_frame_name]['framepath']+[child_frame_name])
        ...
        do something involving this child_frame
        ...
        browser.switch_to.default_content()
        if len(framedict[child_frame_name]['framepath'])>0:
            for parent in framedict[child_frame_name]['framepath']:
                parent_xpath = '//frame[@name="{}"]'.format(parent)
                browser.switch_to.frame(browser.find_element_by_xpath(parent_xpath))
    return framedict

You'd kick it off by calling: frametree = iframe_search([]), and the framedict would end up looking something like this:

frametree = 
{'child1' : 'framepath' : [], 'children' : {'child1.1' : 'framepath' : ['child1'], 'children' : {...etc}}, 
 'child2' : 'framepath' : [], 'children' : {'child2.1' : 'framepath' : ['child2'], 'children' : {...etc}}}

A note: The reason that I wrote this to use attributes of the frames to identify them instead of just using the result of the find_elements method is that I've found in certain scenarios Selenium will throw a stale data exception after a page has been open for too long, and those responses are no longer useful. Obviously, the frame's attributes are not going to change, so it's a bit more stable to use the xpath. Hope this helps.

Andere Tipps

Finding iframes solely by HTML element tag or attributes (including ID) appears to be unreliable.

On the other hand, recursively searching by iframe indexes works relatively fine.

def find_all_iframes(driver):
    iframes = driver.find_elements_by_xpath("//iframe")
    for index, iframe in enumerate(iframes):
        # Your sweet business logic applied to iframe goes here.
        driver.switch_to.frame(index)
        find_all_iframes(driver)
        driver.switch_to.parent_frame()

You can nest one iFrame into another iFrame by remembering the simple line of code to position, then re-position, the cursor back to the same area of the screen by using the as in the following COMPLETE code, remembering always to put the larger iFrame FIRST, then define the position of the SMALLER iFrame SECOND, as in the following FULL example:---

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>
<title>Daneiella Oddie, Austrailian Ballet Dancer, dancing to Bach-Gounod's Ave Maria</title>
</head>
<body bgcolor="#ffffcc">

<DIV style="position: absolute; top:0px; left:0px; width:0px; height:0px"></div>
<DIV style="position: absolute; top:10px; left:200px; width:900px; height:500px">

<iframe width="824" height="472" src="http://majordomoers.me/Videos/DanielaOddiDancingToBack_GounodsAveMaria.mp4" frameborder="0" allowfullscreen></iframe>
</div>

<DIV style="position: absolute; top:0px; left:0px; width:0px; height:0px"></div>
<DIV style="position: absolute; top:10px; left:0px; width:50px; height:50px">

<iframe src="http://majordomoers.me/Videos/LauraUllrichSingingBach_GounodsAveMaria.mp4" frameborder="0" allowfullscreen></iframe>

</div>

<DIV style="position: absolute; top:0px; left:0px; width:0px; height:0px"></div>
<DIV style="position: absolute; top:470px; left:10px; width:1050px; height:30px">

<br><font face="Comic Sans MS" size="3" color="red">  
<li><b>Both Videos will START automatically...but the one with the audio will preceed the dancing by about 17 seconds.  You should keep
<li>both videos at the same size as presented here.  In all, just lean back and let it all unfold before you, each in its own time.</li></font>
</div>
<br>

</body>
</html>

You can use the below code to get the nested frame hierarchy... Change the getAttribute according to your DOM structure.

static Stack<String> stackOfFrames = new Stack<>();

....
....

public static void getListOfFrames(WebDriver driver) {   
    List<WebElement> iframes = wd.findElements(By.xpath("//iframe|//frame"));
    int numOfFrames = iframes.size();
    for(int i=0; i<numOfFrames;i++) {
        stackOfFrames.push(iframes.get(i).getAttribute("id"));
        System.out.println("Current Stack => " + stackOfFrames);
        driver.switchTo().frame(i);
        getListOfFrames(driver);
        driver.switchTo().parentFrame();
        stackOfFrames.pop();
        count++;
    }   
}      
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top