Cypress: Traverse document & perform select action in child elements - cypress

Goal: In Cypress, traverse tree of <div>, find child <div>s with <select>, and command .select
Simplified, my page document is arranged as follows:
<div.Root>
...
<div.Actions>
<div.Action>
...
<div.Box>
...
<select.Do>
<div.Action>
...
<div.Box>
...
<select.Do>
With Cypress, I get stuck traversing children and performing action <select> of each child:
div.Root > div.Actions > div.Action > div.Box > select.Do
> div.Action > div.Box > select.Do
Example Cypress.io code, I get as far as finding the child HTMLelement(s) for <div.Action>, but get stuck there not discovering how to traverse further and perform command .select:
cy.get('div.Root').then($divRoot => {
expect($divRoot.find('div.Actions').length).eq(1);
cy.get('div.Actions').then( $divActions => {
expect($divActions.find('div.Action').length).gte(1);
// Here is where I get lost at to what to do
// Within each <div.Action>, I want to perform select
$divActions.find('div.Action').each(($index, $divAction) => {
// $divAction is not a Cypress element
// that can perform commands like get, find, select
})
Much appreciate assistance

https://docs.cypress.io/api/commands/each.html#DOM-Elements.
According to the Cypress documentation in the each callback the element is passed first and then the index. In your example you would be trying to perform cypress actions on the index.
Secondly, you have to wrap the dom element that is passed in with cy.wrap to be able to perform cypress actions on that element.
Here's how I was able to do it:
cy.get("div.actions")
.find("select")
.each(($el) => {
cy.wrap($el).select();
});

Related

cypress - deleting a sub-row while trapped inside .within and .then

I have a EditParentAndChildren screen where I want a test that:
navigates to page
remembers the name of the parent
pick one of the children rows
remember its id/name
delete it via the Trashcan button on that row
save
navigate to a View
ensure the parent's name appears and the deleted child's name does not
I can't seem to pluck text off of the screen and put it into one of Cypress's #alias variables, and standard js variables aren't allowed by cypress. So, I use .then to get the value that way.
But when I choose a child row and go .within to get its name and click its delete button, I can't then issue the final assertions for the test because I'm still in the .within, I can't escape the .within because the .then for getting the child's name is completely inside, and, trying to .root().closest() doesn't work because the <tr> I'm in is not only getting deleted but I'm doing a page nav afterward.
cy.get('[name=parentname]')
.invoke('val')
.then(parentName => {
cy.get('[class^=childrenTable]')
.find('[name=child_id]')
.first()
.parents('tr')
.within(tr => {
cy.get('[name=child_id]')
.invoke('val')
.then(nameOfchildToDelete => {
// delete this child
cy.get('[class*=trash]').click();
cy.get(loadingSpinner).should('not.exist');
// ERROR can't find submit button, you are still .within the <tr>
cy.contains(/Submit/i).click();
cy.url().should('match', /parent\/\d+$/);
cy.get(loadingSpinner).should('not.exist');
cy.contains('[class*=breadcrumb_currentcrumb]', parentName).should('exist');
cy.contains('table', nameOfChildToDelete).should('not.exist');
});
});
});
One solution is simply never to use .within. Formerly I was selecting the row, then within it selecting & using each piece of the row. Instead, select each piece of a row using the same selector that selects that row.
Not this:
cy.get('[class^=childrenTable]')
.find('[name=child_id]')
.first()
.parents('tr')
.within(tr => {
cy.get('[name=child_id]')
.invoke('val')
.then(nameOfchildToDelete => {
More like this:
cy.get('[class^=childrenTable] [name=sample_id]:first-child')
.invoke('val')
.then(nameOfSampleToDelete => {
// etc...
cy.get('[class^=childrenTable] [class*=trash]').first().click();
Code inside a .then is just like outside the .then excepting the level of indent, so most of the code is the same. But code inside a .within is kind of at a dead-end. You can't return values from a .within and can't set state or js vars from the outer context.
So: don't use .within, always use long selectors, and don't worry about picking "sections" like a particular <tr> or a particular card in a FlexBox for re-use.
If the selectors are very long consider moving them to a const string outside of the file and possibly concatting them if need be. But generally in Cypress trying to enter into a context is something of an anti-pattern.

Cypress assert label and checkbox child elements

Issue I'm having is I have a list of items (all with the same selector)and I want to loop through these items and assert a text label on one child and a checkbox on another child of the same parent element match what I expect. I can easily solve this solution in Selenium, but struggling to find the answer in Cypress.
I found the answer to this myself, essentially using filters, which I hadn't used before, to find the parent with the child text I wanted, before checking the checkbox of another child. Ignore the If as it's not necessary for the solution
if (parentSelector) {
cy.get(parentSelector).filter((_, element) => {
return element.querySelector('h2').textContent === labelValue
}).then( element => {
cy.wrap(element).find(selector).should('not.be.checked')
})

How to test the text of the second element in cypress?

I can test the first element of the Dom element but I don't know how to get the second element?
it('should display highlight', () => {
const highlights = cy.get(`.${pageClass} .page_highlight`);
highlights.should('have.length', 2);
highlights.first().should('contain.text', translations.highlight);
});
There are two options for your case.
Since the total number of elements is just two in your case, you can use something called last. You can read more here.
If the number of elements is dynamic, you can use something called eq and pass the order of element as an index. You can read more here.

Conditional Tests in Cypress using data-cy attributes as selectors

I see some posts about this exact topic, but none of them using data classes like I am as selectors, so it makes this conditional test a bit harder to write.
The idea is that I have a table with pagination on it. My idea is to check if the [data-cy-pagination-next] has or doesn't have the disabled attribute on it, which would mean there's more than one page and therefore the test can continue.
Most posts I see use a syntax like this:
cy.get('my-button')
.then($button => {
if ($button.is(':enabled')) {
cy.wrap($button).click()
}
})
But I don't have the $button like they described. What I would be clicking on is a button, but does that really matter?
It doesn't seem like I can write
cy.get('[data-cy=pagination-next]')
.then('[data-cy=pagination-next]' => {
if ('[data-cy=pagination-next]'.is(':enabled')) {
cy.wrap('[data-cy=pagination-next]').click()
}
})
How can I get this conditional to work?
If there is more than one page, this test works great, but in the cases that there is no second page, I just want the test to end there.
Any tips would be greatly appreciated!
Cheers!
Here is the test currently
it('Data Source has Pagination and test functionality', () => {
cy.get('[data-cy=pagination]').should('exist')
// assert that we are at the first page and the start and back button is disabled
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
// If there are multiple pages then do the following tests
// click next button and assert that the current page is page 2
cy.get('[data-cy=pagination-next]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 2 of')
// click end button and assert that the end and next buttons are disabled
cy.get('[data-cy=pagination-end]').click()
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-end]').should('be.disabled')
// click start button button and assert that the current page is page 1 and next and start buttons are disabled
cy.get('[data-cy=pagination-start]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
})
You can use the page indicator to split the test logic
it('Data Source has Pagination and test functionality', () => {
cy.get('[data-cy=pagination]').should('exist')
cy.get('[data-cy=pagination-page-list]')
.then($pageList => {
if ($pageList.text() === 'Page 1 of 1')
// single page assertions
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-end]').should('be.disabled')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
} else {
// multi page assertions
cy.get('[data-cy=pagination-next]').click()
cy.get('[data-cy=pagination-page-list]')
.should('contain', 'Page 2 of') // assert on second page
cy.get('[data-cy=pagination-end]').click()
cy.get('[data-cy=pagination-next]').should('be.disabled')
cy.get('[data-cy=pagination-start]').click()
cy.get('[data-cy=pagination-page-list]').contains('Page 1 of')
cy.get('[data-cy=pagination-start]').should('be.disabled')
cy.get('[data-cy=pagination-back]').should('be.disabled')
}
})
Better still, control the test data so that only a single page exists, then run two tests under known conditions and eliminate flaky conditional testing.
I think you're misunderstanding how yielding and callbacks work. The reason there is $button in the .then() is because it is yielded by cy.get(). It could be named anything, so long as it is a valid name (note: a string literal, like you are trying to do, is not valid).
So, $button is just the yielded element from your cy.get('my-button'). Which is why we can then use JQuery functions and Chai assertions on it.
cy.get('[data-cy=pagination-next]')
.then($el => { // naming the yielded object from `cy.get()` to $el
if ($el.is(':enabled')) { // using JQuery function `.is` to check if the element is enabled
cy.wrap($el).click() // Cypress requires the JQuery element to be wrapped before it can click it.
}
})
You can do something like this. You can use an each to loop over all the pagination elements. So in case, you don't have only 2 buttons the loop will check for only 2 buttons and then terminate.
cy.get('[data-cy=pagination-page-list]').should('contain.text', 'Page 1 of')
cy.get('[data-cy=pagination-next]').each(($ele, index) => {
if ($ele.is(':enabled')) {
cy.wrap($ele).click()
cy.wrap($ele).should('contain.text', `Page ${index + 2} of`) //index starts from 0
}
})

Cypress:Finding parent element

I got a tree of elements and each element got a toggle icon to expand it -My intention is to click on the toggle icon corresponding to the element have a text for ex "TIME PERIODS"
Currently i write my code like below , Is there a better way to do this?
Please see the screenshot for my element structure.
cy.get('.tree-node',{ timeout: 60000 }).contains('TIME PERIODS',{force: true}).parent().parent().find('.tree-node-collapsed').click()
each() method is available in Cypress.io. Using which we can travell through tree of elements and can filter using text. Please follow below code approach:
Code
cy
.get('.tree-node')
.each(($el, index, $list) => {
// $el is a wrapped jQuery element
$el.get('.tree-item').contains('TIME PERIODS').siblings('.tree-node-
collapsed').click();
});
I have fixed issues -working code given below
cy.get('.tree-node').each(($el, index, $list) => {
// $el is a wrapped jQuery element
cy.wrap($el).get('.tree-item').contains('TIME PERIODS').parent().siblings('.tree-node-collapsed').click();
We can do like shown below also with out using .each
cy.get('.tree-node').get('.tree-item').contains('Header').parent().siblings('.tree-node-collapsed').click();

Resources