In one of the tests, I need to scroll into view of an element which can be done via scrollIntoView() method parameterizing the script with an element located via Protractor:
var elm = element(by.id("myid"));
browser.executeScript("arguments[0].scrollIntoView();", elm.getWebElement());
But, we can also find the element directly via getElementById():
browser.executeScript("document.getElementById('myid').scrollIntoView();");
What is the difference between the two approaches?
The scrollIntoView() is chosen for sample purposes only. The logic inside the script can be more complex.
The first one will tell you explicitly when the element is missing while the second one will raise a JavaScript error saying that null doesn't have the .scrollIntoView method. So to keep the second one maintainable, you need to implicitly handle the case when the element is missing and raise an error with an appropriate message.
The first one is exposed to an unexpected/stale state. The element found by element(by.id("myid")) could be removed from the page just before executing browser.executeScript(). The second one is not exposed to this issue since the page is not updated while the script is executed.
The second one is less expensive since it executes one Selenium command (ExecuteScript), while the first one executes two (FindElement and ExecuteScript). Sending a Selenium command to the browser is relatively expensive (minimum of 25ms) and might become significant with multiple calls.
Both will produce the exact same result and will end up calling the same API on the browser side.
A couple things come to mind.
Maintenance going forward. What happens if your .scrollIntoView() element locator changes? I think I would prefer finding the element using Selenium instead of JS. Reduced chance to have typos, type checking, and so on with an IDE but the IDE won't look into the string that contains your JS.
Selenium accessibility. Since Selenium can't see invisible elements, it could affect your choice either way. Would you want an exception to be thrown if you are trying to scroll to an invisible element? Selenium would let you know where JS wouldn't. Maybe you want to intentionally scroll to an invisible element. Finding invisible elements is a job for JS but not Selenium.
There may be more but this is all I can think of off the top of my head.
Related
There's a website I need to check at constant intervals but it has certain JS functions programmed into it that are more aesthetic than anything else, and the result is that it eats up my CPU for no good reason, not to mention that it adds 1-2 seconds of time for the DOM to be ready, when I need it to load as quickly as possible.
For instance, the website displays several elements on it, each in its own wrapper. Each element has a title, which may be long or short. So instead of just letting the title run on and be cut off by the border (as the overflow is hidden as per the original CSS) it parses all the titles and then trims them, adding an ellipsis at the end. Now, this is purely aesthetic, of course, as you still have no access to the part of the title that's cut of, so it's the same if you just let it be cut off "behind" the hidden overflow. On the other hand, having to take care of each title element (sometimes there may even be 100 elements on screen at a time) uses up a lot of time and resources.
There are several other instances such as this one. And my question is: is there any way I can inject some JS on my side that overrides or cancels such functions as the one that affects the titles as I've described?
P.S. The website in question uses also jQuery. I don't know if that detail makes any difference.
You can't "stop a JS process" per-se (your injected javascript will be running in the same process), but you might be able to interfere with it.
Find a function which does the thing you want to stop, and just overwrite it with an empty function:
some.object.in.the.other.code.doSomething = function() {}
Note that you will need to ensure that your code injection occurs before the other script is executed. If this is not feasible, you will have to find some function further up in the call stack which will be executed after your injected script.
I'm developing an jsf based application using a library called primefaces.
Primefaces generates a web page containing javascript functions that are generating some buttons. Buttons are not visible in the source code of the page (like < button>...< /button>, and so Watir can't click on these to test something.
My question is : is it possible to recover the id or class of button generated by a javascript function with Watir ?
Thanks
In practice, using sleep is not recommended. Depending on the amount of time the browser & JavaScript code takes to perform its action, you could run into race conditions and end up with tests that only work sometimes.
If you're considering using sleep, use wait_until instead. It will make your test code more resilient to timing issues in those cases where you really need to use it.
As #JustinKo recommended, you should probably be using Watir's wait methods to wait until the element is displayed on the page.
In your case, you'd want to try something like these examples:
# Assuming your browser object is named: $b
#Ex: $b = Watir::Browser.new :chrome
$b.button(:id => 'my_button').wait_until_present
Watir::Waiter::wait_until { $b.button(:id => 'my_button').visible? }
If you are having trouble finding the element on the page, you might want to look a the source code in FireBug or the Developer Tools console in Chrome (Shift+Ctrl+I). Try to find some identifying attribute for that button, or a container element with an id, class, name or some other identifying attribute.
However, at this time I'm not sure that there is a way to detect whether an element was generated or added to the DOM by JavaScript.
I have been struggling with choosing unobtrusive javascript over defining it within the html syntax. I want to convince my self to go the unobtrusive route, but I am having trouble getting past the issues listed below. Can you please help convince me :)
1) When you bind events unobtrusively, there is extra overhead on the client's machine to find that html element, where as when you do stuff, you don't have to iterate the DOM.
2) There is a lag between when events are bound using document.ready() (jquery) and when the page loads. This is more apparent on very large sites.
3) If you bind events (onclick etc) unobtrusively, there is no way of looking at the html code and knowing that there is an event bound to a particular class or id. This can become problematic when updating the markup and not realizing that you may be effecting javascript code. Is there a naming convention when defining css elements which are used to bind javascript events (i have seen ppl use js_className)
4) For a site, there are different pieces of javascript for different pages. For example Header.html contains a nav which triggers javascript events on all pages, where as homepage.html and searchPage.html contains elements that trigger javascript on their respective pages
sudo code example:
header.html
<script src="../myJS.js"></script>
<div>Header</div>
<ul>
<li>nav1</li><li>nav2</li>
</ul>
homepage.html
<#include header.html>
<div class="homepageDiv">some stuff</div>
searchpage.html
<#include header.html>
<div class="searchpageDiv">some other stuff</div>
myJS.js
$(document).ready(function(){
$("ul.li").bind("click",doSomething());
$(".homePageDiv").bind("click",doSomethingElse());
$(".searchPageDiv").bind("click",doSomethingSearchy());
});
In this case when you are on the searchPage it will still try to look for the "homepageDiv" which does not exist and fail. This will not effect the functionality but thats an additional unnecessary traversal. I could break this up into seperate javascript files, but then the browser has to download multiple files, and I can't just serve one file and have it cached for all pages.
What is the best way to use unobtrusive javascript so that I could easily maintain a ( pretty script heavy) website, so another developer is aware of scripts being bound to html elements when they are modifying my code. And serve the code so that the client's browser is not looking for elements which do not exist on a particular page (but may exist on others).
Thanks!
You are confused. Unobtrusive JavaScript is not just about defining event handlers in a program. It's a set of rules for writing JavaScript such that the script doesn't affect the functionality of other JavaScript on the same page. JavaScript is a dynamic language. Anyone can make changes to anything. Thus if two separate scripts on the same page both define a global variable add as follows, the last one to define it will win and affect the functionality of the first script.
// script 1
var add = function (a, b) {
return a + b;
};
// script 2
add = 5;
//script 1 again
add(2, 3); // error - add is a number, not a function
Now, to answer your question directly:
The extra overhead to find an element in JavaScript and attach an event listener to it is not a lot. You can use the new DOM method document.querySelector to find an element quickly and attach an event listener to it (it takes less than 1 ms to find the element).
If you want to attach your event listeners quickly, don't do it when your document content loads. Attach your event listeners at the end of the body section or directly after the part of your HTML code to which you wish to attach the event listener.
I don't see how altering the markup could affect the JavaScript in any manner. If you try to attach an event listener to an element that doesn't exist in JavaScript, it will silently fail or throw an exception. Either way, it really won't affect the functionality of the rest of the page. In addition, a HTML designer really doesn't need to know about the events attached any element. HTML is only supposed to be used for semantic markup; CSS is used for styling; and JavaScript is used for behavior. Don't mix up the three.
God has given us free will. Use it. JavaScript supports conditional execution. There are if statements. See if homePageDiv exists and only then attach an event listener to it.
Try:
$(document).ready(function () {
$("ul.li").bind("click",doSomething());
if (document.querySelector(".homePageDiv")) {
$(".homePageDiv").bind("click",doSomethingElse());
} else {
$(".searchPageDiv").bind("click",doSomethingSearchy());
}
});
Your question had very little to do with unobtrusive JavaScript. It showed a lack of research and understanding. Thus, I'm down voting it. Sorry.
Just because jQuery.ready() executes does not mean that the page is visible to the end user. This is a behaviour defined by browsers and these days there are really 2 events to take into consideration here as mootools puts it DomReady vs Load. When jQuery executes the ready method it's talking about the dom loading loaded however this doesn't mean the page is ready to be viewed by the user, external elements which as pictures and even style sheets etc may still be loading.
Any binding you do, even extremely inefficient ones will bind a lot faster than all the external resources being loaded by the browser so IMHO user should experience no difference between the page being displayed and functionality being made available.
As for finding binding on elements in your DOM. You are really just fearing that things will get lost. This has not really been my actual experience, more often than not in your JS you can check what page you are on and only add javascript for that page (as Aadit mentioned above). After that a quick find operation in your editor should help you find anything if stuff gets lost.
Keep in mind that under true MVC the functionality has to be separate from the presentation layer. This is exactly what OO javascript or unobtrusive javascript is about. You should be able to change your DOM without breaking the functionality of the page. Yes, if you change the css class and or element id on which you bind your JS will break, however the user will have no idea of this and the page will at least appear to work. However if this is a big concern you can use OO-Javascript and put div's or span's as placeholders in your dom and use these as markers to insert functionality or tell you that it exists, you can even use html comments. However, in my experience you know the behavior of your site and hence will always know that there is some JS there.
While I understand most of your concerns about useless traversals, I do think you are nickle and dime'ing it at this point if you are worried about 1 additional traversal. Previous to IE8 it used to be the case that traversing with the tag name and id was a lot faster than my selector but this is no longer true infact browsers have evolved to be much faster when using just the selectors:
$("a#myLink") - slowest.
$("a.myLink") - faster.
$("#Link") - fastest.
$(".myLink") - fastest.
In the link below you can see that as many as 34 thousand operations per second are being performed so I doubt speed is an issue.
You can use firebug to test the speed of each in the case of a very large dom.
In Summary:
a) Don't worry about losing js code there is always ctrl+f
b) There is no lag because dom ready does not mean the page is visible to start with.
Update
Fixed order of speed in operations based on the tests results from here
However keep in mind that performances of IE < 8 are really had if you don't specify the container (this used to be the rule, now it seems to be the exception to the rule).
Say we have 2 javascript scripts trying to change the same DOM element.
One tries to enter html into a div and the other tries to move the div elsewhere on the page (for A/B testing) and we cant predict when they will finish loading and run.
Can it mess up the html page? Do the browsers know to prevent that?
You can't really call something at the same time. JavaScript isn't multi-threaded and you can only do one thing at a time.
Your example deals with the DOM which is not really related to the JavaScript engine but the DOM engine (which is multi-threaded). You can do many things concurrently in the DOM, however, since JavaScript is the interface to the DOM, JavaScript will not let you call two DOM related functions at the same time - one will always be called before the other, even though they happen concurrently inside the DOM memory space (not JavaScript's).
Well, JavaScript is single-threaded, so although it may seem that both are executing at the same time, they aren't really. If they're modifying the same element, sooner or later one will try to modify something that doesn't exist. This case, however, will be no different from any other attempt to change a nonexistent DOM element. The browsers won't try to prevent this (after all, your code tells them to do it).
(excuse me if this is not the right forum to post - i couldn't find anything related to non-native programming and related to this topic)
I Am trying to set a dynamic HTML into an iFrame on the webpage. I have tried a couple of things but none of them seem to work. I m able to read the innerHTML but can't seem to update it.
// Able to read using
document.getElementById('iFrameIdentifier').innerHTML;
// On Desktop IE, this code works
document.getElementById('iFrameId').contentWindow.document.open();
document.getElementById('iFrameId').contentWindow.document.write(dynamicHTML);
document.getElementById('iFrameId').contentWindow.document.close();
Ideally the same function should work as how it works for div's but it says 'Object doesn't support this method or property".
I have also tried document.getElementById('iFrameId').document.body.innerHTML.
This apparently replaces the whole HTML of the page and not just the innerHTML.
I have tried out a couple of things and they didn't work
document.getElementById('iFrameId').body.innerHTML
document.frames[0].document.body.innerHTML
My purpose is to have a container element which can contain dynamic HTML that's set to it.
I've been using it well till now when I observed that the setting innerHTML on a div is taking increasing amount of time because of the onClicks or other JS methods that are attached to the anchors and images in the dynamic HTML. Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Also being discussed - http://www.experts-exchange.com/Programming/Languages/Scripting/JavaScript/Q_26185526.html#a32779090
I have tried a couple of things but none of them seem to work.
Welcome to IEMobile! Nothing you know about DOM scripting applies here.
Unfortunately, cross-iframe scripting does not appear to be possible in IEMobile6-7.
frameelement.contentDocument (the standard DOM method) isn't available
frameelement.contentWindow.document (the IE6-7 workaround version) isn't available
the old-school Netscape window.frames array only works for frames, not iframes
having the child document pass up its document object to the window.parent only works for frames, not iframes. In an iframe, window.parent===window.
So the only ways forward I can see are:
use frames instead of iframes. Nasty. Or,
use document.cookie to communicate between parent and child: the child document is just a script, that checks for a particular cookie in document.cookie on a poller, and when it's found that's a message from the parent, and it can write some HTML or whatever. Slow and nasty. Or,
using the server-side to inject content into the frames, passing it in as an argument to a script. Slow, nasty, and potentially insecure. Or,
avoid frames completely (best, if you can). Or,
drop support from IEMobile6-7 (best for preserving your sanity, if you can get away with it!)
Appears the JS methods or the HTML is some how not getting cleaned up properly (memory leak?)
Yes, probably. IEMobile6-7(*) is close to unusable at dynamic HTML. It gives you a lovely flavour of what scripting used to be like for us poor gits back in the Netscape 4 days.
Try to avoid creating and destroying lots of nodes and event handlers. Keep the page as static as possible, re-using element nodes where possible and setting text node data properties in preference to tearing everything down and making anew with createElement or innerHTML. Use an event stub (onclick="return this._onclick()") in the HTML together with writing to _onclick if you need to set event handlers from JavaScript, in preference to recreating the HTML with a new event handler (or just trying to set the property, which of course doesn't work in IEMobile). Avoid long-running single pages when you can.
It'll still crash, but hopefully it'll take longer.
*: that is, the versions of IE present on WinMo before version 6.1.4, where it became the infinitely better IEMobile8, marketed as “Internet Explorer Mobile 6” (thank you Microsoft).
Okay, I kinda resolved the issues that i was facing earlier and the bigger issue which was setting HTML to an iFrame on IEMobile. But i still have one more PIA which is related to double scollbars - which i am currently looking into. There seems to be more poor souls facing similar problem - if i fix that too. I will post an update here.
How did i finally write to iFrame on IEMobile?
Have 2 divs one to wrap the iFrame and the other to write inside an iFrame.
document.getElementById('OuterDiv').innerHTML = '';
document.getElementById('OuterDiv').innerHTML = '<iframe id="iFrameId" src="somefile.html"></iframe>';
This creates an iFrame each time and in the somefile.html on load there is a InnerDiv.innerHTML which doesn't seem to leak the memory.
In the somefile.html there will be an onLoad method which will fetch the HTML (explained below on how i managed to get it) and do a
document.getElementById('InnerDiv').innerHTML = dynamicHTML;
How did I manage to pass the HTML between parent and child iFrame
As well explained by #bobince earlier, one has to rely on 3rd party service like a cookie or a server to pass around the data between parent and the child iFrame.
I infact used an ActiveXControl to set and get data from the parent and child iFrame's javascript respectively. I won't recommend doing this if you have to introduce an ActiveX Control just for this. I accidentally already have one which I use to get the Dynamic HTML in the first place.
If you need any help you can DM me - Twitter #Swaroop
Thanks #bobince for your help. I am marking this one as an answer because it says what i did to fix the issue.