Want to access a text from the group of the elements.
this approach doesn't work, the runner is giving an error saying that invoke is not a function
cy.get('div[class^="lalala"]')
.each(function($sec, i, $sects) {
$sec.find('header[class^="tatata"]')
.invoke('text').then((text) => {
let secText = text
cy.log(secText);
});
})
But without each() it is working when I am accessing any of the elements:
cy.get('div[class^="lalala"]').first()
.find('header[class^="tatata"]')
.invoke('text')
.then((text) => {
let secText = text
cy.log(secText);
});
})
How can I handle that?
$sec is a wrapped jQuery element and $sec.find() returns a jQuery element as well. You have to use cy.wrap to call invoke on it.
cy.get('div[class^="lalala"]').each(function ($sec, i, $sects) {
cy.wrap($sec.find('header[class^="tatata"]'))
.invoke('text')
.then((secText) => {
cy.log(secText)
})
})
Related
I'm using an each method in cypress to find a list of li elements.
What I'm after is to find a child inside the li and test its href value.
The child is an a tag.
Here is what I have so far:
it("should have ..... ", () => {
cy.get("ul > li")
.each((el, index, list) => {
expect(el.text()).to.be.oneOf(["hello", "world"]);
const a = cy.wrap(el).find("a");
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);
})
.then((list) => {
expect(list).to.have.length(2);
});
});
Error: a.attr is not a function
Don't wrap the el. If you do, it becomes a Cypress command which is always async.
You would have to do
cy.wrap(el).find("a").then(a => {
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);
})
But find works for jQuery as well.
const a = el.find("a")
expect(a.attr("href")).to.be.oneOf(["/hello", "/world"]);
The problem is you're storing the results of a Cypress (Promise-like) Chainable. Also using an expect inside each won't benefit from the retry-ability provided by should.
it("should have ..... ", () => {
cy.get("ul > li")
.should("have.length", 2)
.each(($el) => {
cy.wrap($el)
.invoke("text")
.should("be.oneOf", ["hello", "world"])
cy.wrap($el)
.find("a")
.invoke("attr", "href")
.should("be.oneOf", ["/hello", "/world"])
});
});
I am trying to check the items shown in my table for some assertions. My way is to put all of the items in an array and then iterate over that array.
My problem: All assertions already passed but the cypress runner still takes a lot of time to finish the cy.wrap(.invoke(text)) jobs.
Since this is a very core command of my cypress tests it would be great to have a more efficient function.
My command:
cy.get('table tbody').within(() => {
cy.get('tr').each((tr) => {
cy.wrap(tr.children().eq(index)).invoke('text').then((text) => {
text = text.trim();
arrayWithValuesOfTheList.push(text);
});
})
.then(() => {
//in here all the (quickly passing) assertions are...
});
});
Thanks for any help in advance. I appreciate you all!
You can avoid wrapping the value, will give some increase in speed but it's hard to say what is the slowest part.
const arrayWithValuesOfTheList = []
cy.get('table tbody tr')
.each($tr => {
arrayWithValuesOfTheList.push($tr.children().eq(index).text())
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
You can do something like this. It gets the tr values one by one and matches in against a regex pattern.
cy.get('table tbody tr').each(($ele) => {
cy.wrap($ele.text().trim())
.should('match', /myregexp/)
.and('not.include', 'some text')
})
If you want to assert on individual cells, using .each($cell => {...}) is fine but if you want whole-column assertions (e.g sorted, unique-values) it gets difficult with .each().
To build something adaptable for various tests, take a look at the pattern here Sorting the table.
The idea is to create helper functions using .map() to selected table rows and columns.
const { _ } = Cypress
// helpers, reusable
const getColumn = (colIndex) => {
return (rows$) => {
const children$ = _.map(rows$, 'children')
return _.map(children$, `[${colIndex}]`)
}
}
const toStrings = (cells$) => _.map(cells$, 'textContent')
const toNumbers = (texts) => _.map(text, Number)
cy.get('table tbody tr') // rows of table
.then(getColumn(1)) // extract 2nd column
.then(toStrings) // get the text value
.then(toNumbers) // convert if assertions require numeric values
// whole-column assertion example
.should(values => {
const sorted = _.sortBy(values)
expect(values, 'cells are sorted 📈').to.deep.equal(sorted)
})
// individual value assertion
.should(values => {
values.forEach(value => {
expect(value).to.be.lt(100)
})
})
Addressing performance issue
If performance is poor, you can reduce the number of process steps at the Cypress command level by using jQuery-only commands.
This will avoid adding commands to the Cypress queue which is likely to be the slowest part
const arrayWithValuesOfTheList = []
cy.get('table tbody tr td:nth-child(2)') // 2nd col of each row
.then($tds => { // only jQuery methods inside
$tds.text((index, cellText) => {
arrayWithValuesOfTheList.push(cellText.trim())
})
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
Here is my code snippet:
verifyBookingSuccess(){
cy.findByTitle(/created/i).parent().parent().then(async($ele)=>{
bookingId= ($ele.attr("href").split("/"))[2]
cy.log("Booking ID:"+bookingId)
})
return bookingId;
}
I can able to read the bookingId value inside the then() method. But Outside am unable to access it. Is there any way to access that bookingId value?
You have to use aliases as pointed out by #jonrsharpe You can do something like this:
it('Some Test', () => {
//Some Other code
cy.findByTitle(/created/i)
.parent()
.parent()
.invoke('attr', 'href')
.then((text) => {
cy.wrap((text.split('/'))[2]).as('hrefValue')
})
//Some Other code
cy.get('#hrefValue').then((hrefValue) => {
cy.log(hrefValue) //prints the href value
})
})
Note: This will only work if the alias value is used in the same test as it was stored in.
I'm able to retrieve the value outside of then() method using Cypress.env
verifyBookingSuccess(){
cy.findByTitle(/created/i).parent().parent().then(async($ele)=>{
var bookingId= ($ele.attr("href").split("/"))[2]
cy.log("Booking ID:"+bookingId)
Cypress.env("bookingId", bookingId);
})
return Cypress.env("bookingId");
}
find more info at: https://docs.cypress.io/api/cypress-api/env#Name-and-Value
In my DOM i have an input and a div, and i want to get the value of both in one command.
Here is an HTML exemple
<div id="myDiv">Content of the div</div>
<input id="myInput" value="2000" />
Here is what i tried for my command
Cypress.Commands.add("getDomValues", () => {
var divValue = cy.get('#myDiv').invoke('text')
var inputValue = cy.get('#myInput').invoke('val')
return cy.wrap({
divValue:divValue,
inputValue:inputValue
});
});
If there is no cy.wrap around my returned object i get this error
Unhandled rejection CypressError: Cypress detected that you invoked
one or more cy commands in a custom command but returned a different
value.
And then in my test for now i use it like that
cy.getDomValues().then((values)=>{
console.log(values)
})
In my console inside the returned object i have something like that for both values
$Chainer {userInvocationStack: " at Context.eval (http://localhost:8888/__cypress/tests?p=cypress/support/index.js:181:24)", specWindow: Window, chainerId: "chainer4419", firstCall: false, useInitialStack: false}
Do you have any idea how i could have a result like that ?
{
divValue:"Content of the div",
inputValue:"2000"
}
You need to access the values with .then()
Cypress.Commands.add("getDomValues", () => {
cy.get('#myDiv').invoke('text').then(divValue => {
cy.get('#myInput').invoke('val').then(inputValue => {
// no need to wrap, Cypress does it for you
return {
divValue, // short form if attribute name === variable name
inputValue
}
});
The error you received was because you were returning Chainers instead of values.
You can use .as() to assign an alias for later use.
cy.get('#myDiv').invoke('text').as('divValue')
cy.get('##myInput').invoke('val').as('inputValue')
Then use these values later separately like -
cy.get('#divValue').then(divValue => {
//Do something with div value
})
cy.get('#inputValue').then(inputValue => {
//Do something with input value
})
OR, use these values later together like -
cy.get('#divValue').then(divValue => {
cy.get('#inputValue').then(inputValue => {
//Do something with div value
//Do something with input value
})
})
It probably is a matter of Promise:
Look at the since field of my return block,
using an arrow function it doesn't return any result:
{
link: 'www.xxxxxx.com/1234',
name: 'jhon doe',
since: {}
},
instead to return directly the value, it works as expected!
Since i need to perform complex operations with selectors, I'd like to use an inline arrow function in that point, how can I fix to get the result out?
let rawMembers = await page.evaluate(() => new Promise((resolve) => {
....
//captute all the link
const anchors = Array.from(document.querySelectorAll('a'));
let result = anchors.map(x => {
return {
link: x.getAttribute("href"),
name: x.innerText,
//since : x.parentNode.parentNode.parentNode.parentNode.getAttribute("class") <--- this works
since: x => { <---using an arrow function, it returns and empty objecy `{}`
// i need a function here to do complex and multiline operations
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
}
....
resolve(results);
i've tried this as well but with the same result
since: x => new Promise((resolve) => {
// i need a function here to do complex and multiline operations
resolve(x.parentNode.parentNode.parentNode.parentNode.getAttribute("class"));
})
In since you have a reference to the arrow function itself, not to its result. Functions are not serialaizable, so you get an empty object. You need to call the function, i.e. to use IIFE:
since: (() => {
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
})()