Cypress does not always executes click on element - javascript

I am automating Google Calculator.
And from time to time Cypress is not able to execute click on button.
The tests click on buttons (0 to 9 ) and do some simple math operations.
And in 30% chance it can not click on element and the test will fail.
I also recorded a video when issue appears.
Video here
My Project is located here:
https://github.com/afiliptsov/test-project
To run the test run : "npm run test:e2e:functional"
I tried to use different locator. Initially i was using just ID ex(#cwbt15 ) but after i made more specific locator ( #cwbt15 > .cwbtpl > .cwbts) and still having same issue.
Does anyone knows why it happens and how to avoid such behavior?
The project structure is :
cypress/PageObject.js - place where all elements declared.
cypress/support/commands.js - place where function click created and
verification of value getting updated.
cypress/integration/functional/delete.spec.js - test which was on the
video

2022 here and tested with cypress version: "6.x.x" until "10.x.x"
You could use { force: true } like:
cy.get("YOUR_SELECTOR").click({ force: true });
but this might not solve it ! The problem might be more complex, that's why check below
My solution:
cy.get("YOUR_SELECTOR").trigger("click");
Explanation:
In my case, I needed to watch a bit deeper what's going on. I started by pin the click action like this:
Then watch the console, and you should see something like:
Now click on line Mouse Events, it should display a table:
So basically, when Cypress executes the click function, it triggers all those events but somehow my component behave the way that it is detached the moment where click event is triggered.
So I just simplified the click by doing:
cy.get("YOUR_SELECTOR").trigger("click");
And it worked 🎉
Hope this will fix your issue or at least help you debug and understand what's wrong.

For me this code worked:
Inside your click methods add : { force: true } It will make force click.
Also add: cy.wait(150) to beforeEach or before click where your test fails.
It is just workaround not a solution.
Link to Cypress Issue
Also i saw this alternative:
cy.get('#query-btn').invoke('width').should('be.gt', 0)
cy.get('#query-btn').invoke('width').should('be. greaterThan', 0)
But it didnt work out for me. Maybe will be usefull for someone!

https://github.com/cypress-io/cypress/issues/2928 helped me.
cy.get('[data-qa="select_workers-list"]'.contains('+ New Worker').trigger('mouseover').click();

Whoever finds this problem, the official way of handling it is described here: https://www.cypress.io/blog/2019/01/22/when-can-the-test-click/
TLDR:
What #jpvantuyl said, cypress clicks the button before the onclick event is there.
The lib cypress-pipe will add a .pipe method that if followed by .should will retry a function until the condition is true or it times out.
Example:
cy
.get(numbers.result.idLocator)
.pipe($el => $el.click()) // try this
.pipe(
// next line will make assertions on the element returned by this pipe
() => cy.get(calculatorScreen.resultOutput.idLocator)
)
.should("contain", "0"); // until this happens or times out

This could be because the application is attaching behaviors to the button via JavaScript. When that script takes a long time to execute it allows Cypress to click the button before the OnClick event is there.
See: https://www.cypress.io/blog/2018/02/05/when-can-the-test-start/
Cypress recommends tracking when the underlying JS is in place with something like:
function waitForAppStart() {
// keeps rechecking "appHasStarted" variable
return new Cypress.Promise((resolve, reject) => {
const isReady = () => {
if (appHasStarted) {
return resolve()
}
setTimeout(isReady, 0)
}
isReady()
})
}
it('greets', () => {
cy.visit('app.html', {
onBeforeLoad: spyOnAddEventListener
}).then(waitForAppStart)
// all other assertion will run only when
// the application has started
cy.get('#name').type('Cypress{enter}')
cy.contains('#answer', 'Cypress')
})

Something I just learned from a colleague after none of the above worked for me and after hours of searching. Just blew my mind. Just add another .click()...
before:
cy.contains('some string').click();
In the left Cypress menu click on the action and you'll see the indicator that it clicks the correct part, but nothing happens. Do it manual in the browser and it works.
Fix:
cy.contains('some string').click().click();
and all of the sudden the string is clicked and test is moving on to the next page

In my case I had to make sure the button is visible(or not disabled), then mouseover, then click:
cy.contains(/activate/i)
.should('be.visible')
.trigger('mouseover')
.click({ force: true });
PS. /***/i means ignore letter case.

I don't know why, but for me this worked:
cy.get('[role="button"][aria-haspopup="listbox"]').trigger('mouseover').wait(1000).click().click({force:true});

I found that my button's event listeners were loaded but the button was not receiving the focus it needed. It was definitely clickable. I tried using the cypress-pipe package but to no avail. So, instead of invoking .click() on it 2 or more times, I ensured it was first set in focus prior to being clicked. This fixed it for me.
cy.get('button').focus().click()

FWIW: I was having problems submitting/navigating after a Google Places address selection. I believe my component wasn't re-rendering post-address selection.
To solve my issue, after address selection, I selected a random text element, clicked on it (no-op) and then clicked on my continue button and it worked fine.

I switched my app from being rendered as SPA to using SSR (basically removing ssr: false from my svelte.config) which started to fail my Cypress tests. Reverting fixed it for my test setup.

Related

Moving from page to page creates errors (duplicates?)

I'm trying to use the pushPage() function of the OnsenUI 2 (rc15 as of now). Alongside the OnsenUI, I'm using jQuery 3.
Here is my function that upon clicking certain elements should push a page:
$(".tile_handler").on("click", ".imalink", function () {
var link = $(this).data().href;
if(link != null){
document.querySelector("#myNavigator").pushPage(link, { animation: "slide-ios" });
}
})
When I push the page for the first time, it works fine. I use the iOS back button to return. Then I click it again and I get this error (and increasingly more as I repeat the process):
[index.js:450] Uncaught (in promise) pushPage is already running.
Here is another function that is supposed to load splitter pages:
$(".splitter_item").click(function () {
var address = $(this).data('address');
$('#content')[0].load(address).then(menu.close.bind($('#menu')[0]));
})
When I switch between two pages via Splitter it starts throwing this ( and more every time I switch between pages)
[undefined:1] Uncaught (in promise) Splitter side is locked.
What I assume is happening is that I load a page, leave it and when I access it again, it loads the page again. However that doesn't seem to be the behavior shown in OnsenUI examples such as this:
document.addEventListener('init', function(event) {
var page = event.target;
if (page.id === 'page1') {
page.querySelector('#push-button').onclick = function() {
document.querySelector('#myNavigator').pushPage('page2.html', {data: {title: 'Page 2'}});
};
} else if (page.id === 'page2') {
page.querySelector('ons-toolbar .center').innerHTML = page.data.title;
}
});
There is the popPage() function which should delete previously loaded page and would prevent this. But the references don't use it, so I assume I'm doing something wrong. But I don't know what.
UPDATE:
I managed to reproduce both problems in CodePen. Here is the Splitter error and here is the pushPage() one. It seems like the pushPage() one is a problem with my function as it adds a pushPage() request every time I click it, not sure why though.
Both errors only seem to happen in Ripple emulator (via VS2015). I don't seem to be able to reproduce them in Android Emulator (but the $(".tile_handler").on("click", ".imalink", function () { code fires incorreclty multiple times anyway). I'm testing this further.
Basically each time you push a page, that page fires an init event. However Onsen still keeps the initial page in the dom.
Navigator example (the same logic applies to the splitter one):
<ons-navigator>
<ons-page id="dashboard">
<div class="imalink" data-href="request_list.html"></div>
</ons-page>
</ons-navigator>
You have an init event for dashboard. Then you click the tile and go to another page.
Then request_list fires its own init event. However our initial page is still in the dom.
<ons-navigator>
<ons-page id="dashboard" style="display: none">
<div class="imalink" data-href="request_list.html"></div>
</ons-page>
<ons-page id="request_list">
...
</ons-page>
</ons-navigator>
You have something like this. However the following is called a second time:
$(".tile_handler").on("click", ".imalink", function () {
...
})
Which adds the listeners once more. $el.on("click") like an alias of addEventListener, meaning you are adding more and more listeners.
So whenever you navigate you keep adding them because the initial page was never removed from the dom.
Alternative solutions:
Use only the current page (e.target)
$('selector') // instead of this
$(e.target).find('selector') // use this
That way you limit to only finding elements within the page which you just created.
Enable the handlers from the start.
Since you are using jQuery there is actually a simpler way to do the things without relying on the init event.
Just do this outside of any init handler:
$(document).on("click", ".tile_handler .imalink", function () { ... })
This actually means that the handler is attached to the document itself and the handler will be called only when the target is .tile_handler .imalink - so it works with any future imalinks which you create.
This may not be the most efficient approach, but is definitely one of the simplest.
Both errors which you mention are actually a safe measure from you pushing or doing some action twice by mistake if you double click a button/link for example.
They appear when you try to make an action while an animation is running. As you can see in the examples generally there is no problem to push a page twice or more, as long as you start the second push after the first one is finished.
Here are demos with your exact code for both the splitter and navigator.
So the error does not come from the code which you provided, its someplace elsewhere.
The only thing I can come up with is that the behaviour which you mentioned would be seen if for some reason the popPage method failed to finish correctly. Maybe if you provide us with your own codepen where the issue can be reproduced we could debug it further.
An alternative, though highly NOT recommended would be to force the state before you do your action. This however would not be solving the problem, rather only masking it. And of course as with all hacks - it may break in future versions.
myNavigator._isRunning = false;
UPDATE:
Here are the two updated pens which you gave in the comments:
https://codepen.io/IliaSky/pen/YWOOkW?editors=1010
https://codepen.io/IliaSky/pen/QEVVGm?editors=1010
Basically you were adding the handlers on init event, which is fired whenever a page is added. so with your logic you are adding more and more handlers with each page. Just make sure you add them only once and you will be fine.
Add things like:
if (e.target.id == 'pagename') ...
or simply
$(document).on("init", '#dashboard_page', function(){ ... }
Take a look at .one() from jQuery - the event handler will only execute once per element in order to prevent the error: Uncaught (in promise) pushPage is already running
$(".tile_handler").one("click", ".imalink", function () {
var link = $(this).data().href;
if(link != null){
document.querySelector("#myNavigator").pushPage(link, { animation: "slide-ios" });
}})

Multiple timepickers in the same page bug

I notice a strange behavior when there are multiple timepickers in the same page.
After switching between the two timepickers, eventually one of them isn't removed/hidden, and will persist till we refresh the page.
I thought it was a problem with my code but then i did a test with a simple bootstrap example i found on google.
Fiddle bug example:
http://jsfiddle.net/kW3G7/282/
The only code that is used is:
`$('.timepicker').timepicker();
Thanks in advance.
Edit: i created a issue in there github project
Edit 2: It seems that the timepicker stops calling the document onclick event(code above)
$(document).on('mousedown.timepicker, touchend.timepicker', function (e) {
// This condition was inspired by bootstrap-datepicker.
// The element the timepicker is invoked on is the input but it has a sibling for addon/button.
if (!(self.$element.parent().find(e.target).length ||
self.$widget.is(e.target) ||
self.$widget.find(e.target).length)) {
self.hideWidget();
}
});
Solution, for now, remove the "touchend.timepicker" from the "on" event trigger.
I don't think it is the proper/final solution but it seams to work.

Dynamic CFTab onClose event

I simply can't find any real documentation on ColdFusion Layout Tabs. For the most part I've got them working, but I'd like to tie some logic to a close event. I was wondering if anyone had a working example they could show me? The catch is that I will need to trigger these events in JavaScript. But if you have a working version in plain ColdFusion, I'd still love to see it!
var tab = ColdFusion.Layout.getTabLayout("innerTabLayout").activeTab._cf_body;
$('#' + tab).on('close', blah); // doesn't work
tab.on('close', blah) // doesn't work
ColdFusion.Layout.getTabLayout("innerTabLayout").activeTab._cf_body.onTabClose( function(), blah ); //doesn't work
I have also tried setting the event on tab creation:
var tab = ColdFusion.Layout.createTab();
tab.onTabClose()
tab.on('close');
However, none of these work either. I've tried looking at EXT.JS which is what CFtabs were created from, but I don't seem to have any luck there either.
The Coldfusion.Layout object has a function for tab closing, so there must be a way to trigger it! (I would think, haha).
So after spending some too much messing around with the tab, I've got a solution.
ColdFusion.Layout.getTabLayout('innerTabLayout').activeTab.on('close', function(e) {
console.log(this) //this will return the tab object
console.log(e)//this also returns the tab object
});
This will trigger the event when the active tab within a parent is closed. I'm interested to see if there's another, better solution.

How to remove an event out of content script scope?

I'm trying to unbind an event from a specific element and upon research, I found this. Which is useful. In itself. I didn't know you could do that. But:
Is there a way to make it work in a browser/Chrome extension? I'm talking about content scripts.
The reason why this doesn't work the way it's described there is that the website which has attached the event in question with its own script is using a different jQuery object than the one in my extension's includes/ folder. And I can try to search the event via jQuery._data(el, 'click'); but that is my jQuery object, not the one of the website where the events are apparently stored. I'm glad I figured that out after hours of fiddling around.
Or maybe it is possible to access the website's jQuery object itself?
EDIT:
What I'm ultimately trying to achieve works in theory but … it's complicated. The original script uses a plugin event and keeps reinstalling it with .on('mouseleave',….
Anyway, this is what I got thanks to you, pdoherty926:
var $el = $('div.slideshow');
$('h2', $el).click(function(){ console.log('ouch!'); }); // test event
var $slides = $('.slides', $el).detach();
$copy = $el.clone(false);
$slides.prependTo($copy);
$el.replaceWith($copy);
The test event doesn't get triggered but the event I'm actually trying to remove still fires. I can imagine figuring it out, though, now that I got closer to my goal.
Okay, the aforementioned re-installation on mouseleave really messed up this otherwise satisfying suggestion. (The site is using the jQuery Timer plug-in by Cyntaxtech). So here's how I solved it instead: I simply changed the class name (-.-' )
Now the re-installation code cannot find the element anymore.
This is how my finished script looks like:
function stop_happening() {
var $el = $('div.fullwall div.slideshow');
$el
// first, stop the current automation.
.timer('stop') // Timer plug-in
// next, change class name in order to prevent the timer
// from being started again.
.removeClass('slideshow').addClass('slideshow-disabled-automation');
//--- copied some extra code from the website itself for the onclick
// events which are supposed to keep working. I wish I could do *that*
// programmatically but I'm glad I got as far as I got. ---//
// […]
}

How do we set an onExpand event in a cfLayout accordion

We're using CFLayout to create a tab structure in our web application. After creation of that layout we call this function:
mytabs = ColdFusion.Layout.getTabLayout("#attributes.cflayoutName#");
mytabs.on('tabchange',
function(tablayout,tab) {
var tabtitle = tab.title;
alert(tabtitle); // Actual code does various useful 'stuff' here.
}
);
That piece of code works very well, and the alert will show each time the user clicks on a tab.
The problem is that we are now trying to do the same thing with a CFLayout type of "accordion", and I cannot get an event to fire when the user switches which accordion pane they are looking at. We've tried leaving the above as is, as well as changing the "tabchange" attribute to "expand", "beforeexpand", "activate", and "collapse".
For this testing I'm using the following simple JS function to avoid issues arising from the JS within the onchange event:
mytabs = ColdFusion.Layout.getAccordionLayout("#attributes.cflayoutName#");
mytabs.on('expand',
function(tablayout,tab) {
console.log('test');
}
);
We do not receive any errors. Nothing is logged to the console at all. I've tried replacing the console.log to an alert to rule out any problems with that line.
I found that the Ext library documentation to be very helpful with finding a solution to this problem: here.
The Ext library has a getComponent method that allows you to reference the accordion layout panel that you are trying to add the expand event to. Once you have this, you can use the "on" method you are using above to assign the expand event to each panel individually.
for (x=1; x<accordionLayoutArray.length; x++) {
mytabs.getComponent(accordionPanelName).on('expand',
function(tab) { ... });
}
This became too long for a comment so adding as an answer
After some Google searches I found what I think are some related posts. It appears as though the accordion in Ext JS does not have the same events as the tab. Instead you need to add a listener in order to catch the expanding.
See this post - in case something happens to that page here is the relevant piece:
You'd need to listen to the expand event of the child panels in the accordion, you could do something like:
Code:
myAccordion.add(myFunc('myTitle'));
function myFunc(title)
{
return new Ext.Panel(
{
title: title,
listeners: { 'expand': {fn: something, scope: foo}}
}
);
}
And I also found another similar post here on SO - see both answers
Once you know that the accordion needs a listener you can find a number of results on Google. Such as How do I attach an event handler to a panel in extJS?
This Google search will give you lots of examples.
Hope that helps.

Categories