Cypress interactions commands race condition - cypress

When I try to, for example, click on a button using Cypress, the get command will get the button before it is actionable (still invisible for example). The click command later will fail because the subject passed to it is not actionable. How to avoid this behavior?

Try adding a visibility assertion
cy.get('#button')
.should('be.visible')
.click();

You can add visibility check as well as make sure the button is enabled and then perform a click().
cy.get('selector').should('be.visible').and('be.enabled').click()
I won't suggest you to overwrite an existing cypress command, instead create a custom command under cypress/support/commands.js like this:
Cypress.Commands.add('waitAndClick', (selector) => {
cy.get(selector).should('be.visible').and('be.enabled').click()
})
And in your test you can add:
cy.waitAndClick('button')

Related

How can I check element attribute after click before redirect?

I have a simple login form and I need to check whether button becomes disabled after clicking on it. The issue is that after click on submit, redirect happens, causing assertion step to fail due to element being detached from the DOM. My question is whether it is possible to somehow check visibility attribute of the element after click.
cy.get('[data-cy="email"]').type(this.user.email);
cy.get('[data-cy="password"]').type('valid');
cy.get('button[type="submit"]').click().should('be.disabled'); // this fails due to dettachment from the DOM.
I've attempted to use .then() after click, but that did not help. Using cy.intercept() is also not an option due to how app is written.
Thank you for any idea.
You can do this:
cy.get('button[type="submit"]').click()
cy.get('button[type="submit"]').should('be.visible').and('be.disabled')
When you get the message "element is detached from DOM", it means an action on the page (in this case submit event) has replaced the element with a new version.
If you want to check that the old version of the element has the disabled attribute, this kind-of works.
cy.get('button[type="submit"]')
.click()
.should($el => {
const disabledAttr = $el.attr('disabled')
expect(disabledAttr).to.eq('disabled')
})
But I think this would be flaky if there is a delay between .click() and the disabled attribute getting applied.
If you want to check that the new version of the element has the disabled attribute, this should work.
cy.get('button[type="submit"]').click();
cy.get('button[type="submit"]').should('be.disabled'); // runs after "page load" event

failed because this element is disabled

I have an error in cypress that I get sometimes but other times the tests pass.
I have tried everything but I need it to always work
You should dig deeper to understand why your test is flaky. You haven't specified where you are running this test so it could be your app issue or your resources being tied if you are running in CI.
Either way, it is always good to add assertions before invoking an action on the HTML element. This will ease out the debugging any failures/flakiness. Plus, these assertions will be relatively quick if your app is ready.
// sometimes it is good to check a checkbox is disabled before you enable it
cy.get(x.foward_button1)
.should('be.visible')
.and('be.disabled')
cy.get(x.checkboxes)
// good assertion for any element
.should('be.visible')
// good for any button, checkbox, radio, etc
.and('be.enabled')
.click({multiple:true})
cy.get(x.foward_button1)
.should('be.visible')
.and('be.enabled')
Is button changing state from disabled to enabled by some conditions for exmaple validation or data request?
if yes before clicking button tell cypress to wait for these conditions to end. You can do it ugly way by telling cypress to wait for example 5secounds.
https://docs.cypress.io/api/commands/wait
cy.intercept('GET', '/exampleGet').as('someFetch')
cy.wait(5000) or cy.wait('#someFetch')
You have to add force: true to click the disabled button:
cy.get('x.forward_button1').click({force: true})

Assertions "is.visible" pass on elements which are not visible in cypress

Is there a workaround for this? I want to force Cypress to wait until the loading screen ends but it's asserting a bunch of elements that are not yet visible on screen.
The assertion is:
cy.get('[data-cy="cam-button"]').should('be.visible');
the most important thing is to add to cypress.json flexible/Explicit wait
"defaultCommandTimeout": 25000,
it will wait until your element will be visible and also will apply globally for the whole framework
cypress docs https://docs.cypress.io/guides/references/configuration#Timeouts
You should assert the button is not visible first, then assert the loader is not visible & then assert the button is visible like so:
cy.get('[data-cy="cam-button"]').should('not.be.visible');
cy.get('<loader-selector-here>').should('not.be.visible');
cy.get('[data-cy="cam-button"]').should('be.visible');
You can even use an alias if you use the same selector through the code like so:
cy.get('[data-cy="cam-button"]').as('camButton');
cy.get('#camButton').should('not.be.visible');
cy.get('<loader-selector-here>').should('not.be.visible');
cy.get('#camButton').should('be.visible');
You can add a timeout if you how long it takes
cy.get('[data-cy="cam-button"]', { timeout: xxx }).should('be.visible');

Custom child command in cypress doesn't perform click

Background:
I'm writing test automation for a React web application, using Cypress. In the application I have a dialog box in which there are elements I need to click. When I try to click any of these elements normally, Cypress gives me an error that the element in not visible because its content is being clipped by one of its parent elements, which has a CSS property of overflow: 'hidden', 'scroll' or 'auto'. Because these DOM elements are generated by some 3rd party React components, I cannot change this, and the only way I can work-around it is to use {force:true} in the click command.
The problem:
Because I have few of these elements and in order to keep the DRY principle, I wanted to create a custom child command named forceClick that simply wraps subject.click({force:true}). However, for some reason, when I do that, Cypress does not perform the click command at all!
Note: For debugging purposes I added a cy.log command to the custom command as well, and strangely enough, I see that this log command is executed and only the click command doesn't.
Here's the code:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
subject.click({force:true})});
And inside my test I have the following line:
cy.get("[data-test='panel-VALUES']").forceClick();
Note that if I change it to the following line, it works as expected:
cy.get("[data-test='panel-VALUES']").click({force:true});
Any idea why the click command isn't executed by the forceClick custom command?
You are almost there, you just missed that you have to wrap the subject if you want to work with it.
Cypress.Commands.add('forceClick', {prevSubject: 'element'}, (subject, options) => {
// wrap the existing subject and do something with it
cy.wrap(subject).click({force:true})
})
I never saw a solution with subject.click({force:true}), I'm not saying it won't work, but I just never saw it before. What works anyway is this:
Custom command:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
cy.get(subject)
.click({force:true})});
}
Test step:
cy.forceClick('[data-test="panel-VALUES"]');
If you only use the forceClick you could even shorten it further to this:
Custom command:
Cypress.Commands.add('forceClick', {prevSubject:'element'}, subject => {
cy.log('forceClick was called!');
cy.get(`[data-test=${subject}]`)
.click({force:true})});
}
Test step:
cy.forceClick('panel-VALUES');

Creating a fake command, am I doing it wrong?

I've implemented a login dialog for an application. When the "login" button is clicked, the UI is disabled and a spinner is shown while the login happens. Also, if the user has previously logged in, the app can use a saved token to automatically log in again.
To implement this, I have created two commands. One command for the user-initiated login, and one command for the automatic login. This is so I can observe the IsExecuting Observable for both commands, something like this
_isExecuting = this.WhenAnyObservable(
x => x.CmdLogin.IsExecuting,
x => x.CmdAutoLogin.IsExecuting)
.ToProperty(this, x => x.IsExecuting);
The IsExecuting viewmodel property is then bound to the Isnabled property of the view. This is working and the UI behaves perfectly, but it feels very unclean having two commands. Also, I am triggering the automatic login in the viewmodel like so:
this.WhenActivated((Action<IDisposable> disposer) =>
{
(CmdAutoLogin as System.Windows.Input.ICommand).Execute(null);
});
My question is, what is a cleaner way to do this? Can I do this without having two commands? Cheers.
You don't need to cast to ICommand.
Import this namespace:
using System.Reactive.Linq;
Then just await a command:
await CmdAutoLogin.Execute();
Or use the Adrian Romero way:
Observable.Return(Unit.Default).InvokeCommand();
I think your approach its ok, I am on a similiar situation and I have AutoLogin and Login on separate commands, If you think about it, auto login and login are different things, at least to me. The only thing I'd do different would be to put
CmdAutoLogin execution on the view and not in de view model:
this.WhenActivated(disposables =>
{
Observable.Return(Unit.Default).InvokeCommand(ViewModel.CmdAutoLogin);
});

Resources