What is by.js locator for in Protractor/WebDriverJS? - javascript

Recently, I've noticed a new locator being added to the Protractor documentation - the by.js():
Locates an elements by evaluating a JavaScript expression, which may be either a function or a string.
I understand what this locator provides, but I am missing the real-world use cases when this locator can be useful. When should I prefer to use by.js instead of other built-in locators like by.css?

I feel the use case is to get elements using core javascript functions, whenever css and other element locators won't help or are don't have properties that we could use. Scenarios -
If you are getting an element using core javascript functions passing it to browser.executeScript then by.js can be used to replace it.
Example: -
Suppose if you had to get an element that appears on top between the two, you could get it this way -
var ele = element(by.js(function(){
var ele1 = document.getElementById('#ele1');
var ele2 = document.getElementById('#ele2');
var val = ele1.compareDocumentPosition(ele2);
if(val === 4) return ele1;
else return ele2;
}));
If you want to get element using its css values like color, font, etc... Though filter can be used in this case, but by.js also supports it.
If the elements are not accessible by css or xpath or any other locators, for example pseudo-elements that have animations or transitions.
Example: -
Suppose if there's element which has :before and :after transitions -
.element:before {
color: rgb(255, 0, 0);
}
To verify the color of the element, we could use by.js passing in a javascript statement to get the element -
var ele = element(by.js(function(){
return window.getComputedStyle(document.querySelector('.element'), ':before');
}));
expect(ele.getCssValue('color')).toEqual('rgb(255, 0, 0)');
Hope it helps.

I think the cases for this are pretty slim, but I can see this being of use when there's data on the client that is not available (or unreliably so) through selenium.
The example on the docs page includes a reference to offsetWidth:
spans[i].offsetWidth > 100
Used in context:
var wideElement = element(by.js(function() {
var spans = document.querySelectorAll('span');
for (var i = 0; i < spans.length; ++i) {
if (spans[i].offsetWidth > 100) {
return spans[i];
}
}
}));
expect(wideElement.getText()).toEqual('Three');
Alternatively, there may be a use case if there's a third party API on the window or some other service that can help locate an element.

Related

How to get the content of all spans that have the email attribute with JavaScript

I want to find all spans in a document that have the "email" attribute and then for each email address i'll check with my server if the email is approved and inject to the span
content an img with a "yes" or a "no". I don't need the implementation of the PHP side, only JavaScript.
So say "newsletter#zend.com" is approved in my db and the HTML code is:
<span dir="ltr"><span class="yP" email="newsletter#zend.com">Zend Technologies</span></span>
Then the JavaScript will change the HTML to:
<span dir="ltr"><span class="yP" email="newsletter#zend.com"><img src="yes.gif" />Zend Technologies</span></span>
I need someone to guide me to the right direction on how to approach this.
Note: i don't want to use jQuery.
If you don't want to use a library, and you don't want to limit yourself to browsers that support querySelectorAll, you're probably best off with a simple recursive-descent function or getElementsByTagName. Here's an RD example:
The function (off-the-cuff, untested):
function findEmailSpans(container, callback) {
var node, emailAttr;
for (node = container.firstChild; node; node = node.nextSibling) {
if (node.nodeType === 1 && node.tagName === "SPAN") { // 1 = Element
emailAttr = node.getAttribute("email");
if (emailAttr) {
callback(node, emailAttr);
}
}
}
switch (node.nodeType) {
case 1: // Element
case 9: // Document
case 11: // DocumentFragment
findEmailSpans(node, callback);
break;
}
}
}
Calling it:
findEmailSpans(document.documentElement, function(span, emailAttr) {
// Do something with `span` and `emailAttr`
});
Alternately, if you want to rely on getElementsByTagName (which is quite widely supported) and don't mind building such a large NodeList in memory, that would be simpler and might be faster: It would let you get one flat NodeList of all span elements, so then you'd just have a simple loop rather than a recursive-descent function (not that the RD function is either difficult or slow, but still). Something like this:
var spans = document.getElementsByTagName("span"),
index, node, emailAttr;
for (index = 0; index < spans.length; ++index) {
node = spans.item(index);
emailAttr = node.getAttribute("email");
if (emailAttr) {
// Do something with `node` and `emailAttr`
}
}
You'll want to compare and decide which method suits you best, each probably has pros and cons.
References:
DOM3 Core Spec
However, for this sort of thing I really would recommend getting and using a good JavaScript library like jQuery, Prototype, YUI, Closure, or any of several others. With any good library, it can look something like this (jQuery):
$("span[email]").each(function() {
// Here, `this` refers to the span that has an email attribute
});
...or this (Prototype):
$$("span[email]").each(function() {
// Here, `this` refers to the span that has an email attribute
});
...and the others won't be massively more complex. Using a library to factor our common ops like searching for things in the DOM lets you concentrate on the actual problem you're trying to solve. Both jQuery and (recent versions of) Prototype will defer to querySelectorAll on browsers that support it (and I imagine most others will, too), and fall back to their own search functions on browsers that don't.
You would use document.getElementsByTagName() to get a list of all spans. Then, check each span for the email attribute using Element.hasAttribute. Then you would use the Node interface to create and insert newe tags accordingly.
EDIT:
window.addEventListener('load', callback, true);
var callback = function() {
var spanTags = document.getElementsByTagName('span');
for (var i = 0; i < spanTags.length; i += 1) {
if (spanTags[i].hasAttribute('email')) {
var imgElement = document.createElement('img');
imgElement.setAttribute('src', 'yes.gif');
spanTags[i].insertBefore(imgElement, spanTags[i].firstChild);
}
}
}

Javascript Regex to target and remove a css rule from a style tag, based on its selector?

What would be the regex I could use to completely remove a css rule from an inline stylesheet based on the selector that I provide, via javascript?
For example, I get the contents of the style tag and it contains a rule:
.some_class
{
color: #FFF;
font-style: italic;
}
If i provide the selector '.some_class' what is the correct regex/js method that will find any occurrence of that selector and remove it, its associated brackets, and all the properties/values within those brackets
Consider correctly using the DOM API to achieve what you want, rather than a complex regular expression that may not work very well:
function deleteRule(selector) {
// Get the collection of stylesheets and iterate over them
var ss = document.styleSheets;
// Exit if no stylesheets
if (!ss.length)
return;
// Create an uppercase tagname version of our selector for IE
var uSelector = selector.replace(/^[a-z]+|\s+[a-z]+/gi, function ($0) {
return $0.toUpperCase();
});
// Create a map so we don't get SO's code block scrollbars involved
var map = {};
map[selector] = map[uSelector] = 1;
// `deleteRule` for standards, `removeRule` for IE
var del = "deleteRule" in ss[0] ? "deleteRule" : "removeRule";
for (var a = 0, maxA = ss.length; a < maxA; a++) {
// `cssRules` for standards, `rules` for IE
var rules = ss[a].cssRules || ss[a].rules;
for (var b = 0, maxB = rules.length; b < maxB; b++) {
// Check for selector existence in our map
if (rules[b].selectorText in map)
ss[a][del](b); // remove using our stored delete method
}
}
}
​
Bear in mind that browsers may reformat the stylesheet rule's selector to include whitespace (so body>div.selector becomes body > div.selector). You should try and be as consistent as possible with your whitespace in your CSS selectors. If you want to delete the rule from a specific stylesheet and not all stylesheets, eliminate the first for loop and specify the stylesheet object instead.
Example
var selector = ".some_class";
var pattern = new RegExp(selector.replace(/\./g, "\\.") + "\\s*{[^}]*?}", "gim");
$("style").html($("style").html().replace(pattern, ""));
I don't really understand what/why you're doing, but the existing expression is over-complex and looks wrong.
Here's a simple version:
^\s*\.some_class\s*\{[^}]*\}
This should get you close - you might need to tweak for specific characters in your css name:value section.
.some_class\{([a-zA-Z0-9#]+:[a-zA-Z0-9#]+;\n)*\}
Enjoy!
This lib may help: https://github.com/Box9/jss
But it's very hard for #media or some special classes.
The best way maybe render CSS rules directly from JS:
https://github.com/cssobj/cssobj
This way it's only change rules from js object and it's easier.
The demo: https://cssobj.github.io/cssobj-demo/

Is Greasemonkey not capable of using jQuery's full power?

I'm trying to find an element using jQuery, but it is not working.. I found out that this kind of selector can't be done in Greasemonkey:
($("#app7019261521_hover_container > [id^=app7019261521_the_coin]"))
Please help me translate this into raw Javascript. This kind of selector is hardcore to do in Javascript. Please help me Javascript gurus!
This should do it, and now I remember why I started using jQuery:
var children = document.getElementById('app7019261521_hover_container').childNodes;
var ids = []; //to store the IDs of all matching elements
for(var i = 0; i < children.length; i++)
{
//indexOf returns zero is subject starts with passed string
if(children.item(i).id.indexOf('app7019261521_the_coin') == 0)
{
alert('Got One!');
ids.push(children.item(i).id);
}
}
Since you are targeting directly to Firefox, you may want to give a look to the Selectors API implemented on Firefox 3.5.
Check the document.querySelectorAll function:
var elements = document.querySelectorAll("#app7019261521_hover_container > [id^=app7019261521_the_coin]")

Remove element created by JavaScript on load

How can I remove elements which are created by JavaScript, I tried using CSS by setting display:none; but that doesn't seem to solve my problem, also I can't remove them since I don't have them in HTML, any other ways? Thank you
UPDATE:
Can't use any JavaScript such as jQuery, MooTools, ExtJS etc, and actual elements I want to remove are divs, with a specified class so I can't use getElementById.
I found this script on Google, but it doesn't seem to work but this is what I need:
HERE
This is fairly simple to do this using jQuery.
$("#myId").remove();
will remove
<div id="myId"></div>
Edit: You can also do it with "old school" javascript.
The function you're looking for is removeChild()
Example:
function removeElement(divNum) {
var d = document.getElementById('myDiv');
var olddiv = document.getElementById(divNum);
d.removeChild(olddiv);
}
You will want something like this to take advantage of browser support where you can:
if(document.getElementsByClassName){
// Safari 3+ and Firefox 3+
var itms = document.getElementsByClassName('your_class');
for(var i = 0; i<itms.length; i++){
var it = itms[i];
if(it.tagName == "DIV"){
it.parentNode.removeChild(it);
i = i - 1; //It actually gets removed from the array, so we need to drop our count
}
}
} else {
// All other browsers
var itms = document.getElementsByTagName('div');
for(var i = 0; i<itms.length; i++){
var it = itms[i];
// Test that className contains your class
if(/your_class/.test(it.className)) it.parentNode.removeChild(it);
}
}
JavaScript handles all memory mangement for you using garbage collection so once all references to an object cease to exist that object will be handled by the browsers specific implementation.
If you have the dom element itself:
if(node && node.parentNode){
// return a ref to the removed child
node.parentNode.removeChild(node);
}
Since you say you can't use Javascript, you're pretty stuck. Have you tried this CSS:
.classname {
display: none !important;
}
It's possible that the created elements have inline styles set on them, in which case normal CSS is overridden. I believe the !important will override inline styles.
Of course, the best solution is not to add the element in the first place... but I'm guessing you're in one of those (unfathomably common) scenarios where you can't change or get rid of the JS?
Not all browsers have a
document.getElementsByClassName
method, but for your purpose you can
fake it well enough- This method does
not work like the native
HTMLElement.getElementsByClassName- it
returns an array, not a live nodelist.
You can specify a parent element and a
tag name to speed it up.
function getElementsByClass(css, pa, tag){
pa= pa || document.body;
tag= tag || '*';
css= RegExp('\\b'+css+'\\b');
var A= [], elements, L, i= 0, tem;
elements= pa.getElementsByTagName(tag);
L= elements.length;
while(i<L){
tem= elements[i++];
if(css.test(tem.className)) A[A.length]= tem;
}
return A;
}
// test
var A= getElementsByClass('removeClass'), who;
while(A.length){
who= A.pop(); // remove from the end first, in case nested items also have the class
if(who.parentNode) who.parentNode.removeChild(who);
who= null;
}
If you have assigned event handlers to
the elements being removed, you should
remove the handlers before the
elements.
You will probably have to be more specific.
The general answer is 'with JavaScript'. As long as you have a way of navigating to the element through the DOM, you can then remove it from the DOM.
It's much easier if you can use a library like jQuery or prototype, but anything you can do with these you can do with JavaScript.
marcgg has assumed that you know the ID of the element: if you don't but can trace it in the DOM structure, you can do something like this (in prototype - don't know jQuery)
var css_selector = 'table#fred tr td span.mydata';
$$(css).invoke('remove');
If you can't use a JS library, you'll have to do the navigation through the DOM yourself, using Element.getElementsByTagName() a lot.
Now you've specified your question a bit: use Element.getElementsByTagName, and loop through them looking at their className property.
Use:
document.removeChild('id_of_element');

Unique element ID, even if element doesn't have one

I'm writing a GreaseMonkey script where I'm iterating through a bunch of elements. For each element, I need a string ID that I can use to reference that element later. The element itself doesn't have an id attribute, and I can't modify the original document to give it one (although I can make DOM changes in my script). I can't store the references in my script because when I need them, the GreaseMonkey script itself will have gone out of scope. Is there some way to get at an "internal" ID that the browser uses, for example? A Firefox-only solution is fine; a cross-browser solution that could be applied in other scenarios would be awesome.
Edit:
If the GreaseMonkey script is out of scope, how are you referencing the elements later? They GreaseMonkey script is adding events to DOM objects. I can't store the references in an array or some other similar mechanism because when the event fires, the array will be gone because the GreaseMonkey script will have gone out of scope. So the event needs some way to know about the element reference that the script had when the event was attached. And the element in question is not the one to which it is attached.
Can't you just use a custom property on the element? Yes, but the problem is on the lookup. I'd have to resort to iterating through all the elements looking for the one that has that custom property set to the desired id. That would work, sure, but in large documents it could be very time consuming. I'm looking for something where the browser can do the lookup grunt work.
Wait, can you or can you not modify the document? I can't modify the source document, but I can make DOM changes in the script. I'll clarify in the question.
Can you not use closures? Closuses did turn out to work, although I initially thought they wouldn't. See my later post.
It sounds like the answer to the question: "Is there some internal browser ID I could use?" is "No."
The answer is no, there isn't an internal id you can access. Opera and IE (maybe Safari?) support .sourceIndex (which changes if DOM does) but Firefox has nothing of this sort.
You can simulate source-index by generating Xpath to a given node or finding the index of the node from document.getElementsByTagName('*') which will always return elements in source order.
All of this requires a completely static file of course. Changes to DOM will break the lookup.
What I don't understand is how you can loose references to nodes but not to (theoretical) internal id's? Either closures and assignments work or they don't. Or am I missing something?
Closure is the way to go. This way you'll have exact reference to the element that even will survive some shuffling of DOM.
Example for those who don't know closures:
var saved_element = findThatDOMNode();
document.body.onclick = function()
{
alert(saved_element); // it's still there!
}
If you had to store it in a cookie, then I recommend computing XPath for it (e.g. walk up the DOM counting previous siblings until you find element with an ID and you'll end up with something like [#id=foo]/div[4]/p[2]/a).
XPointer is W3C's solution to that problem.
A bit confused by the wording of your question - you say that you "need a string ID that [you] can use to reference that element later, " but that you "can't store the references in [your] script because when [you] need them, the GreaseMonkey script itself will have gone out of scope."
If the script will have gone out of scope, then how are you referencing them later?!
I am going to ignore the fact that I am confused by what you are getting at and tell you that I write Greasemonkey scripts quite often and can modify the DOM elements I access to give them an ID property. This is code you can use to get a pseudo-unique value for temporary use:
var PseudoGuid = new (function() {
this.empty = "00000000-0000-0000-0000-000000000000";
this.GetNew = function() {
var fourChars = function() {
return (((1 + Math.random()) * 0x10000)|0).toString(16).substring(1).toUpperCase();
}
return (fourChars() + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + "-" + fourChars() + fourChars() + fourChars());
};
})();
// usage example:
var tempId = PseudoGuid.GetNew();
someDomElement.id = tempId;
That works for me, I just tested it in a Greasemonkey script myself.
UPDATE: Closures are the way to go - personally, as a hard-core JavaScript developer, I don't know how you didn't think of those immediately. :)
myDomElement; // some DOM element we want later reference to
someOtherDomElement.addEventListener("click", function(e) {
// because of the closure, here we have a reference to myDomElement
doSomething(myDomElement);
}, false);
Now, myDomElement is one of the elements you apparently, from your description, already have around (since you were thinking of adding an ID to it, or whatever).
Maybe if you post an example of what you are trying to do, it would be easier to help you, assuming this doesn't.
UPDATE: Closures are indeed the answer. So after fiddling with it some more, I figured out why closures were initially problematic and how to fix it. The tricky thing with a closure is you have to be careful when iterating through the elements not to end up with all of your closures referencing the same element. For example, this doesn't work:
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", function(ev) {
// do something with element here
}, false)
}
But this does:
var buildListener = function(element) {
return function(ev) {
// do something with event here
};
};
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
var button = document.createElement("button");
button.addEventListener("click", buildListener(element), false)
}
Anyway, I decided not to select one answer because the question had two answers: 1) No, there are no internal IDs you can use; 2) you should use closures for this. So I simply upvoted the first people to say whether there were internal IDs or who recommended generating IDs, plus anyone who mentioned closures. Thanks for the help!
If you can write to the DOM (I'm sure you can). I would solve this like this:
Have a function return or generate an ID:
//(function () {
var idCounter = new Date().getTime();
function getId( node ) {
return (node.id) ? node.id : (node.id = 'tempIdPrefix_' + idCounter++ );
}
//})();
Use this to get ID's as needed:
var n = document.getElementById('someid');
getId(n); // returns "someid"
var n = document.getElementsByTagName('div')[1];
getId(n); // returns "tempIdPrefix_1224697942198"
This way you don't need to worry about what the HTML looks like when the server hands it to you.
If you're not modifying the DOM you can get them all by indexed order:
(Prototype example)
myNodes = document.body.descendants()
alert(document.body.descendants()[1].innerHTML)
You could loop through all of the nodes and give them a unique className that you could later select easily.
You can set the id attribute to a computed value. There is a function in the prototype library that can do this for you.
http://www.prototypejs.org/api/element/identify
My favorite javascript library is jQuery. Unfortunately jQuery does not have a function like identify. However, you can still set the id attribute to a value that you generate on your own.
http://docs.jquery.com/Attributes/attr#keyfn
Here is a partial snippet from jQuery docs that sets id for divs based on the position in the page:
$(document).ready(function(){
$("div").attr("id", function (arr) {
return "div-id" + arr;
});
});
You can generate a stable, unique identifier for any given node in a DOM with the following function:
function getUniqueKeyForNode (targetNode) {
const pieces = ['doc'];
let node = targetNode;
while (node && node.parentNode) {
pieces.push(Array.prototype.indexOf.call(node.parentNode.childNodes, node));
node = node.parentNode
}
return pieces.reverse().join('/');
}
This will create identifiers such as doc/0, doc/0/0, doc/0/1, doc/0/1/0, doc/0/1/1 for a structure like this one:
<div>
<div />
<div>
<div />
<div />
</div>
</div>
There are also a few optimisations and changes you can make, for example:
In the while loop, break when that node has an attribute you know to be unique, for example #id
Not reverse() the pieces, currently it is just there to look more like the DOM structure the ID's are generated from
Not include the first piece doc if you don't need an identifier for the document node
Save the identifier on the node in some way, and reuse that value for child nodes to avoid having to traverse all the way up the tree again.
If you're writing these identifiers back to XML, use another concatenation character if the attribute you're writing is restricted.
Use mouse and/or positional properties of the element to generate a unique ID.
In javascript, you could attach a custom ID field to the node
if(node.id) {
node.myId = node.id;
} else {
node.myId = createId();
}
// store myId
It's a bit of hack, but it'll give each and every node an id you can use. Of course, document.getElementById() won't pay attention to it.
You can also use pguid (page-unique identifier) for unique identifier generation:
pguid = b9j.pguid.next() // A unique id (suitable for a DOM element)
// is generated
// Something like "b9j-pguid-20a9ff-0"
...
pguid = b9j.pguid.next() // Another unique one... "b9j-pguid-20a9ff-1"
// Build a custom generator
var sequence = new b9j.pguid.Sequence({ namespace: "frobozz" })
pguid = sequence.next() "frobozz-c861e1-0"
http://appengine.bravo9.com/b9j/documentation/pguid.html
I 'think' I've just solved a problem similar to this. However, I'm using jQuery in a browser DOM environment.
var objA = $("selector to some dom element");
var objB = $("selector to some other dom element");
if( objA[0] === objB[0]) {
//GREAT! the two objects point to exactly the same dom node
}
OK, there is no ID associated to DOM element automatically.
DOM has a hierarchycal structure of elements which is the main information.
From this perspective, you can associate data to DOM elements with jQuery or jQLite. It can solve some issues when you have to bind custom data to elements.

Categories

Resources