returning stuff from custom commands cypress - cypress

I created the below function but im having great difficulties in getting stuff returned from the last if block (elArr[3]).
It works fine if i remove all the return statements and say put: cy.get(el).click() in the if blocks but i dont want to have that command in this function but performed in the test instead like this:
cy.traverseIFrame(['1', '2', '3'], 'myelement').click()
can someone help me out it is driving me crazy trying to get this working.
Cypress.Commands.add('traverseIFrame', (elArr, searchEl) => {
return cy.iframe(elArr[0]).within((res) => {
if (res.find(searchEl).length > 0) {
console.log('true1');
cy.get(searchEl).click(150, 220, {
force: true
});
}
return cy.iframe(elArr[1]).within((res) => {
if (res.find(searchEl).length > 0) {
console.log('true2');
cy.get(searchEl).click(150, 220, {
force: true
});
}
return cy.iframe(elArr[2]).within((res) => {
if (res.find(searchEl).length > 0) {
console.log('true3');
cy.get(searchEl).click(150, 220, {
force: true
});
}
return cy.iframe(elArr[3]).within((res) => {
if (res.find(searchEl).length > 0) {
console.log('true4');
return cy.get(searchEl);
}
});
});
});
});
});

It's because the .within() commands pays no attention to inner return, but gives back the previous subject
within#Yields
.within() yields the same subject it was given from the previous command.
Trying to return a different element the .within callback have no effect
Instead, an equivalent should be .then() & cy.wrap(...).find(...) combo,
Cypress.Commands.add('traverseIFrame', (elArr, searchEl) => {
return cy.iframe(elArr[0]).then(res => {
// this find sb ok since it's jquery
if (res.find(searchEl).length > 0) {
console.log('true1');
// cy.wrap().find() here equates to .within(() => cy.get()
cy.wrap(res).find(searchEl).click(150, 220, { force: true });
}
// this nested cy.iframe may be ok - depends on internals of this command
return cy.iframe(elArr[1]).then((res) => {
...
// inner-most return
return cy.wrap(res).find(searchEl)

Related

Why added value en each loop hasn't been stored on next iteration?

Brief logic is the next: after clicking on 'li' element, request is sent, and based on response value of 'contacts', it should select if it's greater than 0, once such element is find, i need to break each loop. But currently, despite I set value, which should break each loop on next iteration (returns false). count[] has been restored with old values, what's an issue?
cy.get('div[id=company_select]').then($el => {
const count = []
cy.wrap($el).click().find('li').each((element, index) => {
cy.intercept('GET', '**/company/view-json**').as('getCompanyProps')
cy.log(count.length)
if (count.length > 0) {
return false
} else {
cy.wrap(element).click().wait('#getCompanyProps').then((interception) => {
if (interception.response.body.contacts.length === 0) {
cy.wrap($el).find('button[vs__clear]').click()
} else {
count.push(1)
cy.log(count.length)
}
})
}
})
})
You can't early-exit with return false in this scenario, but you can use count to prevent inner execution after body.contacts.length > 0.
cy.intercept('GET', '**/company/view-json**').as('getCompanyProps') // this can be here
cy.get('div[id=company_select]').then($el => {
const count = []
cy.wrap(count).as('count') // make an alias value of the count variable
cy.wrap($el).click().find('li')
.each((element, index) => {
cy.get('#count').then(count => { // retrieve count asynchronously
if (count.length === 0) { // no result yet?
cy.wrap(element).click().wait('#getCompanyProps')
.then((interception) => {
if (interception.response.body.contacts.length === 0) {
cy.wrap($el).find('button[vs__clear]').click()
} else {
count.push(1)
console.log(count.length)
}
})
}
})
})
})
The reason for this behaviour is the mixture of asynchronous commands like .wait('#getCompanyProps') and synchronous code for checking the early exit.
If you use console.log() instead of cy.log() to debug the values, you'll see the logs before the early exit all run before the logs after count.push(1).
If I understand your question correctly, you wish to know why on the second pass of your cypress each loop cy.wrap($el).click().find('li').each((element, index) => { the cy.log(count.length) is equal to 0.
Even though further down inside another then loop cy.wrap(element).click().wait('#getCompanyProps').then((interception) => { you increase count by count.push(1) and right after cy.log(count.length) which returns 1.
The short answer is scope. If you change a variable within a cypress loop to return that variable you need to add something like this after .then( () =>{ cy.wrap(count)} My solution is below but I also changed the position of your const count = [] if you want to know why I suggest reading What is the difference between 'let' and 'const' ECMAScript 2015 (ES6)?
const count = []
cy.intercept('GET', '**/company/view-json**').as('getCompanyProps')
cy.get('div[id="company_select"]')
.then( ($el) => {
cy.wrap($el)
.click()
.find('li')
.each( ($el) => {
if (count.length === 0){
cy.wrap($el)
.click()
.wait('#getCompanyProps')
.then((interception) => {
if (interception.response.body.contacts.length === 0) {
cy.wrap($el)
.find('button[vs__clear]')
.click()
} else {
count.push(1)
cy.log(count.length)
}
})
}
})
.then( () =>{
cy.wrap(count).as('count')
})
})
cy.get('#count')
.then( (count) =>{
cy.log(count.length)
})

How to get the return value from a custom command in Cypress?

I am writing a long test so I added the most reusable part to a Command Folder, however, I need access to a certain return value. How would I get the return value from the command?
Instead of directly returning the salesContractNumber, wrap it and then return it like this:
Your custom command:
Cypress.Commands.add('addStandardGrainSalesContract', () => {
//Rest of the code
return cy.wrap(salesContractNumber)
})
In your test you can do this:
cy.addStandardGrainSalesContract().then((salesContractNumber) => {
cy.get(FixingsAddPageSelectors.ContractNumberField).type(salesContractNumber)
})
Generally speaking, you need to return the value from the last .then().
Cypress puts the results of the commands onto the queue for you, and trailing .then() sections can modify the results.
Cypress.Commands.add('addStandardGrainSalesContract', () => {
let salesContractNumber;
cy.get('SalesContractsAddSelectors.SalesContractNumber').should($h2 => {
...
salesContractNumber = ...
})
.then(() => {
...
return salesContractNumber
})
})
cy.addStandardGrainSalesContract().then(salesContractumber => {
...
Or this should work also
Cypress.Commands.add('addStandardGrainSalesContract', () => {
cy.get('SalesContractsAddSelectors.SalesContractNumber').should($h2 => {
...
const salesContractNumber = ...
return salesContractNumber; // pass into .then()
})
.then(salesContractNumber => {
...
return salesContractNumber // returns to outer code
})
})
cy.addStandardGrainSalesContract().then(salesContractumber => {
...
Extra notes:
const salesContractHeader = $h2.text() // don't need Cypress.$()
const salesContractNumber = salesContractHeader.split(' ').pop() // take last item in array

Why doesn't EMPTY complete the observable?

In the code below, I am conditionally switching to another observable. If the condition is met it works fine. If the condition is not met and I return EMPTY from switchMap, the code in the subscribe block is not executed.
If I change return EMPTY to return of(x) it works.
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(() => {
this.isSaving = false;
this.goIntoDisplayMode();
}, () => {
this.isSaving = false;
});
Try to use the third callback:
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe(
() => this.goIntoDisplayMode(),
console.err,
() => this.isSaving = false,
);
It is probably more clear if you pass to the subscribe function an Observer. Your code would become
this.claimStoreService.setProducts(this.claim.products)
.pipe(switchMap(x => {
if (this.incomeEligibility) {
return this.claimStoreService.saveIncomeEligibility();
} else {
return EMPTY;
}
}))
.subscribe({
next: () => this.goIntoDisplayMode(),
error: err => console.error(err),
complete: () => this.isSaving = false,
});

Observable from array poll server

I'm trying to create an Observable from an array of items that each regularly check for server updates and then sends an action when it gets the result it wants for each item.
The answer below is helpful, however not quite what I'm looking for
This is the other approach I've been trying:
export function handleProcessingScenes(action$,store) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.mergeMap(scene => updateScene(scene))
}
function updateScene(scene) {
return Observable.interval(3000)
.flatMap(() => requestSceneUpdates(scene.id))
.takeWhile(res => res.payload.status < 4)
.timeout(600000, Observable.throw(new Error('Timeout')))
}
The API function returns an Observable
export function requestSceneUpdates(sceneId){
console.log('requestSceneUpdate')
const request = fetch(`${API_URL}/scene/task/${sceneId}/update`, {
method: 'get',
credentials: 'include',
crossDomain: true,
}).then(res => res.json())
return Observable.fromPromise(request)
}
However this only calls the 'requestSceneUpdate' function once.
I basically want to call that function every 3 seconds for each scene in scenesByLocation. I then want to return an action when each one is finished.
The epic that I have for a single scene is
export function sceneProcessingUpdate(action$) {
return action$.ofType(REQUEST_SCENE_PROCESSING_TASK_SUCCESS)
.switchMap(({task}) =>
Observable.timer(0, 30000).takeUntil(action$.ofType( REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS))
.exhaustMap(() =>
requestSceneUpdates(task.id)
.map((res) => {
if (res.error)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: res.message }
else if(res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.catch(err => { return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: err } })
)
)
}
I think you need something like this. The idea is to retry the scene update if it fails, after 3 seconds and not use a timer.
export function handleProcessingScenes(action$) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.mergeMap(scene => updateScene(scene));
}
function updateScene(scene) {
return requestSceneUpdates(scene.id)
.map((res) => {
if (res.error)
throw res.error;
else if (res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.retryWhen(errors => errors.delay(3000));
}
This worked in the end, #Andrew fixed the first part.
export function handleProcessingScenes(action$,store) {
return action$.ofType(REQUEST_ALL_SCENES_BY_LOCATION_FULFILLED)
.switchMap(({ scenesByLocation }) => Observable.from(scenesByLocation))
.filter(scene => scene.scenePanoTask)
.flatMap(scene => {
return Observable.timer(0, 5000).takeUntil(action$.ofType( REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS))
.exhaustMap(() =>
requestSceneUpdates(scene.id)
.map((res) => {
if (res.error)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: res.message }
else if(res.payload.status === 4)
return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_SUCCESS, task: res.payload }
else
return requestSceneProcessingTaskMessage(res.payload)
})
.catch(err => { return { type: REQUEST_SCENE_PROCESSING_TASK_UPDATE_FAILED, message: err } })
)
})
}

How to return from within an observer?

I was trying to return filter function but return doesn't seem to work with callbacks. Here this.store.let(getIsPersonalized$) is an observable emitting boolean values and this.store.let(getPlayerSearchResults$) is an observable emiting objects of video class.
How do I run this synchronously, can I avoid asynchronus callback altogether given that I can't modify the observables received from store.
isPersonalized$ = this.store.let(getIsPersonalized$);
videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this.myFilter(vids));
myFilter(vids) {
this.isPersonalized$.subscribe((x){
if(x){
return this.fileterX(vids);//Return from here
}
else {
return this.filterY(vids);//Or Return from here
}
});
}
fileterX(vids) {
return vids.filter((vid) => vids.views>100;);
}
fileterY(vids) {
return vids.filter((vid) => vids.views<20;);
}
I got it working this way, you don't need myFilter(vids) at all if you can get the branching out on isPersonalized$'s subscribe. Here is the updated code.
this.store.let(getIsPersonalized$);
videos$: Observable<any>;
ngOnInit() {
this.isPersonalized$.subscribe((x) => {
if (x) {
this.videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this. fileterX(vids));
} else {
this.videos$ = this.store.let(getPlayerSearchResults$)
.map((vids) => this. fileterY(vids));
}
});
}
fileterX(vids) {
return vids.filter((vid) => vids.views>100;);
}
fileterY(vids) {
return vids.filter((vid) => vids.views<20;);
}
It looks like you want to evaluate the latest value of isPersonalized$ within the map function, i'd do that via withLatestFrom (Example: The first one toggles true/false every 5s, the second emits an increasing number every 1s):
const isPersonalized$ = Rx.Observable.interval(5000)
.map(value => value % 2 === 0);
const getPlayerSearchResults$ = Rx.Observable.interval(1000)
.withLatestFrom(isPersonalized$)
.map(bothValues => {
const searchResult = bothValues[0];
const isPersonalized = bothValues[1];
...
});

Resources