How to use values from DOM in cypress test? - cypress

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

Related

Traverse through a list, perform actions and return back to the next item in list

Is there a way I can traverse through the list, perform click again and then return to the same page again for the next item in list.
cy.get('#collaborators').next().children().each((items) => {
// Here I have to write code to access list element
cy.log(cy.wrap(items))
}
Log gives me a structure like this and am not sure how to access it. Please help as I am new to cypress.
cy.get('#collaborators').next().children().each( (items,index)=>{
cy.wrap(items[index]).click()
}
)
Having a code written like this, is causing DOM element to be detached although it goes to the next page.
var itemsCount = cy.get('#collaborators').next().children().its('length')
Not sure if I can loop over to get to each of the elements this way.
cy.children() enables you to select child elements and use the selector to filter them. In your case, to get the a tag element, you can do something like:
cy.wrap(items).children('a');
I am also new to cypress, but I believe you can access the href attribute with the invoke() command:
invoke() - https://docs.cypress.io/api/commands/invoke
Try something like this:
cy.wrap(items).children('a').invoke('attr', 'href')
.then((url) => {
cy.visit(url);
});
If you evaluate the href attribute before starting the loop, you'll avoid the detached from DOM error.
Essentially, iterate over a string array not an element array.
cy.get('#collaborators').next()
.find('a') // find all <a> within the <ul>
.then($els => [...$els].map((a) => a.href)) // extract the href
.each(href => { // now iterate list of URL strings
cy.visit(href)
cy.pause() // substitute whatever test you need
cy.go('back')
})
Clicking the link
If you prefer to click the link, extract the last part of the href and use it to find the link element inside the loop
cy.get('#collaborators').next()
.find('a')
.then($els => [...$els].map((a) => a.href))
.each(href => {
const slug = href.split('/')[3]
cy.get('#collaborators').next().find(`a[href="/${slug}"]`).click()
const title = slug.replace('~', '')
cy.contains('h2', title)
cy.go('back')
})

Do something "if an element with some text is not present"

This is basically for a ReactSelect element (behaves like a Select2 element with multi-select enabled), on which I'm trying to select some values which are not already selected.
If an option is selected, then there'll be an element as given below in the DOM
<div class="select__multi-value__label">option1</div>
and hence that options won't be present in the list. So, any code to click() that "option" will fail.
Is there a way to check whether an element with some particular text is available in the DOM?
something like,
options = ['option1','option2','option3'];
options.forEach(option =>{
cy.get('[test-id="react-select"]').then(reactSelect =>{
if(reactSelect.find('[class="select__multi-value__label"]').contains(option).length == 0){
//code to select that option
cy.get('div.select__menu-list > div[role="option"]').contains(option).click();
}
})
})
This find().contains() part doesn't work.
How can I achieve this?
Thanks for any help.
Edit
Adding to the solution given below, can I get an exact match selector - say using a Regex?
let r = new RegExp("^" + option + "$");
...........
const selector = `div.select__multi-value__label:contains(${r})`;
This somehow doesn't work. Found a thread that recommends using filter(), but I don't know how to use it.
Is it possible?
You can do it by moving the contains() inside the selector,
options = ['option1','option2','option3'];
options.forEach(option =>{
cy.get('[test-id="react-select"]').then(reactSelect =>{
const selector = `[class="select__multi-value__label"]:contains(${option})`
if(reactSelect.find(selector).length == 0){
//code to select that option
cy.get('div.select__menu-list > div[role="option"]')
.contains(option)
.click();
}
})
})
The .find().contains() part in your code is using a different .contains() to to the Cypress .contains().
It's invoking the jQuery .contains() which has a different purpose
Description: Check to see if a DOM element is a descendant of another DOM element.
I suppose you could also select the options directly and iterate them
options = ['option1','option2','option3'];
cy.get('div.select__menu-list > div[role="option"]')
.each(option =>{
if (options.includes(option.text()) {
option.click();
}
})
Exact match
options = ['option1','option2','option3'];
options.forEach(option =>{
cy.get('[test-id="react-select"]').then(reactSelect =>{
const matchedOptions = reactSelect
.find('[class="select__multi-value__label"]')
.filter((index, el) => el.innerText === option) // exact
if(matchedOptions.length === 0){
//code to select that option
cy.get('div.select__menu-list > div[role="option"]')
.contains(option)
.click();
}
})
})
You should avoid conditionals in tests as it goes against best practice.
A solution in this case following good practices would be to mock the response that comes from the API for you to handle the request as you want, so you will know exactly when there will be specific text on the screen instead of being a random behavior and you won't have to do conditionals.
You can read more about mocks here: https://docs.cypress.io/api/commands/intercept#Stubbing-a-response
But I also advise you to read this Cypress documentation on testing with conditionals: https://docs.cypress.io/guides/core-concepts/conditional-testing#What-you-ll-learn

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 do you check the equality of the inner text of a element using cypress?

I have a div that has another div inside of it and I want to check the equality of the inner text of the div. I have figured out how to do it using the invoke('text') function, but i am wondering if that is the best way. So my question is: how do you check the equality of the inner text of a element using cypress?
it('the channel name should contain be Anakin Skywaler', () => {
//This works but can we be more specific with our selector
cy.get("[data-test-id='Skywalker,Anakin']").should('contain', 'Skywalker,Anakin');
})
it('the channel name should equal Skywalker,Anakin', () => {
cy.get("[data-test-id='Skywalker,Anakin']").find('.channel-name').invoke('text').then((text) => {
expect(text.trim()).equal('Skywalker,Anakin')
});
});
Please ignore the Star War Reference!
I think you can simplify this.
Assuming you have HTML that looks like this:
<div data-test-id="Skywalker,Anakin">
<div class=".channel-name">Skywalker,Anakin</div>
</div>
You can write your assert like this:
cy.get('[data-test-id="Skywalker,Anakin"]').should(
"have.text",
"Skywalker,Anakin"
);
This passed for me and if I modified the HTML to Skywalker,Anakin 1 it failed as you would expect. Cypress uses the have.text to look at what is rendered out so it will not worry about any markup and just see what the result is.
This did not work for trimming. you would need to add a callback to do the trimming.
cy.get('[data-test-id="Skywalker,Anakin"]').should(($div) => {
expect($div.text().trim()).equal("Skywalker,Anakin");
});
You can check if a string is contained somewhere inside the div:
cy.get("[data-test-id='Skywalker,Anakin']").contains('Skywalker,Anakin');
Or, if you need to make sure the div contains only the specified text and nothing else, you can tag on this extra assertion:
cy.get("[data-test-id='Skywalker,Anakin']").contains('Skywalker,Anakin').should((elem) => {
expect(elem.text()).to.equal('Skywalker,Anakin');
});
Explanation:
// Get the data
cy.get("[data-test-id='Skywalker,Anakin']")
// Get the child or children of the previous command that
// contain the text - this command fails if no child
// element is found containing the given text
.contains('Skywalker,Anakin');
// These two lines are explained above
cy.get("[data-test-id='Skywalker,Anakin']")
.contains('Skywalker,Anakin')
// Like a .then(), except the contents are retried until
// all contained assertions are successful or until the
// command times out
.should((elem) => {
// Expect the element's text to exactly equal the
// string, not just contain it
expect(elem.text()).to.equal('Skywalker,Anakin');
});
I think currently this is the best option, because it does not check for contains. I was hoping for a shorter piece of code to do this.
it('the channel name should equal Skywalker,Anakin', () => {
cy.get("[data-test-id='Skywalker,Anakin']").find('.channel-name').invoke('text').then((text) => {
expect(text.trim()).equal('Skywalker,Anakin')
});
});
Following is how you can check exact or partial match for a string in an element:
//matches exact text of result string
cy.get("[data-test-id='Skywalker,Anakin']").should('have.text', 'Skywalker,Anakin');
//matches partial text of result string
cy.get("[data-test-id='Skywalker,Anakin']")
.text()
.then(value => {
cy.log("Text value is :", value);
expect(value).to.include('Anakin');
});
where text() is defined in command.js file as following:
Cypress.Commands.add("text", { prevSubject: true }, (subject, options) => {
return subject.text();
});
Can't believe I didn't see one of the magical cypress .should matches. Also I use typescript cypress which gives great lookups/intellisense.
using should. However, these are exact text matches and may have a lot of spaces
cy.get("[data-test-id='Skywalker,Anakin']")
.should("have.text", "Skywalker,Anakin")
.should("have.attr", "data-test-id","Skywalker,Anakin'")
adding a new command would be better such as shouldHaveTrimmedText
I got it from https://github.com/cypress-io/cypress/issues/3887#issuecomment-522962482
but below is the setup to get it working also typescript with type
lookup
commands.ts
Cypress.Commands.add(
'shouldHaveTrimmedText',
{
prevSubject: true,
},
(subject, equalTo) => {
if (isNaN(equalTo)) {
expect(subject.text().trim().replace(/ +/g, ' ')).to.eq(equalTo);
} else {
expect(parseInt(subject.text())).to.eq(equalTo);
}
return subject;
},
);
cypress/support/index.d.ts
Cypress.Commands.add(
'shouldHaveTrimmedText',
{
prevSubject: true,
},
(subject, equalTo) => {
if (isNaN(equalTo)) {
// removes double spaces and ending spaces, does not remove special characters such as tabs or \n
expect(subject.text().trim().replace(/ +/g, ' ')).to.eq(equalTo);
} else {
expect(parseInt(subject.text())).to.eq(equalTo);
}
return subject;
},
);
tsconfig
{
"types": ["cypress","./cypress/support"]
// or "typeRoots": ... but not both
}
cy.get("[data-test-id='Skywalker,Anakin']")
.shouldHaveTrimmedText("Skywalker,Anakin")
Simple exact matching
cy.get('[data-test-id=Skywalker,Anakin]')
.invoke('text')
.should('equal', 'Skywalker,Anakin')

Resources