Cypress E2E: Before hook not working on retries - automation

Our tests have a reset state cypress command on the before hook that clears local storage and cookies so that before each run this function makes sure no session was stored.
But we are somehow getting test fail upon retry because it looks like its ignoring the reset state function and doesn't start the test on the signup page.
Is there a way to force the before hook when a retry happens?

The before-hook is excluded from retry as described here: https://docs.cypress.io/guides/guides/test-retries#How-It-Works
Also notable, if something went wrong within before-hook there will be no retry.
There is also an issue that requests this feature: https://github.com/cypress-io/cypress/issues/19458
As a workaround you can use a beforeEach:
let isError = false;
beforeEach(() => {
cy.once('fail', (err) => {
isError = true;
throw err;
});
if (isError) {
cy.resetAll(); // or whatever you have to do before retry
isError = false;
}
});
Explanation: If there is an assertion error then the 'fail'-event is triggered an is catched. In the catch-block the 'isError'-flag is set. Then on the first retry we make our reset-work and also resets the 'isError'-flag.
If you have some initial work that you do in before-hook normally, you have to do a little modification:
let isError = false;
let firstTime = true;
beforeEach(() => {
cy.once('fail', (err) => {
isError = true;
throw err;
});
if (isError) {
cy.resetAll();
}
if (firstTime || isError) {
firstTime = false;
isError = false;
// setupMyBackend()
}
});

Slightly simpler, make the before() callback callable elsewhere (declare and name it).
Use test:after:run event to call it depending on test results.
const beforeCallback = () => {...}
before(beforeCallback)
Cypress.on('test:after:run', (result) => {
if (result.currentRetry < result.retries && result.state === 'failed') {
beforeCallback()
}
})
it('fails', {retries:3}, () => expect(false).to.eq(true)) // failing test to check it out

Related

timer still in the queue despite flush in fakeAsync

I have this test that will result in the infamous "1 timer(s) still in the queue" error:
import {
discardPeriodicTasks,
fakeAsync,
flush,
flushMicrotasks,
tick
} from "#angular/core/testing";
describe("Sleep", () => {
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
it("should sleep async", async () => {
let slept = false;
await sleep(0).then(() => (slept = true));
expect(slept).toBeTruthy();
});
it("should sleep fakeAsync", fakeAsync(async () => {
let slept = false;
await sleep(0).then(() => (slept = true));
flush();
flushMicrotasks();
discardPeriodicTasks();
tick(1000);
expect(slept).toBeTruthy();
}));
});
No amount of flushing or ticking including the hints from this answer will get rid of the timer. What else can I do? The variant without fakeAsync() works fine.
Stackblitz: https://stackblitz.com/edit/test-jasmine-karma-fakeasync-timer?file=app/test.ts
For whatever reason, it works if you convert the sleep(0) Promise into an Observable instead.
it("should sleep fakeAsync", fakeAsync(async () => {
let slept = false;
//await sleep(0).then(() => (slept = true));
from(sleep(0)).subscribe(() => (slept = true));
expect(slept).toBeFalsy();
tick(0);
expect(slept).toBeTruthy();
}));
I ran into a similar problem with debounceTime from Rxjs where no amount of flush(), flushMicroTasks() or discardPeriodicTasks() would release the debounce. However in my case I was able to resolve my problem by making a call to tick() with a sufficiently large time value after my expectation had finished to allow the debounceTime to complete.
I was able to solve the problem in stackblitz removing the await in fakeAsync, because the point of fakeAsync is run synchronously.
So, the modified working test is:
it("should sleep fakeAsync", fakeAsync(() => {
let slept = false;
sleep(100).then(() => (slept = true));
flush();
expect(slept).toBeTruthy();
}));
You just need to use flush to process your timeout time synchronously and the test will pass as expected. Another answer to support my point about fakeAsync and async: Angular testing: using fakeAsync with async/await.
I was still getting this error in my test for nested timeout, a timeout inside a service that is inside a request subscribe. This solution doesn't solve my problem. So I dropped the fakeAsync approach and use the one suggested here: Test a function that contains a setTimeout() and finally I solved my problem.

Vuex, best practice with a global errors and notifications handling

here is what i do, and i'am not realy sure its correct :
//store
async addUser({commit}) {
try {
const {data} = await apiService.addUser()
commit('SET_USER', data)
commit('SET_NOTIFICATION', {type:'success', message: 'user successfuly created'})
} catch (error) {
commit('SET_NOTIFICATION', {type:'error', message:error})
}
}
SET_USER(state, user) {
state.users.push(user)
}
//my component:
async addUser() {
this.isLoading = true
await this.$store.dispatch('updatePatient', this.form)
this.isLoading = false
}
is it legit ?
sometimes i think i would need more logic inside my component depending on the succes or rejected api request. Should i put all the logic in my actions ? like i do at the moment ?
Maybe should I add a status state for each actions, for example :
state {
users: []
postUserSuccess: null
postUserError: false
updateUserSuccess: null
updateUserError: false
// ...
}
and do what i want in the component with a computed property mapped to the store ?
What do you think ?
I don't know if it's a best practice but I let the components the exception handling. That method has its pros (you don't have to pollute the state with error management) and cons (you have to repeat the error management code for every action call).
All service calls will be made in actions
The state will only be set in mutations.
All service calls will return a promise with a resolve(data to load in the state) and a reject(message errors to present).
There will be an interceptor to reject the response in case there's a custom error (here you can put if the response has an error prop reject the response and send as an error the error prop, now you don't have to deconstruct the response in the action).
I'm going to give you a simplified example (I use axios, you can learn how to do it with the library that you use).
Actions in Vuex are asynchronous. So you don't need to try/catch them.
ApiService - Add User
const addUser = () => {
return new Promise((resolve, reject) => {
axios
.post(url, user)
.then(response => resolve(response.data))
.catch(error => reject(error));
});
};
store
async addUser({commit}) {
const data = await apiService.addUser();
commit('SET_USER', data);
return data;
}
if the promise in apiService.addUser is resolved the commit is going to be made if is rejected axios will return the promise and you can catch the error in the component that calls the action.
Component
async addUser() {
this.isLoading = true;
try {
await this.$store.dispatch('updatePatient', this.form);
} catch (error) {
// here goes the code to display the error or do x if there is an error,
// sometimes I store an errors array in the data of the component other times I do x logic
}
this.isLoading = false;
}
State
Your state will be cleaner now that you don't need to store those errors there.
state {
users: []
}

How to test `state` changes in stage in jest and enzyme when simulate?

How do we test changes in state variable in a stage which means I want to test not the final result but also the result in between.
Below is the code I want to test.
onLogin = () => {
this.setState({ isLoggingIn: true }, () => {
try {
} catch (err) {
// Alert.alert(getString('ERROR'), err.message);
} finally {
this.setState({ isLoggingIn: false });
}
});
}
Below is my test code.
it('should call onLogin callback', () => {
const spy = jest.spyOn(wrapper.instance(), 'onLogin');
const loginBtn = wrapper.find('#login');
loginBtn.props().onPress();
expect(spy).toHaveBeenCalled();
expect(wrapper.instance().state.isLoggingIn).toEqual(false);
// Attempt => Below I've tried
// wrapper.update();
// expect(wrapper.instance().state.isLoggingIn).toEqual(true);
// wrapper.update();
// expect(wrapper.instance().state.isLoggingIn).toEqual(false);
wrapper.instance().state.isLoggingIn is always false. How do I monitor this changes to false then true?
How do we test changes in state variable
We don't. Doing this would:
make tests less maintainable
hard-code state structure in the tests: so unit tests would fail even if everything is fine(more needless work)
still keep us unsure if it works at all.
Just test render() result and interact with everything it returns. This way you would test what really matters.

Check if the dispatch vuex is done

Hi I have this create and update zone function. After the API call success. I will callback again the dispatch on vuex store. Then Go back to main zone page.
The problem is it will took around 5 secs to get the list the results of dispatch. Making my list not updated.
How to know if the dispatch is done before going back to the page?
loadZones(){
this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
this.loadZones();
this.$router.push('/zone');
} else{
this.has_error = true;
})
}
Vuex actions always return Promise, just add return when you create request in your getZones action to chain your ajax request promise with returned by action, then you can do something like this:
//... in actions, example
getZones(context) {
return some_http_request()
}
//...
loadZones(){
return this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
// next "then" will be invoked when this request will be done
return this.loadZones();
}
else throw new Error();
})
.then(() => {
this.$router.push('/zone');
})
.catch(() => this.has_error = true);
}
You can use async await.
When you make loadZones async function, in it you can use await on the dispatch getZones. But remember that the getZones action should return a promise. I believe that it already returning a promise, so you just have to add async await.
async loadZones(){
await this.$store.dispatch('getZones');
},
createOrUpdateZone(zone, region_checkbox, callback){
this.$http.post(process.env.API_URL +'/api/.....)
.then(res=> {
if(res.data.success == true){
this.loadZones();
this.$router.push('/zone');
} else{
this.has_error = true;
})
}

NodeJS Restify Nested Promise Error Handling

I have made API on restify for my product. I have installed loggly and newrelic for monitoring purposes.
Recently newrelic notified me that there was an error somewhere in my code.
app.get('/myroute', (req, res, next) => {
let payload = { /*...*/ };
return new Promise((resolve, reject) => {
/* some code */
return resolve();
})
.then(/* some promise */)
.then(() => {
/* some code */
sendEmail({params}); // some error was thrown from one of the promise chain
// inside this function...
return something;
})
.then(/* some promise */)
.then(() => {
res.send(something);
})
.catch(next);
});
The promise chain resolved perfectly fine and user gets proper response, because I didn't return the sendEmail function. And I don't want to, because I don't want the user to wait for the response for long.
function sendMail(params) {
return new Promise((resolve, reject) => {
/* some code */
return resolve();
})
.then(() => {
params.abc.replace('a', 'b'); // error thrown, cannot find replace of undefined.
return something;
});
}
newrelic caught the error, but loggly didn't. I already have restify's formatters setup but it won't be enough as the request was successfully served and didn't fall into .catch(next).
I don't want a solution to fix the sendMail function. I just want to log if there is a bug like this.
Is there anyway to catch and log this type of error without putting a .catch() to sendMail function?
I figured out. I was trying process.on('uncaughtException') to handled it.
But I came across process.on('unhandledRejection') which did the job!
https://nodejs.org/api/process.html#process_event_unhandledrejection