Cypress e2e test without using wait function - reactjs

I'm new in e2e testing with Cypress and I have a question.
I wrote a test and I want not to use Cypress wait() command in it.
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS).then(() => {
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', { timeout: 10000 }).click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox).should('be.checked')
);
});
cy.get('span').contains('Change').click().then(() => {
cy.get('li').contains('Disable').click().then({ timeout: 5000 }, () => {
cy.wait(500);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[1]')
.then(checkbox => checkbox[0]).should('be.checked')
);
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[18]').click().then(() => {
expect(
cy.xpath('(//input[#type="checkbox"])[2]')
.then(checkbox => checkbox[3]).should('not.checked')
);
});
});
cy.xpath('//span[contains(text(), "Disable Selected")]').click().then(() => {
cy.get('li').contains('Enable').click().then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('not.checked')
);
});
});
cy.get('input[type=checkbox]').then(el => el[0].click()).then(() => {
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
}).then(() => {
cy.xpath('//i').then(x => x[1].click());
cy.get('input[type=checkbox]').should('not.checked');
});
cy.get('div[data-main-select=true]').then(list => list[1].click()).then(() => {
cy.xpath('(//li[contains(text(), "50")])[1]').click().then(() => {
cy.get('input[type=checkbox]').should(checkboxes => {
expect(checkboxes).to.have.length(51);
});
});
});
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click().then(() => {
cy.wait(150);
expect(
cy.get('input[type=checkbox]')
.then(checkbox => checkbox).should('be.checked')
);
});
});
});
});
My problem is that if I not use that cy.wait(500) command, the test fails.
I googled a lot but I cannot find an answer for my problem.
The project is an React project, using Hooks. What may be caused the problem is that my component renders multiple times after that click() event. The page does not reload, but the main component rerenders several times. If this is the problem, how can I wait for finishing all renderings, and just after that continue the test, without using the wait function ?

You shouldn't need to use wait with time, but sometimes you may need to wait for a certain request to finish. I find it's the case with some AJAX requests. You can get it to wait for a specific request to finish using route definitions:
cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')
// visit the dashboard, which should make requests that match
// the two routes above
cy.visit('http://localhost:8888/dashboard')
// pass an array of Route Aliases that forces Cypress to wait
// until it sees a response for each request that matches
// each of these aliases
cy.wait(['#getActivities', '#getMessages'])
// these commands will not run until the wait command resolves above
cy.get('h1').should('contain', 'Dashboard')
Read more here: https://docs.cypress.io/guides/guides/network-requests.html#Waiting

Looking at it a bit further, you seem to be using a promise-style syntax unnecessarily.
Because Cypress has automatic retry on (most) commands, you can just call the commands sequentially without awaiting the result with .then(... constructs.
As an example, your code might be revised to something like this,
it('Should modify checkbox state', () => {
login();
cy.visit(TabsSettings.SIZE_FORMATS);
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]', {timeout: 10000 }).click();
// cy.xpath('(//input[#type="checkbox"])[1]').then(checkbox => checkbox).should('be.checked')
cy.get('input[#type="checkbox"]).eq(0).should('be.checked'); // NB eq() has zero-based indexing.
cy.get('span').contains('Change').click();
cy.get('li').contains('Disable').click();
// OR if you have to wait for the Disable button/link to appear, use this
cy.contains('li', 'Disable').click();
cy.xpath('(//div[#data-qa="sizeFormats"]//*[4]//*//*[1]//*//*//*)[1]').click();
cy.get('input[#type="checkbox"]).eq(0).should('be.checked');
...
I'm not familiar with xpath syntax, so the translation is a guess, but it seems Cypress' commands are a little more readable, have a flatter layout, PLUS you get auto-retry built in, which removes the need to wait for specific periods.
If you want to add to your question a pseudocode translation of the xpath expressions, I'll give you the equivalent Cypress commands.

Related

Testing with Karma Angular NgRX effects with applied timer

Having the following NgRX effect
loadData$: Observable<any> = createEffect((): Observable<Action> => {
return this.actions$.pipe(
ofType(loadDataStarted),
switchMap(() => {
return timer(0, 10000).pipe(
switchMap(() => this.dataService.loadNewData().pipe(
)),
);
}),
);
});
after all the requirements are mocked try to test if dataService.loadNewData() is getting called, but the test will fail
beforeEach(() => {
service.loadNewData.and.returnValue(of(data));
});
it('should call service', fakeAsync(() => {
tick(10000)
expect(service.loadNewData).toHaveBeenCalledOnceWith();
}));
How do I mock correctly this case
It seems you perform the subscription to the effect outside of the actual test (the it block). This means the subscription lives outside of the fakeAsync timer and because of this, the tick(..) has no effect.
To solve this, make sure to setup everything inside your fakeAsync test block.
A general recommendation would be to use marble testing for this: https://ngrx.io/guide/effects/testing#marble-diagrams
Edit: A concrete solution is found in https://stackoverflow.com/a/65857864/2544163

cypress - TypeError cannot set property 'status' of undefined

I am trying to use cypress on my UI testing each time I got TypeError cannot set property 'status' of undefined error in a different place
describe('cats app', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
cy.visit(Cypress.env('host'))
})
it('login to cats', () => {
// clear local storage
cy.clearLocalStorage()
cy.get('#email').focus().type(Cypress.env('email'))
.should('have.value', Cypress.env('email'))
cy.screenshot("login-email")
cy.get('#password').focus().type(Cypress.env('password'))
.should('have.value', Cypress.env('password'))
cy.screenshot("login-password")
cy.get("#login").click({force: true})
// to ensure login is working
cy.get("#TicketsNav").should('be.visible')
cy.screenshot("TicketsNav")
})
})
I tried to handle the issue as it happens most of the time while typing by adding and use these commands
Cypress.Commands.add(
'paste',
{
prevSubject: true,
element: true,
},
($element, text) => {
const subString = text.substr(0, text.length - 1);
const lastChar = text.slice(-1);
cy.get($element)
.click()
.then(() => {
$element.text(subString);
$element.val(subString);
cy.get($element).type(lastChar);
});
}
);
Cypress.Commands.overwrite('type', (originalFn, subject, text, options = {}) => {
options.delay = 20
return originalFn(subject, text, options)
})
also, I tried to force the chrome browser
cypress run --browser chrome
but I am still getting that error if different places

debounceTime not limiting api calls

I have this code:
await commuteReportService.getAddresses(query).pipe(debounceTime(1000))
.subscribe((response: AddressesAPIResponse) => {
console.log('execute call', response);
});
However, in the network tab I still see that all the requests are being sent. This code is used in a autocomplete component where I want to limit the calls being sent.
I'm using rxjs 5.5, React and Typescript.
debounceTime is only applied to whatever comes after it, meaning it only applies to your 'subscribe' — not getAddresses(query), which is where I assume you're making calls.
Consider this minimal example:
// html
<input type="text" id="example" />
// js
const input = document.getElementById('example');
Rx.Observable
.fromEvent(input, 'keyup')
.map(v => {
console.log('called')
return v.target.value;
})
.debounceTime(500)
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Even though Debounced... is delayed, you'll still see called being logged to the console on every keystroke. If we change the order
// js
Rx.Observable
.fromEvent(input, 'keyup')
.debounceTime(500)
.map(v => {
console.log('called')
return v.target.value;
})
.subscribe(val => {
console.log(`Debounced: ${val}`);
});
Now both Debounced... and called are delayed (Here's the fiddle for the code above).

How do I unit test calling a service with promise-retry using Mocha?

I have an action in my ReactJS project that calls a notification service. It is required that, if the service call fails once, I must try calling the service again only one time before proceeding with the error state in the application. I used the promise-retry module for this and was able to get it working locally. However, I am now trying to write unit tests (Mocha) for the promiseRetry-wrapped service calls themselves and having incredible difficulty getting meaningful tests to pass. First, here is the action that calls the service, wrapped in a promiseRetry.
import promiseRetry from 'promise-retry';
...
const sendNotification = () => {
return (dispatch, getState) => {
const request = buildNotificationRequest(getState);
dispatch(createNotificationAttempt());
promiseRetry((retry) => {
return createNotificationService(request)
.catch(retry);
}, {retries: 1}).then(
() => {
dispatch(createNotificationSuccess());
},
(error) => {
dispatch(createNotificationError(error));
}
);
};
};
Typically, the way that I would write unit tests for actions calling services is something like this:
describe('notification actions', () => {
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.resolve({}));
});
it('should log an attempt', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT);
});
});
This works fine for testing the initial attempt, but for some reason, even though I can debug and step through the tests and hit all of the code inside the promiseRetry, the actions inside of them (such as dispatch(createNotificationSuccess())) are not logged in the store, so I cannot run expect statements on them. Every angle I have tried up to this point only retrieves the attempt from the store, and I cannot get any data from the success or failure side of the Promise.
I have found some information on Stack Overflow about testing promise-retry itself, but I need to know that if I stub the service I'm calling and force it to fail, that it will log another attempt and another failure. Or, if I stub the service and force it to succeed, it will only log one attempt, one success, and complete. As I mentioned previously, the only action I am getting in the store is the attempt, and nothing about success or failure, even though stepping through debug shows that all of those lines of code are hit.
Here is an example of a test that I cannot get to pass:
import * as services from 'services.js';
...
describe('the first time the service call fails', () => {
const error = {status: 404};
beforeEach(() => {
sendNotification = sinon.stub(services, 'createNotificationService').returns(Promise.reject(error));
});
it('should log a retry', () => {
store.dispatch(notificationActions.sendNotification());
const actions = store.getActions();
expect(actions[0].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_ATTEMPT); // this passes
expect(actions[1].type).to.equal(notificationActions.ACTION_TYPES.CREATE_NOTIFICATION_FAILURE); // this fails because there are no other actions logged in the store.
Maybe I am misunderstanding the way promise-retry works? Shouldn't it hit my error action (dispatch(createNotificationError(error)) the first time it fails, and the second time (if applicable)? If not, it should be at least logging two attempts. Any advice?

Wait for page redirection in Protractor / Webdriver

I have a test that clicks a button and redirects to a user dashboard. When this happens Webdriver returns:
javascript error: document unloaded while waiting for result.
To fix this I insert browser.sleep(2000) at the point where redirection occurs and assuming my CPU usage is low, this solves the issue. However, 2000 ms is arbitrary and slow. Is there something like browser.waitForAngular() that will wait for the angular to load on the redirected page before the expect(..)?
it('should create a new user', () => {
$signUp.click();
$email.sendKeys((new Date().getTime()) + '#.com');
$password.sendKeys('12345');
$submit.click();
browser.sleep(2000); // Need alternative to sleep...
// This doesn't do it...
// browser.sleep(1);
// browser.waitForAngular();
$body.evaluate('user')
.then((user) => {
expect(user).toBe(true);
});
});
do you think something like this could work for you? This will wait up to 10 seconds for the url to include the text 'pageTwo', or whatever you put in.
var nextPageButton = $('#nextPage');
nextPageButton.click().then(function(){
return browser.driver.wait(function() {
return browser.driver.getCurrentUrl().then(function(url) {
return /pageTwo/.test(url);
});
}, 10000);
};
Just stick in the regex of the url you are expecting.
Alternatively, you could wait for an element from the next page to appear as well:
var nextPageButton = $('#nextPage');
nextPageButton.click();
var elementFromSecondPage = $('#coolElement');
browser.wait(protractor.until.elementIsVisible(elementFromSecondPage), 5000, 'Error: Element did not display within 5 seconds');
When using .click, protractor will naturally wait for angular to finish the action attached to the click, such as changing the page. But, while the page change, you may still be needing something specific to be loaded, so the test fails before that part is available. Using this, it should wait for the click part to finish, then wait for the element to appear.
To expand on user2020347's answer:
Thanks that solved my issue. I wonder why this isn't a built in function. I'll be using this in many places to wait for browser navigation.
To make it more concise, I made a little helper:
Object.assign(global, {
waitUntilURLContains: string => {
let fn = () => {
return browser.driver.wait(() => {
return browser.driver.getCurrentUrl().then((url) => {
return url.includes(string);
});
}, waitDelay);
}
return fn.bind(null, string);
}
});
In my test:
$button.click().then(waitUntilURLContains('dashboard'));
keeping it very simple. I was also running into the same problem but was able to solve it using the following code :
page.setUsername(objectrepository.userdetails.useremail);
page.setPassword(objectrepository.userdetails.userpassword);
page.login().click();
**browser.wait(EC.visibilityOf(page.greetingMessageElement()), 5000);**
page.greetingMessageElement().getText()
.then(function (value){
expect(browser.getCurrentUrl()).toContain("#/mytickets");
});

Resources