Cypress assert is element inside array - arrays

Can someone please, assist in following:
Short explanation: opened one page, taken text element, then opened second page nd among 4 or 5 elements, need to assert that element from page one, is inside created array of those several elements. Wrote this code:
Cypress.Commands.add(
'assertForOpenedElementVisible',
(list1, list2, notDisplayedElementMsg) => {
const textsArray = []
cy.get('body').then((body) => {
if (body.find(list1).length > 0) {
cy.get(list1).each(($el, index) => {
const text1 = $el.text().replace(', ', '')
cy.get(list1).eq(index).click()
cy.wait(1000)
cy.get(list2)
.each(($el, index) => {
const text = $el.text().replace(', ', '')
textsArray.push(text)
cy.log(textsArray)
cy.log(text)
})
.then(() => {
cy.wrap(expect(textsArray).to.include(text1))
})
})
} else {
cy.log(notDisplayedElementMsg)
}
})
}
)
And when check Test runner - I got elements, but test fails:
How to correctly assert that? Thank you in advance

You can do an assertion like:
expect(text1).to.be.oneOf(textsArray)
OR, you can directly assert without using each() as well like:
cy.get(list2).should(($list2) => {
expect($list2.eq(3)).to.contain('49') //If you know the position
})
cy.get(list2)
.invoke('text')
.then((text) => {
expect(text).to.contain('49') //If you don't know the position
})

Related

Iterating Array in Cypress

I am pushing Div element in an array and then iterating each one by one, but when I came out of first each section the array length becomes 0 and i am unable to enter into for loop.
in for loop, i want to click a section in each div and perform an assertation and go back to the previous section.
let Array =[]
cy.get('.tag-wrapper-item').each(($pills) => {
cy.log($pills)
// cy.log('Success')
Array.push($pills)
cy.log(Array.length)
})
cy.log(Array.length)
for (const element of Array) {
cy.wrap(element).click()
cy.wrap(element).find('.--tag.tag-link.--active.tag-selector-button-link').click()
var OneOffModel = element.text()
cy.get('.heading-markdown').contains(OneOffModel)
cy.go('back')
}
A better approach might be to get the count of .tag-wrapper-item and loop on that
cy.get('.tag-wrapper-item')
.its('length')
.then(count => {
for (let index = 0; index < count; index++) {
cy.get('.tag-wrapper-item').eq(index)
.click()
cy.get('.tag-wrapper-item').eq(index)
.find('.--tag.tag-link.--active.tag-selector-button-link')
.click()
cy.get('.tag-wrapper-item').eq(index)
.invoke('text')
.then(OneOffModel => {
cy.get('.heading-markdown').contains(OneOffModel)
})
cy.go('back')
}
})
If you alias each element in the loop, Cypress will try to overcome the detached-from-DOM error when you read the alias
cy.get('.tag-wrapper-item')
.its('length')
.then(count => {
for (let index = 0; index < count; index++) {
cy.get('.tag-wrapper-item').eq(index)
.as('current')
.click()
cy.get('#current')
.find('.--tag.tag-link.--active.tag-selector-button-link')
.click()
cy.get('#current')
.invoke('text')
.then(OneOffModel => {
cy.get('.heading-markdown').contains(OneOffModel)
})
cy.go('back')
}
})
Cypress commands asynchronous, so at the time the Cypress walks through your array, it's not yet been initialized.
To fix this, you can wrap your code with a then callback:
let Array =[]
cy.get('.tag-wrapper-item').each(($pills) => {
cy.log($pills)
// cy.log('Success')
Array.push($pills)
cy.log(Array.length)
})
cy.then(() => {
cy.log(Array.length)
for (const element of Array) {
cy.wrap(element).click()
cy.wrap(element).find('.--tag.tag-link.--active.tag-selector-button-link').click()
var OneOffModel = element.text()
cy.get('.heading-markdown').contains(OneOffModel)
cy.go('back')
}
})

Arraylist doesn't get refilled and/or filtered

I have a list in angular, an array. OnInit it gets filled from the right corresponding database items. I created a form above it. When you enter something in the form, it acts like a filter. This works, the first time. When you erase something from the form and enter something else, the list should be refreshed and afterwards filtered based on the new input. This doesn't happen. I put the formula that happens on onInit in my function to refill the list.
Below you can find my function (I left the console logs in) and a screenshot of the problem. First I look for a user (joeri.boons#hi10.be) which returns three results. Than I erase the user and look based on a month 7. The screen returns a new unfilterd list while in the console it still holds the list of 3 x user joeri.boons#hi10.be. So there is an inconsistency to. If you look at screen result you would think of a filter problem, the console points at a refreshproblem.
if more code is required let me know.
updateWithFilter(): void {
console.log("function update filter reached")
console.log(this.listadapted);
if(this.listadapted == true){
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log("getHandledSheet executed")
}
if(this.filterUsername.trim() && !this.filterYear && !this.filterMonth){
console.log("option 1 reached")
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.username == this.filterUsername);
this.listadapted = true;
} else if(!this.filterUsername.trim() && !this.filterYear && this.filterMonth){
console.log("option 2 reached");
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log("before filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth);
console.log("after filter");
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
} else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}) ;
}
My guess would be that this is caused by loading data in ngOnInit. As the documentation (https://angular.io/api/core/OnInit) states : [...] It is invoked only once when the directive is instantiated.
I suspect that you create one instance and re-use it and the ngOnInit method does not get called again.
UPDATE:
The issue is that the call to this.getHandledSheet(); does a call to .. .subscribe({next: .. which is delayed and the rest of the function is executed first.
So the actual code after next: is only executed after the timeSheetService is done loading the data.
So either you apply the filter in the
{next: (response: TimeSheet[]) => {this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED') }}
block after filtering for 'HANDLED' or you'll try to await in the update function.
Create two variables, one that will always remain unfiltered, then another that will be filtered.
The problem will be that the original list is filtered, hence you are losing the original data after filtering!
timesheetHandled: TimeSheet[];
timesheetHandledOriginal: TimeSheet[];
updateWithFilter(): void {
console.log('function update filter reached');
console.log(this.listadapted);
if (this.listadapted == true) {
// this.timesheetsHandled = {} as TimeSheet[];
this.getHandledSheet();
console.log('getHandledSheet executed');
}
if (this.filterUsername.trim() && !this.filterYear && !this.filterMonth) {
console.log('option 1 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.username == this.filterUsername
);
this.listadapted = true;
} else if (!this.filterUsername.trim() && !this.filterYear && this.filterMonth) {
console.log('option 2 reached');
console.log(this.filterUsername.trim());
console.log(this.filterYear);
console.log(this.filterMonth);
console.log('before filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
this.timesheetsHandled = this.timesheetHandledOriginal.filter(
sheet => sheet.month == this.filterMonth
);
console.log('after filter');
this.timesheetsHandled.forEach(sheet => console.log(sheet.username));
// console.log(this.timesheetsHandled.filter(sheet => sheet.month == this.filterMonth));
this.listadapted = true;
}
// else if .. more options
}
ngOnInit(): void {
this.getHandledSheet();
}
getHandledSheet(): void {
this.timesheetService.getAllTimesheets().subscribe({
next: (response: TimeSheet[]) => {
this.timesheetsHandled = response.filter(sheet => sheet.status == 'HANDLED');
this.timesheetHandledOriginal = JSON.parse(JSON.stringify(this.timesheetsHandled));
},
});
}

drag to reorder and save state with reactjs

I'm writing a small app that uses the react-sortable-hoc
everything is great but im having issues displaying the list ordered by order
I have
user 0
user 1
user 2
when I drag user 2 above user 0
instead of getting
user 2
user 0
user 1
I get
user 2
user 1
user 0
I think It has to do with the way I'm setting the order in the state. but I can't figure it out.
this is how I set the order on sort end
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
newItems[newIndex].order = oldIndex;
newItems[oldIndex].order = newIndex;
return newItems.sort((a, b) => a.order - b.order);
})
};
here's the app running so you can play with it.
https://codesandbox.io/s/winter-https-xelrd?fontsize=14&hidenavigation=1&theme=dark
I have fixed it,
here is the working url to play with https://codesandbox.io/s/quizzical-colden-rm62y
You were correct in guessing that the problem was with the onSortEnd function. Instead of swapping the newIndex and oldIndex position we just need to either bubble them up or down.
Here is a working code, it can be cleaned up a bit, but you got the idea :)
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
const newItems = [...prevState];
if (oldIndex > newIndex) {
for (let i = oldIndex - 1; i >= newIndex; i--) {
newItems[i].order++;
newItems[oldIndex].order = newIndex;
}
} else if (oldIndex < newIndex) {
for (let i = oldIndex + 1; i <= newIndex; i++) {
newItems[i].order--;
newItems[oldIndex].order = newIndex;
}
}
return newItems.sort((a, b) => a.order - b.order);
});
};
Hope it helps. Happy coding :)
What you do is swapping.
If you want to just "insert" the element in the new position you will have to update all the items between the two positions.
In your case, one approach would be to just move the element and re-create the order for all items
setUsers(prevState => {
const newItems = [...prevState];
newItems.splice(newIndex, 0, newItems.splice(oldIndex, 1)[0]).forEach((item,index)=>{
item.order = index;
});
return newItems
});
Demo at https://codesandbox.io/s/confident-river-mrh3p
So looks like your code is simply swapping the elements. This does not seem like what you really want to do. In fact you really want to remove the element and insert it at a given position. I think since you already have the oldIndex and newIndex, you can approach the sort function as follows:
const onSortEnd = ({ oldIndex, newIndex }) => {
setUsers(prevState => {
var newItems = [...prevState];
let elem = newItems[oldIndex]
newItems.splice(oldIndex, 1)
newItems.splice(newIndex, 0, elem)
return newItems
});
};
There isn't really a need for order and is capturing more than the minimum state required (unless you use it elsewhere).

How do I render/animate out items that leave array

Currently, I have a scraper that scrapes slack messages and stores them in a db somewhere.
On the frontend, I am pulling every second to see if new messages pop up. And then I render those messages on screen.
If anyone on slack replies or emotes on a message, the message gets removed from the backend thus getting removed from the frontend.
What I am trying to do now is when an item gets removed, I would like to animate it somehow.
Here is some of my current code:
async componentDidMount() {
await this.grab_channels()
await this.grab_slack_user_data()
await this.grab_items()
setInterval(() => {
this.grab_items()
}, this.state.settings.seconds_per_slack_messages_pull * 1000 )
}
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise( resolve => {
axios.get( url )
.then( res => {
this.setState( { items: res.data } )
resolve()
} )
})
}
And finally, items get rendered:
this.props.items.map( t => {
return (
<Item
key={ t.usr + '_' + t.ts }
task={ t }
user={ this.props.slack_users[ t.usr ] }
settings={ this.props.settings }
now={ this.state.now }
/>
)
} )
I was thinking of doing some sort of check within grab_items() but I wouldn't know how to continue after that. It would be easy to determine which ones should be rendered out but the problem is actually doing it.
Anyone have experience building something like this out?
Thanks!
Using Transition Groups is one way to do this:
https://github.com/reactjs/react-transition-group
Take a look at this example:
https://reactcommunity.org/react-transition-group/transition-group
For the check part in your function grab_items
/* include "clone" so that we don't modify state directly */
import clone from 'clone'
grab_items() {
let url = this.state.settings.api_url + 'channel/' + this.state.selected_channel + '/now'
return new Promise(resolve => {
axios.get(url).then(res => {
/* figure out what items to remove before you set the state */
let itemsToShow = []
for (let i = 0; i < this.state.items.length; i++) {
let ifFound = false
let t = clone(this.state.items[i])
for (let j = 0; j < res.data.length; j++) {
if (t.key === res.data[j].key) {
ifFound = true
}
}
/* if ifFound is false, it means it is not in the messages any more. */
if(!ifFound){
t.haveAnimation = true
itemsToShow.push(t)
}
itemsToShow = itemsToShow.concat(res.data)
this.setState(itemsToShow)
}
})
})
}
Then every second when it re-pull the data, you will have a list of items to show. The list has the items need to have the "disappear" animation and also it has the new messages.
To make the animation work, in the render part:
this.props.items.map(t => {
return (
<Item
key={t.usr + '_' + t.ts}
className={t.haveAnimation ? 'animationCSS' : ''}
task={t}
user={this.props.slack_users[t.usr]}
settings={this.props.settings}
now={this.state.now}
/>
)
}
Above code should attach the css class to the Item. You can put whatever css animation in the class

Protractor - How do I return an element by the text contained within a child element?

was wondering if I could get some help on this. I have an array of <tr> elements returned when I declare this webelement reference
locationAuditsBySourceListViewTableRows : { //returns an array
get : function(){
return this.locationAuditsBySourceListViewTable.all(by.repeater('audit in location.displayedProfileAudits'));
}
},
Within one of those <tr> elements is a span that contains the text that specifies the row I would like to select.
The current framework I am working within has some similar methods, which I tried to refactor for my purposes, but this code is failing:
locationAuditsListViewRowBySource : {
value : function(sourceName){
return this.locationAuditsBySourceListViewTableRows.filter(elem => {
//elem - placeholder as we iterate through each element within the locationAuditsBySourceListViewTableRows array
return elem.element(by.css(".source-name.ng-binding")).getText().then((val) => {
return val.toLowerCase() == sourceName.toLowerCase();
});
}).first();
}
},
My feeling is that the way in which I try to reference the span within the table row is incorrect (call is made within the .filter() method):
return elem.element(by.css(".source-name.ng-binding")).getText().then((val) => {
Here is the step definition itself:
Then(/^I should see that location audit for source row: "([^"]*)" has a label for the source$/, function (sourceName, callback) {
browser.wait(EC.visibilityOf(listingsPageObj.locationAuditsBySourceListViewContainer), timeouts.EC_TIMEOUT).then(() => {
browser.wait(EC.visibilityOf(listingsPageObj.locationAuditsBySourceListViewTable), timeouts.EC_TIMEOUT).then(() => {
browser.wait(() => {
return listingsPageObj.locationAuditsBySourceListViewTableHeaders.count().then(cnt => (cnt > 0)); //which means that there are audit results displayed
}).then(() => {
//find a row in the list of displayed audits
var tableRow = listingsPageObj.locationAuditsListViewRowBySource(sourceName);
});
});
});
});
The usage of elem.element(by.css(".source-name.ng-binding")).getText() is right.
But some small changes in code is better:
return val.trim().toLowerCase() == sourceName.trim().toLowerCase();
return listingsPageObj.locationAuditsBySourceListViewTableHeaders
.count().then(cnt => {return cnt > 0;}) // I think you need return

Resources