How to store the value from text box in another variable using Cypress - input

name="titleEn" data-testid="edit-category-title-en" type="text" placeholder="Enter category title" value="vxfifyem"

Depending on the purpose, there are few ways to do it:
Global scope variable: (BAD PRACTICE)
let myValue;
before(()=> {
cy.get('[data-testid="edit-category-title-en"]').then($el => {
myValue = $el.text();
})
})
it(()=> {
cy.log(myValue) // myValue is now available
})
but, as you can see the callback and the variable call cannot be used in the same scope, otherwise the variable will be null.
In case you need it in a common scope, then you can use the Cypress.Promise utility, so Cypress will not continue until the promise is resolved.
it('', async () => {
const myValue = await new Cypress.Promise((resolve) => {
cy.get('[data-testid="edit-category-title-en"]')
.invoke('text')
.then((txt) => resolve(txt.toString()))
})
})
cy.log(myValue) // myValue is available
Using Cypress Aliases (BEST PRACTICE)
I do recommend to go throw documentation first: Cypress Variables and Aliases
cy.get('[data-testid="edit-category-title-en"]').invoke('text').as('myValue');
then you can call it via: cy.get('#myValue').

Related

How to increase the range of an array taken from an API query

I have a function for when clicking a button increase the contents of a list.
Content is removed from an API by the following code:
const [data, setData] = useState();
const [maxRange, setMaxRange] = useState(2);
const getAPIinfo = ()=>{
GetEvents(maxRange, 0).then((response) => response.json())
.then(result_events => {
const events = result_events;
setData({events:events});
}).catch(e => setData({events:events}));
}
And my function is this:
const buttonLoadMore = ({data,type}) =>{
setMaxRange(prevRange => prevRange + 4);
data = data.slice(0,maxRange);
}
what I'm not able to do is update the maxRange value of the API query to increase the list...
this function should be heavily refactored:
const buttonLoadMore = ({data,type}) =>{
setMaxRange(prevRange => prevRange + 4);
data = data.slice(0,maxRange);
}
when you use maxRange here, you are setting new state, while the function itself ir running, the state is not instantly updated, buttonLoadMore is a function in a particular time. it cannot get new maxRange instantly, while running buttonLoadMore does that make sense? Also you cannot update data state just like a regular variable by assigning new variable using = operator, you should refactor this function to something like this:
const buttonLoadMore = ({data})=> {
const newMaxRange = maxRange + 4;
setMaxRange(newMaxRange);
const newData = {events: [...data.events.slice(0, newMaxRange)]};
setData({...newData})
}
also you will get bug here. since your getAPIinfo is setting data state to an object {events: events}. I took the liberty and tried refactoring it here.
There is also a bug in your getAPIinfo in line }).catch(e => setData({events:events})); the events variable you declared in .then function cannot be reached here. It is simply out of scope. unless you know that .catch resolves into data, you will get an error in this line.
take a look at this example here:
const promiseFunction = ()=>{
return new Promise<string>((resolve)=>resolve('i like coca cola'))
}
const getter = () => {
promiseFunction()
.then(response => {
const thenVariable = response;
console.log(thenVariable) // i like coca cola
})
.catch(error=>{
console.log(thenVariable) // Error:Cannot find name 'thenVariable'.
})
}
as you can see .catch() is in different scope than .then() will not be available outside so events cannot be reached by .catch function.
Usually you would use catch for error handling. Maybe show a line on screen, that error has accoured, and data cannot be fetched at this time. etc. There's a very good book that explains all these concepts in detail here: https://github.com/getify/You-Dont-Know-JS
I would strongly recommend for you to switch to typescript because your code is crawling with bugs that should be easily avoided just by type checking, and adding eslint configurations.

Cypress Command never runs, doesn't fail, and returns some meta data instead

DEFINE COMMAND
Cypress.Commands.add('getLocalStorage', () => {
const state = window.localStorage.getItem('state');
return JSON.parse(state);
});
USE COMMAND
const localState = cy.getLocalStorage();
RESULT
localState variable holds this value:
chainerId: "chainer6"
firstCall: false
specWindow: Window {parent: global, opener: null, top: global, length: 0, frames: Window, …}
useInitialStack: false
userInvocationStack: " ......"
Using version 5.5.0
Just use a javascript function
const getLocalStorage = () => {
const state = window.localStorage.getItem('state');
return JSON.parse(state);
});
const localState = getLocalStorage();
Custom commands produce chainers that are for, well, chaining
cy.getLocalStorage().then(state => ...
Cypress runs a command queue asynchronously from the javascript in the test. If you want a piece of JS to run synchronously inside the queue, you create a custom command.
Or you can use .then() to hook into the command sequence.
You are using the wrong window. The global window is for the Cypress runner, cy.state('window') gets you the window in the iframe, the one the app is using.
Cypress.Commands.add('getLocalStorage', () => {
const state = cy.state('window').localStorage.getItem('state');
return JSON.parse(state);
});
The Cypress documentation advises against assigning the return value of Cypress commands (eg. const localState = cy.getLocalStorage();)
https://docs.cypress.io/guides/core-concepts/variables-and-aliases.html#Return-Values

Understanding then() in Cypress

I am reading through the documentation in Cypress and I think I have an idea as to what then() does. It works like promises, where a promise returns another promise, but with then(), we are returning a new subject.
If we look at the code example below, we are using then() because we are returning a new variable, which in this case is called target.
Am I understanding this correctly? If not, can someone correct me?
it.only('Marks an incomplete item complete', () => {
//we'll need a route to stub the api call that updates our item
cy.fixture('todos')
.then(todos => {
//target is a single todo, taken from the head of the array. We can use this to define our route
const target = Cypress._.head(todos)
cy.route(
"PUT",
`api/todos/${target.id}`,
//Here we are mergin original item with an object literal
Cypress._.merge(target, {isComplete: true})
)
})
.then is used to receive the results from cy.fixture('todos'). The variable target is not significant in this code.
In your code sample, the variable that is returned from cy.fixture is named todos - the spacing of the code may be throwing you off here? The .then call is attached to the cy.fixture() call
// These 2 code blocks are the same - just different spacing
cy.fixture('todos')
.then(todos => {});
cy.fixture('todos').then(todos => {});
https://docs.cypress.io/api/commands/fixture.html#Usage
cy.fixture('logo.png').then((logo) => {
// load data from logo.png
})
Using .then() allows you to use the yielded subject in a callback function and should be used when you need to manipulate some values or do some actions.
To put it simply, it is used to play around with the yield of the previous command and work around with it in that case. THEN() command is handy and helpful in debugging the yield of the previous command.
const baseURL = "https://jsonplaceholder.typicode.com";
describe("Get Call-Expect+ normal req", () => {
it("GetPostById-Expect", () => {
cy.request(baseURL + "/posts/1").as("GetPostById");
cy.get("#GetPostById").then((response) => {
//response: status
expect(response.status).to.equal(200);
expect(response.status).to.eq(200);
});
});
Refer: https://docs.cypress.io/api/commands/then#Promises

Vuex Object Shows Null in Vue Component

In the app I'm creating I have a base store for objects that might be used across the entire app, such as the logged in user, validation errors and the like.
I also have other namespaced modules for specific sections of my app.
When my parent component is loaded there is an ajax call that pulls in data and commits it to the various stores.
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => {
dispatch('base/setLoggedInUser', response.data.user, { root: true })
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
Then I use mapped getters to access items from my stores.
computed: {
...mapGetters({
user: 'base/getUser',
fetishList: 'setup/getFetishesList',
localeData: 'setup/getLocale',
colorsList: 'setup/getColorsList',
rolesList: 'setup/getRolesList',
genderList: 'setup/getGendersList',
orientationList: 'setup/getOrientationsList',
lookingList: 'setup/getLookingsList',
seekingList: 'setup/getSeekingsList',
validation: 'base/getValidationErrors',
}),
}
All is working as expected except for my user object.
In my Vue inspector I can see that the user object is stored properly in Vuex as expected, but when I console.log(this.user) I get null and anytime I try to access a user property I get console errors.
Can anyone explain why this might be happening, I've never seen this before and have no idea what I'm looking for?
Thanks.
My guess is that your dispatch() (Vue.js actions are ALWAYS expected to be async) is not completing properly. This is how I would rewrite it with a single caveat:
Your base/setLoggedInUser Vuex action MUST return a Promise for this to work properly.
/*
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => {
dispatch('base/setLoggedInUser', response.data.user, { root: true })
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
*/
export const instantiate = ({ commit, dispatch }) => {
return axios.get('/setup/fetch')
.then((response) => Promise.all([
dispatch('base/setLoggedInUser', response.data.user, { root: true }),
Promise.resolve(response)
]))
.then(([dispatchResponse, response]) => {
commit('setFetishesList', response.data.fetishes)
commit('setColorsList', response.data.colors)
commit('setRolesList', response.data.roles)
commit('setGendersList', response.data.genders)
commit('setOrientationsList', response.data.orientations)
commit('setLookingsList', response.data.lookings)
commit('setSeekingsList', response.data.seekings)
commit('setBodiesList', response.data.bodies)
commit('setHeightsList', response.data.heights)
commit('setEthnicitiesList', response.data.ethnicities)
commit('setHairsList', response.data.hairs)
commit('setEyesList', response.data.eyes)
commit('setPiercingsList', response.data.piercings)
commit('setTattoosList', response.data.tattoos)
commit('setSmokingsList', response.data.smokings)
commit('setDrinkingsList', response.data.drinkings)
commit('setStatusesList', response.data.statuses)
commit('setEducationsList', response.data.educations)
commit('setAgesList', response.data.ages)
return Promise.resolve(response)
})
}
There are two main posibilities here:
The first one is that you might not be defining properly the user getter.
The second one, console.log is being executed previous to the data being set by this action:
dispatch('base/setLoggedInUser', response.data.user, { root: true })
Vuex actions are asynchronous, so setLoggedInUser could have started before the console.log (and the code giving you errors) is executed, but the actual data might not have been received yet at that point (it would be undefined).
If this is the case, add the following condition to the part of the template or the component(s) that are using the block of code where you are getting those errors:
v-if="user"
This will make Vue to wait for the mapped getter user to have a value to mount said template segment or components, avoiding trying to access properties of undefined.

cypress: variable scope outside the then block. I am facing issue

I have issue with scope outside the then block. Need help of exerts
it("Badges Count", () => {
var totalBadges
cy.get("div:nth-child(4) > span.quick-stat-figure").then($el => {
cy.get("div.profile-quick-stats > div:nth-child(4)").click();
totalBadges = $el.get(0).textContent;
cy.log("Total Badges", totalBadges); // scope is working here
});
cy.log("Badeges", totalBadges) // scope is not working here
});
In cases such as these I like to use aliases.
Simply wrap() your value, whatever it is, and save it with an alias, then get() and use it wherever you need.
Based on your example it should look something like this:
it("Badges Count", () => {
// var totalBadges - you don't need this anymore
cy.get("div:nth-child(4) > span.quick-stat-figure").then($el => {
cy.get("div.profile-quick-stats > div:nth-child(4)").click();
let totalBadges = $el.get(0).textContent;
cy.wrap(totalBadges).as('totalBadges');
// this is now accessible globally via get('#totalBadges')
});
cy.get('#totalBadges').then((myBadges) => {
cy.log("Badeges :"+ myBadges) // should work just fine
}
});
I know it might not look very intuitive, but working with Cypress you will rarely ever need to use variables.
You can check out the Cypress documentation on aliases here.