Recursively loop through frames to find an element in Watir-WebDriver? (Works in Watir, not WebDriver)
-
26-10-2019 - |
Question
We wrote some Watir that would recursively look through a page's frames until it found the element you were asking for, and it would then return it to you. It doesn't seem like we can do this with Watir-WebDriver.
What we used to do:
So essentially we'd run something like:
findButton(:id, "Login_button")
And we'd loop through all the frames in the page, looking for this button.
def findButton(desc, b = @browser)
# look for object in main page
if b.button(desc).exist?
obj = b.button(desc)
#return and be done.
else
# look for object in frames
count = b.document.frames.length
(1..count).each do |i|
if b.frame(:index,i).button(desc).exist?
obj = b.frame(:index,i).button(desc)
break
end
end
if obj == nil
(1..count).each do |i|
obj = find(desc, b.frame(:index,i))
if obj != nil
break
end
end
end
end
if obj == nil && b == @browser
raise "Can't find button with descriptor #{desc}"
end
obj
end
We'd then use the element object returned:
findButton(:id, "login_button").click
, for example.
Why it doesn't seem like we can anymore
So now we're evaluating Watir-WebDriver and the document element doesn't appear to be part of the Watir browser object anymore... but that's okay, right? So then I went and looked through the frames, collection:
browser = Watir::Browser.new :ie
{......}
browser.frames
First off, Browser.frames
takes about 2-3 seconds to return any data, even when there's only one frame (Latest watir-webdriver Gem as of today's date, Ruby 193p0, and IE9). Secondly, it doesn't seem like frame object that is returned actually contains the element access I need. browser.frames[1].button(:id, "Login_Button")
returns a variety of errors, depending on what I'm looking for.
It almost seems like this exposes a WebDriver limitation that Watir-WebDriver was hoping to work around, in that WebDriver just doesn't resemble a DOM structure, and elements don't always have the proper "Types" that is familiar to Watir, and in fact to the DOM itself.
Some might say it's silly to loop through frames looking for an element, but it's just one of those times where it's best if I have that capability. Our application uses anywhere from 3-5 frames at any given moment, and you can't always predict what frame or what order an element might be in.
Have I missed something? Am I not understanding a fundamental principle of Watir-WebDriver?
Solution
There doesn't seem to be a problem with the watir-webdriver API. For example:
require 'watir-webdriver'
def find_element_in_all_frames browser, &block
element = block.call browser
return element if element.exists?
browser.frames.each do |frame|
result = find_element_in_all_frames frame, &block
return result if result
end
return nil
end
Watir::always_locate = false
browser = Watir::Browser.start 'http://www.w3schools.com/html/tryit.asp?filename=tryhtml_frame_mix'
element = find_element_in_all_frames(browser) { |b| b.h3(:text => 'Frame A') }
puts element.text
browser.close
works completely fine, and returns the correct element. When running with Watir::always_locate set to true, it takes about 5 seconds, with it set to false, about 2 seconds.
You'd use it as:
element = find_element_in_all_frames(browser) { |b| b.button(:id :> "login_button") }
element.click
I don't think this time is unreasonable as the tool is basically switching contexts multiple times to find the element.
If you really want things to work better, it's time to rewrite your app without frames.