1

I have a html element like so:

<table>
   <tbody>
      <tr>
         <td>My Text</td>
         <td>Some Other Text</td>
         <td><img src="../delete.png" alt="trashcan"></td>
      </tr>
      <tr>
         <td>More Text</td>
         <td>Some More Text</td>
         <td><img src="../delete.png" alt="trashcan"></td>
      </tr>
   </tbody>
</table>

I'm want to find a table row by text and then click the trashcan icon to delete it.

So my idea is to loop over the rows <tr/> and then loop over the cells <td/>. If the text matches the cell text, find the image from that row by XPATH and then click it.

tbody = driver.find_element_by_tag_name("tbody")
tr = tbody.find_elements_by_tag_name("tr")
for row in tr:
   td = row.find_elements_by_tag_name("td")
   print(len(td))
   for cell in td:
     if cell.text == "More Text":
       delete = row.find_element_by_xpath('//img[@alt="trashcan"]')
       delete.click()

My understanding is that driver is the entire page. tbody is just the tbody element in that page. So whatever I try to locate from there has to be a child of that element. To confirm I print out the length of the td elements which is prints out "3".

The delete button delete = row.find_element_by_xpath('//img[@alt="trashcan"]') selects the delete button from the first table row instead of from the second row.

I also tried

delete = row.find_elements_by_xpath('//img[@alt="trashcan"]')
delete[0].click()

But it also selects the row.

Just to be sure I printed out the row (print(row.get_attribute("innerHTML")) in the if condition and it prints out the second row.

Any ideas what's going on or how I could select the img in the second row instead?

vitaliis
  • 4,082
  • 5
  • 18
  • 40
Tom
  • 2,545
  • 5
  • 31
  • 71
  • 1
    Check my edit for HTML formatting. You probably had a mistake there. Check here for rules https://www.w3schools.com/tags/tag_tr.asp – vitaliis Jul 31 '21 at 03:46

3 Answers3

2

You can find the correct XPath with text() method like this:

//td[contains(text(),'Some More Text')]/../td[3]

Or:

//td[contains(text(),'Some More Text')]/parent::tr/td[3]

The first one just goes one level up and then looks for the third td element.

The second approach looks the parent element, like described here XPath: Get parent node from child node.

Try to apply it to your loop.

vitaliis
  • 4,082
  • 5
  • 18
  • 40
1

find_element_by_xpath is only going to give you the first path it finds.

I'm not sure how familiar you are with JavaScript, but I tend to use find_elements_by_css_selector. It lets you then put in the same argument that you would when you run document.querySelectorAll.

In your case, you can do:

text_to_find = 'If this is spotted, then delete'
# Find all tds
trs = driver.find_elements_by_css_selector("td")
# Iterate over each tr insde a td.
for tr in trs:
    for td in tr.find_elements_by_css_selector('tr'):
        # If any td text is equal to the text you wish to delete, then click on the trashcan and then move onto the next td.
        if td.text == text_to_find:
            tr.find_element_by_css_selector("[alt='trashcan']").click() # notice that I removed the 's' in elements.
            break
Salaah Amin
  • 382
  • 3
  • 14
1

You can try to change the for loop. Like for i in range(1,len(tr)): It skips the 1st tr. Or you can pop the 1st element in tr before for loop.

pmadhu
  • 3,373
  • 2
  • 11
  • 23
  • that only works if the values are hardcoded, the table actually changes with more input, thanks anyway – Tom Aug 01 '21 at 01:47