Return text from function cypress - 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)
})

Related

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
})

Retrieve value from json based on key provided using 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)
})

How to map different JSON objects from the fixture into specific spec test file in the cypress

I have the below Input.json as fixture and It contains two different test cases.
Input.json (Fixture folder)
[
{
"searchKeyword":"cypress"
},
{
"username":"QATesting",
"password":"testprofile"
}
]
The above data will validate two different functionality of Google. One is going to validate search engine and another one is going to validate the user login activity (This is just for sample use case which may imitate my actual requirement).
I just created the cypress runner and I just want to run the spec file by using the below runner.js file
const cypress = require('cypress')
const fixtures = require('./cypress/fixtures/Test.json')
const promises = fixtures.map(fixture => {
return cypress.run({
env: {
fixture
},
spec: './cypress/integration/test.spec.js',
});
});
I just added two different It(test cases) respectively in the below "test.spec.js" file. And one test is gonna do the search function and another one is gonna check the existing user login activity:
describe("How to map two different data set with respective test function",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
cy.xpath("//input[#value='Google Search']").click();
cy.get("//ul/li[2]").should("be.visible");
});
it("Test Case2: login to the gmail account", function(){
cy.xpath("//a[contains(text(),'Sign in')]").click();
cy.xpath("//div[contains(text(),'Use another account')]").click();
cy.xpath("#identifierId").type(testData.username);
cy.xpath("//*[contains(text(),'Next')]").click();
cy.xpath("#password").type(testData.password);
cy.xpath("#submitbtn").click();
})
});
But the second test is getting failed and the testData.username return undefined.
Is there anyway to map the specific JSON array object with specific function in the test.spec.js file?
Not sure how to map the first dataset index with first It (Test case 1) and second dataset index with second test case respectively.
One quick way is to skip if the testData does not have the required properties,
describe("How to map two different data set with respective test function",() =>{
const baseUrl = "https://www.google.com/";
const testData = Cypress.env('fixture')
beforeEach("",()=>{
cy.visit(baseUrl);
});
it("Test Case1: Search the keyword", function () {
if (!testData.searchKeyword) this.skip
cy.xpath("//input[#name='q']").type(testData.searchKeyword);
cy.xpath("//input[#value='Google Search']").click();
cy.get("//ul/li[2]").should("be.visible");
});
it("Test Case2: login to the gmail account", function() {
if (!testData.username) this.skip
cy.xpath("//a[contains(text(),'Sign in')]").click();
cy.xpath("//div[contains(text(),'Use another account')]").click();
cy.xpath("#identifierId").type(testData.username);
cy.xpath("//*[contains(text(),'Next')]").click();
cy.xpath("#password").type(testData.password);
cy.xpath("#submitbtn").click();
})
});
Tagging
You can also get into tags, adding a tag property to the testData
[
{
"tag": "search",
"searchKeyword":"cypress"
},
{
"tag": "user",
"username":"QATesting",
"password":"testprofile"
}
]
Perhaps use a library like cypress-tags, then in the runner script
const cypress = require('cypress')
const fixtures = require('./cypress/fixtures/Test.json')
const promises = fixtures.map(fixture => {
if (fixture.tag) {
process.env.CYPRESS_INCLUDE_TAGS = fixture.tag
}
return cypress.run({
env: {
fixture
},
spec: './cypress/integration/test.spec.js',
});
});
Since your fixtures data is in a array and the username and password fields are at index 1, so in order to access those you have to use:
testData[1].username
testData[1].password
In case if you don't want to use the index value, change the fixture structure to:
{
"searchKeyword": "cypress",
"username": "QATesting",
"password": "testprofile"
}
And in your test directly use:
testData.username
testData.password

Problems with using external variables in an observable sequence

What problems could arise when using variables that are external to an observable sequence inside the sequence?
For example:
updateCar(newCar: any): Observable<any> {
return of(...).pipe(
switchMap(
(value: any) => {
if (newCar.has4Wheels && value.lovePizza) {
// return a 4 wheel observable
} else {
// return a not 4 wheel observable
}
}
),
switchMap(
(value: any) => {
if (newCar.has4Windows && !value.lovePizza) {
// return a 4 window observable
} else {
// return a 2 window observable
}
}
)
);
}
I know the example above is weird, but i am just using it to ask the question.
What problems could arise with using newCar inside the sequence like being used in the example when it is external to the sequence? If there are no problems, great! Just feels like there is something wrong with this usage to me.
I think nothing (at least as far as you don't modify newCar).
It's true that you could rewrite this and start with for example the following:
of([whatever, newCar])
.pipe(
switchMap(([whatever, newCar]) => {
...
})
)
...
But I think this isn't necessary and would make things just more complicated without any real benefit.

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