Repository: robotframework/SeleniumLibrary

Issue:
Wait... keywords should not fail StaleElementReferenceException
The `Wait ...` keywords can fail to `StaleElementReferenceException` because in between finding the element and doing action with the element (like checking is the element displayed) the DOM may change. Finding the element and doing something with the element are always two different Selenium API calls, even when doing it Python way: `self.find_element(locator).text` and therefore there is always small delay between the calls. 

There are various reasons why the DOM may change, but the `Wait ...` keywords should not fail, in middle of the waiting, if Selenium raises `StaleElementReferenceException`. Change the `Wait ...` keywords to suppress the `StaleElementReferenceException` and log it. 

This issue replaces the #475



Discussion:

Patch:
diff --git a/src/SeleniumLibrary/keywords/waiting.py b/src/SeleniumLibrary/keywords/waiting.py
index d5b6dde4..755f815d 100644
--- a/src/SeleniumLibrary/keywords/waiting.py
+++ b/src/SeleniumLibrary/keywords/waiting.py
@@ -16,6 +16,8 @@
 
 import time
 
+from selenium.common.exceptions import StaleElementReferenceException
+
 from SeleniumLibrary.base import LibraryComponent, keyword
 from SeleniumLibrary.errors import ElementNotFound
 from SeleniumLibrary.utils import is_noney, secs_to_timestr
@@ -228,6 +230,9 @@ class WaitingKeywords(LibraryComponent):
                     return
             except ElementNotFound as err:
                 not_found = str(err)
+            except StaleElementReferenceException as err:
+                self.info('Suppressing StaleElementReferenceException from Selenium.')
+                not_found = err
             else:
                 not_found = None
             time.sleep(0.2)
diff --git a/tasks.py b/tasks.py
index ac75e2ed..c9ddd181 100644
--- a/tasks.py
+++ b/tasks.py
@@ -41,7 +41,9 @@ to install the latest available release or use
 to install exactly this version. Alternatively you can download the source
 distribution from PyPI_ and install it manually.
 
-SeleniumLibrary {version} was released on {date}.
+SeleniumLibrary {version} was released on {date}. SeleniumLibrary supports
+Python **ADD VERSIONS**, Selenium **ADD VERSIONS** and
+Robot Framework **ADD VERSIONS**.
 
 .. _Robot Framework: http://robotframework.org
 .. _SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary


Test patch:
diff --git a/utest/test/keywords/test_waiting_stale_element_refereance_exception.py b/utest/test/keywords/test_waiting_stale_element_refereance_exception.py
new file mode 100644
index 00000000..49e2e80a
--- /dev/null
+++ b/utest/test/keywords/test_waiting_stale_element_refereance_exception.py
@@ -0,0 +1,84 @@
+import unittest
+
+from selenium.common.exceptions import StaleElementReferenceException
+from mockito import mock, when, unstub
+
+from SeleniumLibrary.keywords import WaitingKeywords
+
+
+def _raise(*a):
+    raise StaleElementReferenceException('Darn')
+
+
+class TableKeywordsTest(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.ctx = mock()
+        cls.waiting = WaitingKeywords(cls.ctx)
+        cls.timeout = 0.5
+        cls.count = 0
+
+    def tearDown(self):
+        unstub()
+
+    def test_wait_until_element_is_visible(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, required=False).thenReturn(element)
+        when(element).is_displayed().thenRaise(StaleElementReferenceException()).thenReturn(True)
+        self.waiting.wait_until_element_is_visible(locator, self.timeout)
+
+    def test_wait_until_element_is_visible_fails(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, required=False).thenReturn(element)
+        when(element).is_displayed().thenRaise(StaleElementReferenceException('foo'))
+        with self.assertRaisesRegexp(AssertionError, 'Message: foo'):
+            self.waiting.wait_until_element_is_visible(locator, self.timeout)
+
+    def test_wait_until_element_is_not_visible(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, required=False).thenReturn(element)
+        when(element).is_displayed().thenRaise(StaleElementReferenceException()).thenReturn(False)
+        self.waiting.wait_until_element_is_not_visible(locator, self.timeout)
+
+    def test_wait_until_element_is_enabled(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, None).thenReturn(element)
+        when(element).is_enabled().thenRaise(StaleElementReferenceException()).thenReturn(True)
+        self.waiting.wait_until_element_is_enabled(locator, self.timeout)
+
+    def test_wait_until_element_is_enabled_get_attribute_readonly(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, None).thenReturn(element)
+        when(element).is_enabled().thenReturn(True)
+        when(element).get_attribute('readonly').thenRaise(StaleElementReferenceException()).thenReturn(None)
+        self.waiting.wait_until_element_is_enabled(locator, self.timeout)
+
+    def test_wait_until_element_is_enabled_fails(self):
+        locator = '//div'
+        element = mock()
+        when(self.waiting).find_element(locator, None).thenReturn(element)
+        when(element).is_enabled().thenRaise(StaleElementReferenceException('foo'))
+        with self.assertRaisesRegexp(AssertionError, 'Message: foo'):
+            self.waiting.wait_until_element_is_enabled(locator, self.timeout)
+
+    def test_wait_until_element_contains(self):
+        locator = '//div'
+        text = 'foo'
+        element1, element2 = mock(), mock({'text': 'foobar'})
+        element1.__class__.text = property(_raise)
+        when(self.waiting).find_element(locator).thenReturn(element1).thenReturn(element2)
+        self.waiting.wait_until_element_contains(locator, text, self.timeout)
+
+    def test_wait_until_element_does_not_contain(self):
+        locator = '//div'
+        text = 'foo'
+        element1, element2 = mock(), mock({'text': 'tidii'})
+        element1.__class__.text = property(_raise)
+        when(self.waiting).find_element(locator).thenReturn(element1).thenReturn(element2)
+        self.waiting.wait_until_element_does_not_contain(locator, text, self.timeout)