Selenium WebDriver crashes when I try to get innerText from an element container - selenium

I am trying to get the innerText from all themessage.spoilers-container, but when I scroll up the webpage, the program crashes, and give me an error.
Code:
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
def find_message_container(driver):
try:
elements = driver.execute_script("return document.querySelectorAll('.message.spoilers-container')")
unique_texts = set()
for element in elements:
text = element.get_attribute("innerText")
if text not in unique_texts:
unique_texts.add(text)
with open("unique_texts.txt", "w") as file:
for text in unique_texts:
file.write("\n" + text + "\n")
except NoSuchElementException as e:
print('Could not find the given element container. The following exception was raised:\n', e)
pass
return unique_texts
Error:
Traceback (most recent call last):
File "c:\~\Desktop\Project\file.py", line 11, in find_message_container
text = element.get_attribute("innerText")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\~\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webelement.py", line 179, in get_attribute
attribute_value = self.parent.execute_script(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\~\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 506, in execute_script
return self.execute(command, {"script": script, "args": converted_args})["value"]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\~\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\webdriver.py", line 444, in execute
self.error_handler.check_response(response)
File "C:\~\AppData\Local\Programs\Python\Python311\Lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 249, 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=109.0.5414.120)
What could cause this problem? The website I am testing this on is Web Telegram. Everytime new chats is loaded by scrolling up, a new container appears.
Any help would be helpful, I tried with some wait statements and wait.until, but it did not work.

I don't have a Web Telegram account so I can't test this but I would change these things:
The main issue is the StaleElementReferenceException. A stale element is an element that you assigned to a variable, the page changed, and then you try to perform a .click() or .text on the element. Once the page changes, the reference you had is gone... it now points to nothing. A quick code example of how this happens
element = driver.find_element(locator) # got a reference
# while doing stuff, page changes
value = element.text # accessing the element using .text throws the exception
To avoid this, you want to refetch the reference before accessing it
element = driver.find_element(locator)
# while doing stuff, page changes
element = driver.find_element(locator) # refetch the element
value = element.text
In your case, this is happening because of the loop through the messages. You create your list before the loop so if the elements change while looping, the exception is thrown. The way to fix this is to refetch the elements within the loop.
for element in driver.find_elements(...)
One potentially big problem is that if you are in a fast moving chat with lots of new messages constantly, your script may not be able to keep up since the page DOM seems to change with each new message. NOTE: This is an assumption based on your comments.
Prefer the native API instead of using driver.execute_script() to find elements. Replace
elements = driver.execute_script("return document.querySelectorAll('.message.spoilers-container')")
with
elements = driver.find_element(By.CSS_SELECTOR('.message.spoilers-container'))
Use .text instead of .get_attribute("innerText"). Replace
text = element.get_attribute("innerText")
with
text = element.text
Writing a file is a relatively slow operation. I would avoid writing until the loop is done.
Why are you returning unique_texts if you've already written them to file?
Here's my rewrite of your code based on these suggestions
def find_message_container(driver):
try:
unique_texts = set()
for element in driver.find_elements(By.CSS_SELECTOR('.message.spoilers-container')):
message = element.text
if message not in unique_texts:
unique_texts.add(message)
with open("unique_texts.txt", "w") as file:
for text in unique_texts:
file.write("\n" + text + "\n")
except NoSuchElementException as e:
print('Could not find the given element container. The following exception was raised:\n', e)
pass

The core exception is StaleElementReferenceException...
selenium.common.exceptions.StaleElementReferenceException: Message: stale element reference: element is not attached to the page document
...which implies by the time document.querySelectorAll() completes obtaining the NodeList of all of the matching elements in the document, some elements turns stale as new chats are loaded within a new container. In short the reference to the elements changes within the DOM Tree.
Solution
A possible solution would be to induce WebDriverWait for the visibility_of_all_elements_located() and you can use either of the following locator strategies:
Using CSS_SELECTOR:
elements = WebDriverWait(browser, 20).until(EC.visibility_of_all_elements_located((By.CSS_SELECTOR, ".message.spoilers-container")))
Using XPATH:
elements = WebDriverWait(browser, 20).until(EC.visibility_of_all_elements_located((By.XPATH, "//*[#class='message spoilers-container']")))
Note : You have to add the following imports :
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC

Related

Webdriver Selenium not loading new page after click()

I´m using selenium to scrape a webpage and it finds the elements on the main page, but when I use the click() function, the driver never finds the elements on the new page. I used beautifulSoup to see if it´s getting the html, but the html is always from the main. (When I see the driver window it shows that the page is opened).
html = driver.execute_script('return document.documentElement.outerHTML')
soup = bs.BeautifulSoup(html, 'html.parser')
print(soup.prettify)
I´ve used webDriverWait() to see if it´s not loading but even after 60 seconds it never does,
element = WebDriverWait(driver, 60).until(EC.presence_of_element_located((By.ID, "ddlProducto")))
also execute_script() to check if by clicking the button using javascript loads the page, but it returns None when I print a variable saving the new page.
selectProducto = driver.execute_script("return document.getElementById('ddlProducto');")
print(selectProducto)
Also used chwd = driver.window_handles and driver.switch_to_window(chwd[1]) but it says that the index is out of range.
chwd = driver.window_handles
driver.switch_to.window(chwd[1])

Selenium not able to locate the element

I'm trying to scrape data from https://in.puma.com/in/en/mens/mens-new-arrivals . The complete data is loaded when the show all button is clicked.
I used selenium to generate the click and load the rest of the page, however - I'm getting an error
"TimeoutException(message, screen, stacktrace)
selenium.common.exceptions.TimeoutException: Message: "
See my code below.
from selenium.webdriver.support import expected_conditions as EC
import time
from lxml import etree as et
chrome_driver_path = "driver/chromedriver"
url = 'https://in.puma.com/in/en/mens/mens-new-arrivals'
browser = webdriver.Chrome(ChromeDriverManager().install())
browser.get(url)
x_path_to_load_more = '//*[#data-component-id="a_tspn9cqoeth"]'
browser.execute_script("window.scrollTo(0,document.body.scrollHeight)")
button_locate = wait(browser,10).until(EC.presence_of_element_located((By.XPATH,x_path_to_load_more)))
button_locate.click()
The xpath is not correct, try
x_path_to_load_more = "//button[contains(., 'Show All')]"
To verify the effectiveness of the xpath inspect the page, open the find bar with Command + F or Control + F and paste your xpath
#data-component-id seem to be dynamic. It means that the value will be different (not "a_tspn9cqoeth") each time you open that page. Try to search by another attribiute value:
x_path_to_load_more = '//div[#class="text-center"]/button[contains(#class, "show-more-button")]'
or
x_path_to_load_all = '//div[#class="text-center"]/button[contains(#class, "show-all-button")]'
Also it's better to use EC.element_to_be_clickable instead of EC.presence_of_element_located
UPDATE
Since click on button might be intercepted by Cookies footer try to scroll page down before making click:
from selenium.webdriver.common.keys import Keys
driver.find_element('xpath', '//body').send_keys(Keys.END)

Different test behavior in Chrome and Firefox [duplicate]

I have a rather complex webpage setup I need to test, containing nested frames.
In the actual problem the selenium code is loading new webpage contents containing a frame, which I want to switch to. In order to avoid any explicit waits, I tried the following code snippet:
self.driver.switch_to_default_content()
WebDriverWait(self.driver, 300).\
until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame1')))
WebDriverWait(self.driver, 300).\
until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame2')))
However, this snippet always fails and results in the following error:
...
File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/wait.py", line 71, in until
value = method(self._driver)
File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/expected_conditions.py", line 247, in __call__
self.frame_locator))
File "/home/adietz/Projects/Venv/nosetests/local/lib/python2.7/site-packages/selenium/webdriver/support/expected_conditions.py", line 402, in _find_element
raise e
WebDriverException: Message: TypeError: can't access dead object
However, if I use a sleep in addition:
time.sleep(30)
self.driver.switch_to_default_content()
WebDriverWait(self.driver, 300).\
until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame1')))
WebDriverWait(self.driver, 300).\
until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'frame2')))
selenium is able to find the frame inside the frame and switch to it. It looks like in the error case selenium switches to 'frame1' while 'frame2' is not yet loaded, but 'frame2' gets loaded in some other instance of 'frame1', or not recognized by selenium (maybe a bug?). So now selenium is inside some 'frame1' and for some reasons does not realize that the 'frame2' has been loaded.
The only way I can fix this (without using a long sleep) is by using this ugly piece of code:
mustend = time.time() + 300
while time.time() < mustend:
try:
self.driver.switch_to_default_content()
self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
break
except WebDriverException as e:
self.log("Sleeping 1 sec")
time.sleep(1)
if time.time() > mustend:
raise TimeoutException
So whenever I get a WebDriverException (dead object), I go to the top-level frame and try to switch to the inner frame - frame by frame.
Is there any other approach I can try?
Additional information
The iframes are nested, i.e. 'frame2' is inside 'frame1'.
Better approach is to make your own expected_condition.
For example:
class nested_frames_to_be_available_and_switch:
def __init__(self, *args):
"""
:param args: locators tuple of nested frames (BY.ID, "ID1"), (BY.ID, "ID2"), ...
"""
self.locators = args
def __call__(self, driver):
try:
for locator in self.locators:
driver.switch_to.frame(driver.find_element(*locator))
except WebDriverException:
driver.switch_to_default_content()
return False
return True
WebDriverWait(driver, 300).until(nested_frames_to_be_available_and_switch((By.ID, 'frame1'), (By.ID, 'frame1')))
But maybe there is no need for that.. To tell so I need to see your html DOM.
This error message...
WebDriverException: Message: TypeError: can't access dead object
...implies that there was an error while switching between <iframes>.
Some more information in terms of:
The relevant HTML
Presence of Frameset
Presence of Frames
Hierarchy of Nested Frames
Sequence of Frame Loading
Presence of JavaScript and AJAX Calls within the respective <iframe> tags
would have helped us to analyze the issue in a better way. However, at this point it is worth to mention that initially Selenium always gets the focus on the default_content. Here are a few approaches to work with nested <frames> and <framesets>:
If both frame1 and frame2 are at same level i.e. under the Top Level Browsing Context you need to:
self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
self.driver.switch_to_default_content()
self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
If frame2 is nested within frame1, to switch from frame1 to frame2 you need to:
self.driver.switch_to_default_content()
self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
//code
self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
If frame2 and frame3 is within frame1 then, to switch from frame2 to frame3 you need to:
self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
self.driver.switch_to.frame(self.driver.find_element_by_id("frame3"))
If frame2 and frame3 is within a frameset23 which is within frame1 then, to switch from frame2 to frame3 you need to:
self.driver.switch_to.frame(self.driver.find_element_by_id("frame2"))
#ignore presence of frameset23
self.driver.switch_to.frame(self.driver.find_element_by_id("frame1"))
#ignore presence of frameset23
self.driver.switch_to.frame(self.driver.find_element_by_id("frame3"))
Better approach with proper WebDriverWait
While dealing with iframe and frameset you need to induce WebDriverWait inconjunction with expected_conditions:
frame_to_be_available_and_switch_to_it()
As an example to switch from Top Level Browsing Context to an <iframe> an effective line of code will be:
Using frame ID:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.ID,"frameID")))
Using frame NAME:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.NAME,"frameNAME")))
Using frame CLASS_NAME:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.frame_CLASS_NAME,"u_0_f")))
Using frame CSS_SELECTOR:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR,"frame_CSS_SELECTOR")))
Using frame XPATH:
WebDriverWait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it((By.XPATH,"frame_XPATH")))
tl; dr
Ways to deal with #document under iframe

selenium button click python

I can't click a button containing JavaScript:
<div style="width:100%; float:left;"><div class="btn btn-search" onclick="javascript: search(); " style="cursor:pointer;width:100%" align="center">Ara</div></div>
I found the element, but the code below doesn't click:
browser.find_element_by_xpath('//div[#class="btn btn-search"]').click()
or
browser.find_element_by_xpath('//div[#onclick="javascript:"]').click()
This message is returned:
Message: unknown error: Element is not clickable at point (1153, 417)
From the error it looks like.
The element is loaded into the DOM, but the position is not fixed on the UI yet.
There can be some other divs not getting loaded completely.
Possible solutions:
Use WebdriverWait with click()
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(browser, 30)
element = wait.until(EC.visibility_of_element_located((By.XPATH, //div[#class="btn btn-search"]')))
element.click()
Use WebdriverWait with Javascript execution
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(browser, 30)
element = wait.until(EC.visibility_of_element_located((By.XPATH, '//div[#class="btn btn-search"]')))
browser.execute_script("arguments[0].click();", element)
Further Reference: https://www.seleniumeasy.com/selenium-tutorials/element-is-not-clickable-at-point-selenium-webdriver-exception
Also when you frame question please share,
Full error and the code (especially the line before you are trying to
click).
Other details such as browser (from error looks like Chrome browser
as it is indicating point location).
This will help the community understand your issue more clearly.

Having issues while running Robot framework script for safari [duplicate]

I am using explicit wait like below to check if element is clickable.
WebDriverWait(driver, 30).until(
expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, "#search")))
But I get error
<class 'selenium.common.exceptions.WebDriverException'>
Message: The command 'GET /session/.../displayed' was not found.
If I use time.sleep() it works fine instead of explicir wait it works fine. I have initialized safari driver as
from selenium.webdriver import Safari
driver = Safari()
Here is stacktrace
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/support/wait.py", line 71, in until
value = method(self._driver)
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/support/expected_conditions.py", line 283, in __call__
element = visibility_of_element_located(self.locator)(driver)
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/support/expected_conditions.py", line 127, in __call__
return _element_if_visible(_find_element(driver, self.locator))
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/support/expected_conditions.py", line 147, in _element_if_visible
return element if element.is_displayed() == visibility else False
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/remote/webelement.py", line 490, in is_displayed
return self._execute(Command.IS_ELEMENT_DISPLAYED)['value']
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/remote/webelement.py", line 628, in _execute
return self._parent.execute(command, params)
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/remote/webdriver.py", line 314, in execute
self.error_handler.check_response(response)
File "/Users/Library/Python/2.7/lib/python/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
raise exception_class(message, screen, stacktrace)
WebDriverException: Message: The command 'GET /session/7.../displayed' was not found.
This error message...
<class 'selenium.common.exceptions.WebDriverException'>
Message: The command 'GET /session/.../displayed' was not found.
...implies that the GET request to /session/{session id}/element/{element id}/displayed failed.
element displayed
As per the WebDriver W3C Editor's Draft the element displayed algorithm is a boolean state where true signifies that the element is displayed and false signifies that the element is not displayed. To compute the state on element, invoke the Call(bot.dom.isShown, null, element). If doing so does not produce an error, return the return value from this function call. Otherwise return an error with error code unknown error.
This function is typically exposed to GET requests with a URI Template of
/session/{session id}/element/{element id}/displayed
Analysis
You have invoked:
WebDriverWait(driver, 30).until(expected_conditions.element_to_be_clickable((By.CSS_SELECTOR, "#search")))
element_to_be_clickable() class internally invokes visibility_of_element_located() and is defined as:
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
Now, visibility_of_element_located() class returns _element_if_visible(_find_element(driver, self.locator)) which is defined as:
class visibility_of_element_located(object):
""" An expectation for checking that an element is present on the DOM of a
page and visible. Visibility means that the element is not only displayed
but also has a height and width that is greater than 0.
locator - used to find the element
returns the WebElement once it is located and visible
"""
def __init__(self, locator):
self.locator = locator
def __call__(self, driver):
try:
return _element_if_visible(_find_element(driver, self.locator))
except StaleElementReferenceException:
return False
Again, _element_if_visible() returns element if element.is_displayed() == visibility else False which is defined as:
def _element_if_visible(element, visibility=True):
return element if element.is_displayed() == visibility else False
So, as the if() condition of element.is_displayed() == visibility failed hence you see the error as GET request to the /session/{session id}/element/{element id}/displayed endpoint failed.
Without the relevant HTML and previous steps it is tough to guess the exact reason but possibly can be either of the following:
Reason
The reason for NoSuchElementException can be either of the following :
The Locator Strategy you have adopted doesn't identifies any element in the HTML DOM.
The Locator Strategy you have adopted is unable to identify the element as it is not within the browser's Viewport.
The Locator Strategy you have adopted identifies the element but is invisible due to presence of the attribute style="display: none;".
The Locator Strategy you have adopted doesn't uniquely identifies the desired element in the HTML DOM and currently finds some other hidden / invisible element.
The WebElement you are trying to locate is within an <iframe> tag.
Solution
The solution to address NoSuchElementException can be either of the following :
Adopt a Locator Strategy which uniquely identifies the desired WebElement. You can take help of the Developer Tools (Ctrl+Shift+I or F12) and use Element Inspector.
Here you will find a detailed discussion on how to inspect element in selenium3.6 as firebug is not an option any more for FF 56?
Use execute_script() method to scroll the element in to view as follows :
elem = driver.find_element_by_xpath("element_xpath")
driver.execute_script("arguments[0].scrollIntoView();", elem)
Here you will find a detailed discussion on Scrolling to top of the page in Python using Selenium
Incase element is having the attribute style="display: none;", remove the attribute through executeScript() method as follows :
elem = driver.find_element_by_xpath("element_xpath")
driver.execute_script("arguments[0].removeAttribute('style')", elem)
elem.send_keys("text_to_send")
To check if the element is within an <iframe> traverse up the HTML to locate the respective <iframe> tag and switchTo() the desired iframe through either of the following methods :
driver.switch_to.frame("iframe_name")
driver.switch_to.frame("iframe_id")
driver.switch_to.frame(1) // 1 represents frame index
Here you can find a detailed discussion on How can I select a html element no matter what frame it is in in selenium?.