Enable PhantomJS for running Selenium tests

This patch enables the PhantomJS webdriver for running
the Selenium test suite.

Use it with the --selenium-phantomjs command-line switch
when executing the selenium and integration suite.

Change-Id: I443e6f6d7d1911df500b360f7c22686b417fbeae
Blueprint: enable-phantomjs-selenium
This commit is contained in:
Richard Jones 2015-11-09 15:35:48 +11:00
parent ab7d5c3ec5
commit 861f9c2c4a
7 changed files with 89 additions and 54 deletions

1
.gitignore vendored
View File

@ -35,3 +35,4 @@ dist
AUTHORS
ChangeLog
tags
ghostdriver.log

View File

@ -45,6 +45,16 @@ for a Debian OS flavour, or for Fedora/Red Hat flavours:
$ sudo yum install xorg-x11-server-Xvfb
If you can't run a virtual display, or would prefer not to, you can use the
PhantomJS web driver instead:
$ ./run_tests.sh --with-selenium --selenium-phantomjs
If you need to install PhantomJS, you may do so with `npm` like this:
$ npm -g install phantomjs
Writing tests
=============

View File

@ -25,71 +25,71 @@ import subprocess
LOG = logging.getLogger(__name__)
# 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:
# NOTE: 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.
# The entire file is encapsulated in the try block because the classes
# inherit from the firefox class contained in selenium.webdriver, and
# python will throw a NameError if the import is skipped.
from selenium.common import exceptions as selenium_exceptions
from selenium.webdriver import firefox
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.
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:
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(),
- 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.
- 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'.
"""
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
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)
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)
# 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."""
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)
raise
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)
raise
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'] = ''

View File

@ -17,7 +17,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
from socket import timeout as socket_timeout # noqa
import unittest
from django.core.urlresolvers import reverse
from django import http
@ -411,6 +413,8 @@ class SeleniumTests(test.SeleniumTestCase):
self.assertTrue("ISO" in body.text,
"ISO should be selected when the extension is *.iso")
@unittest.skipIf(os.environ.get('SELENIUM_PHANTOMJS'),
"PhantomJS cannot test file upload widgets.")
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_modal_create_image_from_file(self):
driver = self.selenium
@ -471,6 +475,8 @@ class SeleniumTests(test.SeleniumTestCase):
self.assertTrue("ISO" in body.text,
"ISO should be selected when the extension is *.iso")
@unittest.skipIf(os.environ.get('SELENIUM_PHANTOMJS'),
"PhantomJS cannot test file upload widgets.")
@test.create_stubs({api.glance: ('image_list_detailed',)})
def test_create_image_from_file(self):
driver = self.selenium

View File

@ -9,6 +9,7 @@
# 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 os
import time
from selenium.common import exceptions
@ -145,7 +146,14 @@ class WebElementWrapper(WrapperFindOverride, webelement.WebElement):
return result
class WebDriverWrapper(WrapperFindOverride, webdriver.Firefox):
# select the active webdriver based on whether we --selenium-phantomjs or not
if os.environ.get('SELENIUM_PHANTOMJS'):
WebDriver = webdriver.PhantomJS
else:
WebDriver = webdriver.Firefox
class WebDriverWrapper(WrapperFindOverride, WebDriver):
"""Wrapper for webdriver to return WebElementWrapper on find_element."""
def __init__(self, logging_prefs=None, capabilities=None, **kwargs):
if capabilities is None:

View File

@ -0,0 +1,3 @@
---
features:
- Selenium tests may now be exercised using the headless PhantomJS driver.

View File

@ -33,6 +33,7 @@ function usage {
echo " --only-selenium Run only the Selenium unit tests"
echo " --with-selenium Run unit tests including Selenium tests"
echo " --selenium-headless Run Selenium tests headless"
echo " --selenium-phantomjs Run Selenium tests using phantomjs (headless)"
echo " --integration Run the integration tests (requires a running "
echo " OpenStack environment)"
echo " --runserver Run the Django development server for"
@ -79,6 +80,7 @@ runserver=0
only_selenium=0
with_selenium=0
selenium_headless=0
selenium_phantomjs=0
integration=0
testopts=""
testargs=""
@ -121,6 +123,7 @@ function process_option {
--only-selenium) only_selenium=1;;
--with-selenium) with_selenium=1;;
--selenium-headless) selenium_headless=1;;
--selenium-phantomjs) selenium_phantomjs=1;;
--integration) integration=1;;
--docs) just_docs=1;;
--runserver) runserver=1;;
@ -347,6 +350,10 @@ function run_tests {
export SELENIUM_HEADLESS=1
fi
if [ $selenium_phantomjs -eq 1 ]; then
export SELENIUM_PHANTOMJS=1
fi
if [ -z "$testargs" ]; then
run_tests_all
else