Cypress and unstable DOM - javascript

I am using cypress for some e2e tests on a react application and I am having problems clicking on an element.
I have this simple script:
it('can bookmark/unbookmark the posting', () => {
cy.visit(pdfPosting.url)
.wait(1000)
.contains('Save')
.should('be.visible')
.click();
cy.visit('/jobs/jobbox')
.contains(pdfPosting.title)
.should('be.visible');
cy.visit(pdfPosting.url)
.wait(1000)
.contains('Saved')
.should('be.visible')
.click();
cy.visit('/jobs/jobbox')
.findByText(pdfPosting.title)
.should('not.exist');
}
});
My problem starts because the Save and Saved buttons are inside a react component that is refreshed several times, for unknown reasons, when page is loaded. Test fails because sometimes the component is refreshed between the contains and the click on the button.
Right now the only way to workaround this problem was adding the waits you see on the test, but I really hate this hack.
First thing I tried to do, was catching the error and retrying the contains.should.click block, but cypress does not allows this (https://docs.cypress.io/guides/core-concepts/conditional-testing.html#Error-Recovery) and ever worst the workarounds proposed on this article are not working to me (or I do not know how to do it)
I also tried to work with events (https://docs.cypress.io/api/events/catalog-of-events.html#Uncaught-Exceptions) but I could not found any that I could use to retry only a part of the code and continue with the execution of the test.
I tried to talk with developers, if they could fix the refresh issue, but the short story is that they won't. Final user does not realize about this refresh on the component and there is not budget to investigate the problem.
Also I was discussing with developers if we could add something, somewhere, to indicate that the loading of the component had finished, but, again, this seems to be not possible. They cant not grant that loading of the component has finished because they do not know where these refreshes come from.
Do you know how I can use cypress to handle this situation?

It's a bad practice use waits in cypress.
I'd suggest use the playground
https://docs.cypress.io/guides/core-concepts/test-runner.html#Selector-Playground
to find your items. Try to follow the Uniqueness principle and use the cy.get('[data-test-selector=you-selector']); instead of wait. It'll wait for you.
For example, visit and cy.get the title page.
Also, if you have animations, it might cause DOM issues with back and forth. If you remove them it'll be faster and more stable.

Related

Cypress conditional testing code suggestions

I cant figure out how to automate if & else using Cypress. I am new to Cypress & JS and trying to work this out.
I have questions page, sometimes its 3 and sometimes its 4. Not consistent. I simply want to handle case where cypress click on No, if its finds that 4 "NO" button. Also CTA button in the end changes, if the 4th question is present the text of CTA changes.
The problem with "Should" in Cypress is that it asserts and then fails. I want something like IsDisplayed in selenium.
I know conditional testing is not recommended, but I dont have an option now. Please help me with this.
this is my code:
//cy.get(`button[name="cosmeticDamage"][value="${cosmetic}"]`).then($a => {
cy.get('div span.MultiScreenForm__content').then($a => {
if ($a.attr('name', `[name="cosmeticDamage"][value="${cosmetic}"]`).length > 0) {
cy.log('Cosmetic Damage Button does exist: ==>');
cy.get(`button[name="cosmeticDamage"][value="${cosmetic}"]`).click();
} else {
cy.log('Cosmetic Damage Button does not exist, do Nothing');
}
});
}
I know conditional testing is not recommended, but I dont have an option now.
Yes, you do have an option. I apologize up front, this is not a quick, easy answer. Conditional testing in cypress can be tricky, however, when you do understand it, your tests will be better for it. You do have a better option and I'm going to try my best to explain it, so here goes.
For situations like this, you have to adjust the way that you approach the problem. At the moment, you're basing your condition on the UI element which, as Cypress documentation states, will lead to exploding kittens. No one wants exploding kittens. What you want to do instead is change the source of truth your condition is based on from using the UI element to basing your condition on something more stable, like server response.
For me, personally, this was incredibly difficult to wrap my brain around how to actually do this in practice, so I'll try my best to explain.
So, currently, you're doing something like:
Request is made (#of questions) and page loads and view is set based on response.
If UI element is on page (4th question), then test button
What you want instead is:
Request is made (how many questions?)
Capture response from request with cy.route("someAlias") and cy.wait('#someAlias')
Your response from the server is the information that your UI elements are building themselves from. Base your condition on this instead. (see Routes & Aliases)
So your condition would be something like:
if questions returned in response > 3 then test button.
The theory here is, your server is a solid source of truth which has all the info you need, right up front. The DOM (UI elements) is not because it does not have all of the info you need up front and there is no guarantee at the time of your condition that it will have resolved the stuff you need in order to proceed.
If your server responds with more than 3 questions, there should be a button there. Switching the logic here makes your test more stable and you're actually testing the thing that you want to test. When you've got more than 3 questions, you should have a button. Not if there are more than three UI elements, then you should have a button. There is no guarantee that your button will have resolved by the time your condition is met for the UI elements. Your server response should be your source of truth, not the UI elements.
I don't know the logic that you have that makes the request so my answer isn't exact, however, let's assume that on page load, there is a request to /questions that responds with the questions that you're talking about (3 or 4). Your code would then look something like this:
// setup the route to wait for
cy.server();
cy.route("/questions").as("questions");
// do whatever you do that sends that request
cy.visit("/pageOfQuestions");
// wait for request and grab response using route alias
cy.wait("#questions").then(function(xhr) {
// find your path (I'm guessing here) to the info you need and test condition
// the condition and the path to the info will vary based on what your response
// actually contains
if(xhr.response.questions.length > 3) {
// test your button-y stuff here
cy.get(`button[name="cosmeticDamage"][value="${cosmetic}"]`).click();
} else {
cy.log("nothing to test");
}
The difference is when you base your condition (source of truth) on the UI elements, the DOM does not always resolve itself in the manner you expect. When you reach the condition (in this case the question UI elements), other things still haven't resolved themselves (button UI elements). You have to wait for two elements to align in the DOM -> Your source of truth (condition) and the UI element you want to test (button). Often, one hasn't loaded when the other has and you cannot rely on them loading consistently which is why cypress recommends never basing conditions on UI elements, unless you like exploding kittens.
Explained differently, let's say the DOM has 10 resources to load. You're saying to cypress:
if resource #7 looks like this then play with resource #1
Cypress goes and waits for resource 7. When it's ready it checks that the condition is met and then tries to play with resource 1... which the DOM possibly hasn't resolved yet. And actually, sometimes it could be loaded but you can never guarantee that it will be which will lead to flaky tests.
When you base your source of truth on the server response, you're only waiting for and testing the one UI element and the cypress built-in time outs can successfully wait for that one element to load without depending on another. On page load, did the server give us more than 4 things? Then play with UI element
If there's anything I can clarify, just ask.
Reference:
Conditional testing
Routes and Aliases
What I understand from your description and the snipped you provided is that [name="cosmeticDamage"] is always there for several elements, so I'm not really sure how does .length valorizes [name="cosmeticDamage"][value="${cosmetic}"], which could mean your condition is always true.
I would recommend to go for find, as it filters matching descendent DOM elements:
cy.get('div span.MultiScreenForm__content').then($form => {
if ($form.find(`[name="cosmeticDamage"][value="${cosmetic}"]`).is(':visible')) {
cy.get(`button[name="cosmeticDamage"][value="${cosmetic}"]`).click();
} else {
cy.log('Cosmetic Damage Button does not exist, do Nothing');
}
});

Ninja Kill a Rogue JavaScript Event

Here's the plot, which is a True Story (a problem that exists for a real person, that is - me):
You are working on a large enterprise site, which includes a lot of JavaScript and specifically jQuery code you don't have any control of, and can't possibly change (good luck even finding out who wrote it, or why). Layers of authentication and authority are involved, so just pretend it's written in stone and you can't touch it.
Somewhere in this code, there is an event that scrolls the document to the top of the page after it has loaded. "OK, that sounds harmless" one might think - but it is now your task to scroll the page to a specific item based on a query string or anchor.
Everything works fine generally, but when you click a link that goes to example.com/list#item11, the browser works as expected and you go directly down to the item you want to link to...and then, whammo, the page instantly jumps back to the top of the page.
Now, you might say "well, that's what document.ready() is for!" ...to your horror, you find that the rogue event comes along anyway.
After Stack Overflow searching for an even later event to tie into, you find this gem:
$(document).ready(function(e) {
$(window).load(function(e){ });
}
And surely, this will definitely work! Only, it does not. You try return false and e.preventDefault(), but that does nothing for you here.
All you can be sure of is that this rogue scrolling event occurs after your code runs, after the DOM is ready, and definitely after the window.load() event. You are sure of nothing else.
Can you assassinate this rogue event? Is there some mechanism to intercept scroll events and prevent them from occurring? Can you link into some event later event, like "the DOM is ready, the window is loaded, the page is settled, the children are in bed, and all other events are done being handled.... event()`"?
The only solutions I can imagine now are "give up - scrolling behavior on page load isn't going to work in your scenario", "use a timer and wait! then commit seppuku for being such a dirty hack!", and "ninja-assassination mission!" (since I don't know who wrote the offending code, I'd have to settle for killing their code instead of them - and I'm sure they had their reasons, or have already been assassinated... or at least waiting for the code to pass and do my thing).
Is there some Better Way, some hard to find function, some last resort that invokes the arcane Dark Lords of Reflection, or is it time to give up and solve the problem another way?
TLDR;
How do you stop a disruptive scripted event - like scrolling - from occurring when you can't change the code that is causing it? Acceptable answers include how to make certain your code runs after - without using a timer hack! - and/or if your code always runs first how do you prevent the later code from messing up yours?
It might be helpful to find out how the event is defined, and what events are firing, but I feel that this is a separate question and may not necessarily be required to fix the situation. For illustration purposes, assume there are thousands of active events and listeners spread out across dozens of minified script files. It may just be so hard to narrow down what exactly is happening as to be too much trouble to even try.
The best solution will be to edit the source code where the ready event is declare.
if you can't, you can copy this code somewhere else and edit it.
if its totally not possible, then
you cannot unbind the ready event because that can cause problem.
you can override the window.scrollTop() function by its prototype
window.prototype.scrollTo2 = window.prototype.scrollTo;
window.prototype.scrollTo = function(){
/*Look in the url if you have an hash tab*/
// if yes return false
//if not
window.prototype.scrollTo2()
};
Smack them with a timer if everything else fails:
$(document).ready(function() {
window.setTimeout(function() {
document.location = "#bottom";
}, 200);
});
Live test case.
Ugly, but working.
I would hook into the window.scrollTo prototype to try and catch the burglar in the act. If you know how it's done, it's easier to get rid of it.
If this rogue call is not embedded in too huge a pile of JQuery goo, it could even allow to trace the call to the original culprit, who would soon be smitten with great vengeance and furious anger.

Detecting a cancel in navigation with Javascript or jQuery

I was curious if there was a way to detect the user pressing the "stop navigation" button in the browser using javascript (or, even better, jQuery.) For example, if you click a link for a webpage that takes a while to load, you may want to show a spinning loader. But what if the user cancels navigation to the page? Is there anyway to detect that to get rid of the spinning loader that you put?
EDIT: I did a bit more research, and there seems to be an onStop event in javascript but, wouldn't you know it, it only works in internet explorer. If anyone has any other ideas to implement a cross browser solution like onStop, that'd be wonderful, but if not, I'll answer my own question in a few days to close this.
EDIT 2: https://stackoverflow.com/a/16216193 says it's not possible. As do a few other answers.
Alright so, as promised, I'm going to answer my own question.
I've thought about this quite a bit - and I've come up with a solution. I wasn't able to make it work in code (I didn't try too hard), but it should work in theory.
So I thought about the criteria of deciding when a webpage should decide stop was called. I came up with this:
If the script hasn't died after a reasonable amount of time, it can be assumed navigation has been canceled.
Then a jQuery event can be fired on the body or something like that. But what constitutes "a resonable amount of time?" I figured it would be partially based on page render time (fetching images, etc.) to get an idea of how fast the user's internet is. That can be gotten by doing:
var start = new Date();
var time;
$("body").load(function () {
time = new Date() - start;
...
});
Multiply that by a coefficient (maybe 3 or something) and get an approxamate transfer time. (This would have to be adjusted to account for how long it would take for the server to generate the next page, dependent on how dynamic it is.) Then, using this new found time*3 you'd write something like this:
$("a").click(function() { //Anything that could go to another page should filter through here
setInterval(function() {$(document).trigger("navstopped");},time*3);
}
$(document).on("navstopped") {
//Do stuff now that we assume navigation stopped.
}
Assume. That's really all we're doing here. We may have an inconsistent internet connection, fast one minute, slow the next. Server load could be inconsistent too. Maybe it's serving up images like a ninja for this page, but it's hit with a bunch of requests the next, making it generate/serve the next page a bit slower. So we're just assuming that something interrupted the navigation some how, but we are not certain.
Now, of course, this could be used in conjunction with IE's onStop event, but this was really the only cross browser solution I could think of. I wasn't able to get it to work, but maybe some jQuery god may be able to in the future.
Edit before post: Even before I posted this, I had another idea. More browsers support onAbort. If we have a picture that never loads, and the user presses stop, will onAbort be fired? Even if another webpage is loading? It requires testing but that may work too. I like my first idea better though. Although unstable, it is more stable than this cockamamie idea and I realize this could be bad practice.

All events in Backbone app fire twice. Occurs randomly

First, some background. I'm fairly certain this is not because of zombie views. I use requireJS and I have only one instance of main views at any given time.
Also, this behavior is random, I haven't been able to reproduce it even once, but several of my users have pointed it out and shown me a video where every click on the app seems to trigger the handler twice. The clicks happen very very fast. It can't be mechanical failure of the mouse because the problem has been reported on multiple machines. The reports are from people with fast Internet connections, for what it's worth.
Is it possible that two instances of the app are running at the same time? Are there any steps I can take to isolate a problem of this kind in backbone?
Apologies for the wall of text, please let me know if I can put up any extra information or relevant pieces of source.
Edit : I've managed to recreate this in Opera. After stepping through part of the code that fires twice (I was inspecting code that opens a modal), I was able to look at the view that triggers the event. Both views have the same CID, so this cannot be attributed to Zombie views right?
In my experience, this is almost always related to zombie views, or other DOM leaks. My best friend in this case if the Web Inspector Profiles -> Take Heap Snapshot and look for detached DOM tree (type "detached" in the search field).
It can occur in tricky cases, even if you think you're only instanciating views once.
Beyond that, you'll have to show us some code ;)
The problem here was that I was running a third party library that reports JS errors. Due to a n error on their part, event bindings on page were affected and this caused the confusion inside the application.
Moral of the story - Whenever you hit an error you feel is impossible, remove your third party dependencies one by one and confirm the problem is your fault to begin with.

Diffing the DOM - how to write a unit test that checks that a script doesn't make any user-visible changes to elements on the page?

A JavaScript plugin that I've been writing recently has various failsafes built in that tell either the whole plugin or parts of it to hide itself and die under circumstances where it can't function. For example, let's say that one piece of functionality we offer is automatically generating a popover that shows competitors' prices for an item when the user hovers over it in an online store. Then we'd also have some checks that say that if we don't know any of the competitor's prices, or we can't identify the item, then don't show a popover.
I want to test that functionality-disabling using tests that follow roughly this structure:
Load our plugin onto a page where certain functionality ought to be disabled
Spoof some user action that would otherwise trigger that functionality
Assert that no visible changes have been made to the DOM. (i.e. no styling changes to visible elements, no addition or removal of elements unless they have display:none on)
Step #3 is the interesting one, of course. Is there an easy way to write that 'DOM unchanged' test in JavaScript? (Or alternatively in Selenium for Python, which is what I'm using to write my tests - but writing the check in JavaScript is probably more broadly useful since it can then be used in any JavaScript-testing environment.)
P.S. A couple of notes to head off the "You're doing it wrong!" crowd:
Yes, I know that I could just replace step #3 in the test above with a check that the specific changes that the plugin would otherwise make have not been made, and I may even decide to do this. But where those specific changes are poorly-specced and liable to change, this catch-all approach could be useful.
Yes, I also realise that just checking there are no immediate visual changes to the DOM when a event that's meant to be effect-free is triggered isn't strictly sufficient to prove that the nothing has broken. It's what'd be best for my current purposes, though. Plus it's interesting and fun even if it turns out not to be useful.
Use Mutation observers to detect that no mutations have occurred. You might want to checkout Mutation Summary, a very nice high-level wrapper for mutation observers. Checking that no mutations have occurred could be easy as checking that the returned array has length 0. See https://code.google.com/p/mutation-summary/.

Categories

Resources