Find element inside Iframe fails in cypress - cypress

I am trying to click an element inside frame. When i use cy.wrap i get Error
" Timed out retrying: Expected to find element: '[name="border_top"].contents() body #acceptInvitation', but never found it.
Code
it('Automate C# Corner Page', function() {
cy.get('[name="border_top"]').then(function ($iframe) {
const doc = $iframe.contents().find('body')
// doc.find('#acceptInvitation').click({force:true})
cy.wrap(doc.find('#acceptInvitation')).click({force:true})
})

After you add a helper function to your cypress/support/commands.js as I describe in my answer here (Cypress - run test in iframe), you can evaluate whether the button exists with:
cy.get('#iframe-id')
.iframe('body #yourButtonId')
.should('exist')
I think clicking the element in the iframe could be done like this.
cy.get('#iframe-id')
.iframe('body #yourButtonId')
.click({force:true})

install cypress-iframe()
import cypress-iframe in the file
cy.iframe().find('selector');

you can declare one method into base js file like shown below:
getIframeBody() {
return cy
.get('object')
.its('0.contentDocument').
its('body')
.then(cy.wrap)
};
Then use this method as:
getIframeBody().find("cssLocater).click(); OR getIframeBody().xpath("xpath).click();
This will work for sure.

Related

How Cypress test HTML element is assigned after Ajax call

I have a search input and result display area that are being handled by Ajax call. When user enter a keyword, Ajax call the backend and return HTML string. The JS handler then appends the response HTML into result display area.
Here my steps:
Search input: Vancouver (auto input by browser location)
Result: Welcome to Vancouver
Type into search input: Calgary
Expected: Welcome to Calgary
// Test case
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.xpath(selectors.head_info).then((heading) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
cy.get(heading)
.children('h1')
.should('be.visible');
cy.get(heading)
.children('h1')
.invoke('text')
.should('equals', 'Welcome to Calgary');
});
});
});
Test error:
AssertionError
Timed out retrying after 4000ms: expected 'Welcome to Vancouver' to equal 'Welcome to Calgary'
However, the visual screenshot showed that the 'Welcome to Calgary' text was displayed while the test could not see.
I did follow the guide from Cypress app
Timed out retrying after 4000ms: cy.should() failed because this element is detached from the DOM.
...
You typically need to re-query for the element or add 'guards' which delay Cypress from running new commands. Learn more
I added 'a guard', cy.wait()...but nothing works.
Would you please teach me how to handle this issue?
Thank you.
UPDATE SOLUTION:
Thanks for #jhelguero helped me to find out.
Solution 1:
I added this and it worked well.
cy.contains('h1', 'Welcome to Calgary')
.should('be.visible');
Because I have put all the tests in block of
cy.xpath(selectors.head_info).then((heading) => {
...
});
So, when I do cy.get(heading) it caught the element before Ajax done. It was 'Welcome to Vancouver' instead.
Solution 2:
Then, I separated test steps like this.
cy.searchForArea('Calgary');
cy.xpath(selectors.head_info)
.children('h1')
.should('have.text', 'Welcome to Calgary');
and it was successful.
Summary:
Do this:
cy.xpath({selector}).should(have.text,'ABC');
cy.xpath({selector}).find('h1').should('have.text, '123');
DON'T do this:
cy.xpath({selector}).then((ele) => {
cy.get(ele).should(have.text, 'ABC');
cy.get(ele).find('h1').should('have.text, '123');
});
It looks like your DOM rerenders after the ajax call and thus the element('Welcome to') you were previously reference has been detached.
To fix this you will need to re query using the same selector after the ajax call is completed. You will not need cy.xpath(selectors.head_info).then().
Cypress.Commands.add('assessHeadingInfo', (options) => {
cy.fixture('selectors/index-page').then((selectors) => {
cy.searchForArea('Calgary'); // Steps to input keyword and click search
// use .contains() to search a selector with regex text
cy.contains(selectors.head_info, /Welcome to Calgary/)
.should('be.visible');
});
});
If you visually confirmed the text "Welcome to Calgary", the problem might be that .should('equals', 'Welcome to Calgary') is not retrying enough of preceding steps.
Try shortening the query.
You can also increase the timeout if you notice a lag before the text appears.
cy.get(`${heading} > h1`, {timeout:10_000})
.should('have.text', 'Welcome to Calgary')

How do I locate dom elements within nested shadow doms using cypress when `find(...).shadow` is not a function`

I am working with Cypress v9.6.1 and custom elements with shadow dom. We have several custom elements which are deeply nested - I am able to query for an input that is deeply nested with several web components successfully with something like this:
// WORKS!
cy.get(‘custom-element-1’)
.shadow()
.find('custom-element-2’)
.shadow()
.find('custom-element-3’)
.shadow()
.find(`custom-element-4[id=“ce”4]`)
.shadow()
.find('input');
What I'd like to do is determine the presence of that input before moving forward in the test.
Pseudo code -
if (cy.('.deeply-nested-element').exists() ){
do.not.click(button-a)
} else {
click(button-a)
}
Following the cypress docs for conditional testing -> element presence I attempted to express the above in a conditional and use on the length attribute to determine presence
//DOES NOT WORK
cy.get(‘custom-element-1').then(($ce) => {
if($ce.shadow()
.find('custom-element-2’)
.shadow()
.find('custom-element-3’)
.shadow()
.find(`custom-element-4[id=“ce”4]`)
.shadow()
.find('input')) // then do something
}
This gave me an error. Property 'shadow' does not exist on type 'JQuery<HTMLElement>'. Did you mean 'show'?ts(2551)
Ignoring the ts error resulted in cypress test failing with:
$ce.find(...).shadow is not a function
I switched it up a little by chaining off of shadow() with the same result
//ALSO DOES NOT WORK
cy.get(‘custom-element-1').shadow().then(($ceshadow) => {
$ce
.find('custom-element-2’)
.shadow()
.find('custom-element-3’)
.shadow()
.find(`custom-element-4[id=“ce”4]`)
.shadow()
.find('input');
}
And for this one:
$ce.find(...).shadow is not a function
It looks to me like the promise off of the get method does not pass to the callback an element with a shadow dom (JQuery). The small problem I'm trying to figure out is a workaround to that. The larger problem is how to set up a conditional that is determined by the presence of an element that is deeply nested within custom element shadow doms. Any advice would be much appreciated.
The .shadow() command is a Cypress command, but $ce is a jQuery object that can't call Cypress commands directly.
$ce.find(...).shadow is not a function occurs because both jQuery and Cypress have .find(), but only Cypress has .shadow().
By making includeShadowDom:true global either in config or the test options, Cypress commands can be used without needing to chain .shadow() at every step.
If it's custom-element-1 that is conditional, this should work
it('tests deeply-nested shadow elements', {includeShadowDom:true}, () => {
cy.get('body').then($body => {
const ce1 = $body.find('custom-element-1');
if (ce1.length) {
cy.wrap(ce1) // wrap jQuery element into Cypress result
// so that includeShadowDom:true is effective
.find('custom-element-2')
.find('custom-element-3')
.find('custom-element-4[id="ce4"]')
.find('input'))
}
})
})

How to write a custom command in Cypress?

I'm trying to implement a hover command in Cypress, so I'm doing this:
Cypress.Commands.add("hover", { prevSubject: "element" }, (subject) => {
return subject.trigger("mouseenter");
});
And using it like this:
cy.get(`[test-id="my-test-id"]`).hover();
But it's not working: the hover call doesn't seem to have any effect on the execution of the test.
If I remove the custom command and just put the contents of that function inline, it works:
cy.get(`[test-id="my-test-id"]`).trigger("mouseenter");
What am I doing wrong with the custom command?
Have you tried wrapping the subject first?
cy.wrap(subject).trigger("mouseenter");

how does Jasmine 'expect' waits for a protractor promise to resolve

I'm working on e2e testing.
I have a confirm pop which doesnt exist on the page till I click a button.
Once the confirm popup is create I get the text from an element there.
After that I click on OK button which causes the confirm popup to be delete from the DOM and also add a new element to the DOM with the value i got the text earlier.
the problem is, because getText() returns a promise, by the time I do the comparison the first element is not present on the screen and the test fails.
if I do expect while the confirm popup on the screen I can see the text of the confirm popup element.
how does Jasmine expect() resolve the promise?
thanks in advance
Something like this?
element(by.id('dangerous-activity')).click().then(function () {
element(by.id('confirmation-text')).getText().then(function (textToConfirm) {
element(by.id('confirm-button')).click().then(function () {
element(by.id('new-element')).getText().then(function (newText)) {
expect(newText).toBe(textToConfirm);
});
});
});
});
Here all promises are explicitly resolved, so Jasmine does not need to resolve any promise anymore.
You can let expect resolve the new-element promise, replacing the last two lines by:
....
expect(element(by.id('new-element')).getText()).toBe(textToConfirm);
....
But you cannot get the textToConfirm in the same expectation, since it is gone by then as you indicated.
This should be the simplest way to do what you want:
$('#open-popup').click();
var textToConfirm = $('#popup-text').getText();
$('#confirm-button').click();
var newText = $('#new-element').getText();
expect(newText).toBe(textToConfirm);
Note this will not work:
$('#open-popup').click();
var textToConfirmElement = $('#popup-text');
$('#confirm-button').click();
var newText = $('#new-element').getText();
expect(newText).toBe(textToConfirmElement.getText());
because here you get the text after the popup is already closed.

How to use CKEDITOR.replace after CKEDITOR.inline

I'm using CKEDITOR in inline mode with multiple editors per page. To create them, I use <textarea> tags in the HTML and then run a script that executes CKEDITOR.inline() against each of them as soon as the webpage is loaded.
That works fine as long as I define my final configuration in "config.js", but I need to update one of the configuration options dynamically.
Here's my script, using JQuery to loop thru the <textarea> elements after the page is loaded. As a diagnostic, I have the CKEDITOR.replace() sandwiched between alert statements. This code replaces the first <textarea> with an editor and displays the first alert statement. However, it quits during the CKEDITOR.replace() statement and never displays the second alert.
$(function () {
$("textarea").each(function () {
editor = CKEDITOR.inline($(this)[0]);
alert("Before replace, editor=" + editor);
CKEDITOR.replace(editor, {
filebrowserImageUploadUrl: "/new/url/to/be/executed"
});
alert("After replace");
})
});
Not only does the second alert not execute, but the configuration option I'm trying to update remains as it appears in "config.js".
I think I need to specify something other than "editor" as the first parameter for the CKEDITOR.replace() statement, but I don't know what.
After more research, I discovered that I can set configuration options within the CKEDITOR.inline method call. Here's a working version of the script:
$(function () {
$("textarea").each(function () {
CKEDITOR.inline($(this)[0], {
filebrowserImageUploadUrl: "/new/url/to/be/executed"
});
});
});

Resources