How to get a value from a datatable using cypress? - cypress

I want to get the 'Created On Date' from the datatable to find out which timezone it falls into?
I was searching for a particular campaign using filter. But I am not sure how to extract a text from the datatable.
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", this.Advertiser)
.should("contain.text", this.Brand)
.then(text => {
const rowText = text;
});
}
But I got this response from Cypress
CypressError: Timed out retrying: expected '<tr.MuiTableRow-root>' to contain text undefined, but the text was 'Advertiser UKBrand UKcampaign14 Nov 2019'
How do I extract just the date from the datable?

You are on the correct way, but you're not searching deep enough. This should help:
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", this.Advertiser)
.should("contain.text", this.Brand)
.find("td")
.eq(5)
.then(text => {
const rowText = text;
});
You did find the correct row, but not the correct cell. By adding the find("td") it does search for the cells. And the .eq(5) actually selects the 5th occurence of the td, which is the cell for the Created On column

What I can understand from your question is you just want to grab the date values and make an assertion on that.
Assumption Made: With filter applied you always get results and you want to write tests only for the cell with date.
This might help you:
function getTextOfCell(rowIndex){
cy
.get('tbody.MultiTableBody-root')
.get('tr.MultiTableRow-root')
.eq(rowIndex)
.find('td').eq(3).invoke('text').then((txt)=>{
cy.log(txt)
})
}
describe('Test Test', ()=>{
it('Test Test', ()=>{
cy.visit('yourURl')
cy.get('tbody.MultiTableBody-root tr.MultiTableRow-root').its('length').then((rowLength)=>{
for(let i=0; i<rowLength; i++){
getTextOfCell(i)
}
});
})
})
Note:
I am assuming that sometimes you will have more than one row as
result. So iterating over all the available rows.
And the date cell will have always have the index 3 for any
row.
In function getTextOfCell we are grabbing the text of the cell
and saving it in txt. Here I am logging out only. You can
make your assertion here.

The problem is that this.Advertiser is undefined when the should function is called. You didn't provide the piece of code where this member is initialed, but I guess that it's initialed inside a then or it's a property. Anyway, like most Cypress methods, when should is called, it doesn't actually performs the validation, but only queues a command that will be executed later to perform the validation. This means that when should is called, this.Advertiser is still undefined and this gets passed as an argument to theshould function, even though when the should command is executed later on, this.Advertiser has a valid value.
The solution should be, instead of using this.Advertiser, put the entire code block that appears in your question, inside the then block where the value in which this.Advertiser is initialized, and use the parameter passed to this then block instead. It should look something like this:
cy.somethingThatProvidesAdvertiser().then(advertiser => {
cy.get('input[placeholder="Filter..."]:nth(2)').type("campaign1");
cy.get("tbody")
.contains("campaign1")
.closest("tr")
.should("contain.text", advertiser)
.should("contain.text", this.Brand)
.find("td")
.eq(5)
.then(text => {
const rowText = text;
});
});
I guess that you should do the same for this.Brand, which means that you should have the 2 then clauses nested.
I hope I managed to explain it clear enough...

Thanks All for the responses. The below code helped me to fetch the desired value from the datatable.
const txt = [];
cy.get("tbody")
.contains("campaign1")
.parent()
.next("td")
.invoke("text")
.then(x => {
txt.push(x);
});
cy.log((this.txt = txt));

I'd suggest that you assign a cypress-dedicated id on a row/a column to easily extract data and tests are more maintainable.
<tr data-cy='campaign-${id}' ...>
<td data-cy='created-on'>
....
<td>
</tr>
For any given campaign, you can get its text with ease and code is readable.
cy.get('[data-cy=campain-3]').find('[data-cy=created-on]').invoke('text')

Related

Cypress - verify if each table row in one column contains the same item and ignoring header

I have a filtered table, a grid table created by DIVs, so no real table elements, that I want to confirm that my filter has worked in but the ways I have tried keep trying to also check the header which is then telling me that my results are wrong
cy.get('div[data-field="name"]')
.then($col => {
const count = $col.length
.wrap($col)
.should('have.text', 'Bob'.repeat(count))
})
I have 3 bobs in my example, but it is also viewing the header so I am getting an error telling me that it is trying to find 3 instances of Bob but also finds Name, so finds 4.
If I try
cy.get('div[data-field="name"]')
.each($col => {
expect($col.text()).to.eq('Bob')
})
It fails as it tells me the first entry is 'Name' as it is finding the header and I am not sure how to avoid it counting/reading the header.
I'm sure its something simple that I am missing and not thinking of but any help is appreciated!
NOTE: I got both these ways of checking from this other stack answer Cypress - verify if each table row in one column contains the same item
There are probably many ways to solve this, here are a couple.
For the first sample, take one off the count (since there is only one header)
cy.get('div[data-field="name"]')
.then($col => {
const count = $col.length
.wrap($col)
.should('have.text', 'Bob'.repeat(count-1))
})
For the second sample, don't look at the first element
cy.get('div[data-field="name"]')
.each(($col, index) => {
if (index > 0) {
expect($col.text()).to.eq('Bob')
}
})

How can I get my cypress custom command to ingest this data (i think i structured the data wrong)?

Alright, as the title says- i'm trying to write a custom command for a cypress test suite. The situation as as follows: I have several tests that need to select an indeterminate number of fields and select an option from the each fields list of drop downs.
The logic for this is crayons-in-mouth simple and works fine:
cy.get(selector)
.select(selection)
.scrollIntoView()
and this works great. But because I use it a lot it's a lot of highly repetitive code so I'm trying to create a custom command where I can just inject an array of arrays (various sets of selectors and selections depending on the situation) into it and it'll do the rest.
This is the custom command as I have it written now.
commands.js
Cypress.Commands.add("assignImportFields", (array) => {
cy.wrap(array).each((selector, selection) => {
cy.get(selector)
.select(selection)
.scrollIntoView()
cy.log('using ' + selector + ' to select ' + selection)
})
})
I have the data in a seperate file that looks like this:
data.js
const importFields = {
actorListImports : [
[selectors.lastName, 'Last_Name'],
[selectors.firstName, 'First_Name'],
[selectors.phoneNum, 'Phone_Number']
]
}
exports.importFields = importFields;
and finally, in my test file:
tests.js
const {actorListImports} = data.importFields;
cy.assignImportFields(actorListImports)
The response I get from this is that the 'select' failed because it requires a dom element. My selectors are fine, so I think it's trying to use an entire array (both selector and selection at once) as the selector instead of the first part of the array.
I know i'm not structuring the data correctly, but i've tried a few different variations of it and my primitive monkey brain just can't put together.
Can someone help me identify what's wrong with how i've structure this?
You need to de-structure the array elements in the .each() parameter list, like this cy.wrap(data).each(([selector, selection])
This is a minimal example:
const selectors = {
'lastName': 'lastName'
}
const data = [
[selectors.lastName, 'Last_Name'],
// [selectors.firstName, 'First_Name'],
// [selectors.phoneNum, 'Phone_Number']
]
cy.wrap(data).each(([selector, selection]) => {
console.log(selector, selection)
expect(selector).to.eq('lastName') // passing
expect(selection).to.eq('Last_Name') // passing
})

Cypress: Is it possible to select an item out of a dynamic dropdown with only a partial word/s?

I have a dropdown list which is always increasing in the number of options available. I Have the 'RecieptName' from the option, however, it is followed by text that is constantly changing. eg: RecieptName: 'Changing sentence including words and numbers'.
What I am trying to do is something along the lines of:
cy.get('#RecieptName').select('RecieptName:');
However, it can't find an option with it as it is followed by changing the numbers. Is it possible to find the option based on a partial option?
You would need to scan the options first, find it's index and select by number
Using .contains() command
cy.get('option')
.contains('ReceiptName:')
.invoke('index') // index among siblings
.then(index => {
cy.get('dropdown').select(index);
})
Using a regex
cy.get('option')
.contains(/^ReceiptName:/)
.invoke('index') // index among siblings
.then(index => {
cy.get('dropdown').select(index);
})
Using selected prop
cy.get('#RecieptName option')
.contains(/^ReceiptName:/)
.invoke('prop', 'selected', true)
The option containing the the text "RecieptName:" can be selected by adding :contains()
cy.get('#RecieptName option:contains(RecieptName:)')
.then($option => {
cy.get('#RecieptName').select($option.text())
})
cy.get('#RecieptName option:contains(RecieptName:)')
.then($option => {
cy.get('#RecieptName').select($option.index())
})
filter will get the DOM element that match a specific selector. Once we get the element, then we get the exact text of RecieptName option by using the invoke(text) and then we pass the same text to the select command, something like this:
cy.get('#RecieptName option')
.filter(':contains("RecieptName:")')
.invoke('text')
.then((recieptNameText) => {
cy.get('dropdown').select(recieptNameText)
})
You can also use the value attribute to directly select the dropdown as I can see the value attributes are unique, I guess that would be the cleanest way to do this:
cy.get('select#RecieptName').select('scoredesc')
cy.get('select#RecieptName').select('modifieddesc')
cy.get('select#RecieptName').select('createdasc')

Retries until they pass or not pass possible with each()

I have a page with a grid view and the possibility to filter on elements. If i ex. do the following the test will fail as the grid view hasn´t updated when filter is applied.
cy.get('[data-cy="elements"]').each((element)=> {
expect(element.text()).to.equal('something)
});
Currently i have a custom command that wait for a loading indicator to disappear but i would love to see if there is a better solution like you can do when you have a single element: cy.get('[data-cy="elements"]').should('have.text, 'something');
I´ve seen that you could do something like this but then i have to check each element using eq which isn´t that beautiful:
cy.get('[data-cy="elements"]').should((elements)=> {
expect(elements.eq(0).text()).to.equal('something');
expect(elements.eq(1).text()).to.equal('something');
expect(elements.eq(2).text()).to.equal('something');
}}
use this:
cy.get('[data-cy="elements"]').should((elements)=> {
for(var i = 0; i < elements.length; i++) {
expect(elements.eq(i).text()).to.equal('something');
}
}
each is not suitable here because as the docu ( https://docs.cypress.io/api/commands/each.html#Assertions ) also states, it is not repeatable. So it will fail on the first failing assertion.
So use should with a callback function to take advantage of the repeat feature of cypress.

How to update item conditionally with branch in RethinkDB

I am trying to do simple upsert to the array field based on branch condition. However branch does not accept a reql expression as argument and I get error Expected type SELECTION but found DATUM.
This is probably some obvious thing I've missed, however I can't find any working example anywhere.
Sample source:
var userId = 'userId';
var itemId = 'itemId';
r.db('db').table('items').get(itemId).do(function(item) {
return item('elements').default([]).contains(function (element) {
return element('userId').eq(userId);
}).branch(
r.expr("Element already exist"),
//Error: Expected type SELECTION but found DATUM
item.update({
elements: item('elements').default([]).append({
userId: 'userId'
})
})
)
})
The problem here is that item is a datum, not a selection. This happens because you used r.do. The variable doesn't retain information about where the object originally came from.
A solution that might seem to work would be to write a new r.db('db').table('items').get(itemId) expression. The problem with that option is the behavior isn't atomic -- two different queries might append the same element to the 'elements' array. Instead you should write your query in the form r.db('db').table('items').get(itemId).update(function(item) { return <something>;) so that the update gets applied atomically.

Resources