Tom
Tom

Reputation: 2661

Selenium finds element from the entire page instead of just a part of it (Python)

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?

Upvotes: 1

Views: 885

Answers (3)

pmadhu
pmadhu

Reputation: 3433

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.

Upvotes: 1

vitaliis
vitaliis

Reputation: 4212

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.

Upvotes: 2

Salaah Amin
Salaah Amin

Reputation: 452

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

Upvotes: 1

Related Questions