Click on an element which has a specific attribute value in Cypress - xpath

I am writing this code for an element that has an attribute value "123". Since there are many products on that page with the wishlist button. But I want to click on the wishlist button for this specific product id(123)
but I recieve an error saying
cy.click() can only be called on a single element.
Your subject contained 53 elements.
Pass { multiple: true } if you want to serially click each element.
Can someone help here?
cy.xpath('//div[#id="filterProducts"]//div[#data-wish-list-entry-number]').then(thisProduct => {
if (
(cy.wrap(thisProduct)
.invoke('attr', 'data-wish-list-entry-number')
.should('eq', '123') ))
{
cy.wrap(thisProduct)
.click()
}

You don't need the if(), just add the attribute value 123 in the selector.
Same way as you have in [#id="filterProducts"]
with XPath
cy.xpath('//div[#id="filterProducts"]//div[#data-wish-list-entry-number="123"]')
.click()
with Cypress get
cy.get('div[id="filterProducts"] div[data-wish-list-entry-number="123"]')
.click()
also
cy.get('div#filterProducts div[data-wish-list-entry-number="123"]')
.click()

If it is the first of the list you can use first:
cy.get("value").first().click();
If is not the first but you know the element number use eq:
cy.get("value").eq(40).click();

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: select value which exists within the tag

Below is my code and I am trying to select a value(velocity_and_express) which is given within the data-bip-collection attribute. Can anyone please help me on how to select the value from the lists given in data-bip-collection ?
<span
class="best_in_place highlight_on_success funding_type"
data-bip-attr="funding_type"
data-bip-collect="[["velocity_and_express","velocity_and_express"],["velocity","velocity"],["express","express"],["not_yet_set","not_yet_set"]]"
data-bip-confirm="Are you sure you want to change funding type?"
data-bip-object="company"
data-bip-original-content="not_yet_set"
data-bip-type="select"
data-bip-url="/admin/companies/13"
data-bip-value="not_yet_set"
id="best_in_place_company_13_funding_type"
data-ol-has-click-handler=""
>velocity</span>
I tried selecting the option using cy.get() but it did not work
cy.get('[data-bip-attr="funding_type"]')
.click()
.get('[data-bip-value="velocity_and_express"]')
.click()
cy.on('window:confirm', () => true)
You can use the jquery contains selector (docs)
Example:
cy.get(`[data-bip-collect*="velocity_and_express"]`)

How to select elements with same ID but in different shadow DOMs

So I'm working at a co. as a summer intern and have been tasked with writing tests for their application in cypress.
The application extensively uses shadow DOMs and nested shadow DOMs even. I used the includeShadowDom property true to traverse more easily. But I am facing an issue.
I need to type in 2 input boxes having the same ID and same class but they are in separate shadows. Is there a way I can distinguish between them i.e First occurrence of element with id= and nth occurrence of element with id=?
I can't share any code because it goes against company policy
Assuming you have added includeShadowDom: true in your cypress config file then you can use the eq method to get the respective elements.
E.g. eq(0) for first occurrence of the element, eq(1) for the second and so on.
So your code should look like this:
cy.get('input').eq(0).type('some text')
First possible solution is to select every input with class = something and loop over each.
let words = ['First input', 'second input']
cy.get(`input[class="something"]) // this returns x number of Inputs
.each( ($el, index) => {
cy.get($el)
.type(words[index])
})
Second possible solution is to target the parent element incasing the single input.
cy.get('table') //I don't know what is incasing your inputs but lets assume its a table
.within( () => {
cy.get(`input[class="something"`] //trying to get this to return 1 element
.type('words')
})
Without seeing your HTML markup I can't offer up a more exact solutions. Hope this helps. Look up parent() and parentsUntil() cypress commands if you try the second option.
With inputs there's usually some text that allows the user to distinguish them.
Try targeting the input label or placeholder text, finding the input with "Traversal" commands.
<div>
<label>User name</label>
<input type="text" placeholder="Enter user name" />
<div>
Some basic approaches:
cy.contains('label', 'User name')
.next() // move to next element in the DOM
.type('something')
cy.contains('div', 'User name') // go to common parent of label and input
.find('input') // gives just the input inside parent
.type('something')
cy.get('input[placeholder="Enter user name"]') // use unique placeholder attribute
.type('something')
With shadow DOM be aware you can configure it in the test header
it('searches inside all shadow dom roots', {includeShadowDom: true}, () => {
...
})

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