I need to scroll an inner window (i.e. div). I found this web site
How to scroll to an element inside a div?
but this shows how to do Javascript. I am calling the executor but I don't think I can pass values back to java (height, etc). This is my code section. Basically there is a div window with a bunch of elements. Some are hidden on the bottom and do not actually appear in the html unless scrolled to. Once they are scrolled to I believe the will remain there. So I figured I could just scroll a large number and it would not give an error if it were too much, it would just scroll as much as possible.
I scroll to the top and then done, like this:
String sid = rolesScroller.getAttribute("id");
js.executeScript("document.getElementById('" + sid + "').scrollTop -= 1000");
js.executeScript("document.getElementById('" + sid + "').scrollTop += 1000");
waitForXPathVisibility("Scroll", ROLES_SCROLLER_X);
will this be ok or do I need to somehow figure the exact amount to scroll and scroll just by that amount?
I see there is an element.scrollHeight. Does the possible values for scrollTop go from 0 to scrollHeight? Are the units both in pixels?
The elements in the divs are themselves nodes (list values) which can also be expanded creating more elements underneath. Every time I search for a value I have to do the above to make sure everything is in view. The way I have it now works to an extent. But sometimes after scrolling when I try to access a node I get a StaleElementException. However if I do a waitForStaleElement() it sometimes gives an error saying the element did not go stale. Is there a way after executing the javascript that you can make sure all actions have completed so that a stale element won't happen?
To Summarize
When using the javascript executor from Java/Selenium is there a way to pass the javascript variables back to java so they can be used in later jasascript executor commands? (if that example above of -1000 +1000 is OK then this does not matter).
How can you ensure that the javascript command has completed before continuing so when you try to access an element in the scrolled div you will not get a stale element (I tried examining one element in the div, and the div itself).
It seems that the container is dynamically constructed upon scrolling. Try to scroll the last element at the top with scrollIntoView and then wait for a different element at the end:
WebDriverWait wait = new WebDriverWait(driver, 5);
JavascriptExecutor jse = (JavascriptExecutor)driver;
// get the last element
By lastChild = By.cssSelector("#list > div:last-of-type");
WebDriver elem = driver.findElement(lastChild);
// scroll the last element at the top
jse.executeScript("arguments[0].scrollIntoView(true);", elem);
// wait for a new element at the end
wait.until((WebDriver drv) -> !elem.equals(drv.findElement(lastChild)))
Related
I'm writing in C# with selenium. However, the best way I've found to scroll a page was to use:
IJavaScriptExecutor js = driver as IJavaScriptExecutor;
js.ExecuteScript("window.scrollBy(0,900);");
However, in my current case, the window I need to scroll is not the full page but a part of it. And this command doesn't do anything. I imagined that I need to select the element first so I tried something like this:
js.ExecuteScript("document.getElementsByClassName('scroller')[0].scrollBy(0,500)")
This didn't work either and I'm not sure if its because its wrong as I'm not particularly familiar with JS or if I'm doing something else wrong, like selecting the wrong element to try and scroll.
To sum up my questions are, is there a better way to scroll a window in c# selenium? Is my js code to try and scroll the element wrong? And is there a way to figure out which is the correct element i should try to scroll?
You can use the scrollIntoView(true); to do that, it will brings up the passed element view.
Suppose that you want to scroll until the below element
WebElement element = driver.getElementByClassName('scroller');
then you can do like this :
js.ExecuteScript("arguments[0].scrollIntoView(true);", element);
For multiple elements, you can try the below by passing a matching index number :
js.ExecuteScript("arguments[0].scrollIntoView(true);", driver.getElementsByClassName('scroller')[pass the index number here]);
I have created a notification system with JavaScript. Everything works fine except the fade-out animation (notification-anim). If a new notification is displayed, the previous notifications animation will be reset, and notification window will start fading out from the start again. But each should fade out 5 seconds exactly after it's displayed. And if there are no notifications in the last 5 seconds, notification container gets overwritten with innerHTML = ''; and notifCount goes back to 0.
Why is this happening, and is there a solution for this?
jsfiddle
I have tried to explain everything as best as i could
Assigning a new value to innerHTML has the following effect:
Removes all of element's children, parses the content string and
assigns the resulting nodes as children of the element.
This of course causes all animations of these elements to play again.
One solution is to use insertAdjacentHTML instead:
Element.insertAdjacentHTML - An alternative for innerHTML, allowing
you to append the new HTML, instead of replacing it.
Your code will then look as follows:
elem('notifContainer').insertAdjacentHTML('beforeend', notif);
elem('notif' + notifCount).style.animation = 'notification-anim 5s ease-in forwards';
Note the additional forwards in the CSS animation to stop the animation once it's completed.
PS: If you are wondering why innerHTML += notif doesn't just 'append' but performs a regular assignment of a completely new value: a += b is 'just' a shorthand notation of a = a + b (according to the specification https://www.ecma-international.org/ecma-262/8.0/index.html#prod-AssignmentOperator).
I am writing a document reading application using Node-Webkit. A document can be hundreds of pages long and the interface allows opening the document to a specific chapter. Once the initial chapter is displayed, I want to load the rest of the book asynchronously. Since I don't know what direction the reader will scroll, my strategy is to alternately load chapters before and then after the initial chapter:
load chapter x -> load chapter x-1 -> load chapter x+1 -> load x-2 -> load x+2 ...
I am displaying the book in a containing div element with each chapter being a div within the container. I'm using jquery .before() and .after() to insert each chapter as it is fetched.
My problem is that the browser automatically scrolls up when I add chapters earlier in the book. So if I start with chapter 10, the browser scrolls up when I add chapter 9 and again with chapter 8 and so forth.
One (unsatisfactory) solution to the problem is to make an anchor tag for each chapter and store the id of the first chapter loaded. Then after each chapter is fetched, runing the code:
window.location.href = #originalChapter
keeps the initial chapter in the browser viewport. Of course the problem with that is that the reader cannot scroll while the rest of the book is being loaded.
Ideally, I would like to disable scrolling, load a chapter and then re-enable until the next chapter is fetched. I'm so far unable to figure out how to do that.
If you're updating the DOM from the click handler of an anchor (<a/>) tag, make sure you return false from the callback function.
The simple solution in my mind would be to not have the elements in the DOM until they are loaded. The user won't be able to scroll because there won't be content to scroll to. Then, it's just a matter of preserving the viewport when the elements are added.
Take the initial position of your scroll container and the height of the overflow container:
var scrollTop = $scroll.scrollTop(),
height = $container.height();
and use them to preserve the viewport when you prepend new elements. You don't have to do anything when you're doing the append operation.
var newHeight = $container.height(),
newScrollTop = scrollTop + newHeight - height;
$scroll.scrollTop(newScrollTop);
Here's a quick example: http://jsfiddle.net/Wexcode/tfszaocz/
I'm trying to dynamically insert some HTML/JS/CSS on command. (holding
off this code for page loading speed). I found a neat way
of doing this, inserting a HTML5 tag pointing at the html-
file which in turn references the css and js, like so:
function toggleObject() {
var object = document.getElementById('myObject');
if (!object) {
var e = document.createElement('object');
e.setAttribute('data', 'testing.html');
e.setAttribute('id', 'myObject');
// inject data into DOM
document.getElementsByTagName('body')[0].appendChild(e);
} else {
document.getElementsByTagName('body')[0].removeChild(object);
}}
The only problem with this is that upon inserting the tag the object (height, width and position as defined by css) flashes white before loading which isn't very attractive.
Is there a remedy for the ugly white flash?
Note! I experimented with toggling the visibility property of the object and firing up a loader div, but I can't figure what event would be able to call off the loader and turn visibility back on when the object is fully injected in the DOM. In end I settled for just a timeout of 1 sec, which feels less than optimal..
Try setting the visibility to hidden when you create the OBJECT Element and then setting it to visible once it has been appended to its parent Node.
I have a pretty specific scenario where I would like to select all elements with jQuery, make a CSS change, save the elements, then reverse the change I made.
The Goal
I created a jQuery plugin called jQuery.sendFeedback. This plugin allows the user to highlight areas of the screen, as shown in this demo. When they submit their feedback the plugin grabs all the HTML on the page and dumps it into a callback function. Like so:
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
var feedbackInformation = {
subject: $feedbackSubject.val(),
details: $feedbackDetails.val(),
html: '<html>' + $('html').html() + '</html>'
};
if (settings.feedbackSent)
settings.feedbackSent(feedbackInformation);
The callback function accepts this feedback information and makes an AJAX call to store the page HTML on the server (this HTML includes the red box highlights the user drew on the screen). When someone from tech support needs to view the user's "screen shot" they navigate to a page that serves up the stored HTML so the developer can see where the user drew their highlights on the screen.
My original problem was that different screen resolutions made the elements different sizes and the red highlights would highlight the wrong areas as the screen changed. This was fixed pretty easily by selecting all elements on the page and manually setting their height and width to their current height and width when the user takes the snap shot. This makes all the element sizes static, which is perfect.
$('*').each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
The Problem
The issue with this is that when the plugin is done transmitting this HTML the page currently being viewed now has static heights and widths on every element. This prevents dropdown menus and some other things from operating as they should. I cannot think of an easy way to reverse the change I made to the DOM without refreshing the page (which may very well end up being my only option). I'd prefer not to refresh the page.
Attempted Solution
What I need is a way to manipulate the HTML that I'm sending to the server, but not the DOM. I tried to change the above code to pull out the HTML first, then do the operation on the string containing the HTML (thus not affecting the DOM), but I'm not quite sure what I'm doing here.
var html = '<html>' + $('html').html() + '</html>';
$('*', html).each(function ()
{
$(this).width($(this).width());
$(this).height($(this).height());
});
This did not work. So either I need to be able to manipulate the string of HTML or I need to be able to manipulate the DOM and undo the manipulation afterward. I'm not quite sure what to do here.
Update
I employed the solution that I posted below it is working beautifully now. Now I am wondering if there is a way to statically write all the css for each element to the element, eliminating the need for style sheets to be referenced.
I think you are mostly on the right track by trying to make the modifications to the HTML as a string rather than on the current page for the user.
If you check this post, you might also want to follow the recommendation of creating a temporary <div> on the page, cloning your intended content to the new <div> ensuring it is invisible using "display:none." By also putting a custom Id on the new <div> you can safely apply your static sizing CSS to those elements using more careful selectors. Once you have sent the content to the server, you can blow away the new <div> completely.
Maybe?
After much pain and suffering I figured a crude but effective method for reverting my modifications to the DOM. Though I hadn't gotten around to trying #fdfrye's suggestion of cloning, I will be trying that next to see if there is a mroe elegant solution. In the meantime, here is the new code in case anyone else can benefit from it:
$('*').each(function () {
if ($(this).attr('style'))
$(this).data('oldStyle', $(this).attr('style'));
else
$(this).data('oldStyle', 'none');
$(this).width($(this).width());
$(this).height($(this).height());
});
var html = '<html>' + $('html').html() + '</html>';
$('*').each(function () {
if ($(this).data('oldStyle') != 'none')
$(this).attr('style', $(this).data('oldStyle'));
else
$(this).removeAttr('style');
});
When I'm looping through every element and modifying the css, I log the original value onto the element as data. After I assign the DOM HTML to a variable I then loop through all elements again and restore the style attribute to its original value. If there was no style attribute then I log 'none' to the element data and then remove the style attribute entirely when looping through again.
This is more performance heavy than I wish it was since it loops through all elements twice; it takes a few seconds to finish. Not horrible but it seems like a little much for such a small task. Anyway, it works. I get a string with fixed-sized HTML elements and the DOM goes back to normal as if the plugin never touched it.