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
Related
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})
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);
}
});
}
I have a React web app which uses Blockly that I'm currently trying to write automated tests for using the Cypress framework.
Cypress works pretty well for the basic process of signing on, but starts behaving inconsistently once Blockly is supposed to load.
About half the time, the entire Blockly portion of the app doesn't show up at all in the Cypress viewport. Sometimes this shows up, sometimes it doesn't, and I'm unsure what causes it or how to really reproduce it, it seems to be random.
Here is how it looks when it loads properly
Here is how it looks when it doesn't load properly
At first, I thought that the reason it didn't work is because the resources for Blockly hadn't loaded, and Cypress was trying to access resources that didn't exist.
To work around this, I added a delay, using cy.wait(). I tried anywhere from 1s-10s, but the delay doesn't seem to affect anything, no matter how long the delay is, it doesn't seem to impact if the Blockly portion of the app loads properly or not.
Here is the code for the portion of the Cypress test file used:
it('Sign in with created profile', () => {
cy.visit('localhost:3000/');
cy.get('input[name="email"]')
.type('test123#test.com').should('have.value', 'test123#test.com');
cy.get('input[name="password"]')
.type('testtest').should('have.value', 'testtest');
cy.get('button[type="submit"]').click();
});
it('Open created project', () => {
cy.get('div[class="project-container"]').contains('test project').click();
});
it('Drop 1+1 block into grid', () => { //2s delay
cy.wait(2000).then((prom) => {
cy.get('div[class="blocklyTreeRow"]').contains('Math').click({ force: true });
});
});
It works perfectly, until just after the 'Open created project' part of the test is run, then it's a hit or miss if the Blockly part of the app shows up. Refer to the images above for possible scenarios that happen.
Please check you are not trying to automate iframe because cypress do not support iframe
Iframe opened Question :
https://github.com/cypress-io/cypress/issues/136
Else :
Try to use Aliased.
// Wait for the route aliased as 'getAccount' to respond
// without changing or stubbing its response
cy.server()
cy.route('/accounts/*').as('getAccount')
cy.visit('/accounts/123')
cy.wait('#getAccount').then((xhr) => {
// we can now access the low level xhr
// that contains the request body,
// response body, status, etc
})
More documentation available here :
https://docs.cypress.io/api/commands/wait.html#Alias
In my webapp I have a simple textfield. To this textfield I have a jQuery function which will be always executed on every keyup. With this function there is an Ajax request assigned which loads every time the result of the SQL-Query. My code is equivalent to the code of
RailsCasts. Now I'm testing my webapp with Selenium. With this line of code
browser.text_field(:id => 'textfield').set("Search text")
the text will be written and the content will be changed. After it should click on a link which is placed on the dynamic content with this code
browser.a(:id => "link").click
The problem now is that the click event won't be executed. Has somebody an idea what the problem could be? Or maybe an example with Watir and Ajax?
Without an example site to test against it's hard to be sure but I will throw out a few potential solutions for you
If the client side javascript is looking for onkeyup events, you may need to fire one after setting the contents of the field. You can do that via the .fire_event method
You could just be looking at a timing issue. If the nature of the link is changing as a result of the input, it's possible that Watir is firing off the two comments in rapid succession and the client side code is still in the midst of doing it's thing (especially if there is back and forth between the jquery code and the webserver that as happening as that also induces networking delays. You may need a brief sleep between commands (brute force) or to wait for a particular element to assume an expected state (a little more work but also a bit more robust and not subject to breaking is the delay exceeds your sleep duration)
I'd suggest executing the commands manually via IRB (you could just cut and paste from your script as needed) to be able to watch the state of the browser, and note any delay in updating the screen/DOM after a particular action. If stuff works properly in IRB but not when executed via a script it's often taken as confirmation of a timing issue.
If this is the original Watir/Firewatir I would try getting it to hover over the link before it attempts to click it.
I've had this problem previously with links that appear after typing into an "autocomplete" field (i.e. it attempts to guess at the text you want by filtering down from a huge list of possibilities).
Try this:
browser.wait_until{browser.link(:id => "link").present?}
browser.link(:id => "link").fire_event("onmouseover")
browser.link(:id => "link").click
If it works, try it without the .fire_event("onmouseover"), because it could just be that it's trying to click the link before it's visible and failing to do so.
If this is actually a question regarding "Selenium" (wrongly labelled Watir) then ignore all of the above, because I that is an application I've never used.
you can use capybara method.
click_link("link")
for ajax set :js => true in you test case
http://opinionatedprogrammer.com/2011/02/capybara-and-selenium-with-rspec-and-rails-3/
I've been working on Chrome Extension for a website for the past couple of days. It's coming along really nicely but I've encountered a problem that you might be able to help with.
Here's an outline of what the extension does (this functionality is complete):
A user can enter their username and password into the extensions popup - and verify their user account for the particular website
When a user browses http://twitter.com a content script is dynamically included that manipulates the DOM to include an extra button next to each tweet displayed.
When a user clicks this button they are presented with a dialog box
I've made a lot of progress but here is my problem:
When a user visits Twitter the content script is activated and all tweets on the page get my new button - but if the user then clicks 'More...' and dynamically loads the next 20 tweets... these new additions to the page DOM do not get affected by the content script (because it is already loaded).
I could add an event listener to the 'More...' button so it then triggers the original content script again (and adds the new button) but i would have to predict the length of twitter's ajax request response.
I can't tap into their Ajax request that pulls in more tweets and call my addCurateButton() function once the request is complete.
What do you think is the best solution? (if there is one)
What you want to do is to re-execute your content-script every time the DOM is changed. Luckily there is an event for that. Have a look at the mutation event called DOMNodeInserted.
Rewrite your content script so that it attaches an event listener to the body of the DOM for the DOMNodeInserted event. See the example below:
var isActive = false;
/* Your function that injects your buttons */
var inject = function() {
if (isActive) {
console.log('INFO: Injection already active');
return;
}
try {
isActive = true;
//inject your buttons here
//for the sake of the example I just put an alert here.
alert("Hello. The DOM just changed.");
} catch(e) {
console.error("ERROR: " + e.toString());
} finally {
isActive = false;
}
};
document.body.addEventListener("DOMNodeInserted", inject, false);
The last line will add the event listener. When a page loads the event is triggered quite often so you should define a boolean (e.g. var isActive), that you initialize to false. Whenever the inject function is run check whether isActive == true and then abort the injection to not execute it too often at the same time.
Interacting with Ajax is probably the hardest thing to coax a content script to do, but I think you’re on the right track. There are a couple different approaches I’ve taken to solving this problem. In your case, though, I think a combination of the two approaches (which I’ll explain last) would be best.
Attach event listeners to the DOM to detect relevant changes. This solution is what you’ve suggested and introduces the race condition.
Continuously inspect the DOM for changes from inside a loop (preferably one executed with setInterval). This solution would be effective, but relatively inefficient.
The best-of-both-worlds approach would be to initiate the inspection loop only after the more button is pressed. This solution would both avoid the timing issue and be efficient.
You can attach an event-handler on the button, or link that is used for fetching more results. Then attach a function to it such that whenever the button is clicked, your extension removes all the buttons from DOM and starts over inserting them, or check weather your button exists in that particular class of DOM element or not and attach a button if it doesn't.