Storing element text in an array and accessing it later - cypress

I am trying to get element text inside a for loop for multiple elements and wanted to store it in an array in order to use it later. below is the code I am using. I needed to access the array for the later use so that i compare this array with another array. Please let me know how to achieve this in cypress.
it('My test', () => {
let arrayOfElementText = [];
cy.get('#divEl').each(($el) => {
cy.wrap($el).click();
cy.get('#input').invoke('val')
.then(val => {
arrayOfElementText.push(val);
console.log(arrayOfElementText);//Able to access
});
console.log(arrayOfElementText); **//Not able to access**
})
let anotherArray = [];
cy.get('#divEl1').each(($el) => {
cy.get('#input1').invoke('val')
.then(val => {
anotherArray.push(val);
console.log(anotherArray);//Able to access
});
console.log(anotherArray);**//Not able to access**
})
// code to compare two arrays
//both arrays are not accessible here
});

You need to use .then() to access the values, as they are derived from asynchronous commands.
let arrayOfElementText = [];
cy.get('#divEl').each(($el) => {
cy.wrap($el).click();
cy.get('#input').invoke('val')
.then(val => {
arrayOfElementText.push(val);
})
.then(() => {
console.log(arrayOfElementText); // array is available at every step of .each()
})
}).then(() => {
console.log(arrayOfElementText); // full array is available here
cy.wrap(arrayOfElementText).as('myArray1') // alias it for later
})
/*
Next array, same as above
*/
cy.get('#myArray1').then(myArray1 => {
cy.get('#myArray2').then(myArray2 => {
// compare
})
})
Note cy.get('#input') will always get the same input

Related

cypress .each() .wrap() .invoke('text') takes long time

I am trying to check the items shown in my table for some assertions. My way is to put all of the items in an array and then iterate over that array.
My problem: All assertions already passed but the cypress runner still takes a lot of time to finish the cy.wrap(.invoke(text)) jobs.
Since this is a very core command of my cypress tests it would be great to have a more efficient function.
My command:
cy.get('table tbody').within(() => {
cy.get('tr').each((tr) => {
cy.wrap(tr.children().eq(index)).invoke('text').then((text) => {
text = text.trim();
arrayWithValuesOfTheList.push(text);
});
})
.then(() => {
//in here all the (quickly passing) assertions are...
});
});
Thanks for any help in advance. I appreciate you all!
You can avoid wrapping the value, will give some increase in speed but it's hard to say what is the slowest part.
const arrayWithValuesOfTheList = []
cy.get('table tbody tr')
.each($tr => {
arrayWithValuesOfTheList.push($tr.children().eq(index).text())
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})
You can do something like this. It gets the tr values one by one and matches in against a regex pattern.
cy.get('table tbody tr').each(($ele) => {
cy.wrap($ele.text().trim())
.should('match', /myregexp/)
.and('not.include', 'some text')
})
If you want to assert on individual cells, using .each($cell => {...}) is fine but if you want whole-column assertions (e.g sorted, unique-values) it gets difficult with .each().
To build something adaptable for various tests, take a look at the pattern here Sorting the table.
The idea is to create helper functions using .map() to selected table rows and columns.
const { _ } = Cypress
// helpers, reusable
const getColumn = (colIndex) => {
return (rows$) => {
const children$ = _.map(rows$, 'children')
return _.map(children$, `[${colIndex}]`)
}
}
const toStrings = (cells$) => _.map(cells$, 'textContent')
const toNumbers = (texts) => _.map(text, Number)
cy.get('table tbody tr') // rows of table
.then(getColumn(1)) // extract 2nd column
.then(toStrings) // get the text value
.then(toNumbers) // convert if assertions require numeric values
// whole-column assertion example
.should(values => {
const sorted = _.sortBy(values)
expect(values, 'cells are sorted 📈').to.deep.equal(sorted)
})
// individual value assertion
.should(values => {
values.forEach(value => {
expect(value).to.be.lt(100)
})
})
Addressing performance issue
If performance is poor, you can reduce the number of process steps at the Cypress command level by using jQuery-only commands.
This will avoid adding commands to the Cypress queue which is likely to be the slowest part
const arrayWithValuesOfTheList = []
cy.get('table tbody tr td:nth-child(2)') // 2nd col of each row
.then($tds => { // only jQuery methods inside
$tds.text((index, cellText) => {
arrayWithValuesOfTheList.push(cellText.trim())
})
})
.then(() => {
//in here all the (quickly passing) assertions are...
})
})

Return value from nested wrap

I am starting out with cypress and I have a doubt about returning a value form a custom command.
I have multiple tables across my application, and in my tables I can click a row that will open a modal with more deailed information. So I want to build a command to extract the values of a specific row, so I can store them and then compare with the modal values.
I'm also trying to do this command in a way to reuse across the different tables. However I am having issues with my return value. This is my current command:
Cypress.Commands.add(
'getRowInformation',
(rowsSelector, compareValue, mainProperty, nestedSelector) => {
let rowNumber = -1
const propertiesObject = {}
/**
* get all the field in the all the rows that might contain the compareValue
*/
cy.get(`[data-testid="${mainProperty}"]`).then($elements => {
cy.wrap($elements)
.each(($elementField, index) => {
/**
* Find first match and get the row index
*/
if (rowNumber === -1 && $elementField.text() === compareValue) {
rowNumber = index + 1
}
})
.then(() => {
/**
* Access needed row
*/
rowsSelector()
.eq(rowNumber)
.within(() => {
cy.get(nestedSelector).then($property => {
cy.wrap($property)
.each($prop => {
Object.assign(propertiesObject, { [$prop.attr('data-testid')]: $prop.text() })
})
.then(() => {
/**
* Return key value map, where key in data-testid
* and value is the element's text
*/
return cy.wrap(propertiesObject)
})
})
})
})
})
},
)
And I am calling this command in my it() as:
cy.getRowInformation(myCustomSelector, 'Compare value', 'testid', 'span').then(properties => {
console.log('properties', properties)
expect(true).to.be.true
})
My custom selector:
myCustomSelector: () => cy.get('[data-testid="row"]'),
My problem is that what gets to my .then in my it() is the rowsSelector().eq(rowNumber) and what I needed is the created propertiesObject. From the docs I could not get an example as nested as this, so do you guys think this is doable?
Docs say .within() yields the same subject it was given from the previous command.
So it seems you have to move your cy.wrap(propertiesObject) out of the within callback and put it in the outer then
You can also sub in .find() which is syntactically equivalent to .within()
rowsSelector()
.eq(rowNumber)
.find(nestedSelector).each($prop => {
Object.assign(propertiesObject, { [$prop.attr('data-testid')]: $prop.text() })
})
.then(() => cy.wrap(propertiesObject))
You might also want to review this Cypress, get index of th element to use it later for it's td element for the section that finds the rowNumber.
I haven't tried it, but maybe
cy.contains(`[data-testid="${mainProperty}"]`, compareValue) // only 1st match returned
.invoke('index')
.then((rowNumber) => {
rowsSelector()
.eq(rowNumber)
.find(nestedSelector)
.each($prop => {
Object.assign(propertiesObject, { [$prop.attr('data-testid')]: $prop.text() })
})
.then(() => cy.wrap(propertiesObject))
})

Cypress how to get a text from a div and store in a variable for later

I am new to Cypress and some of the things that I expect to work have really weird issues.
For example, I am trying to get the value of a column in a table and use the value in a search input. I have done it like this:
it('should filter', () => {
let id = 0;
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
id = $div1.text();
expect(id).not.to.eq(0);
});
//expect(id).not.to.eq(0);
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
expect(id).to.eq($div1.text());
});
});
But when I run this, I get an error stating that [data-cy=data-table-row] has a length of 25 not 1.
It turns out that the id variable I am using is not accessible outside the should method. I assume that's because it is a promise.
If I try to do this:
it('should filter', () => {
let id = 0;
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
id = $div1.text();
expect(id).not.to.eq(0);
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').should(($div1) => {
expect(id).to.eq($div1.text());
});
});
});
The test goes mental and tries to get the [data-cy=table-filters-search] over and over and over again.
I am not sure why.
Is there an easier way to grab the innerText of a div and store it to compare later?
As someone gave a response, I tried this:
it('should filter', () => {
let variables = {};
cy.get('[data-cy=data-table-row]').should('have.length', 25);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').then(($div1) => {
variables.id = $div1.text();
expect(variables.id).not.to.be.undefined;
});
console.log(variables);
expect(variables.id).not.to.be.undefined;
cy.get('[data-cy=table-filters-search]').find('input').type(variables.id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id').then(($div1) => {
expect(variables.id).to.eq($div1.text());
});
});
But the test fails on the second expect(variables.id).not.to.be.undefined;
Closure problem
The problem with the first example is that the test runs in two phases. The first phase sets up the commands in the queue, and the second runs them.
During the first phase, .type(id) "captures" the current value of id (which is "0") and in the second phase that's the value that gets used.
You can fix it in a couple of ways, with an alias or moving the type(id) inside the callback, as per your second example.
This gets around the closure problem by deferring cy.get(...).find('input').type(id) until the id has actually changed.
Retry problem
The problem with the second example is that should() with an expect in the callback will retry until it succeeds or times out.
Something in the callback is continuously failing (or an error is thrown) causing a continuous retry. It should time out, not sure why that doesn't happen.
You can separate the parts of the callback into two sections, and use a .then() which does not attempt to retry.
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should(($div1) => {
id = +$div1.text(); // Note $div1.text() is always a string
// so convert with "+" to compare numbers
expect(id).not.to.eq(0) // otherwise this always succeeds
})
.then(($div1) => { // use a then which does not retry
id = $div1.text();
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should(($div1) => {
expect(id).to.eq($div1.text())
});
})
Or
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.invoke('text') // find it's text
.should('not.eq', '0') // passes on the id
.then(id => {
cy.get('[data-cy=table-filters-search]').find('input').type(id);
cy.get('[data-cy=data-table-row]').should('have.length', 1);
cy.get('[data-cy=data-table-row]:nth-child(1) > .cdk-column-id')
.should($div1 => expect(id).to.eq($div1.text()) );
})
If you want to use it within a single case - Allias is the cypress way:
cy.wrap('value').as('variable') //setting the variable
cy.get('#variable') // with this you can call it at any time in the test after aliasing
.then(variable =>{
//here you can use it
})
For a variable to use on multiple cases I recommend using an object as a Reference
let variables = {} //This would need to be declared on the scope you wish to use it in
cy.get('element')
.then($el => {
variables.elText = $el.text() //Assigning the value
})
cy.log(variables.elText) //You can call it later like this

using arrow function into the return block of page.evaluate of puppeter script

It probably is a matter of Promise:
Look at the since field of my return block,
using an arrow function it doesn't return any result:
{
link: 'www.xxxxxx.com/1234',
name: 'jhon doe',
since: {}
},
instead to return directly the value, it works as expected!
Since i need to perform complex operations with selectors, I'd like to use an inline arrow function in that point, how can I fix to get the result out?
let rawMembers = await page.evaluate(() => new Promise((resolve) => {
....
//captute all the link
const anchors = Array.from(document.querySelectorAll('a'));
let result = anchors.map(x => {
return {
link: x.getAttribute("href"),
name: x.innerText,
//since : x.parentNode.parentNode.parentNode.parentNode.getAttribute("class") <--- this works
since: x => { <---using an arrow function, it returns and empty objecy `{}`
// i need a function here to do complex and multiline operations
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
}
....
resolve(results);
i've tried this as well but with the same result
since: x => new Promise((resolve) => {
// i need a function here to do complex and multiline operations
resolve(x.parentNode.parentNode.parentNode.parentNode.getAttribute("class"));
})
In since you have a reference to the arrow function itself, not to its result. Functions are not serialaizable, so you get an empty object. You need to call the function, i.e. to use IIFE:
since: (() => {
return x.parentNode.parentNode.parentNode.parentNode.getAttribute("class");
})()

Basics of react-table: where do I put useTable when pulling data from an API?

I am trying to set up react-table but am running into the issue of not being able to call hooks inside regular functions, specifically the useTable hook. Say I have something like this:
const Search = () => {
const [tableRows, changeTableRows] = useState([])
const columns = [
{ Header: 'One', accessor: 'header1'},
{ Header: 'Two', accessor: 'header2'}
]
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ columns, tableRows })
const apiSearch = (field, query) => {
axios.get(`ip/querypath?query=${query}&field=${field}`)
.then(({data}) => {
changeTableRows(data)
})
.catch(err => console.log(err))
return (
/* Render forms to input data and call apiSearch, and render actual table */
)
}
The line with useTable is only being called once, with no initial row data. I need it to update when I make the API call but I can't put a hook inside my apiSearch function. Even if I could, I don't know the right way to do it. This throws a syntax error:
let getTableProps, getTableBodyProps, headerGroups, rows, prepareRow
const apiSearch = (field, query) => {
...
{ getTableProps, getTablyBodyProps, headerGroups, rows, prepareRow } = useTable({ columns, tableRows })
}
I know I'm missing something basic and would appreciate any help.

Resources