Cypress - if - else condition doesn't work - cypress

I need to automate an if/else condition with Cypress, but it doesn't work in my script. If an if element is present (or visible) then click Yes otherwise proceed. But in my case, if the first condition is not present the test try to click Yes anyway.
it('Logout', () => {
const out = new logout();
const lged = new logged();
lged.logged().then($button => {
if ($button.is(':visible')){
lged.logged().click()
out.bar().click()
out.clickout()
}
else {
out.bar().click()
out.clickout()
}
})
})
in my pageObjects I have the class "logged":
class logged {
logged(){
return cy.contains('Yes', { timeout: 10000 })
}
}
export default logged

Related

Cypress : XPATH - Do something if the element exists

I need to click on a button if the button exists. I am using the cypress xpath. Currently i am using below code but not working
cy.xpath('//button[text()="New"]')
.then(($button) => {
if ($button.is(':visible')) {
cy.log('visible')
cy.xpath('//button[text()="New"]').click();
} else {
//do something
}
})
In case if you want to click a button based on it existence, you can do like this:
cy.get('body').then(($body) => {
if ($body.find('button:contains("New")').length > 0) {
cy.log('visible')
cy.xpath('//button[text()="New"]').click()
} else {
//do something
}
})
In case if you want to check if the element is visible and then click on it.
cy.xpath('//button[text()="New"]').then(($button) => {
if ($button.is(':visible')) {
cy.log('visible')
cy.wrap($button).click()
} else {
//do something
}
})
XPath has a count() function that tells you if the element exists.
cy.xpath('count(//button[text()="New"])').then(count => { // check existence first
if (count) {
cy.xpath('//button[text()="New"]').then($button =>
if ($button.is(':visible')) {
cy.log('visible')
cy.wrap($button).click()
} else {
//do something
}
}
})
Splitting exists and visible checks like that will work if the DOM is stable.

Pass a variable value out of the if statement

I just need to pass true for the button variable if the if condition is ok. Then i need to pass the test case if the button variable value is true. Can someone help me to fix this. This is what i have got so far.
cy.get('#checkSkipButton').then(checkSkipButton => {
if (checkSkipButton) {
cy.log("NOT CLICKABLE")
cy.then(button = true)
//cy.log(button)
//return button;
//cy.log(button)
//return button
}else{
cy.log("CLICKABLE")
button = False
//return this
}
} )
cy.log("value is"+button)
button.should(have,value=true)
}
The code is close to working, but false instead of False and return the button to let you chain the .should() test.
cy.get('#checkSkipButton').then(checkSkipButton => {
let button;
if (checkSkipButton) {
cy.log("NOT CLICKABLE")
button = true;
} else {
cy.log("CLICKABLE")
button = false;
}
return button // passes to should
}
.should('eq', true)
If you want to use button globally, you must wait for it's value to be set by using .then()
let button; // undefined now and also later in the test if not wrapped in .then()
cy.get('#checkSkipButton').then((checkSkipButton) => {
if (checkSkipButton) {
cy.log('NOT CLICKABLE')
button = true;
} else {
cy.log('CLICKABLE')
button = false;
}
})
cy.wrap(button).should('equal', true) // Error: expected undefined to equal true
// neither true nor false, but original value (undefined)
// Allow above cy.get() to process
cy.then(() => {
cy.log('Value is: ' + button);
cy.wrap(button).should('equal', true) // passes
})
You were very close, the above will work fine with some minor changes.
let button //Declare it globally so that it can be accessed outside if
cy.get('#checkSkipButton')
.then((checkSkipButton) => {
if (checkSkipButton) {
cy.log('NOT CLICKABLE')
button = true //instead of cy.then directly update the value of button
} else {
cy.log('CLICKABLE')
button = false
}
})
.then(() => {
cy.log('Value is: ' + button) //prints the button value
cy.wrap(button).should('equal', true) //Asserts the button value
})
I think this is the same logic
cy.get('#checkSkipButton')
.then(checkSkipButton => !!checkSkipButton) // convert truthy to true/false
.then(value => cy.log("value is" + value))
.should('eq', true)

Cypress: I need to exit the for loop if the condition is satisfied

Below is the code which I am using.
I am working with Cypress + Cucumber + Typescript.
Scenario: I need to get a list of unique values using a for loop. Then I am passing this value to an API to verify some condition and if the condition is met I want to exit the loop.
To exit the loop I somewhere read a solution that if I use "return false" as first-line in if condition then loop will exit which seems to work fine.
The issue here is, when I try to set a flag from inside the for-if loop to the instance variable then the value read by if condition (for exiting the loop) is not picking the updated value of instance variable. And the loop continues to run.
Below is the code snippet:
class test {
static isVinavailable: boolean = false;
static setEligibleVehicleVinTest() {
cy.xpath(eligibleForSaleVehicleVin).then((esv) => {
const listingCount = Cypress.$(esv).length;
for (let i = 0; i < listingCount; i++) {
let text123 = esv.eq(i).text();
genericAction.getAuthenticationKey();
cy.fixture("authResp.json")
.then((authResp) => {
cy.request({
method: "GET",
url: vehicleCheckEligibility + text123,
headers: {
Authorization: authResp.access_token,
},
});
})
.then((response: any) => {
cy.wait(5000);
let responseDataelig = response.body;
if (
(responseDataelig.val1 =
"Y" &&
responseDataelig.val2 === "N" &&
responseDataelig.val3 === "N")
) {
this.isVinavailable = true;
}
});
if (this.isVinavailable) {
return false;
}
}
});
}
}
class test {
static isVinavailable = false;
static setEligibleVehicleVinTest(): Cypress.Chainable<boolean> {
return cy.xpath(eligibleForSaleVehicleVin).each(($el) => {
let text123 = $el.text();
cy.fixture('authResp.json')
.then((authResp) => {
return cy.request({
// your code block
});
})
.then((response: any) => {
// your code block
if (condition) {
this.isVinavailable = true;
return false;
};
});
}).then(() => {
return this.isVinavailable;
});
}
}

SPFX webpart property pane

I am trying to append sharepoint lists in dropdown of spfx webpart property pane. but its not getting appended. please help out.
export default class ScrollTickerWebPart extends BaseClientSideWebPart<IScrollTickerWebPartProps> {
private dropdownOptions: IPropertyPaneDropdownOption[];
private listsFetched: boolean;
private fetchLists(url: string) : Promise<any> {
return this.context.spHttpClient.get(url, SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
console.log("WARNING - failed to hit URL " + url + ". Error = " + response.statusText);
return null;
}
});
}
private fetchOptions(): Promise<IPropertyPaneDropdownOption[]> {
var url = "https://abc.sharepoint.com/teams/SharepointPOC" + "/_api/web/lists?$filter=Hidden eq false";
return this.fetchLists(url).then((response) => {
var options: Array<IPropertyPaneDropdownOption> = new Array<IPropertyPaneDropdownOption>();
response.value.map((list: IODataList) => {
console.log("Found list with title = " + list.Title);
options.push( { key: list.Id, text: list.Title });
});
return options;
});
}
Wherever you call fetchOptions, make sure to call this.context.propertyPane.refresh() after the promise resolves. This is needed to force a re-render of the property pane with the new dropdownOptions.
As an example (somewhere other than onPropertyPaneConfigurationStart is fine as well):
protected onPropertyPaneConfigurationStart(): void {
this.fetchOptions().then(options => {
this.dropdownOptions = options;
this.context.propertyPane.refresh();
});
}
This is assuming that your PropertyPaneDropdown is setup something like below, where this.dropdownOptions are initially undefined, and you are wanting to asynchronously load them with fetchOptions():
PropertyPaneDropdown('someProperty', {
// ...
options: this.dropdownOptions,
// ...
})
Web part properties – dynamically populate Dropdown options in SPFX
we populate the dropdown with the SharePoint lists in the current site. We do this with an async REST call to SharePoint
/* need some imports e.g.:
import { IODataList } from '#microsoft/sp-odata-types';
import { SPHttpClient, SPHttpClientConfigurations,
SPHttpClientConfiguration, SPHttpClientResponse, ODataVersion,
ISPHttpClientConfiguration } from '#microsoft/sp-http';
*/
private dropdownOptions: IPropertyPaneDropdownOption[];
private listsFetched: boolean;
// these methods are split out to go step-by-step, but you could refactor
and be more direct if you choose..
private fetchLists(url: string) : Promise<any> {
return this.context.spHttpClient.get(url,
SPHttpClient.configurations.v1).then((response: SPHttpClientResponse) => {
if (response.ok) {
return response.json();
} else {
console.log("WARNING - failed to hit URL " + url + ". Error = " +
response.statusText);
return null;
}
});
}
private fetchOptions(): Promise<IPropertyPaneDropdownOption[]> {
var url = this.context.pageContext.web.absoluteUrl + `/_api/web/lists?
$filter=Hidden eq false`;
return this.fetchLists(url).then((response) => {
var options: Array<IPropertyPaneDropdownOption> = new
Array<IPropertyPaneDropdownOption>();
response.value.map((list: IODataList) => {
console.log("Found list with title = " + list.Title);
options.push( { key: list.Id, text: list.Title });
});
return options;
});
}
Then in the getPropertyPaneConfiguration method, we kick-off the call to fetch the data at the beginning, and then in the control declaration we simply set the options property to our variable holding the array:
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
if (!this.listsFetched) {
this.fetchOptions().then((response) => {
this.dropdownOptions = response;
this.listsFetched = true;
// now refresh the property pane, now that the promise has been
resolved..
this.onDispose();
});
}
return {
pages: [
{
header: {
description: "Basic settings"
},
groups: [
{
groupName: "COB dropdown field (PropertyPaneDropdown)",
groupFields: [
PropertyPaneDropdown('dropdownProperty', {
label: 'This is the label',
options: this.dropdownOptions
})
]
}
]
}
]
}
}
Refer this Web part properties – dynamically populate Dropdown
You can use PropertyFieldListPicker control which is really easy to use.
This control generates a list picker field that can be used in the property pane of your SharePoint Framework web parts.
The control can be configured as a single or multi-selection list picker. Please check the below link :
https://sharepoint.github.io/sp-dev-fx-property-controls/controls/PropertyFieldListPicker/
You can use PNP PropertyFieldListPicker,
https://pnp.github.io/sp-dev-fx-property-controls/controls/PropertyFieldListPicker/

Only reloading resolves without reloading html

How can i force ui router to reload the resolves on my state without reloading the entire ui/controller since
I am using components and since the data is binded from the state resolve,
i would like to change some parameters (pagination for example) without forcing the entire ui to reload but just the resolves
resolve : {
data: ['MailingListService', '$transition$', function (MailingListService, $transition$) {
var params = $transition$.params();
var ml = params.id;
return MailingListService.getUsers(ml, params.start, params.count)
.then(function (result) {
return {
users: result.data,
totalCount: result.totalCount
}
})
}],
node: ['lists', '$transition$', function (lists, $transition$) {
return _.find(lists, {id: Number($transition$.params().id)})
}]
},
I would like to change $transition$.params.{start|count} and have the resolve updated without reloading the html.
What you requested is not possible out of the box. Resolves are only resolved, when the state is entered.
But: one way of refreshing data could be, to check for state parameter changes in $doCheck and bind them to the components by hand.
Solution 1
This could look something like this:
export class MyComponent {
constructor($stateParams, MailingListService) {
this.$stateParams = $stateParams;
this.MailingListService = MailingListService;
this.paramStart = null;
this.paramCount = null;
this.paramId = null;
this.data = {};
}
$doCheck() {
if(this.paramStart !== this.$stateParams.start ||
this.paramCount !== this.$stateParams.count ||
this.paramId !== this.$stateParams.id) {
this.paramStart = this.$stateParams.start;
this.paramCount = this.$stateParams.count;
this.paramId = this.$stateParams.id;
this.MailingListService.getUsers(this.paramId, this.paramStart, this.paramCount)
.then((result) => {
this.data = {
users: result.data,
totalCount: result.totalCount
}
})
}
}
}
Then you have no binding in the parent component anymore, because it "resolves" the data by itself, and you have to bind them to the child components by hand IF you insert them in the template of the parent component like:
<my-component>
<my-child data="$ctrl.data"></my-child>
</my-component>
If you load the children via views, you are obviously not be able to bind the data this way. There is a little trick, but it's kinda hacky.
Solution 2
At first, resolve an empty object:
resolve : {
data: () => {
return {
value: undefined
};
}
}
Now, assign a binding to all your components like:
bindings: {
data: '<'
}
Following the code example from above, where you resolve the data in $doCheck, the data assignment would look like this:
export class MyComponent {
[..]
$doCheck() {
if(this.paramStart !== this.$stateParams.start ||
this.paramCount !== this.$stateParams.count ||
this.paramId !== this.$stateParams.id) {
[..]
this.MailingListService.getUsers(this.paramId, this.paramStart, this.paramCount)
.then((result) => {
this.data.value = {
users: result.data,
totalCount: result.totalCount
}
})
}
}
}
And last, you check for changes in the child components like:
export class MyChild {
constructor() {
this.dataValue = undefined;
}
$doCheck() {
if(this.dataValue !== this.data.value) {
this.dataValue = this.data.value;
}
}
}
In your child template, you access the data with:
{{ $ctrl.dataValue | json }}
I hope, I made my self clear with this hack. Remember: this is a bit off the concept of UI-Router, but works.
NOTE: Remember to declare the parameters as dynamic, so changes do not trigger the state to reload:
params: {
start: {
dynamic: true
},
page: {
dynamic: true
},
id: {
dynamic: true
}
}

Resources