Cypress command that return values from DOM - cypress

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

Related

How to return the href value outside cypress then() method

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

Cypress how to get a text from a div and store in a variable for later

I am new to Cypress and some of the things that I expect to work have really weird issues.
For example, I am trying to get the value of a column in a table and use the value in a search input. I have done it like this:
it('should filter', () => {
let id = 0;
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
id = $div1.text();
expect(id).not.to.eq(0);
});
//expect(id).not.to.eq(0);
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
expect(id).to.eq($div1.text());
});
});
But when I run this, I get an error stating that [data-cy=data-table-row] has a length of 25 not 1.
It turns out that the id variable I am using is not accessible outside the should method. I assume that's because it is a promise.
If I try to do this:
it('should filter', () => {
let id = 0;
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
id = $div1.text();
expect(id).not.to.eq(0);
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
expect(id).to.eq($div1.text());
});
});
});
The test goes mental and tries to get the [data-cy=table-filters-search] over and over and over again.
I am not sure why.
Is there an easier way to grab the innerText of a div and store it to compare later?
As someone gave a response, I tried this:
it('should filter', () => {
let variables = {};
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').then(($div1) => {
variables.id = $div1.text();
expect(variables.id).not.to.be.undefined;
});
console.log(variables);
expect(variables.id).not.to.be.undefined;
cy.get('[data-cy=table-filters-search]').find('input').type(variables.id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').then(($div1) => {
expect(variables.id).to.eq($div1.text());
});
});
But the test fails on the second expect(variables.id).not.to.be.undefined;
Closure problem
The problem with the first example is that the test runs in two phases. The first phase sets up the commands in the queue, and the second runs them.
During the first phase, .type(id) "captures" the current value of id (which is "0") and in the second phase that's the value that gets used.
You can fix it in a couple of ways, with an alias or moving the type(id) inside the callback, as per your second example.
This gets around the closure problem by deferring cy.get(...).find('input').type(id) until the id has actually changed.
Retry problem
The problem with the second example is that should() with an expect in the callback will retry until it succeeds or times out.
Something in the callback is continuously failing (or an error is thrown) causing a continuous retry. It should time out, not sure why that doesn't happen.
You can separate the parts of the callback into two sections, and use a .then() which does not attempt to retry.
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should(($div1) => {
id = +$div1.text(); // Note $div1.text() is always a string
// so convert with "+" to compare numbers
expect(id).not.to.eq(0) // otherwise this always succeeds
})
.then(($div1) => { // use a then which does not retry
id = $div1.text();
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should(($div1) => {
expect(id).to.eq($div1.text())
});
})
Or
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.invoke('text') // find it's text
.should('not.eq', '0') // passes on the id
.then(id => {
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should($div1 => expect(id).to.eq($div1.text()) );
})
If you want to use it within a single case - Allias is the cypress way:
cy.wrap('value').as('variable') //setting the variable
cy.get('#variable') // with this you can call it at any time in the test after aliasing
.then(variable =>{
//here you can use it
})
For a variable to use on multiple cases I recommend using an object as a Reference
let variables = {} //This would need to be declared on the scope you wish to use it in
cy.get('element')
.then($el => {
variables.elText = $el.text() //Assigning the value
})
cy.log(variables.elText) //You can call it later like this

Accessing text of the element using invoke() in each(). Cypress

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

Cypress custom command wont return value

I have a function that I want to add as a command so i can reuse it.
Its on cypress/support/commands.js:
Cypress.Commands.add("generatePassword", () => {
return 'randomstring';
}
);
Then on my test I want to use it as:
it("Visits page", () => {
const password = generatePassword();
cy.log({password})
// Here it logs this:
//{password: {chainerid: chainer146, firstcall: false}}
});
Any idea on how to get the actual value? Now i get this:
{chainerid: chainer146, firstcall: false}
Thanks.
Basically cypress works in promise chain and you're returning the promise chainerid from your custom command. You have to chain it to use in next statement. Use something like below.
it("Visits page", () => {
return cy.generatePassword().then(pwd => {
cy.log(pwd);
});
});

Vue js function countSubcategories() returns [object Promise]

countSubcategories() function returns [object Promise] where it should return row counts of mapped subcategories.
This code is in vue.js & Laravel, Any suggestions on this?
<div v-for="(cat,index) in cats.data" :key="cat.id">
{{ countSubcategories(cat.id) }} // Here subcategories row counts should be displayed.
</div>
<script>
export default {
data() {
return {
cats: {},
childcounts: ""
};
},
created() {
this.getCategories();
},
methods: {
countSubcategories(id) {
return axios
.get("/api/user-permission-child-count/" + `${id}`)
.then(response => {
this.childcounts = response.data;
return response.data;
});
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => (this.cats = response.data));
}
}
};
</script>
As Aron stated in the previous answer as you are calling direct from the template the information is not ready when the template is rendered.
As far as I understood you need to run getCategories first so then you can fetch the rest of your data, right?
If that's the case I have a suggestion:
Send an array of cat ids to your back-end and there you could send back the list of subcategories you need, this and this one are good resources so read.
And instead of having 2 getCategories and countSubcategories you could "merge" then like this:
fetchCategoriesAndSubcategories(page) {
if (typeof page === "undefined") {
page = 1;
}
let url = helper.getFilterURL(this.filterpartnerForm);
axios
.get("/api/get-user-permission-categories?page=" + page + url)
.then(response => {
this.cats = response.data;
let catIds = this.cats.map(cat => (cat.id));
return this.countSubcategories(catIds) // dont forget to change your REST endpoint to manage receiving an array of ids
})
.then(response => {
this.childcounts = response.data
});
}
Promises allow you to return promises within and chain .then methods
So in your created() you could just call this.fetchCategoriesAndSubcategories passing the data you need. Also you can update your template by adding a v-if so it doesn't throw an error while the promise didn't finish loading. something like this:
<div v-if="childCounts" v-for="(subcategorie, index) in childCounts" :key="subcategorie.id">
{{ subcategorie }} // Here subcategories row counts should be displayed.
</div>
Hello!
Based on the provided information, it could be 2 things. First of all, you may try replacing:
return response.data;
with:
console.log(this.childcounts)
and look in the console if you have the correct information logged. If not, it may be the way you send the information from Laravel.
PS: More information may be needed to solve this. When are you triggering the 'countSubcategories' method?
I would do all the intial login in the component itself, and not call a function in template like that. It can drastically affect the performance of the app, since the function would be called on change detection. But first, you are getting [object Promise], since that is exactly what you return, a Promise.
So as already mentioned, I would do the login in the component and then display a property in template. So I suggest the following:
methods: {
countSubcategories(id) {
return axios.get("..." + id);
},
getCategories(page) {
if (typeof page === "undefined") {
page = 1;
}
// or use async await pattern
axios.get("...").then(response => {
this.cats = response.data;
// gather all nested requests and perform in parallel
const reqs = this.cats.map(y => this.countSubcategories(y.id));
axios.all(reqs).then(y => {
// merge data
this.cats = this.cats.map((item, i) => {
return {...item, count: y[i].data}
})
});
});
}
}
Now you can display {{cat.count}} in template.
Here's a sample SANDBOX with similar setup.
This is happen 'cause you're trying to render a information who doesn't comeback yet...
Try to change this method inside created, make it async and don't call directly your method on HTML. Them you can render your variable this.childcounts.

Resources