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:
parent
ab7d5c3ec5
commit
861f9c2c4a
1
.gitignore
vendored
1
.gitignore
vendored
@ -35,3 +35,4 @@ dist
|
|||||||
AUTHORS
|
AUTHORS
|
||||||
ChangeLog
|
ChangeLog
|
||||||
tags
|
tags
|
||||||
|
ghostdriver.log
|
||||||
|
@ -45,6 +45,16 @@ for a Debian OS flavour, or for Fedora/Red Hat flavours:
|
|||||||
|
|
||||||
$ sudo yum install xorg-x11-server-Xvfb
|
$ 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
|
Writing tests
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
@ -25,71 +25,71 @@ import subprocess
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
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:
|
try:
|
||||||
# NOTE: Several distribution can't ship selenium due to its
|
if os.environ.get('SELENIUM_PHANTOMJS'):
|
||||||
# non-free license. So they have to patch it out of test-requirements.txt
|
from selenium.webdriver import PhantomJS as WebDriver
|
||||||
# Avoid import failure and force not running selenium tests.
|
else:
|
||||||
# The entire file is encapsulated in the try block because the classes
|
from selenium.common import exceptions as selenium_exceptions
|
||||||
# inherit from the firefox class contained in selenium.webdriver, and
|
from selenium.webdriver import firefox
|
||||||
# python will throw a NameError if the import is skipped.
|
|
||||||
from selenium.common import exceptions as selenium_exceptions
|
|
||||||
from selenium.webdriver import firefox
|
|
||||||
|
|
||||||
class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
|
class FirefoxBinary(firefox.firefox_binary.FirefoxBinary):
|
||||||
"""Workarounds selenium firefox issues.
|
"""Workarounds selenium firefox issues.
|
||||||
|
|
||||||
There is race condition in the way firefox is spawned. The exact cause
|
There is race condition in the way firefox is spawned. The exact
|
||||||
hasn't been properly diagnosed yet but it's around:
|
cause hasn't been properly diagnosed yet but it's around:
|
||||||
|
|
||||||
- getting a free port from the OS with selenium.webdriver.common.utils
|
- getting a free port from the OS with
|
||||||
free_port(),
|
selenium.webdriver.common.utils free_port(),
|
||||||
|
|
||||||
- release the port immediately but record it in ff prefs so that ff can
|
- release the port immediately but record it in ff prefs so that ff
|
||||||
listen on that port for the internal http server.
|
can listen on that port for the internal http server.
|
||||||
|
|
||||||
It has been observed that this leads to hanging processes for 'firefox
|
It has been observed that this leads to hanging processes for
|
||||||
-silent'.
|
'firefox -silent'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _start_from_profile_path(self, path):
|
def _start_from_profile_path(self, path):
|
||||||
self._firefox_env["XRE_PROFILE_PATH"] = path
|
self._firefox_env["XRE_PROFILE_PATH"] = path
|
||||||
|
|
||||||
if platform.system().lower() == 'linux':
|
if platform.system().lower() == 'linux':
|
||||||
self._modify_link_library_path()
|
self._modify_link_library_path()
|
||||||
command = [self._start_cmd, "-silent"]
|
command = [self._start_cmd, "-silent"]
|
||||||
if self.command_line is not None:
|
if self.command_line is not None:
|
||||||
for cli in self.command_line:
|
for cli in self.command_line:
|
||||||
command.append(cli)
|
command.append(cli)
|
||||||
|
|
||||||
# The following exists upstream and is known to create hanging firefoxes,
|
# The following exists upstream and is known to create hanging
|
||||||
# leading to zombies.
|
# firefoxes, leading to zombies.
|
||||||
# subprocess.Popen(command, stdout=self._log_file,
|
# subprocess.Popen(command, stdout=self._log_file,
|
||||||
# stderr=subprocess.STDOUT,
|
# stderr=subprocess.STDOUT,
|
||||||
# env=self._firefox_env).communicate()
|
# env=self._firefox_env).communicate()
|
||||||
command[1] = '-foreground'
|
command[1] = '-foreground'
|
||||||
self.process = subprocess.Popen(
|
self.process = subprocess.Popen(
|
||||||
command, stdout=self._log_file, stderr=subprocess.STDOUT,
|
command, stdout=self._log_file, stderr=subprocess.STDOUT,
|
||||||
env=self._firefox_env)
|
env=self._firefox_env)
|
||||||
|
|
||||||
class WebDriver(firefox.webdriver.WebDriver):
|
class WebDriver(firefox.webdriver.WebDriver):
|
||||||
"""Workarounds selenium firefox issues."""
|
"""Workarounds selenium firefox issues."""
|
||||||
|
|
||||||
def __init__(self, firefox_profile=None, firefox_binary=None,
|
def __init__(self, firefox_profile=None, firefox_binary=None,
|
||||||
timeout=30, capabilities=None, proxy=None):
|
timeout=30, capabilities=None, proxy=None):
|
||||||
try:
|
try:
|
||||||
super(WebDriver, self).__init__(
|
super(WebDriver, self).__init__(
|
||||||
firefox_profile, FirefoxBinary(), timeout, capabilities,
|
firefox_profile, FirefoxBinary(), timeout,
|
||||||
proxy)
|
capabilities, proxy)
|
||||||
except selenium_exceptions.WebDriverException:
|
except selenium_exceptions.WebDriverException:
|
||||||
# If we can't start, cleanup profile
|
# If we can't start, cleanup profile
|
||||||
shutil.rmtree(self.profile.path)
|
shutil.rmtree(self.profile.path)
|
||||||
if self.profile.tempfolder is not None:
|
if self.profile.tempfolder is not None:
|
||||||
shutil.rmtree(self.profile.tempfolder)
|
shutil.rmtree(self.profile.tempfolder)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
except ImportError as e:
|
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)))
|
LOG.warning("{0}, force WITH_SELENIUM=False".format(str(e)))
|
||||||
os.environ['WITH_SELENIUM'] = ''
|
os.environ['WITH_SELENIUM'] = ''
|
||||||
|
@ -17,7 +17,9 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
from socket import timeout as socket_timeout # noqa
|
from socket import timeout as socket_timeout # noqa
|
||||||
|
import unittest
|
||||||
|
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django import http
|
from django import http
|
||||||
@ -411,6 +413,8 @@ class SeleniumTests(test.SeleniumTestCase):
|
|||||||
self.assertTrue("ISO" in body.text,
|
self.assertTrue("ISO" in body.text,
|
||||||
"ISO should be selected when the extension is *.iso")
|
"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',)})
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
def test_modal_create_image_from_file(self):
|
def test_modal_create_image_from_file(self):
|
||||||
driver = self.selenium
|
driver = self.selenium
|
||||||
@ -471,6 +475,8 @@ class SeleniumTests(test.SeleniumTestCase):
|
|||||||
self.assertTrue("ISO" in body.text,
|
self.assertTrue("ISO" in body.text,
|
||||||
"ISO should be selected when the extension is *.iso")
|
"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',)})
|
@test.create_stubs({api.glance: ('image_list_detailed',)})
|
||||||
def test_create_image_from_file(self):
|
def test_create_image_from_file(self):
|
||||||
driver = self.selenium
|
driver = self.selenium
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from selenium.common import exceptions
|
from selenium.common import exceptions
|
||||||
@ -145,7 +146,14 @@ class WebElementWrapper(WrapperFindOverride, webelement.WebElement):
|
|||||||
return result
|
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."""
|
"""Wrapper for webdriver to return WebElementWrapper on find_element."""
|
||||||
def __init__(self, logging_prefs=None, capabilities=None, **kwargs):
|
def __init__(self, logging_prefs=None, capabilities=None, **kwargs):
|
||||||
if capabilities is None:
|
if capabilities is None:
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Selenium tests may now be exercised using the headless PhantomJS driver.
|
@ -33,6 +33,7 @@ function usage {
|
|||||||
echo " --only-selenium Run only the Selenium unit tests"
|
echo " --only-selenium Run only the Selenium unit tests"
|
||||||
echo " --with-selenium Run unit tests including Selenium tests"
|
echo " --with-selenium Run unit tests including Selenium tests"
|
||||||
echo " --selenium-headless Run Selenium tests headless"
|
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 " --integration Run the integration tests (requires a running "
|
||||||
echo " OpenStack environment)"
|
echo " OpenStack environment)"
|
||||||
echo " --runserver Run the Django development server for"
|
echo " --runserver Run the Django development server for"
|
||||||
@ -79,6 +80,7 @@ runserver=0
|
|||||||
only_selenium=0
|
only_selenium=0
|
||||||
with_selenium=0
|
with_selenium=0
|
||||||
selenium_headless=0
|
selenium_headless=0
|
||||||
|
selenium_phantomjs=0
|
||||||
integration=0
|
integration=0
|
||||||
testopts=""
|
testopts=""
|
||||||
testargs=""
|
testargs=""
|
||||||
@ -121,6 +123,7 @@ function process_option {
|
|||||||
--only-selenium) only_selenium=1;;
|
--only-selenium) only_selenium=1;;
|
||||||
--with-selenium) with_selenium=1;;
|
--with-selenium) with_selenium=1;;
|
||||||
--selenium-headless) selenium_headless=1;;
|
--selenium-headless) selenium_headless=1;;
|
||||||
|
--selenium-phantomjs) selenium_phantomjs=1;;
|
||||||
--integration) integration=1;;
|
--integration) integration=1;;
|
||||||
--docs) just_docs=1;;
|
--docs) just_docs=1;;
|
||||||
--runserver) runserver=1;;
|
--runserver) runserver=1;;
|
||||||
@ -347,6 +350,10 @@ function run_tests {
|
|||||||
export SELENIUM_HEADLESS=1
|
export SELENIUM_HEADLESS=1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $selenium_phantomjs -eq 1 ]; then
|
||||||
|
export SELENIUM_PHANTOMJS=1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ -z "$testargs" ]; then
|
if [ -z "$testargs" ]; then
|
||||||
run_tests_all
|
run_tests_all
|
||||||
else
|
else
|
||||||
|
Loading…
Reference in New Issue
Block a user