Cypress: Dynamic Tests: Validate rows in table - cypress

The goal is to iterate over a table and create a test dynamically for each row to validate its status. Table example:
Aware of Cypress Examples: Dynamic tests, however, provided examples addresses iterating over a static list of base types (string, number) and not gathered children of JQuery<HTMLElement>.
Rows are grouped by the first column labeled with GUID labels. The code below collects rows label contains a specific GUID. The added pseudo injection of test it() does not work nor do I expect it would, but this is what I am trying to accomplish testing each row's status with specific GUID:
it('TEST Iterate rows matching GUID', () => {
cy.fixture('esign').then($esignStore => {
expect($esignStore).to.have.property('requestUUID').to.be.a('string').not.empty;
cy.get('table[data-qa="act_ops_log_table"]').within(() => {
cy.log('Ops Logs table found');
cy.get('tbody[data-qa="act_ops_log_table_body"]').then(() => {
cy.get('tr');
cy.get('td');
cy.get('td:nth-child(1)').each(($tableCell, $index, $list) => {
const textEnvelope = $tableCell.text();
if (textEnvelope.includes($esignStore.requestUUID)) {
// pseudo-example of dynamically added test: start
// it(`TEST ${$index}`, () => {
cy.get('td:nth-child(3)')
.eq($index)
.then($field => {
const textStatus = $field.text();
expect(textStatus, 'succeeded');
});
// });
// pseudo-example of dynamically added test: end
}
});
});
});
});
});
Approaches appreciated. Thank you

You cannot create tests dynamically.
Cypress parses the spec file to find out what tests are present before the Cypress commands even start to run.
The best you can do is add extra logging to mark each tested row.
cy.get('tbody tr', {log:false}).each(($tr, index) => {
// Log beginning of row test
cy.then(() => Cypress.log({
displayName: 'Row test',
message: `TEST ${index}`,
}))
cy.wrap($tr, {log:false}).find('td:nth-child(1)', {log:false}).then($firstCol => {
const textEnvelope = $firstCol.text();
if (textEnvelope.includes(esignStore.requestUUID)) {
cy.wrap($firstCol, {log:false}).siblings({log:false}).eq(1, {log:false})
.should('have.text', 'succeed')
} else {
cy.log(`Row ${index} - not required requestUUID`)
}
})
})
Or pre-filtering the required rows so that only those matching requestUUID are tested
cy.get('tbody tr', {log:false}).each(($tr, index) => {
.filter(`:has(td:nth-child(1):contains(${esignStore.requestUUID}))`, {log:false})
.each(($tr, index) => {
// Log beginning of row test
cy.then(() => Cypress.log({
displayName: 'Row test',
message: `TEST ${index}`,
}))
cy.wrap($tr, {log:false})
.find('td:nth-child(3)', {log:false})
.should('have.text', 'succeed')
})

Related

Cypress test: Writing multiple elements to database failed

Trying to write multiple elements into database failed, only the last one is written:
// my_test.cy.js
import CreateProductPage from "../pages/CreateProductPage";
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
})
})
// commands.js
Cypress.Commands.add('createProduct', (type, name, grossPrice) => {
cy.visit('/#/sw/product/create')
CreateProductPage.elements.productDetailTabs().should('not.exist').then(() => {
if(type === 'combinable') {
CreateProductPage.elements.radioBtnCombinableProduct().click()
}
CreateProductPage.elements.inputProductName().clear().type(name)
CreateProductPage.elements.inputPriceFieldGross().type(grossPrice)
SwPageHeader.elements.btnProductSave().click()
})
})
Questions:
This failed because of asynchronous nature of cypress?
If so, how to interrupt? Chaining with then(), the behavior is the same
With this is code (adding wait()) it works, but i'm looking for the right way
// my_test.cy.js
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
cy.wait(300)
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
})
})
EDIT 1
// pages/CreateProductPage.js
class CreateProductPage {
elements = {
productDetailTabs: () => cy.get('div.sw-product-detail-page__tabs'),
radioBtnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-0'),
radioBtnUnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-1'),
inputProductName: () => cy.get('input#sw-field--product-name'),
inputPriceFieldGross: () => cy.get('div.sw-list-price-field__price input#sw-price-field-gross'),
}
}
module.exports = new CreateProductPage();
If the problem is one of waiting, you will need to figure out something that indicates to the user that the save was successful and test that.
For example, if there was a toast message on screen:
...
SwPageHeader.elements.btnProductSave().click()
cy.contains('span', 'Product was saved').should('be.visible')
Blockquote
This failed because of asynchronous nature of cypress?
Code behaves synchronously if you have only cy commands inside the code block.
As suggested in https://stackoverflow.com/a/74721728/9088832 you should wait for some element to appear or for an API request to be completed that is responsible for the product creation.

Unable to read data from fixture file after writing it

I'm writing a URL with an ID in a .json file that I'm extracting from the API, but the problem is that the cy.log() is printing the data before it is wrote in the file. Because the AppelData.json has the written data, but cy.log() prints nothing.
After the second run, the cy.log() prints the previous data from AppelData.json.
So how do I make, that cy.log() to print the data from AppelData.json only after it is been written?
describe('Creer un appel modal', () => {
beforeEach(() => {
cy.fixture('AppelData').then(function(data) {
this.AppelData = data
})
cy.visit('/')
cy.loginAsAdmin()
})
it('Create an intervention request using existing site', function() {
navigateTo.plusAppelButton()
onAppelModal.SelectSite(this.AppelData.Site)
onAppelModal.SelectMetier(this.AppelData.Metier)
onAppelModal.FillMotif(this.AppelData.Motif)
cy.intercept('POST','/documents/datatable/intervention_request/**').as('response')
cy.contains('Valider').click()
cy.wait('#response').get('#response').then(xhr => {
console.log(xhr)
cy.readFile('cypress/fixtures/AppelData.json').then(AppelData => {
AppelData.AppelID = xhr.request.url.replace(/\D/g,'').replace(/3/, '')
cy.writeFile('cypress/fixtures/AppelData.json', AppelData)
cy.log(this.AppelData.AppelID) // logs no value on the first run, and prints old value from the 2nd run
})
})
})
})
Thank you!!
Assuming that that the value is updated into the fixtures file, you can do this:
it('Create an intervention request using existing site', function () {
navigateTo.plusAppelButton()
onAppelModal.SelectSite(this.AppelData.Site)
onAppelModal.SelectMetier(this.AppelData.Metier)
onAppelModal.FillMotif(this.AppelData.Motif)
cy.intercept('POST', '/documents/datatable/intervention_request/**').as(
'response'
)
cy.contains('Valider').click()
cy.wait('#response')
.get('#response')
.then((xhr) => {
console.log(xhr)
cy.readFile('cypress/fixtures/AppelData.json').then((AppelData) => {
AppelData.AppelID = xhr.request.url.replace(/\D/g, '').replace(/3/, '')
cy.writeFile('cypress/fixtures/AppelData.json', AppelData)
})
})
cy.readFile('cypress/fixtures/AppelData.json').then((AppelData) => {
cy.log(AppelData.AppelID)
})
})

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

How to visit pre-defined URLs from file

If I have 30 pages to check, for example, EN has a disclaimer, but other 29 language don't, what would be the best way to do this? For example, right now I have it like this:
const urls = ['http://google.com/en',
'http://google.com/bg'
]
describe('Disclaimer check', () => {
urls.forEach((url) => {
it(`Checks disclaimer text ${url}`, () => {
cy.visit(url)
cy.get('.Disclaimer').should('be.visible')
.and('contain', 'This is disclaimer.')
})
})
})
For 2 sites it's fine to define them in the same code but other file that checks that Disclaimer isn't there would be 29 different URL-s. What would be the best practice here? One idea is to separate all the test but that would mean 30 tests for each feature which doesn't sound too great.
As url I'm working with uses many different values in it, making it short with baseurl doesn't seem to fit also.
Thank you in advance!
You were on the right path. This will be a good case for using cypress-each. Cypress-each will run all tests regardless if one or more fail. Depending on how long it takes, you may want to break down the it.each test into another file.
import 'cypress-each' // can included in /support/index.js
describe('Disclaimer check', () => {
// baseUrl: http://google.com
const noDisclaimerUrl = [
'/bg',
// all other languages
]
it('/en does have disclaimer text', () => {
cy.visit('/en')
// test code
})
it.each((noDisclaimerUrl)
`%s does not have disclaimer text`
(url) => {
cy.visit(url)
// test code
})
})
Adding all of your data to a data object, import that data object, and then using Cypress Lodash to iterate a number of times should achieve your goal.
// data.js
// defining data
export const data =[{
"url": "www.google.com",
"hasDisclaimer": true
}, {
"url": "www.other-url.com",
"hasDisclaimer": false
}...
]
You can then wrap the returned array and use it in a Cypress chain.
import { data } from './path/to/data'
describe('Tests', () => {
Cypress._.times(data.length, (index) => {
const curr = data[index];
it(`Checks disclaimer text ${curr.url}`, () => {
cy.visit(curr.url).then(() => {
if (curr.hasDisclaimer) {
cy.get('.Disclaimer').should('be.visible')
.and('contain', 'This is disclaimer.');
} else {
// code for checking disclaimer does not exist
}
});
});
});
});
Under your Fixtures folder create a urls.json file like this:
[
"https://google.com/en",
"https://google.com/bg",
"https://url3.com",
"https://url4.com"
]
Now assuming that you know which URLs don't have the disclaimer, you can simply add them in the If condition and apply the not.exist assertion.
import urls from '../fixtures/urls.json'
describe('Disclaimer check', () => {
urls.forEach((url) => {
it(`Checks disclaimer text ${url}`, () => {
cy.visit(url)
if (url == 'https://google.com/en' || url == 'https://url3.com') {
//Check for URL's where disclaimer doesn't exist
cy.get('.Disclaimer').should('not.exist')
} else {
//Check for URL's where disclaimer exists
cy.get('.Disclaimer')
.should('be.visible')
.and('contain', 'This is disclaimer.')
}
})
})
})

Resources