selenium scroll element into (center of) view - ruby

When an element is out of view with selenium and one tries to interact with it, selenium will usually scroll the element into view first implicitly. This is great except that what is annoying is that it usually puts in the element just enough into view. What I mean is that if the element is below the window, it will scroll down enough just till when the element is just bordering the edge of the window.
Usually this is fine, but when working on a web site with borders around it, this will lead to numerous of these kinds of errors
Selenium::WebDriver::Error::UnknownError:
unknown error: Element is not clickable at point (438, 747). Other element would receive the click: <body>...</body>
Because usually the border of the web page is over it, but will try to click the element anyway. Is there anyway handle this? perhaps to automatically move elements to the center of the screen when out of view? I am thinking along the lines monkey-patching via ruby.

This should work in order to scroll element into center of view:
WebElement element = driver.findElement(By.xxx("xxxx"));
String scrollElementIntoMiddle = "var viewPortHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);"
+ "var elementTop = arguments[0].getBoundingClientRect().top;"
+ "window.scrollBy(0, elementTop-(viewPortHeight/2));";
((JavascriptExecutor) driver).executeScript(scrollElementIntoMiddle, element);

Yes, it is possible to automatically scroll the browser such that any element we interact with gets centered in the window. I have a working example below, written and tested in ruby using selenium-webdriver-2.41.0 and Firefox 28.
Full disclosure: You might have to edit parts of your code slightly to get this to work properly. Explanations follow.
Selenium::WebDriver::Mouse.class_eval do
# Since automatic centering of elements can be time-expensive, we disable
# this behavior by default and allow it to be enabled as-needed.
self.class_variable_set(:##keep_elements_centered, false)
def self.keep_elements_centered=(enable)
self.class_variable_set(:##keep_elements_centered, enable)
end
def self.keep_elements_centered
self.class_variable_get(:##keep_elements_centered)
end
# Uses javascript to attempt to scroll the desired element as close to the
# center of the window as possible. Does nothing if the element is already
# more-or-less centered.
def scroll_to_center(element)
element_scrolled_center_x = element.location_once_scrolled_into_view.x + element.size.width / 2
element_scrolled_center_y = element.location_once_scrolled_into_view.y + element.size.height / 2
window_pos = #bridge.getWindowPosition
window_size = #bridge.getWindowSize
window_center_x = window_pos[:x] + window_size[:width] / 2
window_center_y = window_pos[:y] + window_size[:height] / 2
scroll_x = element_scrolled_center_x - window_center_x
scroll_y = element_scrolled_center_y - window_center_y
return if scroll_x.abs < window_size[:width] / 4 && scroll_y.abs < window_size[:height] / 4
#bridge.executeScript("window.scrollBy(#{scroll_x}, #{scroll_y})", "");
sleep(0.5)
end
# Create a new reference to the existing function so we can re-use it.
alias_method :base_move_to, :move_to
# After Selenium does its own mouse motion and scrolling, do ours.
def move_to(element, right_by = nil, down_by = nil)
base_move_to(element, right_by, down_by)
scroll_to_center(element) if self.class.keep_elements_centered
end
end
Recommended usage:
Enable automatic centering at the start of any code segments where elements are commonly off-screen, then disable it afterward.
NOTE: This code does not seem to work with chained actions. Example:
driver.action.move_to(element).click.perform
The scrolling fix doesn't seem to update the click position. In the above example, it would click on the element's pre-scroll position, generating a mis-click.
Why move_to?
I chose move_to because most mouse-based actions make use of it, and Selenium's existing "scroll into view" behavior occurs during this step. This particular patch shouldn't work for any mouse interactions that don't call move_to at some level, nor do I expect it to work with any keyboard interactions, but a similar approach should work, in theory, if you wrap the right functions.
Why sleep?
I'm not actually sure why a sleep command is needed after scrolling via executeScript. With my particular setup, I am able to remove the sleep command and it still works. Similar examples from other developers across the 'net include sleep commands with delays ranging from 0.1 to 3 seconds. As a wild guess, I would say this is being done for cross-compatibility reasons.
What if I don't want to monkey-patch?
The ideal solution would be, as you suggested, to change Selenium's "scroll into view" behavior, but I believe this behavior is controlled by code outside of the selenium-webdriver gem. I traced the code all the way to the Bridge before the trail went cold.
For the monkey-patch averse, the scroll_to_center method works fine as a standalone method with a few substitutions, where driver is your Selenium::WebDriver::Driver instance:
driver.manage.window.position instead of
#bridge.getWindowPosition
driver.manage.window.size instead of
#bridge.getWindowSize
driver.execute_script instead of
#bridge.executeScript

The following code will scroll until the element is in view,
WebElement element = driver.findElement(By.id("id_of_element"));
((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", element);
Thread.sleep(500);
//do anything you want with the element

You could use an explicit scroll action via javascript here. In that case you would find the element (this part works already if I understand your question correctly), then scroll the window to a specified position, and then interact with the element.
In java that would be:
WebElement element = driver.findElement(By.id("tabs")).findElement(By.className("youarehere"));
Point p = element.getLocation();
((JavascriptExecutor) driver).executeScript("window.scroll(" + p.getX() + "," + (p.getY() + 200) + ");");

Related

Is there a way to determine if an element is clickable in Chrome Browser using Selenium and Watir?

As I understand it, this question is really only relevant when performing web test automation using Chrome browser due to the manner in which a click event is implemented with Selenium Webdriver and Chromedriver. To preface, I use and am aware of finding an element through the use of the Watir element function "present?", which as I understand is essentially a combination of "visible?" and "exists?". I could also, at need find an element with Webdriver element functions to identify if an element is present with a rescue for an exception if it is not. What I am trying to determine is the following:
At times, due to a lack of responsiveness to a page, there will be page elements that will be found and pass all validation tests for it's existence, but cannot actively actually be interacted with due to the aforementioned lack of page responsiveness. Using Chrome browser (with Chromedriver) attempts to interact with these elements will result in the error:
irb(main):003:0> #browser.button(:id, "button_login").present?
=> true
irb(main):004:0> #browser.button(:id, "button_login").click
Selenium::WebDriver::Error::UnknownError: unknown error: Element ... is not clickable at point (915,
nt would receive the click: ...
(Session info: chrome=66.0.3359.181)
(Driver info: chromedriver=2.38.552522 (437e6fbedfa8762dec75e2c5b3ddb86763dc9dcb),platform=Windows NT 6.3.9600 x86_64)
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/response.rb:69:in 'assert_ok'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/response.rb:32:in 'initialize'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:83:in 'new'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:83:in 'create_response'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/default.rb:107:in 'request'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/http/common.rb:61:in 'call'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/bridge.rb:170:in 'execute'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/oss/bridge.rb:579:in 'execute'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/remote/oss/bridge.rb:328:in 'click_element'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/selenium-webdriver-3.4.4/lib/selenium/webdriver/common/element.rb:74:in 'click'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:131:in 'block in click'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:656:in 'element_call'
from C:/Ruby23/lib/ruby/gems/2.3.0/gems/watir-6.4.1/lib/watir/elements/element.rb:122:in 'click'
from (irb):4
from C:/Ruby23/bin/irb.cmd:19:in ''
I know I can rescue at this point, but that entails that I actually click the element. Essentially I want to write a special function "clickable?" that will return a boolean output without actually clicking the element and possibly navigating away from the page. I would prefer not to attempt this with a type of --ctrl+click, if new window return true, close window, set focus on first window, rescue return false-- workflow.
Watir 6.15.0+
Element#obscured? has been added to check for this scenario. You can now do:
browser.element(id: 'target').wait_while(&:obscured?).click
Watir pre-6.15.0
For older versions, you will need to take a different approach.
I would try waiting for the overlapping element to go away. If the overlapping is something like an overlay that will eventually disappear, it's relatively straightforward - eg:
browser.element(id: 'overlapping_element').wait_while(&:present?)
If the overlapping element gets moved rather than disappears or you don't know the overlapping element, you could try approximating the overlapping element check. When Chrome clicks an element, it gets the element's center location and then clicks at that point. If there the top-level element at that point is not your element, the exception is thrown. The following wait will do this check until there is no overlapping element:
target = browser.button
target_children = target.elements.to_a
browser.wait_until do
location = target.location
size = target.size
center = [location.x + size.width/2, location.y + size.height/2]
element_at_point = browser.execute_script("return document.elementFromPoint(#{center[0]}, #{center[1]});")
[target, target_children].flatten.include?(element_at_point)
end
target.click
Note that I haven't had to do this before, so I don't know if there are edge cases. Seemed to work with Chrome and Firefox.
I can suggest you to wait this button to show on the web page. I experienced the same problem (I was using XPath in my tests). To fix it:
Firstly I defined 2 helper methods because I had to reuse them a lot. One for searching of a exact element on the page(this method is usually takes a while to return a result so you don't need to sleep the browser) and one for clicking a button with given "id".
module Helpers
module Common
def wait_for_element_id(value)
find(:xpath, "(//*[#id='#{value}'])[1]")
end
def click_button_with_id(value)
first(:xpath, "//button[#id='#{value}']").click
end
end
end
After that in your test you can use the helper methods like:
it 'clicks on the login button and magic is executed' do
logout(user)
wait_for_element_id('button_login')
click_button_with_id('button_login')
expect(magic).to be_executed
end
I am also not sure but you can also experience the same problem because of the browser window size (button is not shown because the size is too low) or because of the "headless" mode of your tests.

How to open file at particular line in GtkSource.View without visible flicker?

I need to open file in GtkSourceView and scroll to a particular line. Being a debugger gui of sorts, it needs to do this activity frequently (when switching stack frames), without transient artifacts - visible readjusting of the content and flickering. Currently, I am using the following approach:
from gi.repository import GtkSource
class MyClientWindow:
def __init__(self):
self.__sourceview = GtkSource.View()
self.__sourceview.connect('size-allocate', self.__source_resize)
...
def __source_resize(self, widget, allocation):
self.__sourceview.scroll_to_mark(self.__sourceview.get_buffer().get_insert(), 0.25, False, 0, 0)
With this approach there is visible flicker. Placing 1s sleep in __source_resize above reveals that the GtkSourceView gets readjusted in a few passes (being visibly drawn before and after source highlighting, as well as some additional validation.)
Is there another way to force the GtkSource.View to compute line heights and perform validation without drawing on screen, or to force it to immediately compute line heights when I set the buffer content without deferring it to idle job later? If not, approximately how much of the GtkSource.View control would I need to override to get the desired behavior?
Note: I tried using Gtk.Stack to do the work in invisible "back" view and to make it visible later using Gtk.idle_add, but I got the impression that the visibility of the View is key to line validation. At least so far, it did not provide the desired result.
The Gtk.Stack method worked. It requires specific sequence of operations. This is excerpt (modified for clarity) from the solution I have now:
class MyClientWindow:
def __init__(self):
self.__stack = Gtk.Stack()
...
def __switch_doc(content, line):
buffer = GtkSource.Buffer()
buffer.set_text(content)
# buffer style settings are applied here
...
iter = buffer.get_iter_at_line(line)
buffer.place_cursor(iter)
source_view = GtkSource.View.new_with_buffer(buffer)
prev_sw = self.__stack.get_visible_child()
next_sw = Gtk.ScrolledWindow()
next_sw.add(source_view)
next_sw.show_all()
self.__stack.add(next_sw)
GObject.idle_add(lambda: self.__switch_view(prev_sw, next_sw))
def __switch_view(self, prev_sw, next_sw):
source_view = next_sw.get_child()
buffer = source_view.get_buffer()
source_view.scroll_to_iter(buffer.get_iter_at_mark(buffer.get_insert()), 0, True, 0, 0.5)
if prev_sw:
self.__stack.remove(prev_sw)
I recreate both the GtkSourceView and the parent GtkScrollWindow every time. This may be optimized somewhat (by holding on to old views in the stack instead of removing them.)
It is important that the GtkScrollWindow is shown before adding it to the stack container. Otherwise, it will lack coloring when shown later if scroll_to_iter is used to move to the new location in the text. (I couldn't figure if this is a bug.)
Showing the widget before adding it makes it the "visible child" of the stack, but only in sense that it gets returned by the get_visible_child method. It does not however render it to the screen.
The drawing begins when the old child is removed (or the new one is activated with the set_visible_child method), which due to the use of idle_add above, happens after line validation of GtkSourceView. Thus, the scrolling can be performed with scroll_to_iter, instead of scroll_to_mark, which would defer the action even further.

How can I automate a slider using watir-webdriver and ruby?

I am trying to automate a Jquery UI slider using watir-webdriver and ruby but am having no luck. I want to be able to manipulate the slider to either increase or decrease the value. I have searched everywhere and asked a few of my colleagues but no one has an answer. I am attaching a piece of HTML code for the slider. Any help would be much appreciated.
URL link: http://www.whatcar.com/new-car-deals/ (price range slider)
I haven't done any coding for the slider as I have no idea how to go about it. If I could be set on the right path that would be fantastic. Also it seems the HTML code doesn't seem to be showing on the question I am asking.
it's a Jquery widget that appears (looking at the scripts) to respond to key presses once one of the handles has been clicked on. the handles are link objects, inside a pair of divs, the outer one has the ID value 'slider-range'
You should be able to address them via browser.div(:id => 'slider-range').link(:index => n) where n = 0 for the lefthand one and n=1 for the right hand one.
Try this manually first
Click on the left handle for the slider
Then press arrow keys on the keyboard.. it will move 1K(pounds) up or down with each right or left arrow keypress.
Once you have moved it right, click the Home key and it should reset to its min value
repeat above for the right handle but reset it to far right using the End key.
In theory you could fire a click event at the right slider to get focus, then fire off some keypress events to first set it far right (end) followed by enough left arrows to move it down to the top value you want.
Repeat for the left handle, setting it to the left end with the home key, and moving it with right arrow keypresses
I don't have the time right now to experiment further and develop an exact script, but I think you could probably create two little functions even that took an upper and lower range value and did the required number of keypresses to move the lower up from 5K to the right amount, and the upper down from 100k
Note that this thing is very very event driven, it responds (at least when watching the dev tools while playing with it) to being moused over, mousedown, mouseup, etc.. if using click does not work to 'get the attention' of the control so it will respond to keypresses, try using the onmousedown event instead.
Using the code from your answer, to move the left slider to 12K I'd try
browser.div(:id => 'slider-range').link(:index => 0).click #make sure it has focus
browser.div(:id => 'slider-range').link(:index => 0).send_keys :home #set to 5K
7.times do
browser.div(:id => 'slider-range').link(:index => 0).send_keys :arrow_right
end
Thank you very much Chuck. I got it working. Here is the script I used:
#browser.link(:xpath, "/html/body/div/div[3]/div[4]/div/div/div/a").send_keys :arrow_right
Still needs a bit of tweaking, but I should be ok now.
This worked great. We just added these sliders to our website and my first thought was "how are we going to automate that!?"
I wanted to add an example will full code that is available publicly for people to experiment with. The following interacts with an example on jqueryui.com.
require "watir-webdriver"
#browser = Watir::Browser.new :chrome
#browser.goto "http://jqueryui.com/slider/#default"
#browser.frame(:class => "demo-frame").span(:class =>"ui-slider-handle").click
#browser.frame(:class => "demo-frame").span(:class =>"ui-slider-handle").send_keys :home
puts "clicked home"
7.times do
#browser.frame(:class => "demo-frame").span(:class =>"ui-slider-handle").send_keys :arrow_right
end
To achieve this using Rspec and Watir-Webdriver with Page-Object gem I used:
div(:slider, id: "slider-range") #in page file
#then in spec file
browser.slider_element.input.send_keys :home
This answer comes from http://watir.com/guides/special-keys/

how to scroll with selenium

I have here a challenge that I spent some time addressing.
Selenium tells me that it cannot click a link that is not visible, so that means I need to scroll my canvas? I am using ubuntu 10, firefox 3, selenium 0.1, ruby 1.9.2, and selenium-webdriver 2.5.0
My code is
driver = Selenium::WebDriver.for :firefox;
driver.get login_url
wait = Selenium::WebDriver::Wait.new(:timeout => 2)
wait.until {
driver.find_element(:name => 'j_password')
}
driver.find_element(:name => 'j_username').send_keys(username)
driver.focus(:name => 'j_username')`
and it says that focus() is not defined. How should I modify my code to put the input element on screen?
Um, this is a hypothetical example, I really need to scroll so that some other element is on the screen, but for simplicity I would like to be able to scroll s.t. any element is on the screen, even
location_once_scrolled_into_view to scroll using ruby.
As per your question your element is not visible, so selenium web driver is unable to click on it.
Simple solution to this is:
Store the xpath of the element which is visible and nearby to your element.
Scroll till that visible element, to make your element visible.
Ensure your element is now visible, click on it
eg code:
element = dirver.find_element(:xpath, "xpath of nearby visible element")
element.location_once_scrolled_into_view
my_element = driver.find_element(:xpath, "xpath of your element")
my_element.click
I had an element at the bottom of the page with no other element close enough to select first. Finally got around it by tabbing from the last field in the form on the page.
element.send_keys(:tab)
When the documentation says "not visible" it's not refering the view port, but to the status of the element on the page. If something is hidden with CSS it cannot be clicked.
If you want to scroll on the firefox window using selenium webdriver, one of the way is to use javaScript in the java code, The javeScript code to scroll down is as follows:
JavascriptExecutor js = (JavascriptExecutor)driver;
js.executeScript("window.scrollTo(0,Math.max(document.documentElement.scrollHeight," +
"document.body.scrollHeight,document.documentElement.clientHeight));");
I'm not aware of ruby but the above code can be used as a java Script which will scroll down the whole page.You can even define the area you want to scroll in the view window by simply hard- coding the window.scrollTo(200,350);

How do I know which element has the focus?

How do I know which element has the focus, or how to determine whether the focus of a particular element?
I don't know of anyway to query for this kind of information directly. I suppose you could traverse the DOM and check for it manually. If that's the case you should look at this.
Perhaps what you are trying to do can be achieved by handling an event when specific elements of interest gain focus. Here is how you would do that in jQuery.
$('<selector>').focus(function() {
// Do something here
});
Just for giggles, here is one way you could use the focus method to track which element recieved it last. I highly recommend against using this code.
var gotDaFocus;
$('*').focus(function() {
gotDaFocus = this;
});
not yet implemented yet i'm afraid.
You could do something like this
Shoes.app do
#focused = para ""
stack do
#label1 = edit_line :text => 1
click do
#focused.replace #label1.text
end
end
stack do
#label2 = edit_line :text => 2
click do
#focused.replace #label2.text
end
end
end
But the problem in windows is that the controls steal all the event from Shoes.
In Green Shoes it would work. If you'r in windows you can still see the result if you click just to the right of the edit_line. Of course, this is only when you click with the mouse, you should check keypress tot get the keyboard events also.
Grtz

Resources