How to check if element visible then click to a button of that element CYPRESS - automated-tests

Currently my website often returns fixed popups on the same div when logging in, but the number is not fixed.
I want to check that the div exists, then click close the popup, if not, continue to perform the next action after login.
I tried writing the following but when closing all the popups the program continues to check that div is visible or not then throws an assert error
var checkAndClosePopup = () => {
const dialogContainer = '//*[#role="dialog"]';
const dialogContainerButton = '//*[#role="dialog"]//button[1]';
var isPopupDisplay = () =>{
return cy.xpath(dialogContainer).should('be.visible')
}
cy.xpath(dialogContainerButton).then($button => {
let isButtonVisible = $button.is(':visible');
if(isPopupDisplay() && isButtonVisible){
waitForSecond(1);
cy.xpath(dialogContainerButton).click();
checkAndClosePopup();
}else
{
return;
}
})
}

To check two .if() constions you can chain the .if() commands, but be wary of getting the wrong .else() since there is no bracketing as in a normal if {...} else {...} statement.
const dialogContainer = '//*[#role="dialog"]';
const dialogContainerButton = '//*[#role="dialog"]//button[1]';
cy.xpath(dialogContainer)
.if('visible')
.xpath(dialogContainerButton)
.if('visible')
.click()
.log('dialog button is visible')
.else()
.log('dialog button not visible')
.else()
.log('dialog not visible')
It would be better to add a .then() to wrap the 2nd condition
const dialogContainer = '//*[#role="dialog"]';
const dialogContainerButton = '//*[#role="dialog"]//button[1]';
cy.xpath(dialogContainer)
.if('visible')
.then(() => {
cy.xpath(dialogContainerButton)
.if('visible')
.click()
.log('dialog button is visible')
.else()
.log('dialog button not visible')
})
.else()
.log('dialog not visible')
Looping to dismiss multiple popups
Not sure this will work but try.
Adding a 10s timeout on container to handle delay between.
const dialogContainer = '//*[#role="dialog"]';
const dialogContainerButton = '//*[#role="dialog"]//button[1]';
function dismissOnePopup() {
cy.xpath(dialogContainer, {timeout:10_000}) //timeout = max delay between
.if('visible')
.then(() => {
cy.xpath(dialogContainerButton)
.if('visible')
.click()
.log('dialog button is visible')
.else()
.log('dialog button not visible')
})
.else()
.log('dialog not visible')
}
Cypress._.times(10, () => {
dismissOnePopup()
})
Looking for up to 10 popups with up to 10 seconds between each.
If less actually occur, the .else() part will run for the no-shows.
Problem is, you will wait full 10 seconds for any that do not occur.
To stop the loop over-running itself
Inside cy.each() the test will wait for internal commands to complete before moving to next iteration.
const dialogContainer = '//*[#role="dialog"]';
const dialogContainerButton = '//*[#role="dialog"]//button[1]';
cy.wrap([...Array(10)].each(() => {
cy.xpath(dialogContainer, {timeout:10_000}) //timeout = max delay between
.if('visible')
.then(() => {
cy.xpath(dialogContainerButton)
.if('visible')
.click()
.log('dialog button is visible')
.else()
.log('dialog button not visible')
})
.else()
.log('dialog not visible')
})

cypress-if is a good package to utilize conditional testing and is quite easy to use.
Install:
npm i -D cypress-if
Import into support file
import 'cypress-if'
In your spec file
// can pass timeout option to change if timeout
cy.get('.your-popup-selector')
.if('visible')
// do something with your previous subject if visible (ie .your-popup-selector)
.else()
// do something with your previous subject if not visible (ie .your-popup-selector)

Related

Event Editable: When edit a specific event, other events on the same group unexpectedly edited same as that specific event

I am trying the editable, drag and expand event on my FullCalendar.
First of all, I am using React.
To describe the problem:
If I have this 2 events on my calendar with same group:
before drag and drop
Event1 is Workshop#1pm, Event 2 is Date#5pm
When I drag and drop Workshop to 12 pm, the same will goes to Date and it will located at 4pm, at the same time.
after drag and drop
Same thing happen with changing duration.
I only want single event to be affected with the gesture, not all event on its group.
So far, I tried to read the documentation thoroughly but I could not find any settings related to it. I also tried to search forums but I never found similar problem.
I am suspecting it has something to do with my complex style of populating calendar with event.
constructor(props) {
super(props);
this.state = {
myEventSource: [],
}
// function the fetch event from google API
loadEvents = async () => {
await fetch(fetchCommand)
.then(res => res.json())
.then((res) => {
// making the loop
res.result.forEach(result => {
// when get result, it will compose the events object to be
// stored in this.state.myEventSource
var tempEvent = [];
result[0].eventsInfo.forEach(event => {
var evt = {
id: event.id,
groupId: result[0].userName,
title: event.summary,
allDay: event.start.date!=null?true:false,
start: event.start.dateTime!=null?event.start.dateTime?.toString():event.start.date?.toString(),
end: event.end.dateTime!=null?event.end.dateTime?.toString():event.end.date?.toString(),
}
tempEvent.push(evt);
});
this.TempEventSource[result[0].userName] = tempEvent;
});
});
...
this.setState({ myEventSource: this.updateCalendarOnTabChange(this.state.tabIndex), firstLoad: true });
}
render() {
return(
<FullCalendar
plugins={[ timeGridPlugin, dayGridPlugin, interactionPlugin]} //
editable={true}
...
eventSources={this.state.myEventSource}
/>
)
}

How to accept a window confirm fired by an iframe with Cypress

I'm having some problems with Cypress when I need to accept a window confirm popup that is fired from a iframe. Cypress it's not very friendly with iframes, but I managed to make it work until I've found that need.
So here's what I've tried (based on this):
cy.get("[title='Some title']").then(($iframe) => {
const $body = $iframe.contents().find("body");
const $win = $iframe[0].contentWindow;
cy.stub($win, "confirm").as("windowConfirm");
cy.wrap($body)
.contains("Delete")
.click() // this fires the confirm popup
.should(function () {
expect(this.windowConfirm).to.be.calledWith(
`Continue deletion?`
);
});
});
It actually asserts the text inside the popup, but never accepts it.
I've tried different methods I've found (i.e. using a.on("window:confirm", () => true) but I've got no results.
Thank you!
Just add your truthy function to the stub
cy.stub($win, 'confirm', () => true)
.as('windowConfirm')
Prints CONFIRMED to the console.
it('confirms in iframe', () => {
cy.visit('../app/iframe-confirm-popup.html')
cy.get('iframe').then(($iframe) => {
const $body = $iframe.contents().find('body')
const $win = $iframe[0].contentWindow
cy.stub($win, 'confirm', () => true)
.as('windowConfirm')
cy.stub($win.console, 'log').as('consoleLog')
cy.wrap($body)
.find('input').click().should(function () {
expect(this.windowConfirm).to.be.calledWith('Are you sure you want to submit your application?')
expect(this.consoleLog).to.be.calledWith('CONFIRMED') // passes
})
})
})

Make/ Create checkbox component in react native

I have been facing some issues with the native base checkbox and AsynStorage. In fact, AsynStorage only accepts strings by default BUT can store boolean variables also, I tried to use that method but I get a string stored every time.
While the checkbox does only accept boolean variables and throws a warning if I tried to use a string and it does not show the previous state of the checkbox (checked or not ).
So, I decided to make my own checkbox using TouchbleOpacity .. So do you guys have any idea how to make it ?
Here is the result i want to achieve:
So, the purpose is to make a checkbox settings page that controls the style of a text in another page and to get the checkbox as left the previous time, for an example : if I check it , I change the page and go back again to the settings page , I need to find it checked (indicating the previous state)
The code is
in the settings page :
toggleStatus() {
this.setState({
status: !this.state.status
});
AsyncStorage.setItem("myCheckbox",JSON.stringify(this.state.status));
}
// to get the previous status stored in the AsyncStorage
componentWillMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
this.setState({
status: value
});
if (this.state.status == "false") {
this.setState({
check: false
});
}
else if (this.state.status == "true") {
this.setState({
check: true
});
}
if (this.state.status == null) {
this.setState({
check: false
});
}
});
}
render {
return(
...
<CheckBox
onPress={() => { this.toggleStatus() }
checked={ this.state.check }/>
)}
In other page :
componentDidMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
JSON.parse(value)
this.setState({
status: value
});
});
}
This code change the status after TWO clicks and I don't know why and i get this weird output in the console, every time I click the checkbox
If you take a look at AsyncStorage documentation, you can see that, in fact, the method getItem() will always return a string (inside the promise).
For the problem with the AsyncStorage you should consider trying to parse this string returned to a boolean using this method explained here and then use this parsed value inside the native base checkbox.
But if you want to do your own component, try doing something like this:
export default class Checkbox extends Component {
constructor(){
super();
this.state = { checked: false }
}
render(){
return(
<TouchableOpacity
onPress={()=>{
this.setState({ checked : !this.state.checked });
}}
>
<Image source={this.state.checked ? require('checkedImagePath') : require('uncheckedImagePath')} />
</TouchableOpacity>
);
}
}
You will need to set some style to this image to configure it the way you want.
-- Based on your edition:
I can't see nothing wrong on your toggleStatus() method in settings page, but try changing your componentWillMount() to this:
componentWillMount(){
AsyncStorage.getItem('myCheckbox').then((value) => {
if (value != null){
this.setState({
check: (value == 'true')
});
}
});
}
However in the other page the line you do JSON.parse(value) is doing nothing, once you are not storing the result anywhere.

METEOR - Show loader and run methods at specific time

I wanna build a loader circle what goes from 1 to 100% and in the meantime to run some methods.
loader circle
The scenario is:
load the page and start counting.
When the counter is at 50% pause counting and run the first method and when I have the result to start counting from where I was left.
count until 90% and run the second method.
I was trying something with Meteor.setInterval on onCreated but I'm not sure if it's the right method to do this.
Can someone give me some ideas about how to approach this?
Thanks!
There are several ways you can do this depending on your specific needs and you might even want to use one of the many Reactive Timer packages that are out there.
Here is one working example that only uses the Meteor API (no packages). Note, I did not actually incorporate the loader circle animation since it wasn't specifically part of the question.
Template definition
<template name="loader">
<h1>Loading...({{loadingPercentage}})</h1>
</template>
Template logic
Template.loader.onCreated(function() {
// used to update loader circle and triggering method invocation
this.elapsedTime = new ReactiveVar(0);
// handle for the timer
var timerHandle;
// starts a timer that runs every second that can be paused or stopped later
const startTimer = () => {
timerHandle = Meteor.setInterval(() => {
this.elapsedTime.set(this.elapsedTime.get() + 1);
}, 1000);
};
// pauses/stops the timer
const pauseTimer = () => {
Meteor.clearInterval(timerHandle);
};
// let's first start our timer
startTimer();
// runs every second and triggers methods as needed
this.autorun(() => {
const elapsedTime = this.elapsedTime.get();
// call methods based upon how much time has elapsed
if (elapsedTime === 5) {
pauseTimer();
// call method and restart the timer once it completes
Meteor.call('methodOne', function(error, result) {
// do stuff with the result
startTimer();
});
} else if (elapsedTime === 9) {
pauseTimer();
// call method and restart the timer once it completes
Meteor.call('methodOne', function(error, result) {
// do stuff with the result
// DO NOT RESTART TIMER!
});
}
});
});
Template.loader.helpers({
// helper used to show elapsed time on the page
loadingPercentage: function() {
return Template.instance().elapsedTime.get();
},
});
Let me know if you have any questions.
This is what i was trying to do:
Template.onboarding.onCreated(function(){
var instance = this;
instance.progress = new ReactiveVar(0);
instance.autorun(function(){
var loader = {
maxPercent: 100,
minPercent: instance.progress.get(),
start: function(){
var self = this;
this.interval = Meteor.setInterval(function(){
self.minPercent += 1;
if(self.minPercent >= self.maxPercent) {
loader.pause();
}
if( self.minPercent == 25) {
loader.pause();
Meteor.call('runMethodOne', (err,res)=>{
if (!err) loader.resume();
});
}
if(self.minPercent == 75) {
loader.pause();
Meteor.call('runMethodTwo', (err,res) =>{
if(res) loader.resume();
});
}
}
});
}
instance.progress.set(self.minPercent)
},50);
},
pause: function(){
Meteor.clearInterval(this.interval);
delete this.interval;
},
resume: function(){
if(!this.interval) this.start();
}
};
loader.start();
}
});
});

Material Design Lite - Programatically Open and Close Toast

I would like to open and close MDL toast rather than use the timeout property as indicated in the MDL usage guide. The reason is that I want the toast to remain while geolocation is occuring, which sometimes takes 10+ seconds and other times happens in 1 second.
Any idea how this could be done?
A q&d solution i found, invoke cleanup_ method on the sb object.
With this solution i can show the sb, click action handler to hide it, then re trigger the action to show it without any problem.
var snackbar = form.querySelector("[class*='snackbar']");
if (snackbar) {
var data = {
message: 'Wrong username or password',
timeout: 20000,
actionHandler: function(ev){
// snackbar.classList.remove("mdl-snackbar--active")
snackbar.MaterialSnackbar.cleanup_()
},
actionText: 'Ok'
};
snackbar.MaterialSnackbar.showSnackbar(data);
}
As cleanup_ is not part of the public api, i guess it worth to enclose this with some small checks to avoid a disaster.
snackbar.MaterialSnackbar.cleanup_
&& snackbar.MaterialSnackbar.cleanup_()
!snackbar.MaterialSnackbar.cleanup_
&& snackbar.classList.remove("mdl-snackbar--active")
Got it working as so: I basically set a 30 second timeout on the toast assuming my geolocation and georesults (GeoFire) will take no more than 30 seconds.
I get the length of the returned array of map markers and multiply that by the javascript timeout events. I finally remove mdl-snackbar--active which hides the toast. So, basically - it works.
UPDATED
The above actually had a major problem in that additional toasts would not display until that long timeout completed. I could not figure out how to apply the clearTimeout() method to fix it so I found a solution that works - trigger the toast up and down by just toggling the mdl-snackbar--active class - no timer setting necessary.
So to call toast as normal using this code, simply tools.toast('hello world',error,3000). To programatically open and close toast call tools.toastUp('hey') and tools.toastDown(), respectively. So, you might call tools.toastDown after a promise resolves or something...
var config = (function() {
return {
timeout: 50, //in milliseconds
radius: 96, //in kilometers
};
})();
var tools = (function() {
return {
toast: function(msg,obj,timeout){
var snackbarContainer = document.querySelector('#toast'); //toast div
if(!obj){obj = ''}
if(!timeout){timeout = 2750}
data = {
message: msg + obj,
timeout: timeout
};
snackbarContainer.MaterialSnackbar.showSnackbar(data);
},
toastUp: function(msg){
var toast = document.querySelector('#toast');
var snackbarText = document.querySelector('.mdl-snackbar__text');
snackbarText.innerHTML = msg;
toast.classList.add("mdl-snackbar--active");
},
toastDown: function(count) {
setTimeout(function () {
var toast = document.getElementById("toast");
toast.classList.remove("mdl-snackbar--active");
}, config.timeout * count);
},
};
})();
In case you want to fire tools.toastDown after a timeout loop, you can do:
function drop(filteredMeetings) {
tools.clearMarkers(true);
for (var i = 0; i < filteredMeetings.length; i++) {
//drop toast once markers all dropped
if(i === filteredMeetings.length - 1) {
tools.toastDown(i);
}
tools.addMarkerWithTimeout(filteredMeetings[i], i * config.timeout);
}
}

Resources