Compare two base64 image strings in Cypress - cypress

I would like to compare the two following base64 strings to ensure that a photo is uploaded as expected. How would I do that? This my code below, I currently get the error message:
You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.
The chai-jQuery assertion you used was:
> text
This is my code:
it('should allow the user to select a navbar component and replace a logo', () => {
cy.getIframeBody('builder-showcase').find('[data-cy="navbar-component"]').eq(0).click();
cy.getIframeBody('builder-showcase').find('[data-cy="navbar-logo-image"]').eq(0).click();
cy.get('[data-cy="builder-sidebar-menu-select-image-navbar"]').eq(0).click();
const navbarLogoImage = 'navbar-logo-image.png';
cy.fixture(navbarLogoImage).then(originalLogoImage => {
cy.get('[data-cy="image-file-input"]').attachFile(navbarLogoImage);
cy.get('[data-cy="image-alt-text"]')
.clear()
.type('Twitter logo image');
cy.get('.source-image').invoke('attr', 'src')
.then(uploadedLogoImage => {
expect(uploadedLogoImage).to.have.text(originalLogoImage);
})
});
});

The chai chainer to.have.text verifies that an element (DOM or JQuery) contains the expected text. Your uploadedLogoImage is a string. You should then use a chainer that will work with a string like:
expect(uploadedLogoImage).to.eq(originalLogoImage);
or
expect(uploadedLogoImage).to.contain(originalLogoImage);
or with should(), you can query the element directly:
cy.get('.source-image').should('have.attr', 'src', originalLogoImage);
Hint: instead of using eq(0), you may also use first() which gives a better readability.

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

Can I save a text element using Cypress, and then use its text as an input?

I'd like to get an element on my page, save its text, and then later type that same text into a text input element as part of a single spec. type() only accepts strings. Is there an accepted workaround, or is this a case of not knowing the best practice?
cy.get(".navbar-text").then(($user) => {
const user = $user.text;
cy.get(".historySearch").type($user.text);
//.type(user) and .type($user.text) both cause errors on the previous line
});
});
jQuery for text extract is a function, use it with Cypress invoke command
cy.get('.navbar-text.)
.invoke('text')
.then(user => {
cy.get(".historySearch").type(user);
});
or directly like this
cy.get(".navbar-text")
.then($user => {
const user = $user.text()
cy.get(".historySearch").type(user)
});
You can also use aliases .as() to save the inner text value and use it later.
cy.get('.navbar-text').invoke('text').as('navbarText')
cy.get('#navbarText').then((text) => {
cy.get('.historySearch').type(text);
})

Nightwatch .execute() how to pass parameter to execute function, is it possible?

Please be patient - I am a beginner in programming. Tester for long time but programming is not my domain.
My test is:
from the backend I get some list with some element (e.g. 5 text strings)
I click some element on page which displayed those 5 elements (of course I don't know if listed elements are correct or not)
I need to check if list of elements displayed on ui is the list received from backend
Problem:
I cannot access the elements by Nightwatch api css selector, at least I could not manage (Angular app) to do it with Nightwatch
I found I could do it with .execute()
My code is (failing):
browser
.click(selector.HEADER.APPS_GRID, function () {
for (var app in appsList) {
let appShortName = appsList[app].shortName
let appLongName = appsList[app].longName
let appUrl = appsList[app].url
let appVisibility = appsList[app].visibility
browser.execute(function(app){
var appShortNameDisplayed = document.getElementsByClassName('logo-as-text')[app].innerText
var appLongNameDisplayed = document.getElementsByClassName('app-name')[app].innerText
return [appShortNameDisplayed, appLongNameDisplayed]
}, function(result){
console.log(result.value[0])
})
}
})
It fails in lines:
var appShortNameDisplayed = document.getElementsByClassName('logo-as-text')[app].innerText
var appLongNameDisplayed = document.getElementsByClassName('app-name')[app].innerText
unfortunately I have to make query with [app] - iterating by elements of object. If I skip [app].innerText I get some data like element-6066-11e4-a52e-4f735466cecf instead of text values displayed on page
I get error:
Error while running .executeScript() protocol action: TypeError: document.getElementsByClassName(...)[app] is undefined
Is it possible to pass the "app" param (counter) to the document query?
Or is it the way I have to make one query that will return as many data as necessary and then handle data returned in this block
function(result) {
console.log(result.value[0])
})
The fragment of html page is
<div _ngcontent-c8="" class="ep-app-icon mt-auto mb-auto text-center logo-as-text"> XXX </div>
... and I need to get this "XXX" text.
As your own comment suggests, there is an args argument to .execute that is an array. The array elements will be the arguments in the function passed to execute.
See https://nightwatchjs.org/api/commands/#execute
.executeAsync(function(){
var buttons=document.getElementsByTagName('button');
buttons[2].click();
return buttons;
},[],function(result){
console.log('done')
})
Try Async it works for sure

How to use values from DOM in cypress test?

if I have a page containing:
<span data-testid="credit-balance">
10
</span>
In Cypress, how do I extract the value to a variable to use in tests?
Something along the lines of:
const creditBalance = cy.get('[data-testid="credit-balance"]').value();
Assigning return values with const, var, and let is considered an anti pattern while using Cypress.
However, when you find yourself wanting to do so, is best practice to accomplish this with closures.
it("uses closures to reference dom element", () => {
cy.get("[data-testid=credit-balance]").then(($span) => {
// $span is the object that the previous command yielded
const creditBalance = $span.text();
cy.log(creditBalance);
})
});
Another way to do this would be to use Aliases if you want to store and compare values or share values between tests using hooks.
it("aliasing the value from dom element", () => {
cy.get("[data-testid=credit-balance]").as("creditBalance")
cy.get("#creditBalance").should("contain", 10)
});
How you approach this really depends on the objective of your test. I recommend checking out more examples from the documentation: try Variables and Aliases , Best Practices, and FAQ
If you would like to retrieve the value and perform any assertions with it, a fast, efficient method would also be the use .invoke
it('Getting the value and performing an assertion', () =>{
cy.get('selector').invoke('val').should('eq',10)
})
Doc
The Cypress documentation has an example how to Compare text values of two elements
// will keep text from title element
let titleText
cy.get('.company-details')
.find('.title')
.then(($title) => {
// save text from the first element
titleText = $title.text(); //original uses normalizeText($title.text())
})
cy.get('.company-details')
.find('.identifier')
.should(($identifier) => {
// we can massage text before comparing
const idText = $identifier.text(); //original uses normalizeText($identifier.text())
// text from the title element should already be set
expect(idText, 'ID').to.equal(titleText)
})
If you have to get the value rather than text, use this. It worked for me.
<span data-testid="credit-balance" value='100'></span>
Like above
cy.get('[data-testid="credit-balance"]')
.invoke("val")
.then(($amount) => {
// $span is the object that the previous command yielded
cy.log($amount);
});

Resources