diff --git a/doc/source/ref/run_tests.rst b/doc/source/ref/run_tests.rst index 47ca6aca92..a4803a5584 100644 --- a/doc/source/ref/run_tests.rst +++ b/doc/source/ref/run_tests.rst @@ -70,6 +70,21 @@ To run just the `WorkflowsTests.test_workflow_view` test method:: ./run_tests.sh horizon.test.tests.workflows:WorkflowsTests.test_workflow_view +Running the integration tests +----------------------------- + +The Horizon integration tests treat Horizon as a black box, and similar +to Tempest must be run against an existing OpenStack system. These +tests are not run by default. + +#. Update the configuration file + `openstack_dashboard/test/integration_tests/horizon.conf` as + required (the format is similar to the Tempest configuration file). + +#. Run the tests with the following command: :: + + $ ./run_tests.sh --integration + Using Dashboard and Panel Templates =================================== diff --git a/openstack_dashboard/test/integration_tests/README.rst b/openstack_dashboard/test/integration_tests/README.rst new file mode 100644 index 0000000000..dedf124a0d --- /dev/null +++ b/openstack_dashboard/test/integration_tests/README.rst @@ -0,0 +1,22 @@ +Horizon Integration Tests +========================= + +Horizon's integration tests treat Horizon as a black box. + +Running the integration tests +----------------------------- + +#. Set up an OpenStack server + +#. Update the configuration file at `horizon.conf` + +#. Run the tests. :: + + $ ./run_tests.sh --integration + +More information +---------------- + +https://wiki.openstack.org/wiki/Horizon/Testing/UI + +https://wiki.mozilla.org/QA/Execution/Web_Testing/Docs/Automation/StyleGuide#Page_Objects diff --git a/openstack_dashboard/test/integration_tests/__init__.py b/openstack_dashboard/test/integration_tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/test/integration_tests/config.py b/openstack_dashboard/test/integration_tests/config.py new file mode 100644 index 0000000000..4decb6d4e2 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/config.py @@ -0,0 +1,66 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 os + +from oslo.config import cfg + + +DashboardGroup = [ + cfg.StrOpt('dashboard_url', + default='http://localhost/', + help="Where the dashboard can be found"), + cfg.StrOpt('login_url', + default='http://localhost/auth/login/', + help="Login page for the dashboard"), + cfg.IntOpt('page_timeout', + default=10, + help="Timeout in seconds"), +] + +IdentityGroup = [ + cfg.StrOpt('username', + default='demo', + help="Username to use for non-admin API requests."), + cfg.StrOpt('password', + default='pass', + help="API key to use when authenticating.", + secret=True), + cfg.StrOpt('admin_username', + default='admin', + help="Administrative Username to use for admin API " + "requests."), + cfg.StrOpt('admin_password', + default='pass', + help="API key to use when authenticating as admin.", + secret=True), +] + + +def _get_config_files(): + conf_dir = os.path.join( + os.path.abspath(os.path.dirname(os.path.dirname(__file__))), + 'integration_tests') + conf_file = os.environ.get('HORIZON_INTEGRATION_TESTS_CONFIG_FILE', + "%s/horizon.conf" % conf_dir) + return [conf_file] + + +def get_config(): + cfg.CONF([], project='horizon', default_config_files=_get_config_files()) + + cfg.CONF.register_opts(DashboardGroup, group="dashboard") + cfg.CONF.register_opts(IdentityGroup, group="identity") + + return cfg.CONF diff --git a/openstack_dashboard/test/integration_tests/helpers.py b/openstack_dashboard/test/integration_tests/helpers.py new file mode 100644 index 0000000000..fbe15190e3 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/helpers.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 os + +import selenium +from selenium.webdriver.support import ui +import testtools + +from openstack_dashboard.test.integration_tests import config + + +class BaseTestCase(testtools.TestCase): + + def setUp(self): + if os.environ.get('INTEGRATION_TESTS', False): + self.driver = selenium.webdriver.Firefox() + self.conf = config.get_config() + else: + msg = "The INTEGRATION_TESTS env variable is not set." + raise self.skipException(msg) + super(BaseTestCase, self).setUp() + + def tearDown(self): + if os.environ.get('INTEGRATION_TESTS', False): + self.driver.close() + super(BaseTestCase, self).tearDown() + + def wait_for_title(self): + timeout = self.conf.dashboard.page_timeout + ui.WebDriverWait(self.driver, timeout).until(lambda d: + self.driver.title) diff --git a/openstack_dashboard/test/integration_tests/horizon.conf b/openstack_dashboard/test/integration_tests/horizon.conf new file mode 100644 index 0000000000..c460e7f6e9 --- /dev/null +++ b/openstack_dashboard/test/integration_tests/horizon.conf @@ -0,0 +1,28 @@ +# +# Configuration filed based on Tempest's tempest.conf.sample +# + +[dashboard] +# Where the dashboard can be found (string value) +dashboard_url=http://localhost/ + +# Login page for the dashboard (string value) +login_url=http://localhost/auth/login/ + +# Timeout in seconds to wait for a page to become available +# (integer value) +page_timeout=10 + +[identity] +# Username to use for non-admin API requests. (string value) +username=demo + +# API key to use when authenticating. (string value) +password=pass + +# Administrative Username to use for admin API requests. +# (string value) +admin_username=admin + +# API key to use when authenticating as admin. (string value) +admin_password=pass diff --git a/openstack_dashboard/test/integration_tests/tests/__init__.py b/openstack_dashboard/test/integration_tests/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/test/integration_tests/tests/test_login.py b/openstack_dashboard/test/integration_tests/tests/test_login.py new file mode 100644 index 0000000000..67aa206a4c --- /dev/null +++ b/openstack_dashboard/test/integration_tests/tests/test_login.py @@ -0,0 +1,43 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# 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 selenium.webdriver.common.keys as keys + +from openstack_dashboard.test.integration_tests import helpers + + +class TestLoginPage(helpers.BaseTestCase): + """This is a basic scenario test: + * checks that the login page is available + * logs in as a regular user + * checks that the user home page loads without error + + FIXME(jpichon): This test will be rewritten using the Page Objects + pattern, which is much more maintainable. + + """ + + def test_login(self): + self.driver.get(self.conf.dashboard.login_url) + self.assertIn("Login", self.driver.title) + + username = self.driver.find_element_by_name("username") + password = self.driver.find_element_by_name("password") + + username.send_keys(self.conf.identity.username) + password.send_keys(self.conf.identity.password) + username.send_keys(keys.Keys.RETURN) + + self.wait_for_title() + self.assertIn("Instance Overview", self.driver.title) diff --git a/run_tests.sh b/run_tests.sh index d7dbb23b1a..a0c10cf1ab 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -6,7 +6,7 @@ set -o errexit # Increment me any time the environment should be rebuilt. # This includes dependency changes, directory renames, etc. # Simple integer sequence: 1, 2, 3... -environment_version=41 +environment_version=42 #--------------------------------------------------------# function usage { @@ -32,6 +32,8 @@ function usage { echo " Implies -V if -N is not set." echo " --only-selenium Run only the Selenium unit tests" echo " --with-selenium Run unit tests including Selenium tests" + echo " --integration Run the integration tests (requires a running " + echo " OpenStack environment)" echo " --runserver Run the Django development server for" echo " openstack_dashboard in the virtual" echo " environment." @@ -71,6 +73,7 @@ restore_env=0 runserver=0 only_selenium=0 with_selenium=0 +integration=0 testopts="" testargs="" with_coverage=0 @@ -104,6 +107,7 @@ function process_option { --compilemessages) compilemessages=1;; --only-selenium) only_selenium=1;; --with-selenium) with_selenium=1;; + --integration) integration=1;; --docs) just_docs=1;; --runserver) runserver=1;; --backup-environment) backup_env=1;; @@ -351,6 +355,14 @@ function run_tests_all { exit $TEST_RESULT } +function run_integration_tests { + export INTEGRATION_TESTS=1 + + echo "Running Horizon integration tests..." + ${command_wrapper} nosetests openstack_dashboard/test/integration_tests/tests + exit 0 +} + function run_makemessages { OPTS="-l en --no-obsolete" DASHBOARD_OPTS="--extension=html,txt,csv --ignore=openstack/common/*" @@ -465,6 +477,12 @@ if [ $just_tabs -eq 1 ]; then exit $? fi +# Integration tests +if [ $integration -eq 1 ]; then + run_integration_tests + exit $? +fi + # Django development server if [ $runserver -eq 1 ]; then run_server diff --git a/test-requirements.txt b/test-requirements.txt index e5c993b145..649f11d5fe 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -15,3 +15,4 @@ sphinx>=1.1.2,<1.2 # for bug 1091333, remove after sphinx >1.1.3 is released. docutils==0.9.1 oslo.sphinx +testtools>=0.9.34 diff --git a/tox.ini b/tox.ini index 5e84941511..4bbe7f89e2 100644 --- a/tox.ini +++ b/tox.ini @@ -30,6 +30,14 @@ basepython = python2.7 commands = pip install django==1.4 /bin/bash run_tests.sh -N --no-pep8 +[testenv:py27integration] +basepython = python2.7 +commands = /bin/bash run_tests.sh -N --integration + +[testenv:py26integration] +basepython = python2.6 +commands = /bin/bash run_tests.sh -N --integration + [tox:jenkins] downloadcache = ~/cache/pip