I need to check if the pop up exists, if it does then I need to check if its displayed then perform certain action on it.
I have implemented the below. I was wanting to know if there is any better way of achieving this.
licenseUpdate.isPresent().then(function (item) {
if (item == true) {
licenseUpdate.isDisplayed().then(function (res) {
if (res == true){
licenseUpdate.click();
};
});
}
});
If you are using page object (you should) you can write something like this:
clickLicenseUpdate() {
const licenseUpdate = $(licenseUpdateCssSelector);
return licenseUpdate.isPresent()
.then((isPresent) => {
if (!isPresent) { return false; }
return licenseUpdate.isDisplayed();
})
.then((isDisplayed) => {
if (!isDisplayed) { return false; }
return licenseUpdate.click().then(() => true);
})
}
Note that if you are using and old JS version (you shouldn't) you need to replace arrow functions with traditional anonymous functions.
Some helpful links about Page Object Design Pattern:
PageObjects
Martin Fowler PageObject
Code explained (or at least, that is the plan):
Using $ to locate an element but you can use any strategy
supported by Protractor.
browser.findElement(by.className('license')) equivalent to
$('license'), browser.findElement(by.id('license')) equivalent to
$('#license'). Check Protractor documentation for more examples.
Once you a have found a web element that match your locator, you can
use isPresent method to determine whether the element is present on
the page. isPresent returns a promise that resolve to a boolean
value.
then always return a promise. You can return a primitive value from
onFulfilled callback and that value would be cast to a promise with
resolve with same value. That is what is done here: if (!isPresent) { return false; }; or you can return another promise
from onFulfilled callback and the promise returned by then will be resolved or rejected with same value of returned promise.
That is what is done here: return licenseUpdate.isDisplayed();. isDisplayed() also return a
promise that will resolve with whether this element is currently
visible on the page.
This can be a bit overwhelming if you are not
used to deal with promises. Check this out Promises/A+
Finally, if the element is present and is displayed, click the element with theclick method that, surprise, also return a
promise (WebDriverJS API is based on promises).
Note that if element is not present, isPresent is false in this
line if (!isPresent), returning false immediately bypass
licenseUpdate.isDisplayed() execution and resolve with a false value. In that
case isDisplayed value is false and again false is returned
immediately bypassing the licenseUpdate.click() execution.
Also note that clickLicenseUpdate return a promise that will
resolve to false if the element is not present or if is present but not
displayed. To keep clickLicenseUpdate returned value consistent, I recommend you to wait for licenseUpdate.click() and then return a boolean value as it is done here: return licenseUpdate.click().then(() => true); (using implicit return from arrow functions) because promise returned by click() resolve with a void value.
That is harmless but is considered a good practice maintain a consistent return value, always a boolean value, not sometime a boolean and others a void value.
Related
So I think this is probably me mixing up sync/async code (Mainly because Cypress has told me so) but I have a function within a page object within Cypress that is searching for customer data. I need to use this data later on in my test case to confirm the values.
Here is my function:
searchCustomer(searchText: string) {
this.customerInput.type(searchText)
this.searchButton.click()
cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length > 0) {
{Click some drop downdowns }
return data < ----I think here is the problem
} else {
{Do other stuff }
}
})
}
and in my test case itself:
let customerData = searchAndSelectCustomerIfExist('Joe Schmoe')
//Do some stuff with customerData (Probably fill in some form fields and confirm values)
So You can see what I am trying to do, if we search and find a customer I need to store that data for my test case (so I can then run some cy.validate commands and check if the values exist/etc....)
Cypress basically told me I was wrong via the error message:
cy.then() failed because you are mixing up async and sync code.
In your callback function you invoked 1 or more cy commands but then
returned a synchronous value.
Cypress commands are asynchronous and it doesn't make sense to queue
cy commands and yet return a synchronous value.
You likely forgot to properly chain the cy commands using another
cy.then().
So obviously I am mixing up async/sync code. But since the return was within the .then() I was thinking this would work. But I assume in my test case that doesn't work since the commands run synchronously I assume?
Since you have Cypress commands inside the function, you need to return the chain and use .then() on the returned value.
Also you need to return something from the else branch that's not going to break the code that uses the method, e.g an empty array.
searchCustomer(searchText: string): Chainable<any[]> {
this.customerInput.type(searchText)
this.searchButton.click()
return cy.wait('#{AliasedCustomerRequest}').then(intercept => {
const data = intercept.response.body.data
console.log('Response Data: \n')
console.log(data)
if (data.length) {
{Click some drop downdowns }
return data
} else {
{Do other stuff }
return []
}
})
}
// using
searchCustomer('my-customer').then((data: any[]) => {
if (data.length) {
}
})
Finally "Click some drop downdowns" is asynchronous code, and you may get headaches calling that inside the search.
It would be better to do those actions after the result is passed back. That also makes your code cleaner (easier to understand) since searchCustomer() does only that, has no side effects.
you just need to add return before the cy.wait
here's a bare-bones example
it("test", () => {
function searchCustomer() {
return cy.wait(100).then(intercept => {
const data = {text: "my data"}
return data
})
}
const myCustomer = searchCustomer()
myCustomer.should("have.key", "text")
myCustomer.its("text").should("eq", "my data")
});
I want to use assertion for this checkbox. It depends on duration. If it's checked duration = forever, if not = a month.
cy.wrap(cy.get('span.ant-checkbox').should('have.class','ant-checkbox-checked')).then((a) => {
if a == true {
cy.log('Forever')
}
})
A few thoughts:
You don't need to cy.wrap() your entire statement. cy.wrap() would primarily be used to wrap a JQuery yielded from cy.get() or a similar command, and insert it back into the Cypress command chain.
Your assertion that the element has a certain class will fail and stop your test before even getting to the .then() part of the command if the element does not have the ant-checkbox-checked class.
Instead, if we get the element, we can use JQuery functions (in this case, .hasClass())to determine if it has the class we want.
cy.get('span.ant-checkbox').then(($el) => {
// cy.get yields a JQuery<HTMLElement>
if ($el.hasClass('ant-checkbox-checked')) {
cy.log('Forever');
} else {
cy.log('A month');
}
});
I am using react-redux 5.0.6 and have a reducer with the following code:
export default (state = [], action) => {
switch(action.type) {
case 'ADD_ENGAGEMENT':
let newArr = state.slice();
newArr.push(action.payload);
return newArr;
case 'UPDATE_ENGAGEMENT':
console.info('UPDATE_ENGAGEMENT')
return state.slice();
// return state;
default:
return state;
}}
The issue occurs within the 'UPDATE_ENGAGEMENT' case -- the actual logic has been removed and replaced with the simplest example to demonstrate the problem.
When a new array created from state via state.slice() is returned, a loop is triggered, causing the action to be dispatched until an 'Uncaught RangeError: Maximum call stack size exceeded' error is raised. Screenshot of the browser console during the issue's occurrence
The issue is not limited to 'slice()' and occurs whenever an array containing any element of state is returned e.g. return [state[0]].
When the original state is returned, the issue does not occur.
I am completely baffled by this behavior and cannot fathom how anything in my application could be causing it. Any insight would be immensely appreciated.
To provide some additional context, below is the code involved in the action's being dispatched:
componentWillReceiveProps(newProps) {
let engagementTemplateData = newProps.selectedEngagementTemplate;
let engagements = newProps.engagements;
if (engagementTemplateData && engagementTemplateData.engagementUuid === this.props.uuid) {
let template = engagementTemplateData.template;
this.updateEngagementTemplate(template);
}
}
updateEngagementTemplate(template) {
let url = `/engagements/${this.props.uuid}`;
let requestHelper = new AjaxRequestHelper(url);
let data = {template_uuid: template.uuid};
this.props.updateEngagement({uuid: this.props.uuid, template: template});
// requestHelper.put(data, response => {
// this.props.updateEngagement({uuid: this.props.uuid, template: template});
// });
}
Basically, the function which triggers the action is called in componentWillReceiveProps as a result of another action. However, I am not sure how helpful this information is, since the reducer itself appears to be working properly when responding to the action -- it's just that something strange is happening with the state, which prevents its elements from being returned.
From the sounds of it (and from the react callstack), I imagine the array changing (by reference) in the store is being picked up by a react component props, which in its should/did update logic is calling that action without a guard. This is often a mistake when calling actions or setState from componentDidMount/Update -
It works when the original state is returned as the reference is the same so React does not continue with its update logic, and hence call your code that publishes the action
Consider this pure component that will cause an endless loop with your reducer code...
export interface IMyProps {
myArray: any[],
updateEngagementAction: () => void
}
export class EndlessLoopFromArrayPropComponent extends React.PureComponent<IMyProps> {
// PureComponent compares props by reference only
// so will cause update if this.props.myArray reference has changed in store (ie from slice())
render() {
// blahblah...
}
componentDidUpdate() {
// this will call action after every update
// as every time this action is called it passes in a new reference this.props.myArray to this component
// so react will update this component again, causing the action to be called again
// ... endless loop
this.props.updateEngagementAction()
}
}
Your implementation will differ of course but this will be the principal that is causing it to happen, so you need to add a guard condition in whatever code path leads to your action being called.
For the code above you would need to check an escape condition before sending the action OR implement shouldComponentUpdate or similar to do deeper prop comparison to guard against unnecessary updates, and hence it would not reach that action code in the componentDidUpdate method
EDIT This was written before the react code was added to question. Here I refer to the action being called without guard in componentDidUpdate however the same applies when called in any of the other lifecycle methods triggered by a prop change, in this case componentWillRecieveProps. To be fair it did have a guard already, but never returned false as a more in-depth props check was needed, so caused a loop to occur via willreceive -> true -> action -> reducer -> willreceive -> true ........
I am using ember-cli:2.5.0 and ember-validations:v2.0.0-alpha.5
In my ember-component i have a validation which is running automatically for each change in a attribute but i want to run this validation only if i call "validate()" method in technical term call validation lazily.
Please find the below code samples,
import Ember from 'ember';
import EmberValidations, { validator } from 'ember-validations';
export default Ember.Component.extend(EmberValidations, {
didReceiveAttrs() {
this.set('newBook', this._bookModel().create());
},
_bookModel(data = {}) {
return Ember.Object.extend(EmberValidations, {
bookVersion: null,
isEditable: false,
validationActive: false,
validations: {
bookVersion: {
inline: validator(function() {
if(this.validationActive){ //Here this.validationActive always return undefined
var version = this.model.get('bookVersion') || "",
message = [];
if (Ember.isEmpty(bookVersion)) {
message.push("Book Version is mandatory!!!");
}
if (message.length > 0) {
return message.join(',');
}
}
})
}
}
}, data);
}
});
actions: {
this.get('newBook').set("validationActive",true);
this.get('newBook').validate().then(() => {
//Do the action
}
}
I want the above validation to run only calling "this.get('newBook').validate()". I am entirely new to ember so down-voter please put your comments before down-voting for others kindly let me know for any more code samples.
Your help should be appreciable.
The addon you are using for validations ("ember-validations") is a very popular one and its documentation is pretty well when compared to others. If you look at the documentation there is a part named "Conditional Validators" in documentation. You can use a boolean property to control when the validation is to be performed.
You can see an illustration of what I mean in the following twiddle. I have created a simple validation within the application controller for user's name. The name field must have a length of at least 5 and the validation is to be performed only if the condition validationActive is true. Initially; the condition is false; which means validator did not work and isValid property of Controller (which is inherited from EmberValidations Mixin) is true. If you toggle the property with the button provided; the validation will run (since the condition is now set to true; hence validation is triggered) and isValid will return to false. If you change the value; the validation result will change appropriately with respect to the value of user's name. If you toggle the condition once again to set it to false; then isValid will become true no matter what the valie of user's name is.
I hope this gives you an insight about how to control when your validations should work.
Here is what you should do after your edit: The field is undefined because you are trying to reach component's own validationActive field within inline validator. Please get validationActive as follows this.model.get('validationActive') and give a try. It should work.
I'm having a button toggle whether a referenced div is visible or not. Originally, I was using the code:
$('#search-options-btn').click(function() {
if ($('#search-options').is('.hidden')) {
$('#search-options').removeClass('hidden');
} else {
$('#search-options').addClass('hidden');
}
});
However, in an attempt to find cleaner code, I came across the jQuery toggle() method, which according to the API has a method implementation
.toggle( showOrHide )
showOrHide: A Boolean indicating whether to show or hide the elements.
This description leads me to believe this is a shortcut implementation method for showing or hiding by passing the...identifier? showOrHide into the toggle() method.
Of course, attempting this:
$('#search-options-btn').click(function() {
$('#search-options').toggle(showOrHide);
});
yields an error in my firebug console:
showOrHide is not defined
[Break On This Error] $('#search-options').toggle(showOrHide);
I've also tried defining showOrHide as a boolean initialized to false; the error goes away, but the issue is not fixed.
According to the jQuery online API, this is supposed to be equivalent to
if ( showOrHide == true ) {
$('#foo').show();
} else if ( showOrHide == false ) {
$('#foo').hide();
}
unless I'm completely missing how this works. Can anyone fill me in on what I'm doing wrong here? I haven't been able to find a similar implementation.
you should just need toggle(), nothing else.
$('#search-options-btn').click(function() {
$('#search-options').toggle();
});
showOrHide is the internal name. You pass in a bool:
$('#...').showOrHide(true)
if you want the item to be visible, false if you want it hidden.
Its just toggle between class and not with boolean..
$('#search-options-btn').click(function() { $('#search-options').toggle('yourClassName'); });
If yourClassName is found, then it will remove the same, otherwise it will add it.