summaryrefslogtreecommitdiff
path: root/horizon/test
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-02-08 14:28:55 +0000
committerGerrit Code Review <review@openstack.org>2016-02-08 14:28:56 +0000
commit8f459658e6b3334cc33d1876e959919e8061eda5 (patch)
treef539c6310616821085c15d319699314372c332db /horizon/test
parent1a03ea2a6c7592f4e6adf74c283a2d7853892685 (diff)
parenta17612ed9748d141381fcfff3d88f178cbebe39a (diff)
downloadhorizon-8f459658e6b3334cc33d1876e959919e8061eda5.tar.gz
Merge "Merge the two webdrivers"
Diffstat (limited to 'horizon/test')
-rw-r--r--horizon/test/firefox_binary.py74
-rw-r--r--horizon/test/helpers.py8
-rw-r--r--horizon/test/webdriver.py224
3 files changed, 235 insertions, 71 deletions
diff --git a/horizon/test/firefox_binary.py b/horizon/test/firefox_binary.py
new file mode 100644
index 000000000..2597789d3
--- /dev/null
+++ b/horizon/test/firefox_binary.py
@@ -0,0 +1,74 @@
+# Copyright 2015, Rackspace, US, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import platform
+import shutil
+import subprocess
+
+from selenium.common import exceptions as selenium_exceptions
+from selenium.webdriver.common import desired_capabilities as dc
+from selenium.webdriver import firefox
+
+
+class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
+ """Workarounds selenium firefox issues.
+
+ There is race condition in the way firefox is spawned. The exact
+ cause hasn't been properly diagnosed yet but it's around:
+
+ - getting a free port from the OS with
+ selenium.webdriver.common.utils free_port(),
+
+ - release the port immediately but record it in ff prefs so that ff
+ can listen on that port for the internal http server.
+
+ It has been observed that this leads to hanging processes for
+ 'firefox -silent'.
+ """
+
+ def _start_from_profile_path(self, path):
+ self._firefox_env["XRE_PROFILE_PATH"] = path
+
+ if platform.system().lower() == 'linux':
+ self._modify_link_library_path()
+ command = [self._start_cmd, "-silent"]
+ if self.command_line is not None:
+ for cli in self.command_line:
+ command.append(cli)
+
+# The following exists upstream and is known to create hanging
+# firefoxes, leading to zombies.
+# subprocess.Popen(command, stdout=self._log_file,
+# stderr=subprocess.STDOUT,
+# env=self._firefox_env).communicate()
+ command[1] = '-foreground'
+ self.process = subprocess.Popen(
+ command, stdout=self._log_file, stderr=subprocess.STDOUT,
+ env=self._firefox_env)
+
+
+class WebDriver(firefox.webdriver.WebDriver):
+ """Workarounds selenium firefox issues."""
+ def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30,
+ desired_capabilities=dc.DesiredCapabilities.FIREFOX,
+ proxy=None):
+ try:
+ super(WebDriver, self).__init__(
+ firefox_profile, FirefoxBinary(), timeout,
+ desired_capabilities, proxy)
+ except selenium_exceptions.WebDriverException:
+ # If we can't start, cleanup profile
+ shutil.rmtree(self.profile.path)
+ if self.profile.tempfolder is not None:
+ shutil.rmtree(self.profile.tempfolder)
+ raise
diff --git a/horizon/test/helpers.py b/horizon/test/helpers.py
index 5ad7ebadc..cc69a703d 100644
--- a/horizon/test/helpers.py
+++ b/horizon/test/helpers.py
@@ -41,15 +41,17 @@ from django.contrib.staticfiles.testing \
LOG = logging.getLogger(__name__)
+# NOTE: Several distributions can't ship Selenium, or the Firefox
+# component of it, due to its non-free license. So they have to patch
+# it out of test-requirements.txt Avoid import failure and force not
+# running selenium tests if we attempt to run selenium tests using the
+# Firefox driver and it is not available.
try:
from selenium.webdriver.support import ui as selenium_ui
import xvfbwrapper # Only needed when running the Selenium tests headless
from horizon.test.webdriver import WebDriver # noqa
except ImportError as e:
- # NOTE(saschpe): Several distribution can't ship selenium due to its
- # non-free license. So they have to patch it out of test-requirements.txt
- # Avoid import failure and force not running selenium tests.
LOG.warning("{0}, force WITH_SELENIUM=False".format(str(e)))
os.environ['WITH_SELENIUM'] = ''
diff --git a/horizon/test/webdriver.py b/horizon/test/webdriver.py
index 87e02602a..2a699fcd1 100644
--- a/horizon/test/webdriver.py
+++ b/horizon/test/webdriver.py
@@ -19,77 +19,165 @@
import logging
import os
-import platform
-import shutil
-import subprocess
+import time
LOG = logging.getLogger(__name__)
+
+class ElementNotReloadableException(Exception):
+ """Raised when reload is not possible."""
+ pass
+
+
+from selenium.common import exceptions
+from selenium.webdriver.common import by
+from selenium.webdriver.common import desired_capabilities as dc
+from selenium.webdriver.remote import webelement
+
# Select the WebDriver to use based on the --selenium-phantomjs switch.
-# NOTE: Several distributions can't ship Selenium, or the Firefox
-# component of it, due to its non-free license. So they have to patch
-# it out of test-requirements.txt Avoid import failure and force not
-# running selenium tests if we attempt to run selenium tests using the
-# Firefox driver and it is not available.
-try:
- if os.environ.get('SELENIUM_PHANTOMJS'):
- from selenium.webdriver import PhantomJS as WebDriver
- else:
- from selenium.common import exceptions as selenium_exceptions
- from selenium.webdriver import firefox
-
- class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
- """Workarounds selenium firefox issues.
-
- There is race condition in the way firefox is spawned. The exact
- cause hasn't been properly diagnosed yet but it's around:
-
- - getting a free port from the OS with
- selenium.webdriver.common.utils free_port(),
-
- - release the port immediately but record it in ff prefs so that ff
- can listen on that port for the internal http server.
-
- It has been observed that this leads to hanging processes for
- 'firefox -silent'.
- """
-
- def _start_from_profile_path(self, path):
- self._firefox_env["XRE_PROFILE_PATH"] = path
-
- if platform.system().lower() == 'linux':
- self._modify_link_library_path()
- command = [self._start_cmd, "-silent"]
- if self.command_line is not None:
- for cli in self.command_line:
- command.append(cli)
-
- # The following exists upstream and is known to create hanging
- # firefoxes, leading to zombies.
- # subprocess.Popen(command, stdout=self._log_file,
- # stderr=subprocess.STDOUT,
- # env=self._firefox_env).communicate()
- command[1] = '-foreground'
- self.process = subprocess.Popen(
- command, stdout=self._log_file, stderr=subprocess.STDOUT,
- env=self._firefox_env)
-
- class WebDriver(firefox.webdriver.WebDriver):
- """Workarounds selenium firefox issues."""
-
- def __init__(self, firefox_profile=None, firefox_binary=None,
- timeout=30, capabilities=None, proxy=None):
- try:
- super(WebDriver, self).__init__(
- firefox_profile, FirefoxBinary(), timeout,
- capabilities, proxy)
- except selenium_exceptions.WebDriverException:
- # If we can't start, cleanup profile
- shutil.rmtree(self.profile.path)
- if self.profile.tempfolder is not None:
- shutil.rmtree(self.profile.tempfolder)
+if os.environ.get('SELENIUM_PHANTOMJS'):
+ from selenium.webdriver import PhantomJS as WebDriver
+ desired_capabilities = dc.DesiredCapabilities.PHANTOMJS
+else:
+ from horizon.test.firefox_binary import WebDriver
+ desired_capabilities = dc.DesiredCapabilities.FIREFOX
+
+
+class WrapperFindOverride(object):
+ """Mixin for overriding find_element methods."""
+
+ def find_element(self, by=by.By.ID, value=None):
+ web_el = super(WrapperFindOverride, self).find_element(by, value)
+ return WebElementWrapper(web_el.parent, web_el.id, (by, value),
+ self)
+
+ def find_elements(self, by=by.By.ID, value=None):
+ web_els = super(WrapperFindOverride, self).find_elements(by, value)
+ result = []
+ for index, web_el in enumerate(web_els):
+ result.append(WebElementWrapper(web_el.parent, web_el.id,
+ (by, value), self, index))
+ return result
+
+
+class WebElementWrapper(WrapperFindOverride, webelement.WebElement):
+ """WebElement class wrapper.
+
+ WebElement wrapper that catches the StaleElementReferenceException and
+ tries to reload the element by sending request to its source element
+ (element that created actual element) for reload, in case that source
+ element needs to be reloaded as well, it asks its parent till web
+ driver is reached. In case driver was reached and did not manage to
+ find the element it is probable that programmer made a mistake and
+ actualStaleElementReferenceException is raised.
+ """
+
+ STALE_ELEMENT_REFERENCE_WAIT = 0.5
+ STALE_ELEMENT_REFERENCE_MAX_TRY = 10
+
+ def __init__(self, parent, id_, locator, src_element, index=None):
+ super(WebElementWrapper, self).__init__(parent, id_)
+ self.locator = locator
+ self.src_element = src_element
+
+ # StaleElementReferenceException occurrence counter
+ self.stale_reference_occurrence = 0
+
+ # storing if web element reload succeed or not
+ # in case of fail StaleElementReferenceException is raised
+ self.reload_failed = False
+
+ # in case element was looked up previously via find_elements
+ # we need his position in the returned list
+ self.index = index
+
+ # if reloading of some other web element is in progress
+ # StaleElementReferenceException is not raised within current
+ # context
+ self.web_element_reload = False
+
+ def reload_request(self, locator, index=None):
+ self.web_element_reload = True
+ try:
+ # element was found out via find_elements
+ if index is not None:
+ web_els = self.src_element.find_elements(*locator)
+ web_el = web_els[index]
+ else:
+ web_el = self.src_element.find_element(*locator)
+ except (exceptions.NoSuchElementException, IndexError):
+ return False
+
+ self.web_element_reload = False
+ return web_el
+
+ def _reload_element(self):
+ """Method for starting reload process on current instance."""
+ web_el = self.src_element.reload_request(self.locator, self.index)
+ if not web_el:
+ return
+ self._parent = web_el.parent
+ self._id = web_el.id
+
+ def _execute(self, command, params=None):
+ """Overriding in order to catch StaleElementReferenceException."""
+ result = None
+ while True:
+ try:
+ result = super(WebElementWrapper, self)._execute(command,
+ params)
+ break
+ except exceptions.StaleElementReferenceException:
+
+ # in case we reach the limit
+ # STALE_ELEMENT_REFERENCE_MAX_TRY
+ # it is very probable that it is programmer fault
+ if self.reload_failed or self.stale_reference_occurrence \
+ > self.STALE_ELEMENT_REFERENCE_MAX_TRY:
raise
-except ImportError as e:
- LOG.warning("{0}, force WITH_SELENIUM=False".format(str(e)))
- os.environ['WITH_SELENIUM'] = ''
+ # this is either programmer fault (bad logic in accessing
+ # elements) or web page content is been loaded via ajax,
+ # let's go with the second one and wait
+ # STALE_ELEMENT_REFERENCE_WAIT till the assumed page
+ # content is loaded and try to execute the whole process
+ # STALE_ELEMENT_REFERENCE_MAX_TRY times in case of failures
+ time.sleep(self.STALE_ELEMENT_REFERENCE_WAIT)
+
+ # try to reload the web element if result is false it
+ # means that request has gone to the driver and he did not
+ # find the element -> must be programmer fault, because it
+ # seems we are on entirely different page
+ try:
+ self._reload_element()
+ except ElementNotReloadableException:
+
+ # In case this element was responsible only for loading
+ # some other element raise the exception further
+ if self.web_element_reload:
+ raise
+ else:
+ self.reload_failed = True
+
+ # increment occurrences
+ self.stale_reference_occurrence += 1
+
+ # reset counter
+ self.stale_reference_occurrence = 0
+ return result
+
+
+class WebDriverWrapper(WrapperFindOverride, WebDriver):
+ """Wrapper for webdriver to return WebElementWrapper on find_element.
+ """
+ def reload_request(self, locator, index):
+ try:
+ # element was found out via find_elements
+ if index is not None:
+ web_els = self.find_elements(*locator)
+ web_el = web_els[index]
+ else:
+ web_el = self.find_element(*locator)
+ return web_el
+ except (exceptions.NoSuchElementException, IndexError):
+ raise ElementNotReloadableException()