Retrieve value from json based on key provided using cypress - cypress

let expectedKey = 'Student';
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
if(expectedKey === 'Student'){
cy.get('app-screen').find('#code-details').should('have.text', appDetails.studentCode);
}
if(expectedDKey === 'Department'){
cy.get('app-screen').find('#code-details').should('have.text', appDetails.departmentCode);
}
if(expectedKey === 'Paper'){
cy.get('app-screen').find('#code-details').should('have.text', appDetails.paperCode);
}
if(expectedKey === 'Results'){
cy.get('app-screen').find('#code-details').should('have.text', appDetails.resultsCode);
}
}
I don't want to use these many if blocks as there will more keys in the future. Instead, I have to pick the required value for studentCode, departmentCode, paperCode, or resultsCode from JSON based on expectedKey. Any help please?

You can access object properties by dot notation (foo.bar) or bracket notation (foo['bar']). In your case, you'll have to ensure expectedKey matches a valid key in your object with assertion before the cy commands.
let expectedKey = 'studentCode';
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
expect(appDetails, 'valid key').to.have.property(expectedKey)
cy.get('app-screen').find('#code-details').should('have.text', appDetails[expectedKey]);
}

Assuming that you have the expectedKey inside the cy.readFile(), you can do like this:
Create a custom command at cypress/support/commands.js:
Cypress.Commands.add('codeDetailsText', (expectedKey, appDetails) => {
expectedKeyCode = expectedKey.toLowerCase() + 'Code'
cy.get('app-screen')
.find('#code-details')
.should('have.text', appDetails[expectedKeyCode])
})
In your test just write:
cy.readFile('cypress/fixtures/applicationDetails.json').then((appDetails) => {
//Assuming expectedKey value is available here
cy.codeDetailsText(expectedKey, appDetails)
})

Related

Return text from function cypress

I need to return extracted text in function. I'm extracting text but cannot return it to use in functions
getAmounttxt() {
cy.get('#product_price_1_1_0 > .price').invoke('text').as('ame')
cy.get('#ame').then((ame) => {
cy.log(ame)
})
Cypress commands are asynchronous and thus do not compute a value directly, so you can't return a value from a function using a cypress command.
What you can do is return a Chainable and obtain a value within a then callback:
getAmounttxt() {
return cy.get('#product_price_1_1_0 > .price').invoke('text')
}
it('test', () => {
getAmounttxt().then(ame => {
cy.log(ame)
})
})
Also, you can use an alias to transfer you values. Please look into alias wrapper approach if you want to have an alias represented as a real variable which can be passed through all your code.
Just use your alias as the returned value, it is there to help with asynchronous commands.
getAmounttxt() {
cy.get('#product_price_1_1_0 > .price').invoke('text').as('ame')
}
// in test
getAmounttxt();
cy.get('#ame').then((ame) => {
cy.log(ame)
})

Cypress : How to use variables inside each function

I have a local variable that I am trying to access within the each function as below :
var i = 0
cy.get('body').then((body) => {
cy
.get('.classname')
.each(($element) => {
cy.log(i) //returns empty value
///ACCESS THE "i" variable here
}
}
How can the local variable be accessed inside the each function? Is there a restriction on the scope of the variables inside the each function
You can access the i variable inside the .each() but cy.log() will capture the initial value only.
console.log will show you the current value.
const texts = ['abc', '123']
cy.get('body').then((body) => {
cy.get('.classname')
.each(($element, i) => {
cy.wrap($element).should('contain.text', texts[i])
}
}
You cannot chain off each to cy. Cypress Docs
You can use it like this:
var i = 0
cy.get('selector').each(($ele) => {
cy.log(i) //prints 0
})

Fail a test when certain condition met in cypress

I have the following method in a utils.js
/**
* To verify the any array of objevt with given keyword
* #param {*} list
* #param {*} searchKeyword
* #returns true if any mismatch found and vice versa
*/
export const verifyMatchedItems = (list, searchKeyword) => {
var matchedItems = {
title: [],
region: [],
country: [],
};
var nonMatchedItems = [];
list.map((item, index) => {
if (item.title.includes(searchKeyword)) {
matchedItems.title.push(item.title);
} else if (item.region.includes(searchKeyword)) {
matchedItems.region.push(item.region);
} else if (item.country.includes(searchKeyword)) {
matchedItems.country.push(item.country);
} else {
nonMatchedItems.push(item);
}
});
cy.log("Matched-Items:" + JSON.stringify(matchedItems));
if (nonMatchedItems.length>0) {
cy.log("nonMatchedItems:" + JSON.stringify(nonMatchedItems));
}
return nonMatchedItems.length>0;
};
And I am using it in test like this,
//fetch all data in Search Results page and store it
cy.getResultList().then((searchResultDetails) => {
//verify the keyword is matched with result attributes
var flag = Utils.verifyMatchedItems(searchResultDetails, searchKeyword);
expect(flag).to.be.equal(false);
In case, the flag value is true then I want the test to fail. But when I try, it is failing but not executing any previous steps like cy.log(). How can we possibly make cypress to throw an error / fail a test with a customised message saying Failed to due bla bla bla...
You can use throw new error and force a failure with custom message.
throw new Error('The condition was not met!')
In case anyone want this, try to wrap the expect() in a cy.then().
This is because the previous function contains some Cypress commands (only cy.log() in this case).
To make the final check after those commands, put it on the Cypress queue.
//fetch all data in Search Results page and store it
cy.getResultList().then((searchResultDetails) => {
//verify the keyword is matched with result attributes
var flag = Utils.verifyMatchedItems(searchResultDetails, searchKeyword);
cy.then(() => expect(flag).to.be.equal(false))
})

RxJS logic which solves a filter/merge issue

This is more a logical problem then a RxJS problem, I guess, but I do not get it how to solve it.
[input 1]
From a cities stream, I will receive 1 or 2 objects (cities1 or cities2 are test fixtures).
1 object if their is only one language available, 2 objects for a city with both languages.
[input 2]
I do also have a selectedLanguage ("fr" or "nl")
[algo]
If the language of the object corresponds the selectedLanguage, I will pluck the city. This works for my RxJS when I receive 2 objects (cities2)
But since I also can receive 1 object, the filter is not the right thing to do
[question]
Should I check the cities stream FIRST if only one object exists and add another object. Or what are better RxJS/logical options?
const cities1 = [
{city: "LEUVEN", language: "nl"}
];
const cities2 = [
{city: "BRUSSEL", language: "nl"},
{city: "BRUXELLES", language: "fr"}
];
const selectedLang = "fr"
const source$ = from(cities1);
const result = source$.pipe(
mergeMap((city) => {
return of(selectedLang).pipe(
map(lang => {
return {
lang: city.language,
city: city.city,
selectedLang: lang
}
}),
filter(a => a.lang === selectedLang),
pluck('city')
)
}
)
);
result.subscribe(console.log)
If selectedLang is not an observable (i.e. you don't want this to change) then I think it would make it way easier if you keep it as a value:
const result = source$.pipe(
filter(city => city.language === selectedLang)
map(city => city.city)
);
There's nothing wrong from using external parameters, and it makes the stream easier to read.
Now, if selectedLang is an observable, and you want result to always give the city with that selectedLang, then you probably need to combine both streams, while keeping all the cities received so far:
const selectedLang$ = of(selectedLang); // This is actually a stream that can change value
const cities$ = source$.pipe(
scan((acc, city) => [...acc, city], [])
);
const result = combineLatest([selectedLang$, cities$]).pipe(
map(([selectedLang, cities]) => cities.find(city => city.language == selectedLang)),
filter(found => Boolean(found))
map(city => city.city)
)
Edit: note that this result will emit every time cities$ or selectedLang$ changes and one of the cities matches. If you don't want repeats, you can use the distinctUntilChanged() operator - Probably this could be optimised using an exhaustMap or something, but it makes it harder to read IMO.
Thanks for your repsonse. It's great value for me. Indeed I will forget about the selectedLang$ and pass it like a regular string. Problem 1 solved
I'll explain a bit more in detail my question. My observable$ cities$ in fact is a GET and will always return 1 or 2 two rows.
leuven:
[ { city: 'LEUVEN', language: 'nl', selectedLanguage: 'fr' } ]
brussel:
[
{ city: 'BRUSSEL', language: 'nl', selectedLanguage: 'fr' },
{ city: 'BRUXELLES', language: 'fr', selectedLanguage: 'fr' }
]
In case it returns two rows I will be able to filter out the right value
filter(city => city.language === selectedLang) => BRUXELLES when selectedLangue is "fr"
But in case I only receive one row, I should always return this city.
What is the best solution to this without using if statements? I've been trying to work with object destruct and scaning the array but the result is always one record.
// HTTP get
const leuven: City[] = [ {city: "LEUVEN", language: "nl"} ];
// same HTTP get
const brussel: City[] = [ {city: "BRUSSEL", language: "nl"},
{city: "BRUXELLES", language: "fr"}
];
mapp(of(brussel), "fr").subscribe(console.log);
function mapp(cities$: Observable<City[]>, selectedLanguage: string): Observable<any> {
return cities$.pipe(
map(cities => {
return cities.map(city => { return {...city, "selectedLanguage": selectedLanguage }}
)
}),
// scan((acc, value) => [...acc, { ...value, selectedLanguage} ])
)
}

How can the evaluation of a ngrx-store selector be controlled?

I have a selector:
const mySelector = createSelector(
selectorA,
selectorB,
(a, b) => ({
field1: a.field1,
field2: b.field2
})
)
I know the selector is evaluated when any of its inputs change.
In my use case, I need to control "mySelector" by a third selector "controlSelector", in the way that:
if "controlSelector" is false, "mySelector" does not evaluate a new value even in the case "selectorA" and/or "selectorB" changes, and returns the memoized value
if "controlSelector" is true, "mySelector" behaves normally.
Any suggestions?
Selectors are pure functions..its will recalculate when the input arguments are changed.
For your case its better to have another state/object to store the previous iteration values.
You can pass that as selector and based on controlSelector value you can decide what you can return.
state : {
previousObj: {
...
}
}
const prevSelector = createSelector(
...,
(state) => state.previousObj
)
const controlSelector = createSelector(...);
const mySelector = createSelector(
controlSelector,
prevSelector,
selectorA,
selectorB,
(control, a, b) => {
if(control) {
return prevSelector.previousObj
} else {
return {
field1: a.field1,
field2: b.field2
};
}
}
)
Sorry for the delay...
I have finally solved the issue not using NGRX selectors to build up those "higher selectors" and creating a class with functions that use combineLatest, filter, map and starWith
getPendingTasks(): Observable<PendingTask[]> {
return combineLatest(
this.localStore$.select(fromUISelectors.getEnabled),
this.localStore$.select(fromUISelectors.getShowSchoolHeadMasterView),
this.memStore$.select(fromPendingTaskSelectors.getAll)).pipe(
filter(([enabled, shmView, tasks]) => enabled),
map(([enabled, shmView, tasks]) => {
console.log('getPendingTasks');
return tasks.filter(task => task.onlyForSchoolHeadMaster === shmView);
}),
startWith([])
);
}
Keeping the NGRX selectors simple and doing the heavy lifting (nothing of that in this example, though) in this kind of "selectors":
- will generate an initial default value (startWith)
- will not generate new value while filter condition fails (that is, when not enabled, any changes in the other observables do not fire a new value of this observable)

Resources