Cypress - Aseertions on array properties - javascript

I have yielded some iframe and acessed to its body.
Now i'm trying to make an assertions that an 'href' property exists but can't do it.
See the response (sorry cant post images not as links yet)
1 - can see that i have an acess to the properties
2 - cy.wrap() error that says :
Timed out retrying after 50000ms: cy.should() failed because this element is detached from the DOM.
Cypress requires elements be attached in the DOM to interact with them.
The previous command that ran was:
cy.wrap()
This DOM element likely became detached somewhere between the previous and current command.
Common situations why this happens:
Your JS framework re-rendered asynchronously
Your app code reacted to an event firing and removed the element
Couldnt understand the cy.wrap() error, i can clearly see the properties of the body but can't access them ? how come ?
Feels like i am missing some basic stuff and can be laughed at, but anyway...
That is the part of the test
const iframe = cy.get('*[class^="cbola-banner__tag"]')
.find('iframe')
.its('0.contentDocument.body')
.then(cy.wrap)
iframe.children()
.should('have.a.property', 'href')
I'm expecting to make an assertion that some of the properties exists.

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');
}
});

How to handle weird behaviours with jQuery application using Selenium?

I am working on a stabilization of an old application which is developed using jQuery. In order to stabilize it, I added a method which, using javascript, checks for document is ready document.readyState === "complete"and ajax calls have been completed jQuery.active == 0 before interacting with elements.
But I still see following kind of issues -
Even though element is present on a page, Selenium WebDriver unable to find it even with polling mechanism which has some timeout set. However we can find same element, by checking in DOM.
UI is distorted and when WebDriver tries to click on an element, it gives error Element click intercepted. However same element can be clicked manually.
Even application has jQuery, when we execute javascrit jQuery.active == 0 through WebDriver, it results into jQuery is not defined. However if we execute same query through browser console, it returns correct result.
For all above, when I said something worked that means I've checked that in same browser instance which WebDriver had launched.
Could you please help how this can be resolved? I have been struggling for a week to stabilize this.

No node found for selector // Puppeteer Error

Im trying to build a simple program that checks my college portal and notifies me when the portal changes.
I used Chrome's inspect feature to copy the selector but still error occurs.
I used the selector that pops on when I put my mouse on top of the source code and the error still occurs.
The only solution I found was to actually right click the button I want to find the selector for, then click inspect and then go back to the console and look up the selector.
How can I solve this problem?
await page.waitFor(10000);
await page.click('a#SLO_SS_STDNTCEN_SLO_SS_APP_STATUS.PSHYPERLINK');
await page.waitForSelector('a#SLO_SS_STDNTCEN_SLO_SS_APP_STATUS.PSHYPERLINK');
await page.click('a#SLO_SS_STDNTCEN_SLO_SS_APP_STATUS.PSHYPERLINK');
I expected for the computer to click on the selector I chose- which is correct without a doubt- but an error occurs: No node found for selector

Find out which frame in DOM initiated a postMessage (in IE11)

In JavaScript, once I've received a 'message' event, is there a way to find out which frame in the DOM model has initiated it? This would be helpful when debugging a large web application where a particular message could have come anyone of 15-20 frames. The message event has a source property, but if the frame is cross-domain, it's not accessible:
Since I know these things vary from browser to browser, I'm asking specifically about IE11.
I found a way that actually works even when cross-domain - I add a DOM element by evaluating it in the Add Watch window. Then I search the DOM tree for that element, and figure out the frame in this way.
For example, this code works:
var foo_btn = document.createElement("BUTTON"); var foo_t = document.createTextNode("FOOBAR FOOBAR"); foo_btn.appendChild(foo_t); document.body.appendChild(foo_btn);
You just click Add Watch and paste it, and then after it executes, you can search for FOOBAR FOOBAR in the DOM tree.

IE throws an "Object required" error when trying to use the Event Object from another window/frame

I found a JavaScript error in a script that I'm developing. I whittled it down to a pair of fairly concise pages that still produce the error:
http://troy.onespot.com/static/4505/index.html
http://troy.onespot.com/static/4505/iframe.html
If you load the first page in Internet Explorer 8 (or probably 6 or 7, for that matter), give it about half a second to run the script, then click the "Google" link inside of the <iframe>, you should see an error message appear:
http://skitch.com/troywarr/dui21/ie8-error
(You may need to uncheck Tools > Internet Options > Advanced > "Disable script debugging (Internet Explorer)" and check "Display a notification about every script error" two lines below to see the error messages.)
Starting the debugger shows the beautifully insightful message "Object required":
http://skitch.com/troywarr/dui26/ie8-debugging
The culprit is the line:
target = event_object.target || event_object.srcElement;
I think that's valid code - at least it works in Firefox. My best guess is that there is an issue with trying to access the Event Object of one frame from another - vaguely similar to why you can't rely on instanceof to detect arrays if they were created in a different window/frame (search for "context" at http://javascript.crockford.com/remedial.html if that didn't make sense).
Does that sound like a valid theory? If so, what can I do to fix this? If at all possible, I need to preserve the same general code structure/functionality:
There is a link inside an <iframe> on a page.
A script in the <iframe> calls a function on the parent page, which attaches an event handler to the element in the <iframe> with the specified id attribute.
Clicking that <iframe>d link triggers the event, which calls a function on the parent page, passing the Event Object by default.
The function on the parent page determines information about the clicked element (the <a>) from the Event Object in a cross-browser-compatible way.
I would also like to continue using event delegation, and keep all of the functions in the parent document, just calling them with arguments from the <iframe>d document. However, if you have any suggestions for alternative approaches, I'd love to hear them.
Thanks in advance for any help! Please let me know if you need any more explanation about my requirements or what I'm trying to do - I'm hoping that there's just a better way to access or pass the Event Object that I'm not aware of - an "Oh, yeah, you just need to do it like this" kind of solution. I hope that's not wishful thinking. ;-)
Easily fixed. You need the event object from the iframe's window. Store the iframe's window object next to where you store its document:
var iframe_win = document.getElementsByTagName('iframe')[0].contentWindow;
... and update the line that gets hold of the event object in your event handler function:
event_object = event_object || iframe_win.event;

Categories

Resources