selenium scroll element into (center of) view
Asked Answered
A

4

16

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.

Armed answered 23/11, 2013 at 20:34 Comment(0)
S
43

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);
Swear answered 12/7, 2016 at 6:18 Comment(2)
Thanks. this one is better than scrollIntoViewTiller
This was specifically helpful to me in circumventing some issues with a header and footer which seem to overlay the element I've tried to move to. Worked for me in C#.Popularity
B
3

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
Benson answered 18/3, 2015 at 21:19 Comment(0)
S
1

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
Sunfast answered 24/11, 2013 at 4:19 Comment(2)
This a good suggestion but then I'd have to do this for every element I came in contact with. I'm looking for a way to apply across the entire sessionArmed
This doesn't always work because "into view" can still be under a banner meaning it's still not clickable. The scrollElementIntoMiddle works better.Nur
L
0

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) + ");");
Luminescent answered 23/11, 2013 at 22:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.