Dan at Demand
Dan at Demand

Reputation: 3369

Scroll Element into View with Selenium

Is there any way in either Selenium 1.x or 2.x to scroll the browser window so that a particular element identified by an XPath is in view of the browser? There is a focus method in Selenium, but it does not seem to physically scroll the view in FireFox. Does anyone have any suggestions on how to do this?

The reason I need this is I'm testing the click of an element on the page. Unfortunately the event doesn't seem to work unless the element is visible. I don't have control of the code that fires when the element is clicked, so I can't debug or make modifications to it, so, easiest solution is to scroll the item into view.

Upvotes: 274

Views: 824719

Answers (30)

matt forsythe
matt forsythe

Reputation: 3922

While I hesitate to add yet another answer to a question that already has 30, there are some very subtle things that go on with browser scrolling and Selenium. The crux of the issue is that scrollIntoView is asynchronous: it simply pushes a scroll update request onto a queue of some kind and then exits before the scrolling actually occurs. So it may be easy to scroll to a specific element, but it is difficult to ensure that scrolling has actually completed before moving on so that you can ensure future calls (like click()) won't "miss" their elements and fail due to the elements still moving around on the page. This is true even when smooth scroll-behavior is turned off.

Option 1:

Simply use the common solution mentioned by others, but make sure to add some arbitrary wait afterward:

    ((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", element);
    try {
        Thread.sleep(1000);
    } catch (InterruptedException ex) {
        ex.printStackTrace();
    }

Adding a wait afterward will give the scrolling time to complete after executeScript returns, but there is no good way to determine how long to wait. If scrolling is not something your script does very often, then set it to something way longer than what you think is needed a forget about it.

Option 2:

If you hate using arbitrary waits to gloss over problems (or if you need this operation to take as little time as possible), then use a more complicated script with executeAsyncScript to ensure scrolling completes before the Selenium script moves on.

The script below uses the 'scrollend' event to detect when scrolling has stopped. However, this is not straight forward because (and this is where it gets complicated) we may need to wait for multiple 'scrollend' events because:

  1. each ancestor of the element may have a scrollbar, and
  2. each one of them may or may not need to be scrolled in order to scroll the element into view, and
  3. each ancestor that gets scrolled in order to bring the element into view will generate its own scrollend event (but only if it actually needed to be scrolled during the process)

So, the number of scrollend events we need to wait for is anywhere between 0 and X, where X is the number of ancestor elements with visible scrollbars.

The script below solves this issue by starting at "element" and walking up the hierarchy looking for any ancestor elements that have a scrollbar (vertical or horizontal). For each one, it scrolls that container by a single pixel, just to make sure each scrollable ancestor element has a pending scroll update. It also keeps a count of the number of scrollable ancestor elements it encountered, so that we know exactly how many scrollend events to expect.

Then element.scrollIntoView() is called, which may or may not add more pending scroll updates in order to scroll the element into view. However, these updates will simply be additional updates for the scrollable ancestor elements that our script already programmatically scrolled by one pixel. This means that the element.scrollIntoView() call will not cause the expected number of scrollend events to increase.

After calling element.scrollIntoView(), the script simply needs to wait for the expected number of scrollend events to occur, and then signal the end of the script.

const element = arguments[0];
// keep track of the function to call to signal the script is complete
const scriptDone = arguments[arguments.length - 1];
let scrollableElementCount = 0;

const nonScrollableStyles = ['visible', 'hidden'];
let currentElement = element;
while(currentElement.parentElement && currentElement != currentElement.parentElement) {
    currentElement = currentElement.parentElement;

    // check to see if currentElement is scrollable in either the X or Y direction
    const style = getComputedStyle(currentElement);
    if(currentElement.clientHeight > 0 && currentElement.clientHeight < currentElement.scrollHeight && ! nonScrollableStyles.includes(style.overflowY)) {
        // current element is scrollable in the Y direction - scroll it by one pixel
        currentElement.scrollTop = (currentElement.scrollTop == 0) ? 1 : currentElement.scrollTop - 1;
        scrollableElementCount++;
    } else if(currentElement.clientWidth > 0 && currentElement.clientWidth < currentElement.scrollWidth && ! nonScrollableStyles.includes(style.overflowX)) {
        // current element is scrollable in the X direction - scroll it by one pixel
        currentElement.scrollLeft = (currentElement.scrollLeft == 0) ? 1 : currentElement.scrollLeft - 1;
        scrollableElementCount++;
    } 
}

if(scrollableElementCount > 0) {
    // add a scrollEndHandler to ensure one "scrollend" event is received for each scrollable element that is an ancestor of the target element
    const scrollEndHandler = function () {
        scrollableElementCount--;
        if(scrollableElementCount == 0) {
            element.ownerDocument.removeEventListener('scrollend', scrollEndHandler, true);
            // all of the "scrollend" events are now accounted for
            scriptDone();
        }
    };
    element.ownerDocument.addEventListener('scrollend', scrollEndHandler, true);
    // finally call scrollIntoView and wait for the scrollEndHandler to account for all the expected "scrollend" events
    element.scrollIntoView(true);
} else {
    // no need to call scrollIntoView, since there are no scrollbars present anywhere on the page
    scriptDone();
}

Take that JavaScript, get it into a Java String and call executeAsyncScript

String scrollScript = """
        <multi-line Java String - insert JavaScript from above here!>
        """;
((JavascriptExecutor) driver).executeAsyncScript(scrollScript, element);

Upvotes: 0

Anuraj R
Anuraj R

Reputation: 594

Sometimes I also faced the problem of scrolling with Selenium. So I used javaScriptExecuter to achieve this.

For scrolling down:

WebDriver driver = new ChromeDriver();
JavascriptExecutor js = (JavascriptExecutor)driver;
js.executeScript("window.scrollBy(0, 250)", "");

Or, also

js.executeScript("scroll(0, 250);");

For scrolling up:

js.executeScript("window.scrollBy(0,-250)", "");

Or,

js.executeScript("scroll(0, -250);");

Upvotes: 3

kiranjith
kiranjith

Reputation: 211

I am not sure if the question is still relevant but after referring to scrollIntoView documentation from https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView.

The easiest solution would be

executor.executeScript("arguments[0].scrollIntoView({block: \"center\",inline: \"center\",behavior: \"smooth\"});",element);

This scrolls the element into center of the page.

Upvotes: 5

Sriharsha Karanth
Sriharsha Karanth

Reputation: 47

I agree with everyone here, who say "Selenium has an implicit scroll option". Also if you were in Selenium 1 and now you have upgraded yourself to Selenium 2 and look for previous version's commands, you can use the command known as:

Seleniumbackeddriver.

WebDriver driver = new FirefoxDriver();
public void setUp() throws Exception {

    String baseUrl = "http://www.google.co.in/";
    selenium = new WebDriverBackedSelenium(driver, baseUrl);
}

You could make use of these and use commands of both versions.

Upvotes: -4

Ankur Gaurav
Ankur Gaurav

Reputation: 49

Do a random click down the page:

driver.findElement(By.id("ID of a web element present below")).click

Then perform what you want to do.

Upvotes: -3

Konstantin Ivanov
Konstantin Ivanov

Reputation: 410

Here is how I do it with PHP webDriver for Selenium. It Works for Selenium stand-alone server 2.39.0 + https://github.com/Element-34/php-webdriver + Firefox 25.0

$element=$session->welement("xpath", "//input[@value='my val']");
$element->click();
$element=$session->welement("xpath", "//input[@value='ma val2']");
$element->location_in_view(); // < -- this is the candy
$element->click();

Note: I use a customized version of the Element34 PHP-webdriver. But there is not any change in the core. I just use my "welement" instead of "element". But it makes no influence on the case in question. The driver author says "to allow almost all API calls to be a direct transformation of what is defined in the WebDriver protocol itself." So you should have no problem with other programming languages.

Just clicking will not work in my setup. It will do a scroll instead of click, so I had to click twice without calling "location_in_view()".

Note: This method works for elements that can be viewed, like an input of type button.

Take a look at: http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/location

The description for JsonWireProtocol# suggest usage of location + moveto, because location _in_view is an internal method.

Upvotes: -2

Iain Ballard
Iain Ballard

Reputation: 4828

For OpenQA.Selenium in C#:

WebDriver.ExecuteJavaScript("arguments[0].scrollIntoView({block: \"center\", inline: \"center\"});", Element);

Where WebDriver is new ChromeDriver(options) or similar.

Upvotes: 0

If this object passing does not works:

await browser.executeScript( "arguments[0].scrollIntoView()", await browser.wait( until.elementLocated( By.xpath( "//div[@jscontroller='MC8mtf']" ) ), 1000 ) );

There is a demonstration for scroll to microphone button, on google page. Found it by javascript xpath, using chromedriver and selenium-webdriver.

start1() start a browser in narrow window, the microphone button does not shows.

start2() start a browser in narrow window, then scroll to microphone button.

require('chromedriver');
const { Builder, 
        By, 
        Key, 
        until
      }                = require('selenium-webdriver');

async function start1()  {

  var browser = new Builder()
                    .forBrowser( 'chrome' )
                    .build();
  await browser
        .manage()
        .window()
        .setRect( { width: 80, 
                    height: 1040, 
                    x:-10, 
                    y:0} 
                );
  await browser.navigate().to( "https://www.google.com/" );
}

async function start2()  {

  var browser = new Builder()
                    .forBrowser( 'chrome' )
                    .build();
  await browser
        .manage()
        .window()
        .setRect( { width: 80, 
                    height: 1040, 
                    x:-10, 
                    y:0} 
                );
  await browser.navigate().to( "https://www.google.com/" );
  await browser.executeScript( "document.evaluate(\"//div[@jscontroller='MC8mtf']\", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.scrollIntoView(true);");
}

start1();
start2();

Upvotes: 0

djangofan
djangofan

Reputation: 29689

The default behavior of Selenium us to scroll so the element is barely in view at the top of the viewport. Also, not all browsers have the exact same behavior. This is very dis-satisfying. If you record videos of your browser tests, like I do, what you want is for the element to scroll into view and be vertically centered.

Here is my solution for Java:

public List<String> getBoundedRectangleOfElement(WebElement we)
{
    JavascriptExecutor je = (JavascriptExecutor) driver;
    List<String> bounds = (ArrayList<String>) je.executeScript(
            "var rect = arguments[0].getBoundingClientRect();" +
                    "return [ '' + parseInt(rect.left), '' + parseInt(rect.top), '' + parseInt(rect.width), '' + parseInt(rect.height) ]", we);
    System.out.println("top: " + bounds.get(1));
    return bounds;
}

And then, to scroll, you call it like this:

public void scrollToElementAndCenterVertically(WebElement we)
{
    List<String> bounds = getBoundedRectangleOfElement(we);
    Long totalInnerPageHeight = getViewPortHeight(driver);
    JavascriptExecutor je = (JavascriptExecutor) driver;
    je.executeScript("window.scrollTo(0, " + (Integer.parseInt(bounds.get(1)) - (totalInnerPageHeight/2)) + ");");
    je.executeScript("arguments[0].style.outline = \"thick solid #0000FF\";", we);
}

Upvotes: 0

Hemant
Hemant

Reputation: 99

In Java we can scroll by using JavaScript, like in the following code:

driver.getEval("var elm = window.document.getElementById('scrollDiv'); if (elm.scrollHeight > elm.clientHeight){elm.scrollTop = elm.scrollHeight;}");

You can assign a desired value to the "elm.scrollTop" variable.

Upvotes: 0

Prateek
Prateek

Reputation: 1145

If nothing works, try this before clicking:

public void mouseHoverJScript(WebElement HoverElement) {

    String mouseOverScript = "if(document.createEvent){var evObj = document.createEvent('MouseEvents');evObj.initEvent('mouseover', true, false); arguments[0].dispatchEvent(evObj);} else if(document.createEventObject) { arguments[0].fireEvent('onmouseover');}";
    ((JavascriptExecutor) driver).executeScript(mouseOverScript, HoverElement);
}

Upvotes: 0

frianH
frianH

Reputation: 7563

JAVA

Try scroll to element utilize x y position, and use JavascriptExecutor with this is argument: "window.scrollBy(x, y)".

Following import:

import org.openqa.selenium.WebElement;
import org.openqa.selenium.JavascriptExecutor;

First you need get x y location the element.

//initialize element
WebElement element = driver.findElement(By.id("..."));
        
//get position
int x = element.getLocation().getX();
int y = element.getLocation().getY();
        
//scroll to x y 
JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollBy(" +x +", " +y +")");

Upvotes: 1

Saroj Purbey
Saroj Purbey

Reputation: 280

In most of the situation for scrolling this code will work.

WebElement element = driver.findElement(By.xpath("xpath_Of_Element"));    
js.executeScript("arguments[0].click();",element);

Upvotes: 1

omnomnom
omnomnom

Reputation: 9149

def scrollToElement(element: WebElement) = {
  val location = element.getLocation
  driver.asInstanceOf[JavascriptExecutor].executeScript(s"window.scrollTo(${location.getX},${location.getY});")
}

Upvotes: 1

Bibek
Bibek

Reputation: 64

I have used this way for scrolling the element and click:

List<WebElement> image = state.getDriver().findElements(By.xpath("//*[contains(@src,'image/plus_btn.jpg')]"));

for (WebElement clickimg : image)
{
  ((JavascriptExecutor) state.getDriver()).executeScript("arguments[0].scrollIntoView(false);", clickimg);
  clickimg.click();
}

Upvotes: 1

Satheesh kumar
Satheesh kumar

Reputation: 49

The Ruby script for scrolling an element into view is as below.

$driver.execute_script("arguments[0].scrollIntoView(true);", element)
sleep(3)
element.click

Upvotes: 3

Robbie Wareham
Robbie Wareham

Reputation: 3448

From my experience, Selenium Webdriver doesn't auto scroll to an element on click when there are more than one scrollable section on the page (which is quite common).

I am using Ruby, and for my AUT, I had to monkey patch the click method as follows;

class Element

      #
      # Alias the original click method to use later on
      #
      alias_method :base_click, :click

      # Override the base click method to scroll into view if the element is not visible
      # and then retry click
      #
      def click
        begin
          base_click
        rescue Selenium::WebDriver::Error::ElementNotVisibleError
          location_once_scrolled_into_view
          base_click
        end
      end

The 'location_once_scrolled_into_view' method is an existing method on WebElement class.

I apreciate you may not be using Ruby but it should give you some ideas.

Upvotes: 4

Mohsin Awan
Mohsin Awan

Reputation: 1172

You can use this code snippet to scroll:

C#

var element = Driver.FindElement(By.Id("element-id"));
Actions actions = new Actions(Driver);
actions.MoveToElement(element).Perform();

There you have it

Upvotes: 6

Mayur
Mayur

Reputation: 676

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("javascript:window.scrollBy(250,350)");

You may want to try this.

Upvotes: 29

Ashley Frieze
Ashley Frieze

Reputation: 5458

I found that the bounding rect of my element was not correct, leading to the browser scrolling well off the screen. However, the following code works rather well for me:

private void scrollToElement(WebElement webElement) throws Exception {
    ((JavascriptExecutor)webDriver).executeScript("arguments[0].scrollIntoViewIfNeeded()", webElement);
    Thread.sleep(500);
}

Upvotes: 7

Masih Jahangiri
Masih Jahangiri

Reputation: 10957

Javascript

The solustion is simple:

const element = await driver.findElement(...)
await driver.executeScript("arguments[0].scrollIntoView(true);", element)
await driver.sleep(500);

Upvotes: 1

Mukesh otwani
Mukesh otwani

Reputation: 831

In Selenium we need to take the help of a JavaScript executor to scroll to an element or scroll the page:

je.executeScript("arguments[0].scrollIntoView(true);", element);

In the above statement element is the exact element where we need to scroll. I tried the above code, and it worked for me.

I have a complete post and video on this:

http://learn-automation.com/how-to-scroll-into-view-in-selenium-webdriver/

Upvotes: 12

Ankit Pandey
Ankit Pandey

Reputation: 339

If you want to scroll on the Firefox window using the Selenium webdriver, one of the ways is to use JavaScript in the Java code. The JavaScript code to scroll down (to bottom of the web page) is as follows:

JavascriptExecutor js = (JavascriptExecutor) driver;
js.executeScript("window.scrollTo(0, Math.max(document.documentElement.scrollHeight, document.body.scrollHeight, document.documentElement.clientHeight));");

Upvotes: 17

Amith
Amith

Reputation: 7008

Have tried many things with respect to scroll, but the below code has provided better results.

This 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

Upvotes: 307

DevDave
DevDave

Reputation: 6908

Targeting any element and sending down keys (or up/left/right) seems to work also. I know this is a bit of a hack, but I'm not really into the idea of using JavaScript to solve the scrolling problem either.

For example:

WebElement.sendKeys(Keys.DOWN);

Upvotes: 16

Effie
Effie

Reputation: 11

Selenium can scroll to some element in the scrollbar automatically for some simple UI, but for lazy-load UI, scrollToElement is still needed.

This is my implementation in Java with JavascriptExecutor. You can find more details in Satix source code: http://www.binpress.com/app/satix-seleniumbased-automation-testing-in-xml/1958

public static void perform(WebDriver driver, String Element, String ElementBy, By by) throws Exception {
 try {
  //long start_time = System.currentTimeMillis();           
  StringBuilder js = new StringBuilder();
  String browser = "firefox";

  if (ElementBy.equals("id")) {
   js.append("var b = document.getElementById(\"" +
    Element + "\");");
  } else if (ElementBy.equals("xpath")) {
   if (!"IE".equals(browser)) {
    js.append("var b = document.evaluate(\"" +
     Element +
     "\", document, null, XPathResult.ANY_TYPE, null).iterateNext();");
   } else {
    throw new Exception("Action error: xpath is not supported in scrollToElement Action in IE");
   }
  } else if (ElementBy.equals("cssSelector")) {
   js.append("var b = document.querySelector(\"" +
    Element + "\");");
  } else {
   throw new Exception("Scroll Action error");
  }

  String getScrollHeightScript = js.toString() + "var o = new Array(); o.push(b.scrollHeight); return o;";

  js.append("b.scrollTop = b.scrollTop + b.clientHeight;");
  js.append("var tmp = b.scrollTop + b.clientHeight;");
  js.append("var o = new Array(); o.push(tmp); return o;");

  int tries = 1;
  String scrollTop = "0";
  while (tries > 0) {
   try {
    String scrollHeight = ((JavascriptExecutor) driver).executeScript(getScrollHeightScript).toString();
    if (scrollTop.equals(scrollHeight)) {
     break;
    } else if (driver.findElement(by).isDisplayed()) {
     break;
    }
    Object o = ((JavascriptExecutor) driver).executeScript(js.toString());
    scrollTop = o.toString();
    Thread.sleep(interval);
    tries++;
   } catch (Exception e) {
    throw new Exception("Action error:" +
     " javascript execute error : " + e.getMessage() + ", javascript : " + js.toString());
   }
  }

 } catch (Exception e) {
  try {
   ScreenshotCapturerUtil.saveScreenShot(driver, CLASSNAME);
  } catch (IOException e1) {
   throw new Exception("Save screenshot error!", e1);
  }
  throw e;
 }
}

Upvotes: 0

Lukasz
Lukasz

Reputation: 378

This is a repeated solution with JavaScript, but with an added waiting for element.

Otherwise ElementNotVisibleException may appear if some action on the element is being done.

this.executeJavaScriptFunction("arguments[0].scrollIntoView(true);", elementToBeViewable);
WebDriverWait wait = new WebDriverWait(getDriver(), 5);
wait.until(ExpectedConditions.visibilityOf(elementToBeViewable));

Upvotes: 3

sascha
sascha

Reputation: 2031

You can use the org.openqa.selenium.interactions.Actions class to move to an element.

Java:

WebElement element = driver.findElement(By.id("my-id"));
Actions actions = new Actions(driver);
actions.moveToElement(element);
actions.perform();

Python:

from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver).move_to_element(driver.sl.find_element_by_id('my-id')).perform()

Upvotes: 203

vicentedealencar
vicentedealencar

Reputation: 933

If you think other answers were too hacky, this one is too, but there is no JavaScript injection involved.

When the button is off the screen, it breaks and scrolls to it, so retry it... ¯\_(ツ)_/¯

try
{
    element.Click();
}
catch {
    element.Click();
}

Upvotes: 1

user103
user103

Reputation: 79

This worked for me:

IWebElement element = driver.FindElements(getApplicationObject(currentObjectName, currentObjectType, currentObjectUniqueId))[0];
 ((IJavaScriptExecutor)driver).ExecuteScript("arguments[0].scrollIntoView(true);", element);

Upvotes: 6

Related Questions