Can a scrollIntoView function save its position? - javascript

I am having a problem with JS scrollIntoView getting reset after a selenium webElement.click() was performed. When I have used it on a custom scrollbox, it performs action correctly and then scrolls back to top after hitting .click().
element = driver.findElement(By.xpath("//div[#id='iLegenda']/ul/li[4]/i"));
((JavascriptExecutor)driver).executeScript("arguments[0].scrollIntoView(true);", element);
Thread.sleep(500);
driver.findElement(By.xpath("//div[#id='iLegenda']/ul/li[4]/i")).click(); //this line resets the position of the affected scrollbar
An alternate solution would be force-clicking the element even though it's not displayed on screen, but I haven't found any info on that front yet...

That's not something you can control. It's something to do with whatever the click is doing. If it were me, I would put the .scrollIntoView() code into a function so that it's easier to call, e.g.
public void scrollIntoView(WebElement e)
{
((JavascriptExecutor) driver).executeScript("arguments[0].scrollIntoView(true);", e);
}
and call it like
element = driver.findElement(By.xpath("//div[#id='iLegenda']/ul/li[4]/i"));
scrollIntoView(element);
Also, Thread.Sleep() is a bad practice. (You can google to learn the reasons why). You shouldn't need a wait here anyway but in case you do, it should be something like
WebDriverWait wait = new WebDriverWait(driver, 2);
WebElement element = driver.findElement(locator);
wait.until(ExpectedConditions.elementToBeClickable(element)).click();
I use Java and Selenium all the time and I've never had to scroll before clicking an element. Have you tried something simple like
driver.findElement(By.xpath("//div[#id='iLegenda']/ul/li[4]/i")).click();
with no scroll and no wait? It should just work.
Also, if you are going to click the same element repeatedly, you should do something like
By locator = By.xpath("//div[#id='iLegenda']/ul/li[4]/i");
driver.findElement(locator).click();
// do something
driver.findElement(locator).click();
// ... and so on...

Related

Python Selenium button isn't being clicked

I am trying to scrape data from this website (https://www.ilcollege2career.com/#/) using python (selenium and beautiful soup).
The code I have is this:
driver = webdriver.Chrome('my file path')
driver.get('https://www.ilcollege2career.com/#/')
first_click = WebDriverWait(driver,5).until(EC.element_to_be_clickable((By.XPATH, '//*[#id="tutorial-modal"]/div/div/div/div[3]/button[1]')))
first_click.click()
second_click = WebDriverWait(driver,5).until(EC.element_to_be_clickable((By.XPATH, '//*[#id="tutorial-start-modal"]/div/div/div[2]/div[2]')))
second_click.click()
So my problem is that while the first click works and it goes to the tutorial step the second click which will close the tutorial doesn't click. For some reason time.sleep() works but I don't want to have to keep repeating that every step. Am I doing something wrong?
I have also tried find element by css as well.
Thank you.
The xpath to the second_click is not accurate in the sense that it does not send the click to correct element. try this out,
driver.fullscreen_window()
driver.get('https://www.ilcollege2career.com/#/')
first_click = WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH, '//*[#id="tutorial-modal"]/div/div/div/div[3]/button[1]')))
first_click.click()
Option#1 -
second_click = WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH, "//button[#onclick='closeTutorial()']")))
second_click.click()
Option#2 -
second_click = WebDriverWait(driver,10).until(EC.element_to_be_clickable((By.XPATH, "//div[#onclick='closeTutorial()']")))
second_click.click()
I found the solution to those who are looking for it.
invisible = WebDriverWait(driver, 10).until(EC.invisibility_of_element_located((By.ID, 'tutorial-modal')))
if invisible:
There was something running behind that wouldn't close out so I was never able to close that and by doing this I was able to exit out.

Chrome ignoring hashes in URL

I've spent quite a while trying to find answers for this issue, but haven't had any success. Basically I need to scroll the user to the contact portion of the website when they go to healthdollars.com/#contact. This works just fine in Safari, but in Chrome I haven't had any luck. I've tried using jQuery/Javascript to force the browser to scroll down, but I haven't been able to.
Does anyone have any ideas? It's driving me crazy - especially since it's such a simple thing to do.
Not a full answer but in Chrome if you disable Javascript I believe you get the desired behavior. This makes me believe that something in your JavaScript is preventing default browser behavior.
It looks to me like the target element doesn't exist when when page first loads. I don't have any problem if I navigate to the page and then add the hash.
if (window.location.hash.length && $(location.hash)) {
window.scrollTo(0, $(location.hash).offset().top)
}
check for a hash, find the element's page offset, and scroll there (x, y).
edit: I noticed that, in fact, the page starts at #contact, then scrolls back to the top. I agree with the other answerer that there's something on your page that's scrolling you to the top. I'd search for that before adding a hack.
You can do this with JS, for example` if you have JQuery.
$(function(){
// get the selector to scroll (#contact)
var $to = $(window.location.hash);
// jquery animate
$('html'/* or body */).animate({ scrollTop: $to.offset().top });
});
The name attribute doesn't exists in HTML 5 so chrome looks to have made the name attribute obsolete when you use the DOCTYPE html.
The other browsers have yet to catch up.
Change
<a name="contact"></a>
to
<a id="contact"></a>
Maybe this workaround with vanilla javascript can be useful:
// Get the HTMLElement that you want to scroll to.
var element = document.querySelector('#contact');
// Stories the height of element in the page.
var elementHeight = element.scrollHeight;
// Get the HTMLElement that will fire the scroll on{event}.
var trigger = document.querySelector('[href="#contact"]');
trigger.addEventListener('click', function (event) {
// Hide the hash from URL.
event.preventDefault();
// Call the scrollTo(width, height) method of window, for example.
window.scrollTo(0, elementHeight);
})

Is protractor.findElement() meant to scroll to that element?

My current unit test (protractor + angularJS project) is failing with the error UnknownError: unknown error: Element is not clickable at point (525, 1103). I used debugger to stop it just before failure, and the only reason I can think that it would fail is because the button is not in the view port (you would have to scroll down).
The failing lines are
homeLink = ptor.findElement(protractor.By.linkText('Home'));
homeLink.click();
expect(ptor.getCurrentUrl()).toBe(homeUrl);
From https://github.com/angular/protractor/issues/319, he says '...when i use findElement() it will scroll them to the "top" of the page'. And the comments agree.
In my test homeLink = ptor.findElement(protractor.By.linkText('Home')); is not causing the the page to scroll.
Am I wrong in thinking it should?
What should I be doing?
You need to scroll down (or maximize browser if this allows you to see the button you would like to click on) first so that the button is visible on the page:
var scrollIntoView = function () {
arguments[0].scrollIntoView();
}
browser.executeScript(scrollIntoView, yourwebelement);
don't forget to get the webElement browser.driver.executeScript("arguments[0].scrollIntoView(true);", ed.getWebElement());

force DOM redraw with javascript on demand

The title of the question expresses what I think is the ultimate question behind my particular case.
My case:
Inside a click handler, I want to make an image visible (a 'loading' animation) right before a busy function starts. Then I want to make it invisible again after the function has completed.
Instead of what I expected I realize that the image never becomes visible. I guess that this is due to the browser waiting for the handler to end, before it can do any redrawing (I am sure there are good performance reasons for that).
The code (also in this fiddle: http://jsfiddle.net/JLmh4/2/)
html:
<img id="kitty" src="http://placekitten.com/50/50" style="display:none">
<div>click to see the cat </div>
js:
$(document).ready(function(){
$('#enlace').click(function(){
var kitty = $('#kitty');
kitty.css('display','block');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec)
{
var endtime= new Date().getTime() + usec;
while (new Date().getTime() < endtime)
;
}
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.css('display','none');
});
});
I have added the alert call right after the sleepStupidly function to show that in that moment of rest, the browser does redraw, but not before. I innocently expected it to redraw right after setting the 'display' to 'block';
For the record, I have also tried appending html tags, or swapping css classes, instead of the image showing and hiding in this code. Same result.
After all my research I think that what I would need is the ability to force the browser to redraw and stop every other thing until then.
Is it possible? Is it possible in a crossbrowser way? Some plugin I wasn't able to find maybe...?
I thought that maybe something like 'jquery css callback' (as in this question: In JQuery, Is it possible to get callback function after setting new css rule?) would do the trick ... but that doesn't exist.
I have also tried to separte the showing, function call and hiding in different handlers for the same event ... but nothing. Also adding a setTimeout to delay the execution of the function (as recommended here: Force DOM refresh in JavaScript).
Thanks and I hope it also helps others.
javier
EDIT (after setting my preferred answer):
Just to further explain why I selected the window.setTimeout strategy.
In my real use case I have realized that in order to give the browser time enough to redraw the page, I had to give it about 1000 milliseconds (much more than the 50 for the fiddle example). This I believe is due to a deeper DOM tree (in fact, unnecessarily deep).
The setTimeout let approach lets you do that.
Use JQuery show and hide callbacks (or other way to display something like fadeIn/fadeOut).
http://jsfiddle.net/JLmh4/3/
$(document).ready(function () {
$('#enlace').click(function () {
var kitty = $('#kitty');
// see: http://unixpapa.com/js/sleep.html
function sleepStupidly(usec) {
var endtime = new Date().getTime() + usec;
while (new Date().getTime() < endtime);
}
kitty.show(function () {
// simulates bussy proccess, calling some function...
sleepStupidly(4000);
// when this triggers the img style do refresh!
// but not before
alert('now you do see it');
kitty.hide();
});
});
});
Use window.setTimeout() with some short unnoticeable delay to run slow function:
$(document).ready(function() {
$('#enlace').click(function() {
showImage();
window.setTimeout(function() {
sleepStupidly(4000);
alert('now you do see it');
hideImage();
}, 50);
});
});
Live demo
To force redraw, you can use offsetHeight or getComputedStyle().
var foo = window.getComputedStyle(el, null);
or
var bar = el.offsetHeight;
"el" being a DOM element
I do not know if this works in your case (as I have not tested it), but when manipulating CSS with JavaScript/jQuery it is sometimes necessary to force redrawing of a specific element to make changes take effect.
This is done by simply requesting a CSS property.
In your case, I would try putting a kitty.position().left; before the function call prior to messing with setTimeout.
What worked for me is setting the following:
$(element).css('display','none');
After that you can do whatever you want, and eventually you want to do:
$(element).css('display','block');

Trigger event on animation complete (no control over animation)

I've a scenario that requires me to detect animation stop of a periodically animated element and trigger a function. I've no control over the element's animation. The animation can be dynamic so I can't use clever setTimeout.
Long Story
The simplified form of the problem is that I'm using a third party jQuery sliding banners plugin that uses some obfuscated JavaScript to slide banners in and out. I'm in need of figuring out a hook on slideComplete sort of event, but all I have is an element id. Take this jsfiddle as an example and imagine that the javascript has been obfuscated. I need to trigger a function when the red box reaches the extremes and stops.
I'm aware of the :animated pseudo selector but I think it will need me to constantly poll the required element. I've gone through this, this, and this, but no avail. I've checked jquery promise but I couldn't figure out to use that in this scenario. This SO question is closest to my requirements but it has no answers.
P.S. Some more information that might be helpful:
The element isn't created by JavaScript, it is present on page load.
I've control over when to apply the plugin (that makes it periodically sliding banner) on the element
Most of the slideshow plugins I have used use changing classes at the end of the animation... You could extend the "addClass" method of jQuery to allow you to capture the class change as long as the plugin you use is using that method like it should:
(function($){
$.each(["addClass","removeClass"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
oldmethod.apply( this, arguments );
this.trigger(methodname+"change");
return this;
}
});
})(jQuery);
I threw together a fiddle here
Even with obfuscated code you should be able to use this method to check how they are sending in the arguments to animate (I use the "options" object when I send arguments to animate usually) and wrap their callback function in an anonymous function that triggers an event...
like this fiddle
Here is the relevant block of script:
(function($){
$.each(["animate"],function(i,methodname){
var oldmethod = $.fn[methodname];
$.fn[methodname] = function(){
var args=arguments;
that=this;
var oldcall=args[2];
args[2]=function(){
oldcall();
console.log("slideFinish");
}
oldmethod.apply( this, args );
return this;
}
});
})(jQuery);
Well since you didn't give any indication as to what kind of animation is being done, I'm going to assume that its a horizontal/vertical translation, although I think this could be applied to other effects as well. Because I don't know how the animation is being accomplished, a setInterval evaluation would be the only way I can guess at how to do this.
var prevPos = 0;
var isAnimating = setInterval(function(){
if($(YOUROBJECT).css('top') == prevPos){
//logic here
}
else{
prevPos = $(YOUROBJECT).css('top');
}
},500);
That will evaluate the vertical position of the object every .5 seconds, and if the current vertical position is equal to the one taken .5 seconds ago, it will assume that animation has stopped and you can execute some code.
edit --
just noticed your jsfiddle had a horizontal translation, so the code for your jsfiddle is here http://jsfiddle.net/wZbNA/3/

Categories