Finding an element within an element without knowing the xpath? - python

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.

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.

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()

Related

Alternative to time.sleep() in selenium using python while web scraping?

I need to scrape price of certain listed food items basis different locations in the country. There's an input text box that allows me to enter the name of the city & pressing "Enter" shows me the list of items available in that city.
Here's how I am trying to automate this:
driver.get("https://grofers.com/")
ele = driver.find_element_by_xpath("//input[#data-test-id='area-input-box']")`
ele.send_keys(area)
ele.send_keys(Keys.RETURN)
Here's the HTML I'm working with:
<div style="margin-left: 51px; height: 36px;">
<div style="display: flex; height: 100%;">
<button class="btn location-box mask-button">Detect my location</button>
<div class="oval-container">
<div class="oval">
<span class="separator-text">
<div class="or">OR</div>
</span>
</div>
</div>
<div style="width: 220px;">
<div class="modal-right__input-wrapper">
<div class="display--table full-width">
<div class="display--table-cell full-width">
<div id="map-canvas"></div>
<div class="Select location-search-input-v1 is-searchable Select--single">
<div class="Select-control">
<div class="Select-multi-value-wrapper" id="react-select-2--value">
<div class="Select-placeholder">Type your city Society/Colony/Area</div>
<div class="Select-input" style="display: inline-block;">**<input data-test-id="area-input-box" aria-activedescendant="react-select-2--value" aria-expanded="false" aria-haspopup="false" aria-owns="" role="combobox" value="">**</div>
</div>
<span class="Select-arrow-zone"><span class="Select-arrow"></span></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The problem is - after send_keys, the website takes time to autofill the input box AFTER WHICH I need to press enter.
I tried using time.sleep(2) after send_keys but this leads to pop-up disappearing & a StaleElementException when I do Keys.RETURN.
Have been stuck on this for quite some time now. Any help/pointers would be appreciated.
Selenium actually has an article on this with Explicit and Implicit waits, I think this is the one you're looking for:
# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))
https://selenium-python.readthedocs.io/waits.html That's the article
You can also create custom wait conditions when none of the previous convenience methods fit your requirements. A custom wait condition can be created using a class with call method which returns False when the condition doesn’t match.
class element_has_css_class(object):
"""An expectation for checking that an element has a particular css class.
locator - used to find the element
returns the WebElement once it has the particular css class
"""
def __init__(self, locator, css_class):
self.locator = locator
self.css_class = css_class
def __call__(self, driver):
element = driver.find_element(*self.locator) # Finding the referenced element
if self.css_class in element.get_attribute("class"):
return element
else:
return False
# Wait until an element with id='myNewInput' has class 'myCSSClass'
wait = WebDriverWait(driver, 10)
element = wait.until(element_has_css_class((By.ID, 'myNewInput'), "myCSSClass"))

get updated list elements after each click with selenium/python

Im trying to click on list elements in an unordered list.
It works fine for the first element, but once the loops goes into the second round I get this error message:
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
this is my loop:
catlist=["Buy & Sell", "Books", "Textbooks"]
for r in range(0,len(catlist)):
categoryList = browser.find_element_by_xpath("//*[starts-with(#class, 'categoryList-')]")
avilableButtons=categoryList.find_elements_by_tag_name("li")
for text in avilableButtons:
if text.text == catlist[r] :
text.click()
print (r)
the category list is being replaced/updated(not sure what the correct phrase is here) with each selection (click) that has been made. so after each click I only find one unordered list named "categoryList-".
I tried using browser.implicitly_wait(10) to give it time but the issue remains the same.
Html relevant code:
this is the code before clicking the Buy&sell button.
<ul class="categoryList-3073244717">
<li class="categoryListItem-3726364752">
<button class="categoryButton-3830788057 button-1997310527 button__medium-1066667140">
<h5 class="categoryName-958974558 level1Category-2680817441">Buy & Sell</h5>
</button>
</li>
<li class="categoryListItem-3726364752">
<button class="categoryButton-3830788057 button-1997310527 button__medium-1066667140">
<h5 class="categoryName-958974558 level1Category-2680817441">Cars & Vehicles</h5>
</button>
</li>
after .click() the list from above is invisible/gone and I get the subcategory list.
<ul class="categoryList-3073244717">
<li class="categoryListItem-3726364752">
<button class="categoryButton-3830788057 button-1997310527 button__medium-1066667140">
<h5 class="categoryName-958974558 level2Category-867177555">Arts & Collectibles</h5>
<svg class="icon-459822882 actionIcon-2308908423" focusable="false" height="100%" role="img" width="100%">
<use xlink:href="#icon-arrow-right"></use>
</svg>
</button>
</li>
<li class="categoryListItem-3726364752">
<button class="categoryButton-3830788057 button-1997310527 button__medium-1066667140">
<h5 class="categoryName-958974558 level2Category-867177555">Books</h5>
</button>
</li>
the website Kijiji.ca which is basically like craiglist.
I came across this blog post after some more searching and asking google the right questions. http://darrellgrainger.blogspot.com/2012/06/staleelementexception.html
This describes the same issue, now with the added loop and try/except statement it works!
categoryToPost=["Buy & Sell", "Books", "Textbooks"]
attempts=0
for r in range(0,len(categoryToPost)):
while attempts <2:
categoryList = browser.find_element_by_xpath("//*[starts-with(#class, 'categoryList-')]")
availableButtons = categoryList.find_elements_by_tag_name("li")
try:
for text in availableButtons:
if text.text==categoryToPost[r]:
print (text.text)
text.click()
attempts=0
except EC.StaleElementReferenceException:
attempts+=1
break

Using python selenium to click a ng-click button and upload a file

I am very new to python and selenium, but I need to use selenium to automatically upload some files.
There is a button I have to click, and it will pop a window in which I can select a file to upload.
And the HTML for this button is:
<button class="md-button md-default-theme" ng-transclude="" ng-show="excelshow" ng-click="selectFile()" tabindex="0" aria-hidden="false" style=""><span class="ng-scope">
Excel upload
</span><div class="md-ripple-container" style=""></div></button>
I have no idea how to click this button with selenium and upload the selected file.
I have tried to use driver.find_element_by_class_name('md-button md-default-theme') or driver.find_element_by_css_selector, but it doesn't work.
I think maybe I am using the find_elements_by_css_selector in a wrong way.
Thank you for your help!
EDIT:
The more complete content is here:
<md-toolbar style="background-color: white !important" class="md-default-theme">
<div class="md-toolbar-tools">
<span>Manage Menu</span>
<!-- fill up the space between left and right area -->
<span flex=""></span>
<!-- ngIf: isAuth --><div ng-if="isAuth" class="ng-scope">
<input type="file" style="display:none" id="file" name="file" onchange="angular.element(this).scope().upexecl(this)">
<button class="md-button md-default-theme" ng-transclude="" ng-click="DownLoadS()" tabindex="0"><span class="ng-scope">
Download Example
</span></button>
<button class="md-button md-default-theme" ng-transclude="" ng-show="excelshow" ng-click="selectFile()" tabindex="0" aria-hidden="false" style=""><span class="ng-scope">
Excel upload
</span></button>
<button class="md-button md-default-theme" ng-transclude="" ng-show="offering" ng-click="onlineSave()" tabindex="0" aria-hidden="false" style=""><span class="ng-binding ng-scope">
Save online
</span></button>
<button class="md-button md-default-theme ng-hide" ng-transclude="" ng-click="CancelEditSave()" ng-show="editshow" tabindex="0" aria-hidden="true"><span class="ng-binding ng-scope">
Cancel edit
</span></button>
</div><!-- end ngIf: isAuth -->
</div>
</md-toolbar>
Thank you for your help!
md-button md-default-theme is 2 class names: md-button & md-default-theme. .find_element_by_class_name() only takes a single parameter consisting of a single class name, e.g. .find_element_by_class_name("md-button"). That may or may not find the element you want based on whether that class name uniquely locates the element that you want.
Another option would be to use a CSS selector so that you can use all three classes in a single locator, e.g
driver.find_element_by_css_selector('md-button.md-default-theme')
where the . indicates a class name in CSS selector syntax.
Hope it helps :)

Using Selenium Webdriver, grabbing data not showing up in innerhtml

I am trying to use selenium to grab text data from a page.
Printing the html attributes:
element = driver.find_element_by_id("divresults")
Results:
print(element.get_attribute('innerHTML'))
<div id="divDesktopResults"> </div>
Results:
print(element.get_attribute('outerHTML'))
<div id="divresults" data-bind="html:resultsContent"><div id="divDesktopResults"> </div></div>
Tried grabbing this element
Results:
driver.find_element_by_css_selector("span[class='glyphicon glyphicon-tasks']")
Message: no such element: Unable to locate element: {"method":"css selector","selector":"span[class='glyphicon glyphicon-tasks']"}
This is the code when copied from the Browser. There is much more below 'divresults' that did not show up in the innerhtml printout
<div id="divresults" data-bind="html:resultsContent">
<div>
<div class="row" style="font-size:8pt;">
<a data-toggle="tooltip" style="text-decoration:underline" href="#pdfviewer?ID=D218101736">
<strong>D218101736 </strong>
<span class="glyphicon glyphicon-new-window"></span>
</a>
<div class="btn-group" style="font-size:8pt;margin-left:10px;" id="btnD218101736">
<span style="display:none;font-size:8pt;" id="lblD218101736"> Added To Cart</span>
<button type="button" style="font-size:8pt;" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> Add To Cart
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li> <strong>Regular ($7.00)</strong> </li>
<li> <strong>Certified ($12.00)</strong> </li>
</ul>
</div>
</div> <br>
<ul class="nav nav-tabs compact">
<li class="active">
<a data-toggle="tab" href="#D218101736_Doc">
<span class="glyphicon glyphicon-file"></span>
<span>Doc Info</span>
</a>
</li>
<li class="hidden-xs">
<a data-toggle="tab" href="#D218101736_Thumbnail">
<span class="glyphicon glyphicon-th-large"></span>
<span>Thumbnail</span>
</a>
</li>
....
How to I get data beneath divresults in the instance?
My guess is that it's one of two things:
There is more than one element that matches that locator. To investigate this, try using $$("#divresults") in the dev console and make sure that it returns 1. If it returns more than one, run $$("#divresults")[0] and make sure the element returned is the one you want. If it is, go on to step 2. If it isn't, you will need to find a locator that is more specific. If you want our help, you will need to provide a link to the page or more of the surrounding HTML to the desired element.
You need to add a wait so that the contents of the element can finish loading. You could wait for a locator like #divresults strong or any number of locators to find some of the elements that were missing. You would wait for them to be visible (or at least present). See the docs for more info and options.

Splinter Python Script Button Click

I am trying to write a code which lets me select the gender of my choice (let's use female as an example). The element as below. Please note that the ID keep changes so I cannot use css selector with the ID.
Please advise which method to use in selenium.
Thanks!
<div id="557f72b0-c612-428a-a77e-eb0841869ddb" class="nike-unite-gender-buttons gender nike-unite-component">
<div class="shim"></div>
<div class="error"></div>
<input type="hidden" id="40d664f0-e498-4fa2-8a6f-7d9fc75cfe01" value="" name="gender" data-componentname="gender">
<ul data-componentname="gender">
<li id="222f5bc2-171e-450e-8182-8e15c8d9f47b" class="">
<input type="button">
<span>Male</span>
</li>
<li id="6c6098bb-fc9f-497e-8551-0ae6bb8a235c" class="">
<input type="button">
<span>Female</span>
<</li>
</ul>
<div class="tip">Please select a gender.</div>
</div>
First, locate the gender container by the class names:
gender_container = browser.find_by_css('.nike-unite-gender-buttons.gender').first
Then, locate the button by the following text inside the span element:
female = gender_container.find_by_xpath('.//li[span = "Female"]/input').first
female.click()
Or, you may try finding the element by text directly:
browser.find_by_text('Female').first.click()

Categories

Resources