I have a table I'm testing that the column headers are correct. The problem is .contains() only ever returns one element.
I can repeat the code for each instance, but it seems quite verbose. Feel I must be missing something in Cypress commands that can do this better.
cy.get('th').eq(0).contains('Batman')
cy.get('th').eq(1).contains('Robin')
cy.get('th').eq(1).contains('The Flash')
/// etc
<table>
<caption>Superheros and sidekicks</caption>
<colgroup>
<col>
<col span="2" class="batman">
<col span="2" class="flash">
</colgroup>
<tr>
<td> </td>
<th scope="col">Batman</th>
<th scope="col">Robin</th>
<th scope="col">The Flash</th>
<th scope="col">Kid Flash</th>
</tr>
<tr>
<th scope="row">Skill</th>
<td>Smarts</td>
<td>Dex, acrobat</td>
<td>Super speed</td>
<td>Super speed</td>
</tr>
</table>
You can tidy up the test by mapping elements to inner texts.
Note, specify the first row with tr:eq(0)
const expectedHeaders = ['Batman', 'Robin', 'The Flash', 'Kid Flash']
cy.get('tr:eq(0) th')
.then($th => [...$th].map(th => th.innerText)) // map to texts
.should('deep.eq', expectedHeaders)
One option would be to select the row above, then use .children() to get a list of all the headers.
You can then chain multiple assertions about that list
cy.get('tr').eq(0) // row containing the heasers
.children('th') // collection of header elements
.should('contain', 'Batman') // assert 1st text
.and('contain', 'Robin') // assert 2nd text
.and('contain', 'The Flash') // assert 3rd text
If you store the expected in an array, you could use .each() to iterate through each th.
const expected = ['Batman', 'Robin', 'Flash', 'Kid Flash']
cy.get('tr').eq(0)
.children('th')
.each(($th, index) => {
cy.wrap($th).should('have.text', expected[index]);
});
Related
I want to search Seller and have to click on select link for selected one. When I type seller name, it shows only record for selected seller.
I tried with following code, its not working. Can anyone please help
cy.get('input[name="search"]',{ timeout: 10000 }).type(this.data1.vehicle1_seller1)
//cy.wait(6000)
Cypress.config('defaultCommandTimeout', 10000);
cy.get('td[class="span-3"] div').each(($el, index, $list) => {
if ($el.text().includes('STB002')) {
// cy.contains("Select").eq(index).click()
cy.get('.span-1-5 > div > a > span').contains('select').eq(index).click({force:true})
}
}
this is the DOM structure >
<table>
<tbody>
<tr class="even">
<td class="span-3">
<div title="06V001">06V001</div> == $0
</td>
<td>
<div title="06 Vauxhall Ormskirk">06 Vauxhall Ormskirk</div>
</td>
<td class="span-1-5">
<div>
<a id="link57" href="./wicket/page?7-1.-seller-table-body-rows-10-cells-3-cell-link">
<span>select</span>
</a>
</div>
</td>
</tr>
<tr class="odd">
</tr>
<tr class="even">
</tr>
</tbody>
</table>
The HTML table is set out in rows and cells, exactly as you see it on the screen.
Your test is searching for the cell containing the text, but really you want to search for the row containing the text, then get the select button of that row.
The basic test would be
cy.contains('tr', 'STB002')
.within(() => {
// now inside the row
cy.contains('span', 'select').click()
})
The next problem is the car STB002 isn't on the first page, so you won't find it straight after loading.
Maybe use the search box to load that row (as you have in one screen-shot). I can't say what that code is, because the DOM picture doesn't include the search box.
I'm doing some cypress tests on a rails/react app and I need to check if the value inputted in the form on the last row is, lets say, "Another Random Text". In the provided html below, it's on the 2nd row but it could be in any other last row number.
---- CYPRESS ----
What didn't work
cy.get('.form-something').last().should('have.value', 'Another Random Text')
because it returns cy.should() failed because this element is detached from the DOM.
And by using eq() I couldn't address the last row, just the first or the 2nd last.
Can anyone shine a light?
Thank you in advance
---- HTML ------
<table class="table table-flat">
<thead>
<tr>
<th style="width: 50%;">State</th>
<th>Generic State</th>
<th style="min-width: 100px;"></th>
</tr>
</thead>
<tbody>
<tr class="index-0" data-qa="s-3313">
<td><input class="form-something" type="text" name="name" value="Random Text"></td>
<td data-qa="generic-state">Additional</td>
<td><button class="btn btn-danger btn-sm" data-qa="remove-state"><i class="fa fa-trash"></i></button></td>
</tr>
<tr class="index-1" data-qa="s-3314">
<td><input class="form-something" type="text" name="name" value="Another Random Text"></td>
<td data-qa="generic-state">Other</td>
<td><button class="btn btn-danger btn-sm" data-qa="remove-state"><i class="fa fa-trash"></i></button></td>
</tr>
<tr>
<td colspan="2"></td>
<td><button class="btn btn-success btn-sm" data-qa="add-new-state"><i class="fa fa-plus mr-2"></i>Add</button></td>
</tr>
</tbody>
</table>
If the table get's re-displayed after the text is entered (or some other action like clicking "Add"), elements you query before the action are discarded by the app and replaced by a new version.
Your test still has a reference to the original element, so it's not yet garbage-collected but it is detached from the DOM
So this fails,
cy.get('.form-something').last()
.type('Another Random Text') // action causes detached element
.should('have.value', 'Another Random Text')
but this maybe succeeds
cy.get('.form-something').last()
.type('Another Random Text')
cy.get('.form-something').last()
.should('have.value', 'Another Random Text')
but it's safer to include the full table selector
cy.get('table tbody tr td .form-something').last()
.should('have.value', 'Another Random Text') // requery all parts from table down
You can also use .contains() based on the "Other" text
cy.get('table').should('be.visible')
.contains('td', 'Other') // locate by text
.prev() // previous cell
.find('input.form-something') // get the input
.should('have.value', 'Another Random Text')
Using an alias may help your test.
cy.get('.form-something').last().as('lastInput')
cy.get('#lastInput').should('have.value', 'Another Random Text')
It looks a bit unnecessary, but alias has built-in protection against detached-from-dom errors.
Here is one of the Cypress tests that verifies the feature,
it('can find a custom alias again when detached from DOM', () => {
cy
.get('foobarbazquux:last').as('foo') // creating an alias
.then(() => {
// remove the existing foobarbazquux
cy.$$('foobarbazquux').remove() // detaching from DOM
// and cause it to be re-rendered
cy.$$('body').append(cy.$$('<foobarbazquux>asdf</foobarbazquux>'))
}).get('#foo').should('contain', 'asdf')
})
One safe bet when dealing with element is detached from the DOM errors is to leverage the cypress retry-ability in a proper way.
The trick is to make sure that selecting action is right next to the assertion. In your example that's not the case, because it's broken by the .last() call.
You can try something like this, which should do the trick:
cy.get('.form-something[value="Another Random Text"]').should('exist')
To explain the reasoning behind the answer above: frameworks like React usually load the empty table first, and then fill it up (rerender it). Cypress being too fast often grabs the empty table before its filled, which is afterward rerendered when table data is fetched, resulting in that detached error.
Edit
To solve a problem mentioned in the comment, when you need control of value property rather than value attribute, you can use this:
cy.get('.form-something').should(inputElements => {
expect(inputElements[inputElements.length - 1]).to.have.value('Some test');
});
This will make sure that the last .form-something element contains the right text, and it won't match even if that exact text is in any element other than the last. You can even add multiple assertions that would all together benefit from build-in retry-ability.
You can also use an each loop to find the element with the desired value and then apply the assertion like this:
cy.get('.form-something')
.should('be.visible')
.each(($ele) => {
if ($ele.val().trim() == 'Another Random Text') {
cy.wrap($ele).should('have.value', 'Another Random Text') //Apply assertion
return false //Exit loop once element is found
}
})
I am trying to access data contained in a table that is itself contained in a table with class ='L1'.
So basically my html structure is like this:
<table class="L1">
<table>
<tr></tr>
<tr>
<td></td>
<td>data</td>
</tr>
<tr>
<td></td>
<td>data</td>
</tr>
...ect...ect
</table>
</table>
I need to catch the data contained in a all <a> </a> that are in the second contained in <tr> </tr> but only starting with the second <tr> of the table.
So far I came up with that:
html_body = Nokogiri::HTML(body)
links = html_body.css('.L1').xpath("//table/tbody/tr/td[2]/a[1]")
But seems to me that this doesn't express the fact that I want to start only after the second <tr> (second <tr> included?
What would be the right code to do this ?
You can use position() to select the later elements that you want.
html_body = Nokogiri::HTML(body)
links = html_body.css('.L1').xpath("//table/tbody/tr[position()>1]/td[2]/a[1]")
As the comments on that SO answer say, remember XPath counts from 1, so >1 skips the first tr.
The following is the DOM details:
<div id: "abc_440"
<table class = "listtable" >
<tbody>
<tr>
<td id = "someid" >
<td class = 'someclass_item"> This is Text </td>
<td class = 'someclass_button">
< a > Add </a>
</td>
</tr>
</tbody>
</table>
</div>
I need to click on 'Add' for particular text at "This is Text". I have to use div with ID (abc_440)to locate the corresponding table as there are may divs with this same dom layout. but index at div ID (for example 440) keeps changing to random number. How do I handle it in general ?
Please help.
I think that what you want to do is very similar to the previous Watir question.
Given that the id is randomly generated, it does not sounds like a good way of finding the link. You will likely need to use the text of the same row.
Assuming that the "This is Text" is unique (as you said you want to find the Add link for it), you can find that td, go to the parent row and then get the link.
b.td(:text => 'This is Text').parent.link.click
If you need to ensure that the text is in the second column, then you can do:
b.trs.find{ |tr|
tr.td.exists? and tr.td(:index => 1).text == 'This is Text'
}.link.click
The tr.td.exists? is added in case some of your rows do not have any tds (example a header row), which would cause an exception when checking the second criteria.
Don't mix quotes in HTML tags. id: doesn't work. <td>s should rarely be empty. The Add button should be a button, not an <a>nchor element. Buttons only work in <form>s.
<form id="abc_440" action='someURI'> <!-- The handler for the button. -->
<table class="listtable">
<tbody>
<tr>
<td id = "someid">
<!-- What goes here? -->
</td>
<td class='someclass_item'>
This is Text
</td>
<td class='someclass_button'>
<button name='add'>Add</button>
</td>
</tr>
</tbody>
</table>
</form>
You should be able to find the button through its name attribute, add, and through the form's id attribute, abc_440. Who generates the ids?
Once you have the problem of finding the add button solved, and figuring out where the form's id comes from, please then stop using tables for formatting. There's no need for that. Learn about <form>s, <fieldset>s, <legend>s, and <label>s. I doubt you need the *some_id* part, the text part should probably be a <label>, and you can use CSS to format your <label>s as:
label {
width: 150px;
float: left;
}
fieldset p {
clear: left;
}
Let's say I've got an ill formed html page:
<table>
<thead>
<th class="what_I_need">Super sweet text<th>
</thead>
<tr>
<td>
I also need this
</td>
<td>
and this (all td's in this and subsequent tr's)
</td>
</tr>
<tr>
...all td's here too
</tr>
<tr>
...all td's here too
</tr>
</table>
On BeautifulSoup, we were able to get the <th> and then call findNext("td"). Nokogiri has the next_element call, but that might not return what I want (in this case, it would return the tr element).
Is there a way to filter the next_element call of Nokogiri? e.g. next_element("td")?
EDIT
For clarification, I'll be looking at many sites, most of them ill formed in different ways.
For instance, the next site might be:
<table>
<th class="what_I_need">Super sweet text<th>
<tr>
<td>
I also need this
</td>
<td>
and this (all td's in this and subsequent tr's)
</td>
</tr>
<tr>
...all td's here too
</tr>
<tr>
...all td's here too
</tr>
</table>
I can't assume any structure other than there will be trs below the item that has the class what_I_need
First, note that your closing th tag is malformed: <th>. It should be </th>. Fixing that helps.
One way to do it is to use XPath to navigate to it once you've found the th node:
require 'nokogiri'
html = '
<table>
<thead>
<th class="what_I_need">Super sweet text<th>
</thead>
<tr>
<td>
I also need this
</td>
<tr>
</table>
'
doc = Nokogiri::HTML(html)
th = doc.at('th.what_I_need')
th.text # => "Super sweet text"
td = th.at('../../tr/td')
td.text # => "\n I also need this\n "
This is taking advantage of Nokogiri's ability to use either CSS accessors or XPath, and to do it pretty transparently.
Once you have the <th> node, you could also navigate using some of Node's methods:
th.parent.next_element.at('td').text # => "\n I also need this\n "
One more way to go about it, is to start at the top of the table and look down:
table = doc.at('table')
th = table.at('th')
th.text # => "Super sweet text"
td = table.at('td')
td.text # => "\n I also need this\n "
If you need to access all <td> tags within a table you can iterate over them easily:
table.search('td').each do |td|
# do something with the td...
puts td.text
end
If you want the contents of all <td> by their containing <tr> iterate over the rows then the cells:
table.search('tr').each do |tr|
cells = tr.search('td').map(&:text)
# do something with all the cells
end