failed because this element is disabled - cypress

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

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

If element exists wait for it to disappear

So I'm trying to write some cypress code and the documentation imo isn't really clear.
I have two scenarios.
A page is loaded with no loading spinner.
A page is loaded with a loading spinner.
I would like to write code that would satisfy both scenarios and let the test just continue.
If the page does not have a loading spinner element: Continue with the tests as usual.
If the page does have a loading spinner element: Wait until the element disappears and then continue
Cypress has a lot of fancy functions but the documentation on how to use them just isn't clear enough.
I tried with the following piece of code:
try {
cy.getByTestId('loader-spinner')
.should('exist')
.then(el => {
el.should('not.exist');
});
} catch (error) {
cy.getByTestId('loader-spinner').should('not.exist');
}
Because of the timing aspect it can be tricky to get this test right.
Controlling Triggers
You really need to know what controls the spinner - usually it's a call to API. You can then delay that call (or rather it's response) to "force" the spinner to appear.
To do that, use an intercept
cy.intercept(url-for-api-call,
(req) => {
req.on('response', (res) => res.delay(100)) // enough delay so that spinner appears
}
)
// whatever action triggers the spinner, e.g click a button
cy.getByTestId('loader-spinner') // existence is implied in this command
// if the spinner does not appear
// the test will fail here
cy.getByTestId('loader-spinner').should('not.exist') // gone after delay finishes
Two scenarios
First off, I don't think your two scenario idea is going to help you write the test correctly.
You are trying to conditionally test using try..catch (nice idea, but does not work). The trouble is conditional testing is flaky because of the timing aspect, you get the test working in a fast environment then it starts to break in a slower one (e.g CI).
Better to control the conditions (like delay above) then test page behaviour under that condition.
To test that the spinner isn't appearing, return a stub in the intercept It should be fast enough to prevent the spinner showing.
cy.intercept(url-for-api-call, {stubbed-response-object})
// whatever action triggers the spinner, e.g click a button
cy.getByTestId('loader-spinner').should('not.exist') // never appears
Take a look at When Can The Test Blink?
You should be able to just use a should('not.exist') assertion, causing Cypress to wait for the element to not exist. Remember, Cypress automatically retries up until the timeout, so if you haven't changed the global timeout, then the following will try for up to 4 seconds.
cy.getByTestId('loader-spinner')
.should('not.exist');
If you find the test failing because the element still exists, you can bump the timeout. Below, I've defined a 10s (10000ms) timeout for the should() command.
cy.getByTestId('loader-spinner')
.should('not.exist', { timeout: 10000 });
Additionally, you may find that the element does still exist, but is not visible. In that case, change not.exist to not.be.visible

Cypress interactions commands race condition

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

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');

How to write a Cypress test resilient to changes for a multistep process

I have a multi step process in a Single Page Application. In the future, more steps may be added anywhere in the process and the order of the steps may change. Each step has its component with a form and a next button. That button should be disabled if the user has not selected any options in the form.
I want to write a test that will be resilient to changes in the process. Meaning that I don't want to hardcode the notion of which step is being tested but rather I would like to conditionally go through the whole multi-step process, testing that the next button is disabled if no option was selected. There are no http calls on any of the steps so I don't have to worry about that for this test.
In pseudocode, here is what I have in mind:
visit the first page of the process
while(there is a next button on the page // if not we are on the last step/screen) {
assert that the button is disabled
select the first option in the form
click the button // taking us to the next step/screen in the process
}
I cannot for the life of me figure out how to do this with Cypress. Any help would be greatly appreciated.
Also, if there is another, more cypressy, algorithm to achieve my goals, I'm totally open to it. What matters to me is not so much implementing my exact algorithm but rather writing a test that will not need to be modified if we decide to add a step/screen in the middle of the process.
This is the best I could come up with. It uses recursion and works as intended but is, in my opinion, ugly.
function runTest() {
cy.get('body').then(($body) => {
if ($body.find('button:contains("Next")').length) {
cy.get('input').should('not.have.property', 'checked');
cy.contains('button', 'Next').should('be.disabled');
cy.get('input').first().check({ force: true });
cy.contains('button', 'Next')
.click()
.then(runTest);
}
});
}

Resources