Javascript test results. can anyone explain them? - javascript

I was recently writing a blog post about checking if jquery elements exist before binding event handlers, I did a quick jsfiddle which you can see here
The thing I dont understand, is that the results show (using chrome to measure in microseconds) that test 2 is a lot faster then test 1.
You'll see from the jsfiddle that test 2 checks the existent of the matching before binding a click event
TEST 1 is:
console.time('time_1');
$('.yep').click(function() {
alert('clicked');
});
console.timeEnd('time_1');
test 1 just tried to bind the event
TEST 2:
console.time('time_2');
if ($('.yep').length) {
$('.yep').click(function() {
alert('clicked');
});
}
console.timeEnd('time_2');
test 2 check the element exists before binding.
I am running the two bits of code on some, 87 I think 'section' elemenets, one of which has a class of 'yep'
I cant really see why the second test is faster, as its doing more work.
results:
time_1: 0.856ms
time_2: 0.146ms
Can anyone shed some light and help out a confused developer.
thanks
n.b please dont reply with alternative ways to bind click events in jquery, the .click is just used as a simple test

The primary thing going on here is that the first time you query a selector, the engine has to do more work than subsequent queries, which can sometimes be cached. If you reverse the first two tests, you'll find that whichever one runs first tends to be the slower one.
Despite that, and mostly as a side note, in test 2 you're querying the DOM twice, first to check the length, and then to hook up the handler. If the query is cached it doesn't matter much, but still, just do it once:
console.time('time_x');
var yep = $('.yep');
if (yep.length) {
yep.click(function() {
alert('clicked');
});
}
console.timeEnd('time_x');
Note, though, that calling click on a jQuery set with no elements in it is a harmless no-op (not an error or anything), so there's no need for the length check unless you're also doing something else you haven't shown.

Related

Cypress: Test if element does not exist

I want to be able to click on a check box and test that an element is no longer in the DOM in Cypress. Can someone suggest how you do it?
// This is the Test when the checkbox is clicked and the element is there
cy.get('[type="checkbox"]').click();
cy.get('.check-box-sub-text').contains('Some text in this div.')
I want to do the opposite of the test above.
So when I click it again the div with the class check-box-sub-text should not be in the DOM.
Well this seems to work, so it tells me I have some more to learn about .should()
cy.get('.check-box-sub-text').should('not.exist');
You can also search for a text which is not supposed to exist:
cy.contains('test_invite_member#gmail.com').should('not.exist')
Here you have the result in Cypress: 0 matched elements
Reference: Docs - Assertions, Existence
Use .should('not.exist') to assert that an element does not exist in the DOM.
Do not use not.visible assertion. It would falsely pass in < 6.0, but properly fail now:
// for element that was removed from the DOM
// assertions below pass in < 6.0, but properly fail in 6.0+
.should('not.be.visible')
.should('not.contain', 'Text')
Migration Docs here: Migrating-to-Cypress-6-0
Cypress 6.x+ Migration
According to cypress docs on Existence
The very popular attempt which is a bit naive will work until it doesn't and then you'll have to rewrite it again... and again...
// retry until loading spinner no longer exists
cy.get('#loading').should('not.exist')
This doesn't really work for the title problem which is what most people will be looking for.
This works for the case that it is being removed. but in the case that you want it to never exist... It will retry until it goes away.
However, if you want to test that the element never exists in our case.
Yes lol. This is what you really want unless you want to just have your headache again another day.
// Goes through all the like elements, and says this object doesn't exist ever
cy.get(`img[src]`)
.then(($imageSection) => {
$imageSection.map((x, i) => { expect($imageSection[x].getAttribute('src')).to.not.equal(`${Cypress.config().baseUrl}/assets/images/imageName.jpg`) });
})
cy.get('[data-e2e="create-entity-field-relation-contact-name"]').should('not.exist');
might lead to some false results, as some error messages get hidden. It might be better to use
.should('not.visible');
in that case.
Here's what worked for me:
cy.get('[data-cy=parent]').should('not.have.descendants', 'img')
I check that some <div data-cy="parent"> has no images inside.
Regarding original question, you can set data-cy="something, i.e. child" attribute on inner nodes and use this assertion:
cy.get('[data-cy=parent]').should('not.have.descendants', '[data-cy=child]')
You can use get and contains together to differentiate HTML elements as well.
<button type='button'>Text 1</button>
<button type='button'>Text 2</button>
Let's say you have 2 buttons with different texts and you want to check if the first button doesn't exist then you can use;
cy.get('button').contains('Text 1').should('not.exist')
Could be done also using jQuery mode in cypress:
assert(Cypress.$('.check-box-sub-text').length==0)
I closed an element and checked should('not.exist') but the assertion failed as it existed in the DOM. It just that it is not visible anymore.
In such cases, should('not.visible') worked for me. I have just started using cypress. A lot to learn.
No try-catch flow in cypress
In java-selenium, we usually add the NoSuchElementException and do our cases. if UI is not displaying element for some Role based access cases.
You can also query for the matched elements inside the body or inside the element's parent container, and then do some assertions on its length:
cy.get("body").find(".check-box-sub-text").should("have.length", 0);
In case anyone comes across this, I was having the issue that neither .should('not.exist') nor .should('have.length', 0) worked - even worse: If the element I was querying was actually there right from the get-go, both asserts still returned true.
In my case this lead to the very strange situation that these three assertions, executed right after each other, were true, even though asserts 1+2 and 3 contradict each other:
cy.get('[data-cy="foobar"]').should('not.exist')
cy.get('[data-cy="foobar"]').should('have.length', 0)
cy.get('[data-cy="foobar"]').should('have.text', 'Foobar')
After extensive testing, I found out that this was simply a race condition problem. I was waiting on a backend call to finish before running the above 3 lines. Like so:
cy.wait('#someBackendCall')
cy.get('[data-cy="foobar"]').should('not.exist')
However once the backend called finished Cypress immediately ran the first two assertions and both were still true, because the DOM hadn't yet caught up rerendering based on the backend-data.
I added an explicit wait on an element that I knew was gonna be there in any case, so my code now looks something like this:
cy.wait('#someBackendCall')
cy.get('[data-cy="some-element"]').should('contain', 'I am always here after loading')
cy.get('[data-cy="foobar"]').should('not.exist')
You can also use below code
expect(opportunitynametext.include("Addon")).to.be.false
or
should('be.not.be.visible')
or
should('have.attr','minlength','2')
Voted element is correct but I highly recommend not to using anti-pattern saving you from a lot of headaches. Why? Yes, because;
Your application may use dynamic classes or ID's that change
Your selectors break from development changes to CSS styles or JS behavior
Luckily, it is possible to avoid both of these problems.
Don't target elements based on CSS attributes such as: id, class, tag
Don't target elements that may change their textContent
Add data-* attributes to make it easier to target elements
Example:
<button id="main" name="submission" role="button" data-cy="submit">Submit</button>
And if you want to be more specific and want to indentify more than one selector, it is always good to use .shouldchainer.
Example:
cy.get("ul").should(($li) => {
expect($li).to.be.visible
expect($li).to.contain("[data-cy=attribute-name]")
expect($li).to.not.contain("text or another selector")
})
If there is no element, we can use simple line like:
cy.get('[type="checkbox"]').should('not.exist')
In my case, Cypress was so fast, that simple .should('not.be.visible') was passing the test and after that, loader appears and test failed.
I've manage to success with this:
cy.get('.loader__wrapper')
.should('be.visible')
cy.get('.loader__wrapper', { timeout: 10000 })
.should('not.be.visible')
Also nice to set the timeout on 10 seconds when your application loads more than 4s.
I would use :
cy.get('.check-box-sub-text').should('not.be.visible');
This is safer than
cy.get('.check-box-sub-text').should('not.exist');
( The element can be present in the DOM but not visible with display: none or opacity: 0 )

Closures in For Loops With Onclick Assignment

I'm trying to have a navBar that generates automatically by looping through an array of "Page" objects. Unfortunately, I seem to be falling into the loops/closure trap. I have read several threads related to this and in some cases have copy and pasted solution code and passed in my own variables but I'm struggling to make it assign onclicks correctly.
I know I'm close. In the below code are two options that I have tried.
Am I getting something wrong with the paremeter in parenthesis in the self-calling function? - the ()(divId)? I don't really understand this part.
Could I also be struggling because this is being done as an object method?
Any help much appreciated but go easy on me, I'm learning all this in my spare time! ;)
Thanks in advance.
EDIT: Jsfiddle: https://jsfiddle.net/mcgettrm/fs0mtz6n/
var navBar = {
display: function(){
for(i=0;i<pages.length;i++){
document.getElementById('navBar').innerHTML += pages[i].token;
var divId = pages[i].unique;
// code works fine up to here.
// option one(below): when navBar.display() is called the following code only adds
// the onclick to the final navbar link
document.getElementById(divId).onclick=(function(divId) {
return function() {
alert(divId);
};
})(divId);
//option two(below): when navBar.display() is called the following code logs
// the individual div id's correctly. But, it does it without being clicked. Then,
// only the last item in the loop is clickable.
(function(divId){
document.getElementById(divId).onclick= function(){
console.log(divId);
}
}
)(divId);
}
}
};
I've got it working here - https://jsfiddle.net/pqu9kr85/ it doesn't seem to have been to do with the binding of i more that you needed to build up the navigation html first, making sure it was in the DOM before binding the event. I put two separate loops, one to generate the nav, the second to bind the events. Also updated the page.display() to use this as that will have been affected by the value of i.

First .slice() and then do things with all other elements

I was quite sure that I already did this in some earlier version of jQuery, but http://api.jquery.com/category/traversing/ seems to suggest otherwise.
What I'm looking for is similar kind of the opposite of .addBack() - a traversing function that uses "all other" elements (not .not()!)
Preusdo Example:
$('.some-class li').slice(33,55).hide().allOthers().show()
Edit: This is not actually a hide() / show() based problem, this is just a simple example to clarify what I meant.
First, I'ld like to manipulate a set of elements selected with .slice(), and then manipulate all elements that were not selected by .slice().
Is there a handy traversing function I've missed that does just that? I know how to solve it in general, but a ".allOthers()" method that I might have missed would certainly be more handy and clearer.
In your case you can just call show before calling slice
$('.some-class li').show().slice(33,55).hide();
It's true that there is no method to get all others, the closest is to get back they previous collection as you mentioned, http://api.jquery.com/addback/
You could implement a plugin, since I'm on my mobile, I'll just write some straight code
// o(n*m), could be improved
function allOthers(jqObj) {
var current = [].concat(jqObj);
var prev = jqObj.addBack();
return prev.filter(function(obj){
return !current.includes(obj);
});
}
First show all of them and then hide from 33 to 55, here is the demo
$('.some-class li').show().slice(33,55).hide();
After testing #JuanMendes suggestion, I played around with it a bit and found quite a compact way to implement this kind of functionality, due to jQuery's prevObject:
$.fn.others = function() {
return this.prevObject.not( this );
}
I didn't test it too much with other methods, so it might needs some further changes - but it seems to work fine with .slice() at least.
https://jsfiddle.net/1L3db7k4/

Greasemonkey, replace every occurence of string every second

i try to figure out a greasemonkey script that replaces every onmousedown on a site with an ondblclick. And i want it to constantly update, like every 1,5 Seconds, because the page refreshes using AJAX.
This is the script i came up with, but it doesn't seem to be working.
window.setInterval(document.body.innerHTML= document.body.innerHTML.replace('onmousedown','ondblclick');,1500);
The page it should work with is internal use only. But a good example would be the google search, where onmousedown is used for the links of the results to swap out the URL before you click it.
I also tried it without the semicolon after the document.body.innerHTML.replace.
I'm really new to JavaScript, but since i'm the only one in the company who can code, this one is stuck with me.
Any help would be appreciated.
Also, a small "side question"
Do i have to use #exclude, or is it enough to only use #include internal.companysite.tld* so it will only work on this site ?
A direct answer: you need to supply a function to setInterval - and it's best to set a variable so that you can later cancel it with clearInterval() if necessary.
function myF(){document.body....;}
var myIntv = setInterval(myF, 1500);
You could also do it using an anonymous function in one line as you're trying to do... do that this way:
var myIntv = setInterval(function(){document.body....;}, 1500);
I wouldn't suggest this as the solution to your problem. What it sounds like you want to do is manipulate the active DOM - not really change the UI. You likely need something like this:
var objs = document.getElementsBy__(); // ById/ByName/etc - depends on which ones you want
for (var i in objs){objs[i].ondblclick = objs[i].onmousedown;objs[i].onmousedown = undefined;} // just an example - but this should convey the basic idea
Even better, if you can use jQuery, then you'll be able to select the proper nodes more easily and manipulate the event handlers in a more manageable way:
$(".class.for.example").each(function(){this.ondblclick = this.onmousedown;this.onmousedown = undefined;}); // just an example - there are multiple ways to set and clear these

Capture and Change default alert() behavior via JavaScript

I have done something like this a long time ago where I captured the alerts and prevented the default browser based alert box from popping up and replaced it with a modal of one kind or another. However its been so long since I have done that, and I can't find any reference from old code of mine how I did it, nor can I find anything relevant via google currently. So.. I am hoping someone here can aid me in this and help me out. I havent tried anything yet so save yourself the question of what did I try. Other than spending the last hour or so googling different phrases for any snipplet of code that resembles whats in my blurry memory I've come up empty handed. I know, this is kind of a poor quality question to, but at the same time I am sure others would appreciate knowing the answer as well.
All I want to do in my case is capture the event that would trigger the alert() box and pass the message that was in it to another variation of notification. Currently I am doing some work in appmobi with a couple others and I want to take alert() capture it then use
AppMobi.notification.alert(message,title,buttontext); as the default action for alert()
You can simply overwrite the alert method:
window.alert = function(msg) {
// whatever you want to do with 'msg' here
};
Note that this won't have the blocking behavior of a regular alert
window.alert = function(message,title,buttontext) {
AppMobi.notification.alert(message,title,buttontext);
};
As others have pointed out, it can be overridden - just remember that AppMobi.notification.alert expects three arguments. I would not be surprised if it has fallback defaults, but better safe than sorry:
//alert is a native code function that we may need elsewhere
window.legacyAlert = window.alert;
window.alert = function(_msg) {
AppMobi.notification.alert(_msg, 'Alert', 'OK');//or whatever
};
This overrides the method globally. Alternatively you may require for it to be overridden only in some parts of your code using closures:
alert('this is a vanilla js alert');
(function(alert) {
alert('this alert has been overridden');
})(AppMobi.notification.alert);
alert('this is another vanilla js alert');
Fiddled

Categories