How do I click the correct link based on text contained in another element using Selenium + Python?

37 Views Asked by At

I have a html structure like this

<tbody>
    <tr role="row" class="odd">
        <td class="text-center dtr-control"></td>
        <td class="text-center">
            <a href="#" class="px-2 text-dark" onclick="bookPatient('1351','')">
                <i class="fa-solid fa-pen-to-square cursor-pointer fa-lg"></i>
                <i class="fa-solid fa-pen-to-square cursor-pointer fa-lg"></i>
            </a>
        </td>
        <td>Dedeh</td>
        <td class="text-center">Female</td>
        <td class="text-center">07/02/1983</td>
        <td class style>PT MAJU JAYA</td>
    </tr>
    <tr role="row" class="odd">
        <td class="text-center dtr-control"></td>
        <td class="text-center">
            <a href="#" class="px-2 text-dark" onclick="bookPatient('1352','')">
                <i class="fa-solid fa-pen-to-square cursor-pointer fa-lg"></i>
                <i class="fa-solid fa-pen-to-square cursor-pointer fa-lg"></i>
            </a>
        </td>
        <td>Mira</td>
        <td class="text-center">Female</td>
        <td class="text-center">17/10/2002</td>
        <td class style>PT MAJU JAYA</td>
    </tr>
</tbody>

I'm expecting Selenium to click the <a> element after checking whether there is the corresponding searched_text_bod value or not. When I try with this code, the output cannot always identify the <a> element within the <td>.

The website doesn't have class or id on the elements so I'm having a hard time.

searched_text_bod = "20/03/1990"

# Improved XPath targeting based on confirmed structure
base_xpath = "//tbody/tr[@role='row']"
patient_row_xpath = f"{base_xpath}/td[text()='{searched_text_bod}']"

try:
  # Find patient row containing the exact date of birth
  patient_link = WebDriverWait(driver, 10).until(
      EC.presence_of_element_located((By.XPATH, patient_row_xpath)))

  edit_patient_info = patient_link.find_element(By.XPATH,
                                                "/following-sibling::td/a")

  if patient_link:
    print(f"Found patient with date of birth: {searched_text_bod}")
    edit_patient_info.click(
    )  # Click on the 'a' element (assuming it's the link)

  else:
    print(
        f"Patient with date of birth '{searched_text_bod}' not found using DoB search"
    )
2

There are 2 best solutions below

0
JeffC On

I have a process I walk through when I need to create a custom XPath that is a little complicated, like this one. I build the XPath in steps, verifying that each step is returning the element that I want. I think this makes the process easier and faster than creating the final XPath from the beginning, finding out it doesn't work, and then trying to troubleshoot.

The goal is to find the TR that contains both the TD with the DOB and the A tag that we want to click. That way we've ensured that the DOB and link are in the same table row.

  1. The first thing I do is just find the TD element that contains the DOB

    //tr/td[text()='17/10/2002']
    

    Given your existing XPaths, you seem to have that step under control. Now this is where is starts to get tricky.

  2. Rearrange the XPath so that it returns us the TR instead of the TD

    //tr[./td[text()='17/10/2002']]
    
  3. Now we just need to locate the A from the TR

    //tr[./td[text()='17/10/2002']]/td/a
    

If you aren't already, you want to use the devtools console in the browser to test your locators. Use $x() for XPaths and $$() for CSS selectors. The final XPath would be

$x("//tr[./td[text()='17/10/2002']]/td/a")

For more info on the Chrome devtools, see the docs.

From here, we can update the code...

wait = WebDriverWait(driver, 10)
searched_text_dob = "17/10/2002"

links = wait.until(EC.visibility_of_all_elements_located((By.XPATH, f"//tr[./td[text()='{searched_text_dob}']]/td/a")))
if links:
    links[0].click()
else:
    print(f"Patient with date of birth '{searched_text_dob}' not found using DoB search")

Suggestions that I implemented in the code above...

  1. I changed searched_text_bod to searched_text_dob because dob is date of birth.

  2. Since the final XPath was fairly short, I put it into a single string rather than the separate strings you had before. Feel free to break them apart again if you need to.

  3. When using WebDriverWait, presence means that the element is in the DOM, not that it's visible or clickable. In order to click an element, you should wait for it to be clickable using EC.element_to_be_clickable(). Other uses like .send_keys() or .text would use EC.visibility_of_element_located().

    NOTE: In this case I used visible even though I'm clicking an element. The reason for this is that I needed to return a collection of elements, not just a single element. The problem is that there is no equivalent of EC.visibility_of_all_elements_located() for clickable so I had to fall back to using visible instead.

  4. Avoid try-except when possible because it slows down your code. In this case we don't need it... that's why I pulled a collection instead of a single element. I check to see that the collection is not empty if links: and if so, click the first element.

5
geekay On

maybe

find the element...find its parent....find the tag under it

element = driver.find_element(By.XPATH, "//*[contains(text(), '20/03/1990')]") 
# use any fool proof way of arriving at correct element containing the date

parent=element.find_element(By.XPATH, "./ancestor::tr") 

atag=parent.find_element(By.XPATH,"//a[@onclick='bookPatient']")
atag.click()