Finding an element within an element without knowing the xpath?

Question:

Context

While trying to click a delete button belonging to a GitHub personal access token (PAT) with a certain description, using Selenium in Python. I am able to find the description and the ID of the PAT. However the button itself does not contain any reference to the id. Only the form that is spawned after clicking the button contains that reference. So to find out how to click the right button, I thought I would be able to find the button within the <div id="access-token-836771760" class="access-token js-revoke-item ".. element. However, most solutions that are able to search elements within elements, require one to know the xpath of this entry. I do not know the xpath of the parent element, because I find this element based on the token description. Apparently it is not practical to get the xpath of an element, once you have the element in Selenium.

HTML Code

<div class="listgroup">
    <div id="access-token-836771760" class="access-token js-revoke-item " data-id="836771760" data-type="token">
        <div class="listgroup-item">
            <div class="d-flex float-right">

                <details class="ml-2 details-reset details-overlay details-overlay-dark">
                    <summary data-view-component="true" class="btn-danger btn-sm btn" role="button"> Delete
                    </summary>
                    <details-dialog class="anim-fade-in fast Box Box--overlay d-flex flex-column" role="dialog"
                        aria-modal="true">
                        <div class="Box-header">
                            <button class="Box-btn-octicon btn-octicon float-right" type="button"
                                aria-label="Close dialog" data-close-dialog="">
                                <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16"
                                    data-view-component="true" class="octicon octicon-x">
                                    <path fill-rule="evenodd"
                                        d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z">
                                    </path>
                                </svg>
                            </button>
                            <h3 class="Box-title">Are you sure you want to delete this token?</h3>
                        </div>
                        <div data-view-component="true" class="flash flash-warn flash-full">


                            Any applications or scripts using this token will no longer be able to access the GitHub
                            API. You cannot undo this action.



                        </div>
                        <div class="Box-body overflow-auto">
                        </div>
                        <div class="Box-footer">
                            <!-- '"` -->
                            <!-- </textarea></xmp> -->
                            <form class="js-revoke-access-form" data-id="836771760" data-type-name="token"
                                data-turbo="false" action="/settings/tokens/836771760" accept-charset="UTF-8"
                                method="post" style=""><input type="hidden" name="_method" value="delete"
                                    autocomplete="off"><input type="hidden" name="authenticity_token"
                                    value="somevalue">
                                <button type="submit" data-view-component="true" class="btn-danger btn btn-block"> I
                                    understand, delete this token
                                </button>
                            </form>
                        </div>
                    </details-dialog>

                </details>
            </div>

            <small class="last-used float-right">Last used within the last 6 months</small>

            <span class="token-description">
                <strong>
                    <a href="/settings/tokens/836771760" data-pjax="">
                        Set GitHub commit build status values.</a>
                </strong>
                <span class="color-fg-muted">
                    <em>— <span title="Access commit status">repo:status</span></em>
                </span>
            </span>
            <div>
                <span class="color-fg-attention">
                    <a class="color-fg-attention" href="/settings/tokens/836771760/regenerate?index_page=1">
                        Expired <span class="text-semibold text-italic">on Mon, May 2 2022</span>.
                    </a> </span>
            </div>
        </div>
    </div>

    <div id="access-token-826562783" class="access-token js-revoke-item " data-id="826562783" data-type="token">
        <div class="listgroup-item">
            <div class="d-flex float-right">

                <details class="ml-2 details-reset details-overlay details-overlay-dark">
                    <summary data-view-component="true" class="btn-danger btn-sm btn" role="button"> Delete
                    </summary>
                    <details-dialog class="anim-fade-in fast Box Box--overlay d-flex flex-column" role="dialog"
                        aria-modal="true">
                        <div class="Box-header">
                            <button class="Box-btn-octicon btn-octicon float-right" type="button"
                                aria-label="Close dialog" data-close-dialog="">
                                <svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16"
                                    data-view-component="true" class="octicon octicon-x">
                                    <path fill-rule="evenodd"
                                        d="M3.72 3.72a.75.75 0 011.06 0L8 6.94l3.22-3.22a.75.75 0 111.06 1.06L9.06 8l3.22 3.22a.75.75 0 11-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 01-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 010-1.06z">
                                    </path>
                                </svg>
                            </button>
                            <h3 class="Box-title">Are you sure you want to delete this token?</h3>
                        </div>
                        <div data-view-component="true" class="flash flash-warn flash-full">


                            Any applications or scripts using this token will no longer be able to access the GitHub
                            API. You cannot undo this action.



                        </div>
                        <div class="Box-body overflow-auto">
                        </div>
                        <div class="Box-footer">
                            <!-- '"` -->
                            <!-- </textarea></xmp> -->
                            <form class="js-revoke-access-form" data-id="826562783" data-type-name="token"
                                data-turbo="false" action="/settings/tokens/826562783" accept-charset="UTF-8"
                                method="post"><input type="hidden" name="_method" value="delete"
                                    autocomplete="off"><input type="hidden" name="authenticity_token"
                                    value="someothervalue">
                                <button type="submit" data-view-component="true" class="btn-danger btn btn-block"> I
                                    understand, delete this token
                                </button>
                            </form>
                        </div>
                    </details-dialog>

                </details>
            </div>

            <small class="last-used float-right">Last used within the last 6 months</small>

            <span class="token-description">
                <strong>
                    <a href="/settings/tokens/82653355" data-pjax="">
                        somedescription</a>
                </strong>
                <span class="color-fg-muted">
                    <em>— <span title="something">repo</span></em>
                </span>
            </span>
            <div>
                <span class="color-fg-attention">
                    <a class="color-fg-attention" href="/settings/tokens/826562783/regenerate?index_page=1">
                        Expired <span class="text-semibold text-italic">on Thu, May 19 2022</span>.
                    </a> </span>
            </div>
        </div>
    </div>

</div>

Question

How could I click the delete button belonging to the access-token-836771760 class in Python using Selenium?

Approach

I can find the delete buttons with:

danger_button = website_controller.driver.find_elements(By.CSS_SELECTOR,'btn-danger.btn-sm.btn')
        print_attributes_of_elements(danger_button,website_controller)
def print_attributes_of_elements(elements,website_controller):
    for elem in elements:
        attrs = website_controller.driver.execute_script('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', elem)
        pprint(attrs)

However, within those buttons, I do not know which button is the right one.

Asked By: a.t.

||

Answers:

If you already have the <div id="access-token-836771760" class="access-token js-revoke-item ".. element it should be as easy as that:

# get div by description (you already have your div)
div = driver.find_element(By.XPATH, "//a[normalize-space(text())='Test']//ancestor::div[@data-type='token']")

# click delete button
button = div.find_element(By.XPATH, ".//summary")
button.click()

You don’t need to know the XPATH if you already have the reference to the div.

Edit:
I am already using a method to find an element within an element here.
You just need to call WebElement.find_element(By.XPATH, ".//tag").
Have a look at the XPath Syntax.
Firstly, the . selects the current node (WebElement). The // selects nodes in the document from the current node that match the selection. I think that is exactly what you want.

Answered By: Stefan

In the end, I was able to get the xpaths relative to another element of which I knew the xpath, by manually analysing what the xpath change pattern was. Still a general method to find elements within an element, would be appreciated.

Here is the verified script that deletes a GitHub personal access token if it already exists, based on the GitHub personal access token description:

from pprint import pprint
from typing import List
from code.project1.src.Website_controller import Website_controller
from code.project1.src.control_website import click_element_by_xpath, open_url, wait_until_page_is_loaded
from selenium.webdriver.common.by import By

from selenium.webdriver.common.action_chains import ActionChains
from code.project1.src.helper import scroll_shim

def remove_previous_github_pat(hardcoded,website_controller):
    """Assumes the user is logged in into GitHub. Then lists the already 
    existing GitHub personal access token (PAT) descriptions. If the new GitHub
    PAT description is already existing, it deletes the existing GitHub PAT. 
    Then it verifies the GitHub PAT is not yet in GitHub/is removed 
    succesfully."""

    # Check if the token exists, and if yes, get a link containing token id.
    github_pat_exists,link =github_pat_description_exists(hardcoded,website_controller)
    if github_pat_exists:

        # Delete the GitHub personal access token.
        delete_github_pat(link,hardcoded,website_controller)
            
    # Verify token is deleted.
    if github_pat_description_exists(hardcoded,website_controller)[0]:
        raise Exception("Error, GitHub pat is not deleted succesfully.")
        

def github_pat_description_exists(hardcoded,website_controller):
    """Assumes the user is logged in into GitHub. Then lists the already 
    existing GitHub personal access token (PAT) descriptions. If the new GitHub
    PAT description is already existing, it returns True, otherwise returns 
    False. Also returns the url of the GitHub pat that contains the token id."""
    # Go to url containing GitHub pat.
    website_controller.driver = open_url(
        website_controller.driver,
        hardcoded.github_pat_tokens_url,
    )
    # Wait until url is loaded. 
    wait_until_page_is_loaded(6,website_controller)

    # Get the token descriptions through the href element.
    elems = website_controller.driver.find_elements(By.CSS_SELECTOR,f".{hardcoded.github_pat_description_elem_classname} [href]")
    for elem in elems:
        link=elem.get_attribute('href')
        if hardcoded.github_pat_description in elem.text:
            return True, link
    return False, None

            

def delete_github_pat(link,hardcoded,website_controller):
    """Gets the GitHub pat id from the link, then clicks the delete button, and
    the confirm deletion button, to delete the GitHub pat."""
    
    if link[:len(hardcoded.github_pat_tokens_url)] == hardcoded.github_pat_tokens_url:
        github_pat_id=int(link[len(hardcoded.github_pat_tokens_url):])
        print(f'github_pat_id={github_pat_id}')

        # Get the right table row nr.
        valid_indices=list_of_valid_xpath_indices([],f"{hardcoded.github_pat_table_xpath}/div[","]",website_controller)
        row_nr= get_desired_token_index(hardcoded,website_controller,valid_indices)
        
        # Click delete button and deletion confirmation button.
        click_github_pat_delete_button(hardcoded,website_controller,row_nr)
    else:
        raise Exception(f'{link[:len(hardcoded.github_pat_tokens_url)]} is not:{hardcoded.github_pat_tokens_url}')

def list_of_valid_xpath_indices(valid_indices,left,right,website_controller):
    """Returns the row numbers of the GitHub personal access tokens table, 
    starting at index =1. Basically gets how much GitHub pats are stored."""
    if valid_indices == []:
        latest_index=1
    else:
        latest_index=valid_indices[-1]+1

    try:
        row = website_controller.driver.find_element(By.XPATH,
            f"{left}{latest_index}{right}"
            )
        if not row is None:
            print(row.text)
            valid_indices.append(latest_index)
            return list_of_valid_xpath_indices(valid_indices,left,right,website_controller)
        else:
            return valid_indices
    except:
        if len(valid_indices) ==0:
            raise Exception("Did not find any valid indices.")
        return valid_indices

def get_desired_token_index(hardcoded,website_controller,valid_indices:List[int]):
    """Finds the index/row number of the GitHub pat's that corresponds to the 
    description of the GitHub pat that is to be created, and returns this 
    index."""
    for row_nr in valid_indices:
        row_elem = website_controller.driver.find_element(By.XPATH,
            f"{hardcoded.github_pat_table_xpath}/div[{row_nr}]"
            )
        if hardcoded.github_pat_description in row_elem.text:
            return row_nr

def click_github_pat_delete_button(hardcoded,website_controller,row_nr:int):
    """Clicks the delete GitHub pat button, and then clicks the confirm 
    deletion button."""
    delete_button  = website_controller.driver.find_element(By.XPATH,
            f"{hardcoded.github_pat_table_xpath}/div[{row_nr}]/div/div[1]/details/summary"
            )
    delete_button.click()

    confirm_deletion_button = website_controller.driver.find_element(By.XPATH,
            f"{hardcoded.github_pat_table_xpath}/div[{row_nr}]/div/div[1]/details/details-dialog/div[4]/form/button"
            )
    confirm_deletion_button.click()
Answered By: a.t.
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.