Cypress: Finding the content of a specific td element - cypress

I need to find the content of a specific td element based on the content of the next td element in the row.
The markup (simplified):
<table id="oversiktOverSoknaderTable">
<tr data-e2e-selector="soknad-linje">
<td data-e2e-selector="status">Trukket</td>
<td data-e2e-selector="soknadProsjektNavn">Endret søknad</td>
<td>Lån</td>
<td data-e2e-selector="soknadId" id="2"></td>
</tr>
<tr data-e2e-selector="soknad-linje">
<td> ...
There can be multiple rows, all with the same selector (data-e2e-selector="soknad-linje">. The issue at hand is to verify that the correct status is showing for the correct name. (Sorry for not translating the selector names, but I think they're quite self-explanatory.)
What I want to do, is to use .should('contain'.'value') to verify that the text in the data-e2e-selector="status" quals "Trukket" for the line where the data-e2e-selector="soknadProsjektNavn" is "Endret søknad". (As it is in the above example.)
Ideas?

Something like this, you can do
cy.contains('td', 'Endret søknad')
.sibling('[data-e2e-selector="status"]')
.should('contain', 'Trukket')
where sibling() takes a selector to specify which one.

There is also .prev() command.
Like #jjhelguero I would also recommend being specific with data-e2e-selector in the selector.
cy.contains('td[data-e2e-selector="soknadProsjektNavn"]', 'Endret søknad')
.prev('td[data-e2e-selector="status"]')
.should('have.text', 'Trukket')

Assuming Endret søknad is a unique value in your table.
Since you have data-e2e-selector in your elements you can use the follow
cy.get('#oversiktOverSoknaderTable') // hopefully this is unique enough
.should('be.visible') // assertion to avoid element detached from DOM
cy.contains('[data-e2e-selector="soknad-linje"]', 'Endret søknad') // gets row with unique string
.should('be.visible')
.find('[data-e2e-selector="status"]') // searches in row for element
.should('have.text', 'Trukket') // has status text

If you're getting detached from DOM error, try keeping the commands preceding .should() (which triggers retry) as short as possible.
cy.contains('[data-e2e-selector="soknad-linje"]', 'Endret søknad') // row with project
.should('contain', 'Trukket') // confirm the status
or
cy.get('[data-e2e-selector="soknad-linje"]')
.should('contain', 'Endret søknad')
.should('contain', 'Trukket')

You can do something like this:
cy.contains('td', 'Endret søknad')
.parent('tr')
.within(() => {
cy.get('[data-e2e-selector="status"]').should('have.text', 'Trukket')
})

Related

Idiomatic Cypress way to assert that two elements that contains some text exist in the DOM

Using Cypress, what is the idiomatic way to assert that two elements exist in the DOM for a given selector (that also contain some text)?
Here's how I would do that in JavaScript:
Array.from(document.querySelectorAll("selector")).filter(node => node.textContent.includes("text")).length === 2
Is there an idiomatic way to do this in Cypress?
I've tried:
cy.get('selector')
.contains('text')
.should('have.length', 2);
but I'm getting the following error:
cy.contains() cannot be passed a length option because it will only ever return 1 element.
You can use filter() in combination with contains() like this. (Cypress Docs)
cy.get('selector')
.filter(':contains("text")')
.should('have.length', 2);
Alternatively, you can use the below approach without the 'filter' option.
Example:
<table>
<tbody>
<tr>
<td>Same</td>
<td>Same</td>
<td>Different</td>
<td>Same</td>
</tr>
</tbody>
</table>
// selects all table cells with text "Same"
cy.get('td:contains("Same")').should('have.length', 3)
// if the text does not have white spaces, no need to quote it
cy.get('td:contains(Same)').should('have.length', 3)
// you can find elements NOT having the given text
cy.get('td:not(:contains(Same))')
.should('have.length', 1)
.and('have.text', 'Different')
Read more at here

Cypress - click hyperlink on row based on value of two cells

Trying to get a Cypress script to click a hyperlink based on two values - the text of the hyperlink in column 1 and the value of the cell in the second column:
<tbody>
<tr>
<td>Anything</td>
<td>Casualty</td>
</tr>
<tr>
<td>Declined Prospect</td>
<td>Casualty</td>
</tr>
<tr>
<td>Declined Prospect</td>
<td>Package</td>
</tr>
<tr>
<td>Declined Prospect</td>
<td>Casualty</td>
</tr>
<tr>
<td>Irrelevant</td>
<td>Package</td>
</tr>
</tbody>
cy.get('a').contains('Declined Prospect').click()
fails because there's more than one hyperlink with that value. The id is not useful because it's dynamic.
In the example above, I want to click Declined Prospect when the second column is Casualty (but the order of the rows may vary and values in the first and second column are repeated - but only once for the combination).
Any thoughts?
The trick is to target <td>Casualty</td> then click the preceding <td><a>.
There are quite a few ways to get to sibling elements, the simplest in this case is prev().
cy.contains('td', 'Casualty') // target the 'marker' element
.prev() // move to the previous sibling
.click()
Approach from row and move inwards
To target a row with a specific combination of text in some of it's cells, just concatenate the text and use contains().
cy.contains('tr', 'Declined Prospect Casualty') // target unique text within children
This even works when there are other cells with text that's not relevent to the search, e.g
<tr>
<td>Declined Prospect</td>
<td>Casualty</td>
<td>Irrelevent text here</td>
</tr>
Then you can walk down the HTML tree,
cy.contains('tr', 'Declined Prospect Casualty') // target unique text within children
.find('td a') // descendant of previous subject
.click()
I think this can be useful: https://github.com/cypress-io/cypress-xpath
You can create selectors in xpath instead of css and in xpath you can search tree by text. e.g:
cy.xpath("//text() = 'Declined Prospect'")
==================================Edited====================
You can merge couples of xpatch selectors: it will looks like this //tr[td='Casualty']/td/a
Playgroud

Find element which has no style

In a table, there are rows like this:
<tr id="filtersJob_intrinsicTable_row6" class="evenRow" style="display: none;">some stuff here<tr>
<tr id="filtersJob_intrinsicTable_row7" class="evenRow">some stuff here<tr>
How do i use watir to get the rows which are to be displayed, i.e the rows which do NOT have style="display: none; ?
You have a number of ways of collecting elements without the style attribute:
Using a :css locator:
browser.trs(css: 'tr:not([style])')
Using a :xpath locator:
browser.trs(xpath: '//tr[not(#style)]')
You could also check the attribute value:
browser.trs.select { |tr| tr.attribute_value('style').nil? }
Note that you should be cautious about using the style attribute as an indicator of the row being displayed. Someone could add some other unrelated style property and then all of the tests will fail. Instead, I would suggest that you look for rows that are present:
browser.trs.select(&:present?)
I think that this also makes the purpose of the code more obvious and readable.
Using xpath:
browser.element_by_xpath(".//tr[not(#style)]")
[not(#style)] meaning having no style attribute.

XPath get only first Parent of nested HTML

I am newbie in XPath. Can someone explain how to resolve this problem:
<table>
<tr>
<td>
<table>
<tr>
<td>
<table>
<tr>
<td>Label</td>
<td>value</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
I try to get <tr> which contains Label value, but it does not work for me,
Here is my code :
//td[contains(.,'Label')]/ancestor::tr[1]
Desired result:
<tr>
<td>Label</td>
<td>value</td>
</tr>
Can someone help me ?
This expression matches the tr that you want:
//tr[contains(td/text(), 'Label')]
Like yours, this starts by scanning all tr elements in the document, but this version uses just a single predicate. The td/text() limits the test to actual text nodes which are grandchildren of the row. If you just used td, then all of the td's descendant text nodes would be collected and concatenated, and the outer tr would match.
UPDATE: Also, for what it's worth, the reason your expression isn't working is that the ancestor axis returns elements in document order, not "outward" from the point of the context node. This is something I've run into myself, as it is somewhat unintuitive. To make your approach work, you would need to say
//td[contains(.,'Label')]/ancestor::tr[last()]
instead of
//td[contains(.,'Label')]/ancestor::tr[1]
I had the same issue, except that the text 'Label' was sometimes in a nested span, or even further nested in the td. For example:
<td><span>Label</span></td>
The previous answer only finds 'Label' if it is in a text element that is a direct child of the td. This issue is a bit harder because we need to search for a td that contains the text 'Label' in any of its children. Since the tds are nested, all tds qualify as having a descendant that contains the text 'Label'. So, the only way I found to overcome this is to add a check that makes sure that the td we select does not contain a td with the search text.
//td[contains(., 'Label') and not(.//td[contains(., 'Label')])]/ancestor::tr[1]
This says give me all of the tds that have a decedent text containing 'Label', but exclude all tds that contain a td that has a decedent text containing 'Label' (nesting ancestors). This returns the child most td that contains the text. Then you can go back to the tr that contains this td using ancestor.
Also, if you just want the lowest table that contains text use this:
//table[contains(., 'Label') and not(.//table[contains(., 'Label')])]
or you can select the tr directly:
//tr[contains(., 'Label') and not(.//tr[contains(., 'Label')])]
This seems like a common problem, but I didn't see a solution anywhere. So, I decided to post to this old unanswered question in hopes that it helps somebody.

Complicated xpath for a rookie

I have this piece of html:
<tr>
<td class="has-checkbox">
<input id="abc" class=... value=...>
</td>
<td class="has-label">
<label for="abc">Name1234</label>
</td>
<tr>
I need to make an xpath that gets me the input element, based on whats in the label, in this case Name1234.
In other words, for this case, I need an xpath to the input element, and the path must contain Name1234, as its variable.
Anyone who can help me out here?
//input[#id = //label[. = 'Name1234']/#for] selects input element(s) with an id attribute value equal to the for attribute value of label elements where the contents is Name1234.
You can use /.. , this syntax use to move back to parent node. In your case:
//label[.='Name1234']/../../td/input
You must move back 2 times because input tag is the child of another td tag.
Here are others introduction and example about you should read.
Here is a solution using the Axes parent and preceding-sibling:
//label[.='Name1234']/parent::td/preceding-sibling::td/input
It's not so complicated as you think:
xpath=//tr[//label[.="Name1234"]]//input
in other words, you are looking for the 'tr' which contains 'label' with text "Name1234". If the condition is true, you are getting the 'input' element

Resources