visibility_of_element_located or element_to_be_clickable - Final solution? - python

I read a little about the functions of checking in selenium whether an element is visible on the page (for the user) so that he can hover or click on this element.
We have options:
1.visibility_of_element_located
An expected condition for checking that, element is located by locator present in the DOM, and element is visible on a web page. Visibility means that the elements are not only displayed but also has a height and width that is greater than 0.
2.element_to_be_clickable
Wait for an element identified by the locator is enabled and visible such that you can click on it. Note that element should be in visible state
And here's the question.
I have a page:
https://tvn24.pl/
in which I have to hover over the Element
"button.account-standard__toggle-button"
that the "frame" unfolds.
In which I have to click on the element `
"div.account-standard__popup button.account-content__button.account-content__button--large"
I would like the click to happen after waiting for the element to be visible to the user - which is only possible after hovering over the first element But :
What if the second button, even though I did not hit the first button, is in the DOM and already has a height and width greater than 0.
The condition will be met even though the user will not actually see it.
Isn't the second way (element_to_be_clickable) better in this case?.
What about the situation when the mouse hover occurs, but for some reason the expanded frame disappears / collapses or something else before selenium finds and clicks on the second button.
Below, there is a screen with hovering the button (upper right corner - a button imitating a human) and below, an expanded frame with a "log in" button
Selection in the inspection is in the second button that appears after hovering over the previous one.
And these graphic values ​​are constant all the time, they do not change whether I hover the mouse on the first button or not.
Unless I have something wrong with this development tool
I wrote this codes before.
But I am afraid that it does not predict that, for example, when you hover over the first button, something will load on the page and the expandable element will disappear before the driver finds the second button and clicks.
I know I can use "wait.untill (ec.visible ...) but I want to avoid a possible time exception.
As you can see in this picture, when we hover on button 1, in the highest div for this Li it appears in the class name "--visible"
def move_to_login_page_from_main_page(driver):
wait = WebDriverWait
CSS_account = "button.account-standard__toggle-button"
CSS_login = "div.account-standard__popup button.account-content__button.account-content__button--large"
CSS_roll_out_frame = "div.account-standard--visible"
expected_url = "account.tvn.pl"
attempts = 0
while attempts <= 10:
account = driver.find_element(By.CSS_SELECTOR, CSS_account)
ac(driver).move_to_element(account).perform()
roll_out_frames = driver.find_elements(By.CSS_SELECTOR, CSS_roll_out_frame)
if len(roll_out_frames) > 0:
log_in_button = roll_out_frames.find_element(By.CSS_SELECTOR, CSS_login)
ac(driver).move_to_element(log_in_button).click(log_in_button).perform()
wait(driver, 10, 1).until(ec.url_contains(expected_url))
break
else:
time.sleep(1)
attempts += 1
def move_to_login_page_from_main_page2(driver):
wait = WebDriverWait
CSS_account = "button.account-standard__toggle-button"
CSS_login = "div.account-standard__popup button.account-content__button.account-content__button--large"
expected_url = "account.tvn.pl"
attempts = 0
while attempts <= 10:
account = driver.find_element(By.CSS_SELECTOR, CSS_account)
ac(driver).move_to_element(account).click(account).perform()
login_button = driver.find_element((By.CSS_SELECTOR, CSS_login))
if login_button.is_enabled():
ac(driver).move_to_element(login_button).click(login_button).perform()
wait(driver, 10, 1).until(ec.url_contains(expected_url))
break
else:
time.sleep(0.5)
attempts += 1

This code shows different conditions of your element:
import time
from selenium import webdriver
from selenium.webdriver import ActionChains as AC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
base_url = 'https://tvn24.pl/'
driver = webdriver.Chrome(executable_path=rf"chromedriver.exe")
driver.maximize_window()
driver.get(base_url)
cookie_button = WebDriverWait(driver, 5, 1).until(EC.visibility_of_element_located((By.ID,'onetrust-accept-btn-handler')))
cookie_button.click()
CSS_account = "button.account-standard__toggle-button"
login_xpath = '//div[#class="account-standard__popup"]//button[#class="account-content__button account-content__button--large"]'
def check_elem_state(xpath):
try:
WebDriverWait(driver, 1, 1).until(EC.presence_of_element_located((By.XPATH, login_xpath)))
print('Element present')
except:
print('Sorry - not present')
try:
WebDriverWait(driver, 1, 1).until(EC.visibility_of_element_located((By.XPATH, login_xpath)))
print('Element visible')
except:
print('Sorry - not visible')
try:
WebDriverWait(driver, 1, 1).until(EC.element_to_be_clickable((By.XPATH, login_xpath)))
print('Element clickable')
except:
print('Sorry - not clickable')
print('-------Without slider--------')
check_elem_state(login_xpath)
login = driver.find_element(By.XPATH, login_xpath)
print("Login is displayed: ", login.is_displayed())
print('-------Showing slider-------')
account = driver.find_element(By.CSS_SELECTOR, CSS_account)
AC(driver).move_to_element(account).click(account).perform()
check_elem_state(login_xpath)
print("Login is displayed: ", login.is_displayed())
login.click()
The output:
-------Without slider--------
Element present
Sorry - not visible
Sorry - not clickable
Login is displayed: False
-------Showing slider-------
Element present
Element visible
Element clickable
Login is displayed: True
This way you can check if is both visible and clickable and perform any action if you close the wait in try expect.
Only remark here is to use proper expected exception - in example any exception will be caught so you need add proper exception.
Edit:
In console you can check it this way:
function isVisible(elem) {
if (!(elem instanceof Element)) throw Error('DomUtil: elem is not an element.');
const style = getComputedStyle(elem);
if (style.display === 'none') {console.log('display'); return false;}
if (style.visibility !== 'visible') {console.log('visibility'); return false;}
if (style.opacity < 0.1) {console.log('opacity'); return false;}
if (elem.offsetWidth + elem.offsetHeight + elem.getBoundingClientRect().height +
elem.getBoundingClientRect().width === 0) {
console.log('client'); return false;
}
const elemCenter = {
x: elem.getBoundingClientRect().left + elem.offsetWidth / 2,
y: elem.getBoundingClientRect().top + elem.offsetHeight / 2
};
if (elemCenter.x < 0) {console.log('x<0'); return false;}
if (elemCenter.x > (document.documentElement.clientWidth || window.innerWidth)) {console.log('width'); return false;}
if (elemCenter.y < 0) {console.log('y<0'); return false;}
if (elemCenter.y > (document.documentElement.clientHeight || window.innerHeight)) {console.log('height'); return false;}
let pointContainer = document.elementFromPoint(elemCenter.x, elemCenter.y);
do {
if (pointContainer === elem) {console.log('point continter'); return true;} else { console.log(pointContainer)}
} while (pointContainer = pointContainer.parentNode);
return false;
}
And trigger function:
isVisible(document.getElementsByClassName('account-content__button--large')[1])

your locator locator for the dropdown element is not unique it is detecting some other element , use :
Now if you use visibility of dropdown content without clicking the dropdown button it will time out.
driver.get('https://tvn24.pl/')
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR,
'#onetrust-accept-btn-handler'))).click()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR,
".account-standard__toggle-button"))).click()
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR,'[class="account-standard__popup"] button[class="account-content__button account-content__button--large"]')))
This will first wait for accept cookies and then click ok , then click hover button , and then click the displayed dropdown button
Strategy to find unique locator:
If you are not able to get a unique property for an element then find an unique sibling or parent or child and refrence the element to it
In this case :
[class="account-standard__popup"]
this parent element is unique refrence you can use to uniquely locate the child element :
button[class="account-content__button account-content__button--large"]
To answer your question:
See the is_clickable method. you can see that it will first check for visibilty.
So if use you element_is_visible, it won't check for is_enabled() else it will check if the button is enabled, meaning it will ensure that the button or element doesn't have the disabled html attribute
IF the button is enabled or disabled using css class and not using HTML disabled attribute, then there is no difference between using visibility and clicakable,
As is_Enabled will not check for element disabled state through css but only through html disable attribute.
class element_to_be_clickable(object):
""" An Expectation for checking an element is visible and enabled such that
you can click it."""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
element = visibility_of_element_located(self.locator)(driver)
if element and element.is_enabled():
return element
else:
return False

Related

Unable to select the element in a list

from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.remote.webelement import WebElement
driver = webdriver.Chrome('chromedriver')
driver.get('https://devbusiness.tunai.io/login')
time.sleep(2)
driver.maximize_window()
# Create variables for login credentials.
username = driver.find_element(By.NAME, "loginUsername");
username.send_keys("kevin#tunai");
password = driver.find_element(By.NAME, "loginPassword");
password.send_keys("xxxxx");
login = driver.find_element(By.XPATH,"//*[#id='app']/div/div/div/div/div/div[2]/form/div[4]/button");
login.submit();
time.sleep(2)
# Wait for login process to complete.
WebDriverWait(driver=driver, timeout=10).until(
lambda x: x.execute_script("return document.readyState === 'complete'")
)
# Verify that the login was successful.
error_message = "Incorrect username or password."
# Retrieve any errors found.
errors = driver.find_elements(By.CLASS_NAME, "flash-error")
# When errors are found, the login will fail.
if any(error_message in e.text for e in errors):
print("[!] Login failed")
else:
print("[+] Login successful")
driver.get("https://devbusiness.tunai.io/dashboard/salon_menu_service")
service = driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/button")
service.click();
driver.find_element(By.TAG_NAME,"input").send_keys("Hair Dying")
price = driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[2]/div[1]/div/div/input")
price.clear()
price.send_keys("50")
baseprice = driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[2]/div[2]/div/div/input")
baseprice.clear()
baseprice.send_keys("10")
# Category button
category_button = driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[3]/div/div/div/div[2]")
# An "Category 2 - BeautyPOS" item in list of categories
category_select = driver.find_element(By.XPATH,"""//*[#id="page-content"]/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[3]/div/div/div/div[3]/ul/li[2]/span""")
# Click category button to show list.
category_button.click()
# Click on category you want select.
category_select.click()
time.sleep(3)
# sub-category button
subcategory_button = driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[4]/div/div/div/div[3]")
# An "Category 4 - HairCut" item in list of categories
subcategory_select = driver.find_element(By.XPATH,"""//*[#id="page-content"]/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[4]/div/div/div/div[3]/ul/li[2]""")
# Click category button to show list.
subcategory_button.click()
# Click on category you want select.
subcategory_select.click()
time.sleep(3)
When I moving into the next part which is selecting a value(random from the list), but it said the element is not interactable. I tried with the click function and driver.implicity_wait.
Appreciate if someone could help. Thank in advance.
The link is https://devbusiness.tunai.io/dashboard/salon_menu_service
username and pass can find in the code.
In this case, the error element is not interactable appear because you try to click the element having display: none; attribute.
You need trigger the element with clicking related element first to make visible, and then scroll too.
....
....
# trigger with other element first, add this code
element = driver.find_element(By.XPATH,"//span[text()='No Sub-Category']")
driver.execute_script("arguments[0].scrollIntoView();", element)
time.sleep(1)
element.click()
# sub-category button
subcategory_button = self.driver.find_element(By.XPATH,"//*[#id='page-content']/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[4]/div/div/div/div[3]")
# An "Category 4 - HairCut" item in list of categories
subcategory_select = self.driver.find_element(By.XPATH,"""//*[#id="page-content"]/div/div[2]/div[1]/div[1]/div/div[2]/div/div/form/div[1]/div[1]/div/div[1]/div[4]/div/div/div/div[3]/ul/li[2]""")

XPath Check if ID exists within drop down. Class check

thanks in advance. Noobie to Python here and I am trying to automate value entries into a website via Selenium and the respective XPath values.
How it is supposed to function is that I send keys of the dynamic 'ID' into the input box and the ID will pop up and I select the ID. This works. Right now I am running into the issue where the ID does not exist and the tool ends up stalling out. I know I need an If function, then execute an elif if it does not exist but I am lost when it comes to these kind of statements with XPaths and need a little bit of guidance.
I have the class XPath of the pop up value stating the ID does not exist:
<li class="vv_list_no_items vv_item_indent listNoItems">No results match "1234567890123456"</li>
The confusing part is also having dynamic IDs where "1234567890123456" can be any ID.
Current code is below, sorry for the indenting as this was grabbed out of a larger set of scripts.
try:
wait = WebDriverWait(browser, 10)
# Inputs Legal Entity
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"//*[#id='di3Form']/div[2]/div[2]/div/div[1]/div[3]/div/div[2]/div/div[1]/input"))).send_keys(
LE)
elem = wait.until(
EC.element_to_be_clickable((By.XPATH, "//*[#id='veevaBasePage']/ul[3]/li/a"))).click()
LE = None
# Inputs WWID
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"//*[#id='di3Form']/div[2]/div[2]/div/div[1]/div[4]/div/div[2]/div/div[1]/input"))).send_keys(ID)
elem = wait.until(
EC.element_to_be_clickable((By.XPATH, "//*[#id='veevaBasePage']/ul[4]/li[2]/a/em"))).click()
# Inputs Country
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"//*[#id='di3Form']/div[2]/div[2]/div/div[1]/div[5]/div/div[2]/div/div[1]/input"))).send_keys(
Country)
elem = wait.until(
EC.element_to_be_clickable((By.XPATH, "//*[#id='veevaBasePage']/ul[5]/li/a"))).click()
# Save
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"//a[#class='docInfoSaveButton save vv_button vv_primary']/span[#class='vv_button_text vv_ellipsis' and text()='Save']")))
browser.execute_script("arguments[0].click();", elem)
wait = WebDriverWait(browser, 15)
# Click dropdown menu arrow
elem = wait.until(EC.element_to_be_clickable(
(By.XPATH, "//*[#id='di3Header']/div[3]/div/div[2]/div[1]/div/div[2]/div/div/button")))
browser.execute_script("arguments[0].click();", elem)
wait = WebDriverWait(browser, 100)
# Click "Publish"
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"/html/body/div[6]/div/ul/li")))
browser.execute_script("arguments[0].click();", elem)
#Confirm Publish
elem = wait.until(EC.element_to_be_clickable((By.XPATH,
"//a[#class='save vv_button vv_primary']/span[#class='vv_button_text' and text()='Yes']")))
browser.execute_script("arguments[0].click();", elem)
You can use xpath contains, with find_elements that returns a list and have a if condition that if it is >0, then No match found string would be present in UI.
try :
no_match = "No results match" + " " + '"' + WWID + '"'
if (len(driver.find_elements(By.XPATH, "//li[contains(text(),'{}')]".format(no_match)))) > 0:
print("Code has found No results match, String ")
# do what ever you wanna do here.
else:
print("There is not locator that contains, No results match")
except:
print("Something went wrong")
pass

Selenium: Wait for element's height to be not equal to X

I am trying to take screenshot of webpage when canvas height changes. Its default height is 557. I want selenium to wait till height changes to any other value than 557. Is there any way to do that ?
<canvas id="faceCanvasPhoto" width="600" height="557" class="center-block reportCanvas">
Your browser does not support the canvas element.
</canvas>
I tried EC.visibility_of_element_located but couldnt catch it
try:
WebDriverWait(driver, 5).until(EC.visibility_of_element_located((By.XPATH, "//*[#id='faceCanvasPhoto']"))
)
driver.find_element_by_tag_name('body').screenshot(facemap +'.png')
except TimeoutException:
driver.find_element_by_tag_name('body').screenshot(facemap +'.png')
driver.implicitly_wait(1)
Define your on wait class:
class element_height_changes(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, curnt_val):
self.locator = locator
self.curnt_val = curnt_val
def __call__(self, driver):
# Finding the referenced element
element = driver.find_element(*self.locator)
if element.get_attribute("height") == str(self.curnt_val):
return False
else:
return True
And call it as :
print(WebDriverWait(driver, 10).until(
element_height_changes(
(By.XPATH, "//iframe"),59)
))
THis will print true or false , whether iframe length was changed from 59. If not wait till 10 second for it to change else time out.

How to Wait for an element to be filled of text

I use Selenium + Python and I work on a Page that data is filled from JS and is Dynamic every 10 seconds but it's not important because I will run it once a week, I want to wait as long as the td with id='e5' get its value and be filled Or rather until the site is fully loaded, The site address is as follows :
Site Address
But i dont find Suitable Expected Conditions for this job :
driver = webdriver.Firefox()
driver.get('http://www.tsetmc.com/Loader.aspx?ParTree=151311&i=2400322364771558')
WebDriverWait(driver, 10).until(EC.staleness_of((By.ID, 'e5')))
print(driver.find_element_by_id('e5').text)
driver.close()
I speak about this tag if you cant find it :
There is an Expected Condition for that:
WebDriverWait(driver, 10).until(EC.visibility_of_element_located((By.ID, 'e5')))
You don't need to wait for the element to be stale, you need it to be visible on the DOM.
Try this code to constantly check elements' text value
import time
pause = 1 # set interval between value checks in seconds
field = driver.find_element_by_id('e5')
value = field.text
while True:
if field.text != value:
value = field.text
print(value)
time.sleep(pause)
If you want to use WebdriverWait try
field = driver.find_element_by_id('e5')
value = field.text
while True:
WebDriverWait(driver, float('inf')).until(lambda driver: field.text != value)
value = field.text
print(value)

Python selenium: running into StaleElementReferenceException

I am trying to scrape all job postings for the last 24 hours from Glassdoor and save them to a dictionary.
binary = FirefoxBinary('path_to_firebox_binary.exe')
cap = DesiredCapabilities().FIREFOX
cap["marionette"] = True
driver = webdriver.Firefox(firefox_binary=binary, capabilities=cap, executable_path=GeckoDriverManager().install())
base_url = 'https://www.glassdoor.com/Job/jobs.htm?suggestCount=0&suggestChosen=false&clickSource=searchBtn' \
'&typedKeyword=data+sc&sc.keyword=data+scientist&locT=C&locId=1154532&jobType= '
driver.get(url=base_url)
driver.implicitly_wait(20)
driver.maximize_window()
WebDriverWait(driver, 20).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "div#filter_fromAge>span"))).click()
WebDriverWait(driver, 20).until(EC.element_to_be_clickable((
By.XPATH, "//div[#id='PrimaryDropdown']/ul//li//span[#class='label' and contains(., 'Last Day')]"))).click()
# find job listing elements on web page
listings = driver.find_elements_by_class_name("jl")
n_listings = len(listings)
results = {}
for index in range(n_listings):
driver.find_elements_by_class_name("jl")[index].click() # runs into error
print("clicked listing {}".format(index + 1))
info = driver.find_element_by_class_name("empInfo.newDetails")
emp = info.find_element_by_class_name("employerName")
results[index] = {'title': title, 'company': emp_name, 'description': description}
I keep running into the error message
selenium.common.exceptions.StaleElementReferenceException: Message:
The element reference of is stale; either the element is no longer attached to the
DOM, it is not in the current frame context, or the document has been
refreshed
for the first line inside my for loop. Even if the for loop runs for some number of times, it eventually leads to the exception showing up. I am new to selenium and web scraping, will appreciate any help.
Every time a new post is selected the clicked element is being modified, and therefor the DOM is being refreshed. The change is slow, certainly in comparison to the actions in the loop, so what you want to do is to slow it a little bit. Instead of using fixed sleep you can wait for the changes to occur
Every time you select a posting a new class selected is being added and the style attribute lose it's content. You should wait for this to happen, get the information, and click the next post
wait = WebDriverWait(driver, 20)
for index in range(n_listings - 1):
wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, '.selected:not([style="border-bottom:0"])')))
print("clicked listing {}".format(index + 1))
info = driver.find_element_by_class_name('empInfo.newDetails')
emp = info.find_element_by_class_name('employerName')
if index < n_listings - 1:
driver.find_element_by_css_selector('.selected + .jl').click()
This error means the element you are trying to click on was not found, you have to first make sure the target element exists and then call click() or wrap it in a try/except block.
# ...
results = {}
for index in range(n_listings):
try:
driver.find_elements_by_class_name("jl")[index].click() # runs into error
except:
print('Listing not found, retrying in 1 seconds ...')
time.sleep(1)
continue
print("clicked listing {}".format(index + 1))
info = driver.find_element_by_class_name("empInfo.newDetails")
emp = info.find_element_by_class_name("employerName")
# ...

Categories