mherrmann / helium

Lighter web automation with Python
MIT License
4.93k stars 371 forks source link

StaleElementReferenceException when matching elements exist in both iframe and main page #14

Open thoughtextreme opened 4 years ago

thoughtextreme commented 4 years ago

Hi, I have an SPA that I'm trying to do some automated testing with

helium works fine when the elements are in the main page, but the app uses AJAX to load some content into an iframe, so when I try to find those elements to click on, I get an exception

the following demonstrates the issue:

$ python3
Python 3.7.4 (default, Oct 21 2019, 15:59:56) 
[Clang 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from helium import *
>>> driver=start_firefox()
>>> driver.current_url
'about:blank'
>>> go_to('http://loki.local')
>>> driver.current_url
'http://loki.local/'

>>> find_all(Text('Skip'))
[<div class="cursorPointer carouselSkip skipPurpleText" data-bind="text: tr.MSG_Skip, click: onSkip, css: skipAnimationCss, aria: {role:'button', tabIndex: 3, label: tr.MSG_SkipOnboardingInfo}">Skip</div>]

>>> find_all(S('span'))
[<span class="carouselPageTitleText" data-bind="text: title">Welcome!</span>]

>>> driver.switch_to.active_element
<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="3efdb9c8-c699-7542-acdc-e4fd291ecad9", element="dfd8926b-98f8-b547-ac3a-46bb38ccb389")>
>>> driver.find_elements_by_tag_name('body')
[<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="3efdb9c8-c699-7542-acdc-e4fd291ecad9", element="dfd8926b-98f8-b547-ac3a-46bb38ccb389")>]

>>> driver.find_elements_by_tag_name('iframe')
[<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="3efdb9c8-c699-7542-acdc-e4fd291ecad9", element="bd9232e8-2444-ca4b-b64e-256d579c96b4")>]
>>> find_all(S('iframe'))
[<iframe id="appTepIFrame" name="appTepIFrame" allowfullscreen="" mozallowfullscreen="" webkitallowfullscreen="" oallowfullscreen="" msallowfullscreen="" data-bind="attr: {src: activeTepUrl, tabIndex: getTabIndex()}" tabindex="-1" src="http://loki.local/src/tep.html" width="100%" height="100%" frameborder="0"></iframe>]
>>> iframe=_[0]
>>> iframe.web_element
<selenium.webdriver.firefox.webelement.FirefoxWebElement (session="3efdb9c8-c699-7542-acdc-e4fd291ecad9", element="bd9232e8-2444-ca4b-b64e-256d579c96b4")>

>>> find_all(S('body'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/helium/__init__.py", line 584, in __repr__
    element_html = self.web_element.get_attribute('outerHTML')
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webelement.py", line 141, in get_attribute
    self, name)
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 636, in execute_script
    'args': converted_args})['value']
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <body> 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

>>> driver.switch_to.default_content()
>>> iframe.web_element.get_attribute('src')
'http://loki.local/src/tep.html'
>>> driver.switch_to.frame(0)
>>> iframe.web_element.get_attribute('src')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webelement.py", line 141, in get_attribute
    self, name)
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 636, in execute_script
    'args': converted_args})['value']
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "/Users/fulcrum/src/te-automation/venv/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <iframe id="appTepIFrame" name="appTepIFrame" src="http://loki.local/src/tep.html"> 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
thoughtextreme commented 4 years ago

Here's an even simpler repro case

sanjuro2:~ fulcrum$ cat inner.html 
<html>
<head>
<title>Inner</title>
</head>
<body>
Inner
</body>
</html>

sanjuro2:~ fulcrum$ cat outer.html
<html>
<head>
<title>Outer</title>
</head>
<body>
Outer
<iframe src="inner.html">iframe</iframe>
</body>
</html>

sanjuro2:~ fulcrum$ source ~/venv/helium/bin/activate
(helium) sanjuro2:~ fulcrum$ python3
Python 3.7.3 (default, Mar 27 2019, 09:23:15) 
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from helium import *
>>> driver=start_firefox()
>>> go_to('file:///Users/fulcrum/inner.html')
>>> driver.current_url
'file:///Users/fulcrum/inner.html'
>>> find_all(S('body'))
[<body>Inner</body>]
>>> go_to('file:///Users/fulcrum/outer.html')
>>> driver.current_url
'file:///Users/fulcrum/outer.html'
>>> find_all(S('body'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/fulcrum/venv/helium/lib/python3.7/site-packages/helium/__init__.py", line 563, in __repr__
    element_html = self.web_element.get_attribute('outerHTML')
  File "/Users/fulcrum/venv/helium/lib/python3.7/site-packages/selenium/webdriver/remote/webelement.py", line 141, in get_attribute
    self, name)
  File "/Users/fulcrum/venv/helium/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 636, in execute_script
    'args': converted_args})['value']
  File "/Users/fulcrum/venv/helium/lib/python3.7/site-packages/selenium/webdriver/remote/webdriver.py", line 321, in execute
    self.error_handler.check_response(response)
  File "/Users/fulcrum/venv/helium/lib/python3.7/site-packages/selenium/webdriver/remote/errorhandler.py", line 242, in check_response
    raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.StaleElementReferenceException: Message: The element reference of <body> 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
mherrmann commented 4 years ago

I'll be happy to accept a pull request that adds a test for this and fixes it :-)

karthik233 commented 2 years ago

hi @mherrmann

The problem related to occurrence of stale elements is due to "web_element.get_attribute" happening on main/parent web element. When iframes present we need to switch to the iframe and then execute "web_element.get_attribute('outerHTML')"

The following are code changes, if you think this makes any sense can raise a PR and checkin

in "/helium/init.py" file, class "class HTMLElement(GUIElement)" ,we can ----------------------------------------- CODE SNIP STARTS ----------------------------------------- ` def repr(self):

if self._is_bound():
    try:
        element_html = self.web_element.get_attribute('outerHTML')
    except:
        for new_frame in range(sys.maxsize):
            try:
                self._driver.switch_to.frame(new_frame)
            except WebDriverException:
                break
        element_html = self.web_element.get_attribute('outerHTML')              
    return get_easily_readable_snippet(element_html)
else:
    return super(HTMLElement, self).__repr__()`

----------------------------------------- CODE SNIP ENDS -----------------------------------------

mherrmann commented 2 years ago

@karthik233 thanks. There are a few problems with the snippet you posted I think:

  1. try ... except without any exception type is too general. Ideally, it should catch precisely the exception type that can be raised by .get_attribute(...).
  2. I feel like if switch_to.frame(0) above fails for any reason, then element_html will be undefined.
  3. switch_to(...) in a loop up to sys.maxsize for a simple __repr__ call seems overkill, especially if switch_to(...) changes the web driver's state. I would just try ... except <appropriate exception type> and in the case of that exception return a string such as "<StaleElementException occurred computing __repr__>".
karthik233 commented 2 years ago

Hi @mherrmann ,

thanks for your reply. will improve. Any other corner cases we need to cover?