Click on a dropdown element menu with Selenium Webdriver - python

I'm trying to automate an administration task, so far I have made selenium to click on an element to show a dropdown menu.
When it comes the time to click on one of those menu elements I've got an error saying that the element must be displayed.
Code:
driver = webdriver.Chrome()
driver.implicitly_wait(10)
driver.get(url)
doc = driver.find_element_by_css_selector('td.ms-vb-title > table')
try:
doc.click()
time.sleep(4)
menu = driver.find_element_by_xpath('//menu/span[5]')
time.sleep(4)
print dir(menu)
menu.click()
except:
traceback.print_exc()
driver.quit()
Error:
Traceback (most recent call last):
File "aprobar_docs.py", line 22, in main
menu.click()
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webelement.py",
line 52, in click
self._execute(Command.CLICK_ELEMENT)
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webelement.py",
line 205, in _execute
return self._parent.execute(command, params)
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\webdriver.py", l
ine 156, in execute
self.error_handler.check_response(response)
File "C:\Python27\lib\site-packages\selenium\webdriver\remote\errorhandler.py"
, line 147, in check_response
raise exception_class(message, screen, stacktrace)
ElementNotVisibleException: Message: u'Element must be displayed to click'
As you can see the code waits a lot to get the element loaded. I've also tried to set the element's is_displayed property to True but didn't work neither.
Note: the element that's not displayed is the one on the xpath search, it is present because I've logged it with dir(menu)
Edit:
The menu variable is not the menu itself it's one of the spans that are elements of the menu, doc is the Perfil html element getting clicked to show the dropdown.
Edit 2:
Inspecting the DOM on chrome tools, when you click a doc a new menu gets created on the tree, I don't know if it's because of an ajax call or vanilla js, I don't think it's really that important how it's created. I can't retrieve it from the page and make a python object from it, it's just not being displayed at least on code.
Final Edit:
I ended up executing some JavaScript to make it work. Apparently when Selenium finds the menu item the first element that triggers the menu drop down loses the focus and it makes the menu invisible again, if you don't select a menu item and wait for some time the menu dropdown still is shown, if you try to select one element from the menu the menu disappears.

Why don't you select an option like this
el = driver.find_element_by_id('id_of_select')
for option in el.find_elements_by_tag_name('option'):
if option.text == 'The Options I Am Looking For':
option.click() # select() in earlier versions of webdriver
If your click is not firing an ajax call to populate your list, you don't actually need to execute the click.

You need to find the link of the target. You don't really click elements, you click links... (or rather, you click elements with links inside them). That being said, the most sure fire way to click a link is to isolate the link element.
frame = driver.find_element_by_id('this_is_your_frame_name')
links = frame.find_elements_by_xpath('.//a')
links[1].click()
or alternatively,
for link in links:
if link.text() == "Administratar Permisos":
link.click()

Related

Understanding selenium move_to_element behaviour + StaleElementReference exception

I am using Python3.9+Selenium to write a small script that fills an online form for me.
A bit of context: the webpage contains a field (locationField) expecting a street address as input, and located on top of some sort of "google maps wrapper".
When typing in the field, it loads a drop-down list of one element (locationField_sugg) with the compatible complete address, and when this option is selected the map zooms-in on the chosen part of the city.
Other than this, there is a descriptionField to be filled with some random text, and then a submitButton to be clicked in order to send the form.
I noticed that if I use actionChains.move_to_element(locationField_sugg).click().perform() to click on the address in the drop-down list, then submitButton throws a StaleElementReference exception, while if I just use locationField_sugg.click() this is not the case, and the code proceeds as it should.
I've been reading through many Q/A about this notorious exception handling, but none of them seemed to explain the reason why this happens in my code.
For me it seems to be related to the behaviour of move_to_element() in combination with the "map wrapper" (?) but I do not understand why, since this function is just supposed to move the mouse in the middle of a given element.
No reload nor other changes in the webpage seem to happen (I verified that if I query the button at the beginning and re-query at the end of the script, I get the same exact instance representation string).
Besides, I query the submitButton right before performing an action on it, and I assume it is properly found since it can be printed.
Below there is a snippet of my code and of the output I get (Note: the code works properly if I use the alternative commented option, but I am curious to understand what I am missing)
CODE SNIPPET
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
websiteUrl = "https://mywebsite"
option = webdriver.ChromeOptions()
option.add_argument("-incognito")
browser = webdriver.Chrome(executable_path="/Applications/chromedriver", options=option)
browser.get(websiteUrl)
actionChains = ActionChains(browser)
# Write and select complete address
locationField = browser.find_element_by_id("location")
print("locationField = ", locationField)
locationField.send_keys("my location")
locationField_sugg = WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.ID, "as-listbox")))
print("locationField_sugg = ", locationField_sugg)
#
# this throws stale element reference exception:
actionChains.move_to_element(locationField_sugg).click().perform()
#
# this does not:
# locationField_sugg.click()
# Write description
descriptionField = browser.find_element_by_id("description")
print("descriptionField = ", descriptionField)
descriptionField.send_keys("my description")
# Submit form
submitButton = WebDriverWait(browser, 10).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "span.value")))
print("submitButton = ", submitButton)
actionChains.move_to_element(submitButton).click().perform()
OUTPUT
locationField = <selenium.webdriver.remote.webelement.WebElement (session="e10dc716790c61a0c160599624dd30c6", element="e75539a7-ebd0-4090-9c04-0c4994afe03f")>
locationField_sugg = <selenium.webdriver.remote.webelement.WebElement (session="e10dc716790c61a0c160599624dd30c6", element="53ef0269-aceb-451a-b559-d9e5e7aa7851")>
descriptionField = <selenium.webdriver.remote.webelement.WebElement (session="e10dc716790c61a0c160599624dd30c6", element="dce478de-a23d-4ca2-817c-81ee5ce0c232")>
nowButton = <selenium.webdriver.remote.webelement.WebElement (session="e10dc716790c61a0c160599624dd30c6", element="f2036f1c-d164-4211-a37c-2ee50e5c55c1")>
Traceback (most recent call last):
File "/Users/alice/Desktop/wasteComplaints_selenium.py", line 44, in <module>
actionChains.move_to_element(nowButton).click().perform()
File "/Users/alice/miniconda3/lib/python3.9/site-packages/selenium/webdriver/common/action_chains.py", line 80, in perform
self.w3c_actions.perform()
File "/Users/alice/miniconda3/lib/python3.9/site-packages/selenium/webdriver/common/actions/action_builder.py", line 76, in perform
self.driver.execute(Command.W3C_ACTIONS, enc)
File "/Users/alice/miniconda3/lib/python3.9/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
self.error_handler.check_response(response)
File "/Users/alice/miniconda3/lib/python3.9/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
(Session info: chrome=92.0.4515.131)
I'm not sure I know why this occurs without debugging it. Maybe the suggested address is disappearing at the moment when focus is removed from the input field? If so, when you insert the address string to the input field and then instantly clicking on the suggested address it works correct, but if after inserting the input address string and the suggested address appears you are applying actionChains.move_to_element this moves the mouse from it's initial position to the suggested address element. So the focus is moved from input field to the mouse cursor. This causes the suggested address to disappear and this is why StaleElementReference exception is thrown.

Python/Selenium button click on class does not work

<fieldset class = "A">
::before
<button class = "ButtonA" type ="submit">
::before
Click Me
</button>
::after
</fieldset>
I am trying to open a page and click a button on the page. However, no matter what function call I make it throws the same message telling my the class does not exist, however, I'm positive it does. I don't see it nested in a iFrame. So what am I not understanding here, also why does it throw a error for a css selector if I am looking for an html class.
I get the following error
File "C:\Python38\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 564, in find_element_by_class_name
return self.find_element(by=By.CLASS_NAME, value=name)`
File "C:\Python38\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 976, in find_element
return self.execute(Command.FIND_ELEMENT, { `
File "C:\Python38\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 321, in execute
self.error_handler.check_response(response)`
File "C:\Python38\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 242, in check_response`
raise exception_class(message, screen, stacktrace)selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":".ButtonA"}`
driver.get("https://www.websiteWithButton.com")
#ensure html is loaded
driver.implicitly_wait(10)
#check for iFrame
#driver.switch_to.frame('PossibleiFrame')
#ButtonA is a a class that exists, I'm positive.
button = driver.find_element_by_class_name('ButtonA')
button.click()
I just give my entire solution.Base on your step
driver.get("https://www.websiteWithButton.com")
#ensure html is loaded
driver.implicitly_wait(10)
#check for iFrame
#driver.switch_to.frame('PossibleiFrame')
#ButtonA is a a class that exists, I'm positive.
button_list = driver.find_elements_by_tag('button')
for i, x in enumerate(button_list):
print(f'Index: {i}')
x.click() # you can find the index of ButtonA you want by click them step by step
After you get the index of ButtonA, just click it directly
button_list[index].click().
the index reflect the position of ButtonA in page source, as long as page source unchange, you can access it.
This might not be a smart way, but at least you can find button element you want as long as you are in correct page.

How do I get selenium WebDriver in Python to wait for 5 minutes after a particular form submission?

I don't want to change the implicit wait for the WebDriver, because this problem happens only on a particular click.
Please Note: There is no code executed after the click statement. This isn't the usual wait failure. I tried adding a simple "print("test") after the driver.find_element_by_xpath(xpath).click() but execution fails at the click itself. The print is not executed. Webdriver times out while waiting for the page to load (upwards of 5 minutes).
I need to submit a particular form. This form takes a lot of time after clicking on the submit button. It can take upto 5 minutes to load the next page.
WebDriver times out after clicking this button with a "TimeOut"
exception.
Here is the stack trace:
Traceback (most recent call last):
File
"C:/Users/user.domain/PycharmProjects/server_config/server_config.py",
line 704, in
# Apply the reset
File "C:/Users/user.domain/PycharmProjects/server_config/server_config.py",
line 222, in server_reset
logger.info("Resetting the server config")
File "C:\Program Files (x86)\Python36-32\lib\site-packages\selenium\webdriver\remote\webelement.py",
line 80, in click
self._execute(Command.CLICK_ELEMENT)
File "C:\Program Files (x86)\Python36-32\lib\site-packages\selenium\webdriver\remote\webelement.py",
line 501, in _execute
return self._parent.execute(command, params)
File "C:\Program Files (x86)\Python36-32\lib\site-packages\selenium\webdriver\remote\webdriver.py",
line 308, in execute
self.error_handler.check_response(response)
File "C:\Program Files (x86)\Python36-32\lib\site-packages\selenium\webdriver\remote\errorhandler.py",
line 194, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.TimeoutException: Message: timeout
(Session info: chrome=75.0.3770.100)
(Driver info: chromedriver=74.0.3729.6 (255758eccf3d244491b8a1317aa76e1ce10d57e9-refs/branch-heads/3729#{#29}),platform=Windows
NT 10.0.17763 x86_64)
Sometimes the page errors out and doesn't even show the landing page. On other occasions it takes 5 minutes or even more.
How can I handle this particular click? What is a more elegant way to handle the wait request instead of changing the implicit wait values before and after this click?
implicit wait doesn't effect the page load, it's only relevant to locating web elements with find_element() functions.
You can use driver.set_page_load_timeout(timeout) to increase the time the driver awaits the page to load (although according to the w3c specification the default value is already 5 minutes).
In case the page doesn't load at all you can use try except if you wish to do something else than terminating with exception
try:
driver.find_element_by_xpath(xpath).click()
except TimeoutException:
print('Failed to load next page')
# do something
# or rethrow the exception with 'raise'
I meant something like:
WebElement.click();
int counter = 0;
do
{
switch (elementStatus)
{
case "Element is not present":
Thread.sleep(100000);
counter++;
if (counter == 4)
throw new AutomationException("Form submission is taking time. Please check manually.");
break;
case "Element is present":
counter = 4;
break;
}
} while (counter < 4);
Check for the html element which exists in the case of both successful and invalid form submission,if it exists wait for that particular html element state/value change.
For example , below code is in java.
WebDriverWait wait = new WebDriverWait(driver, 10);
element = wait.until(ExpectedConditions.visibilityOfElementLocated(By.id(<message>)));
String result = null;
if(element.isDisplayed() && element.getText().contains("Successfully"))
result = driver.findElement(By.id(<name>)).getText();
else
result = element.getText();

Xpath exists in chrome, but not in my script. no such element: Unable to locate element:

I am trying to find the button element by xpath, which is found when I type it in chrome, but the script gives me the no attribute id error. I have tried switching into an iframe and frame, I have used webdriver.wait to wait for the button element to show and none of those work. I would also like to cycle through and click the first button if it says "Follow" and then move to the next button if it says "Follow".The Script runs on chrome and I am trying to do this on instagram Html here
popup = browser.find_element_by_xpath('//button[#class="_qv64e _gexxb _4tgw8 _njrw0"]')
ActionChains(browser)\
.move_to_element(popup).click()\
.perform()
File "/Users/trevaroneill/PycharmProjects/Insta/instafollow.py", line 91, in <module>
popup = browser.find_element_by_xpath('//button[#class="_qv64e _gexxb _4tgw8 _njrw0"]')
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//button[#class="_qv64e _gexxb _4tgw8 _njrw0"]"}
Since you are using find_elements, you should rather write:
ActionChains(browser)\
.move_to_element(popup[0]).click()\
.perform()
in order to access first element of the list returned by find_elements.
The problem is if you have several webelements selected by your xpath, in this case, it is not sure that the first one is the one you actually aim at. I would suggest you use find_element if there are no particular reason for which you are using find_elements
You are getting the wanted element in a list.
change
popup = browser.find_elements_by_xpath('//button[#class="_qv64e _gexxb _4tgw8 _njrw0"]')
into
popup = browser.find_element_by_xpath('//button[#class="_qv64e _gexxb _4tgw8 _njrw0"]')
find_element and not find_elements.

Selenium with Python: Stale Element Reference Exception

Working from Test Driven Development with Python, and I'm currently encountering a 'StaleElementReferenceException' when running the functional test immediately after migration. Here's the full text of the error:
ERROR: test_start_and_retrieve_list (__main__.NewVisitorTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "functional_tests.py", line 53, in test_start_and_retrieve_list
rows = table.find_elements_by_tag_name('tr')
File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 237, in find_elements_by_tag_name
return self.find_elements(by=By.TAG_NAME, value=name)
File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 527, in find_elements
{"using": by, "value": value})['value']
File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webelement.py", line 493, in _execute
return self._parent.execute(command, params)
File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/webdriver.py", line 256, in execute
self.error_handler.check_response(response)
File "/usr/local/lib/python3.5/dist-packages/selenium/webdriver/remote/errorhandler.py", line 194, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <table id="id_list_table"> stale: either the element is no longer attached to the DOM or the page has been refreshed
----------------------------------------------------------------------
Ran 1 test in 8.735s
FAILED (errors=1)
Here's the test:
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import unittest
class NewVisitorTest(unittest.TestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
def tearDown(self):
self.browser.close()
def check_for_row(self, row_text):
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.assertIn(row_text, [row.text for row in rows])
def test_start_and_retrieve_list(self):
self.browser.get('http://localhost:8000')
self.assertIn('To-Do', self.browser.title)
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
inputbox = self.browser.find_element_by_id('id_new_item')
self.assertEqual(
inputbox.get_attribute('placeholder'),
'Enter a to-do item'
)
inputbox.send_keys('Buy peacock feathers')
inputbox.send_keys(Keys.ENTER)
self.check_for_row('1: Buy peacock feathers')
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Use peacock feathers to make a fly')
inputbox.send_keys(Keys.ENTER)
table = self.browser.find_element_by_id('id_list_table')
rows = table.find_elements_by_tag_name('tr')
self.check_for_row('1: Buy peacock feathers')
self.check_for_row('2: Use peacock feathers to make a fly')
self.fail('Finish the test!')
if __name__ == '__main__':
unittest.main(warnings='ignore')
How do I configure the test to prevent this? Selenium's own page says this issue can occur when the page refreshes, but this is a necessary part of the application logic as it's configured so far.
Add these imports:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
Change these lines
inputbox.send_keys(Keys.ENTER)
self.check_for_row('1: Buy peacock feathers')
to:
inputbox.send_keys(Keys.ENTER)
WebDriverWait(self.browser, 10).until(
expected_conditions.text_to_be_present_in_element(
(By.ID, 'id_list_table'), 'Buy peacock feathers'))
self.check_for_row('1: Buy peacock feathers')
This replaces the time.sleep(1) with something more "reasonable"
I have been using selenium for a while now so I understand the struggles of the Stale Element Exception. While not perfect, selenium provides a series of "wait" commands to allow for the website to load complete. Unfortunately, its not perfect as loading can take different time on each run, but these are the tools provided by selenium.
I haven't worked in python but have worked on java/selenium. But,I can give you the idea to overcome staleness.
Generally we will be getting the Stale Exception if the element attributes or something is changed after initiating the webelement. For example, in some cases if user tries to click on the same element on the same page but after page refresh, gets staleelement exception.
To overcome this, we can create the fresh webelement in case if the page is changed or refreshed. Below code can give you some idea.(It's in java but the concept will be same)
Example:
webElement element = driver.findElement(by.xpath("//*[#id='StackOverflow']"));
element.click();
//page is refreshed
element.click();//This will obviously throw stale exception
To overcome this, we can store the xpath in some string and use it create a fresh webelement as we go.
String xpath = "//*[#id='StackOverflow']";
driver.findElement(by.xpath(xpath)).click();
//page has been refreshed. Now create a new element and work on it
driver.fineElement(by.xpath(xpath)).click(); //This works
Another example:
for(int i = 0; i<5; i++)
{
String value = driver.findElement(by.xpath("//.....["+i+"]")).getText);
System.out.println(value);
}
Hope this helps you. Thanks
To prevent an element to become stale, place a new element on the current page, hit the link and wait until the element is not available anymore. Then wait for an element on the new page to appear
script_to_execute = """
var new_element = document.createElement('span');
new_element.setAttribute('id', 'wait4me');
document.body.appendChild(new_element);"""
self.driver.execute_script(script_to_execute)
self.driver.find_element_by_xpath("{}".format(locator)).click()
WebDriverWait(self.driver, self.time_out).until (
lambda x: not x.find_elements_by_id("wait4me"))
This issue happens when the loop starts before an updated page has fully loaded. Especially when you update a page in an application or a form.
One workaround is to place an element on the current page, then update and use the WebDriverWait statement until the element is not found anymore.
Then start your loop. (Otherwise the reload happens during the loop...)
I read the same book as you do and encountered the same problem (solutions from this page didn't work for me).
Here's how I resolved it.
Problem
Exception is thrown whenever you try to access a stale object. So we have to wait for situation when this exception is NOT thrown anymore.
My solution
I created method that waits for my actions until they pass
from selenium.common.exceptions import StaleElementReferenceException
[...]
def stale_aware_for_action(self, action):
while(True):
try:
action()
break
except StaleElementReferenceException:
continue
And in test method I defined actions that I want to wait to finish:
def test_can_start_a_list_and_retrieve_it_later(self):
[...]
def insert_second_item_to_inputbox():
inputbox = self.browser.find_element_by_id('id_new_item')
inputbox.send_keys('Use peacock feathers to make a fly')
inputbox.send_keys(Keys.ENTER)
self.stale_aware_for_action(insert_second_item_to_inputbox)
def check_for_first_item():
self.check_for_row_in_list_table('1: Buy peacock feathers')
def check_for_second_item():
self.check_for_row_in_list_table('2: Use peacock feathers to make a fly')
self.stale_aware_for_action(check_for_first_item)
self.stale_aware_for_action(check_for_second_item)

Categories