How to check if an element has 'any' value Cypress? - cypress

I am writing some basic cypress tests and I just want to make sure that a table we have has all of the correct columns and that any data is rendering.
I know .contains() checks the element selection AND allows text matching, but there could be all sorts of data in this table, I just want to make sure something renders from the backend.
Any idea how to check if there is any (non-specific) value?
Cheers!
describe('Data Source Table renders properly', () => {
beforeEach(() => {
cy.viewport(1920, 1080)
cy.visit('http://localhost:3000/source')
// Assert we are on the correct page /source
cy.url().should('include', '/source')
})
it('Data Source header exists', () => {
cy.contains('Data Source')
})
it('Data Source table exists, column headers are correct and there is data', () => {
cy.get('table').should('exist')
cy.wait(2000)
cy.get('table').contains('th', 'Ip')
cy.get('table').contains('th', 'Station')
cy.get('table').contains('th', 'Table')
cy.get('table').contains('th', 'Status')
})
})

You are right, .contains() allows text matching. It also allows regex matching which you can apply for your situation.
// this will check all td's will have any text
// here is regex example regexr.com/6jmi6
cy.contains('td', /\w/g).should('be.visible')

This is more concise, IMO less prone to error
cy.get('table thead th')
.each($th => {
expect($th.text()).not.to.be.empty
})
You can remove cy.get('table').should('exist') and cy.wait(2000)

Related

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

Iterate through elements

I have a page with a few Results panels, each panel has its own delete button.
I wrote a Cypress test to test the delete process, the test works as expected, the panel gets deleted:
cy.get('div[data-test="Results"]')
.first()
.within(() => {
cy.get('p[data-test="Contact ID"]').then($match => {
contactID = $match.html();
cy.get('button[data-test="Delete Contact"]')
.click()
.get('div[data-test="Delete Record Modal"]')
.should('be.visible')
.get('button[data-test="Confirm Deletion"]')
.click();
});
});
Next I'm trying to detect if the correct panel got deleted.
How can I iterate through all the <p />s of all panels and make sure none of them has a contactID equal to the one that was deleted?
I tried this:
cy.get('p[data-test="ContactID"]').then($match2 => {
expect($match2.text()).not.to.eq(contactID);
});
But in $match2 I get all contacts ids all together for example: 12345678 instead of 1234 and 5678
You can use each:
cy.get('p[data-test="ContactID"]').each(($match) => {
cy.wrap($match).invoke('text').should('not.eq', contactID)
})
invoke calls a function on the subject, in this case, .text()
the chained .should makes an assertion on that text
this will retry the assertion until it passes or times out (see retry-ability) due to the cy.wrap

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

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