diff --git a/.travis.yml b/.travis.yml index 701407dc742..3f27616b698 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,8 +25,7 @@ env: # Regular expression selects on which branches to run analysis # Be aware of quotas. Do not run on every branch/commit - COVERITY_SCAN_BRANCH_PATTERN="master" - # COVERITY_SCAN_TOKEN via "travis encrypt" using the repo's public key - - secure: "J1iNtC34kDcFeeOJ6WcfFyfmRXI0dDgyh+AJvn5d4pBSAVAmkeT74i08ZMEck4Ye97LQB38M5+KSzMXyRP7cv4sB7BjAWLvKbXo90od506/6qmhqfahXa1C/lS4VrQRh48QjvnOP0eYYRP/T90sKSpyINlg48H/l2h7zL6sy5/Q=" + # COVERITY_SCAN_TOKEN: is set as secure settings in the travis-ci.org configuration - COVERITY_SCAN_BUILD_URL="https://scan.coverity.com/scripts/travisci_build_coverity_scan.sh" - COVERITY_SCAN_BUILD="curl -s $COVERITY_SCAN_BUILD_URL | bash" # -- END Coverity Scan ENV diff --git a/.travis/all b/.travis/all index e16633a3c42..ea38983dc1b 100755 --- a/.travis/all +++ b/.travis/all @@ -25,14 +25,14 @@ set -v BACKUP_TEST_FILE=/usr/sbin/bareos.test -echo -e "status dir" | bconsole -echo -echo "---- label a volume ----" -echo -e "label volume=testvol pool=Full" | bconsole -echo echo "----- create some file to test backup / restore ----" echo "bareos restore test" > ${BACKUP_TEST_FILE} echo +echo -e "status dir" | bconsole +echo +#echo "---- label a volume ----" +#echo -e "label volume=testvol pool=Full" | bconsole +#echo echo "------ trigger backup job -----" echo -e "run job=backup-bareos-fd yes\rwait" | bconsole | grep "Job queued. JobId=" echo "status dir" | bconsole @@ -42,15 +42,17 @@ echo -e "restore select current\r2\rls\rmark usr\rdone\ryes\rwait" | bconsole echo "status dir" | bconsole grep "bareos restore test" /tmp/bareos-restores/${BACKUP_TEST_FILE} -if [ "${BUILD_WEBUI}" ] -then - service apache2 restart - service apache2 status - export BAREOS_BROWSER="none" - export BAREOS_USERNAME="citest" - export BAREOS_PASSWORD="citestpass" - export BAREOS_CLIENT_NAME=$HOSTNAME-fd - echo "--------- testing webui over selenium -----------" - echo "configure add console name=citest password=citestpass profile=webui-admin" | bconsole - python ${TRAVIS_BUILD_DIR}/webui/tests/selenium/webui-selenium-test.py -v +if [ "${BUILD_WEBUI}" ]; then + # show environment + export | grep " TRAVIS" + + service apache2 restart + #service apache2 status + export BAREOS_BROWSER="none" + export BAREOS_USERNAME="citest" + export BAREOS_PASSWORD="citestpass" + export BAREOS_CLIENT_NAME="$HOSTNAME-fd" + echo "--------- testing webui over selenium -----------" + echo "configure add console name=citest password=citestpass profile=webui-admin" | bconsole + python ${TRAVIS_BUILD_DIR}/webui/tests/selenium/webui-selenium-test.py -v fi diff --git a/.travis/travis_before_install.sh b/.travis/travis_before_install.sh index 47a1816cb78..743779d8f86 100755 --- a/.travis/travis_before_install.sh +++ b/.travis/travis_before_install.sh @@ -5,12 +5,11 @@ sudo apt-get -qq update sudo apt-get remove libqt4-dev cd core dpkg-checkbuilddeps 2> /tmp/dpkg-builddeps || true -if [ $BUILD_WEBUI ] -then - sudo -H pip install --upgrade pip urllib3==1.22 - sudo -H pip install sauceclient selenium - cd ../webui - dpkg-checkbuilddeps 2>> /tmp/dpkg-builddeps || true +if [ $BUILD_WEBUI ]; then + sudo -H pip install --upgrade pip 'urllib3>=1.22' + sudo -H pip install sauceclient selenium + cd ../webui + dpkg-checkbuilddeps 2>> /tmp/dpkg-builddeps || true fi cat /tmp/dpkg-builddeps sed -e "s/^.*:.*:\s//" -e "s/\s([^)]*)//g" -e "s/|/ /g" -e "s/ /\n/g" /tmp/dpkg-builddeps > /tmp/build_depends @@ -20,4 +19,3 @@ echo "installing $pkg" sudo apt-get -q --assume-yes install $pkg done true - diff --git a/.travis/travis_before_script.sh b/.travis/travis_before_script.sh index 77371ff5b48..f08115db35b 100755 --- a/.travis/travis_before_script.sh +++ b/.travis/travis_before_script.sh @@ -9,27 +9,29 @@ print_header() } cd core + if [ "${COVERITY_SCAN}" ]; then # run configure with default options debian/rules override_dh_auto_configure eval "$COVERITY_SCAN_BUILD" -else - print_header "build Bareos core packages" - fakeroot debian/rules binary + echo "result: $?" + exit 0 fi + + +print_header "build Bareos core packages" +fakeroot debian/rules binary + + if [ "${BUILD_WEBUI}" ]; then - cd ../webui - # to avoid timestamp conflicts while autoconfiguring we refresh every file - touch * - if [ "${COVERITY_SCAN}" ]; then - # run configure with default options - debian/rules override_dh_auto_configure - eval "$COVERITY_SCAN_BUILD" - else - print_header "build Bareos webui packages" - fakeroot debian/rules binary - fi + cd ../webui + # to avoid timestamp conflicts while autoconfiguring we refresh every file + #touch * + print_header "build Bareos webui packages" + fakeroot debian/rules binary fi + + print_header "create Debian package repository" cd .. dpkg-scanpackages . > Packages @@ -39,7 +41,11 @@ printf 'deb file:%s /\n' $PWD > /tmp/bareos.list sudo cp /tmp/bareos.list /etc/apt/sources.list.d/bareos.list cd - + print_header "install Bareos core package" sudo apt-get -qq update sudo apt-get install -y --force-yes bareos bareos-database-$DB -if [ "${BUILD_WEBUI}" ]; then sudo apt-get install -y --force-yes bareos-webui; fi + +if [ "${BUILD_WEBUI}" ]; then + sudo apt-get install -y --force-yes bareos-webui +fi diff --git a/README.md b/README.md index f2599288754..abc25554770 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ # Bareos -[![Build Status](https://travis-ci.org/bareos/bareos.png?branch=master)](https://travis-ci.org/bareos/bareos/branches)
Saucelabs +[![Build Status](https://travis-ci.org/bareos/bareos.png?branch=master)](https://travis-ci.org/bareos/bareos/branches) +[![Build Status](https://saucelabs.com/buildstatus/bareossaucelabs)](https://saucelabs.com/u/bareossaucelabs) + ### Bareos @@ -49,3 +51,7 @@ The Bareos project offers two mailing lists: bareos-users and bareos-devel. ### Packages * [http://download.bareos.org](http://download.bareos.org) + +### WebUI Automatic Testing Matrix + +[![Build Status](https://saucelabs.com/browser-matrix/bareossaucelabs.svg)](https://saucelabs.com/u/bareossaucelabs) diff --git a/webui/tests/selenium/README.md b/webui/tests/selenium/README.md index 96c9be7c926..f51ea9c0d4a 100644 --- a/webui/tests/selenium/README.md +++ b/webui/tests/selenium/README.md @@ -29,18 +29,25 @@ To run the test you must set certain environment variables: ## Running the test ``` -BAREOS_BASE_URL=http://127.0.0.1/bareos-webui/ -BAREOS_USERNAME=admin -BAREOS_PASSWORD=linuxlinux -BAREOS_CLIENT_NAME=bareos-fd -BAREOS_RESTOREFILE=/etc/passwd -BAREOS_LOG_PATH=/tmp/selenium-logs/ -BAREOS_DELAY=1 +export BAREOS_BASE_URL=http://127.0.0.1/bareos-webui/ +export BAREOS_USERNAME=admin +export BAREOS_PASSWORD=linuxlinux +export BAREOS_CLIENT_NAME=bareos-fd +export BAREOS_RESTOREFILE=/etc/passwd +export BAREOS_LOG_PATH=/tmp/selenium-logs/ +export BAREOS_DELAY=1 python webui-selenium-test.py -v ``` After setting the environment variables you can run the test. Use `-v`option of our test to show the progress and results of each test. +A single test can be performed by: + +``` +python webui-selenium-test.py -v SeleniumTest.test_login +``` + + ## Debugging After the test fails you will see an exception that was thrown. If this does not help you, take a look inside the generated log file, located in the same path as your `webui-selenium-test.py` file. diff --git a/webui/tests/selenium/webui-selenium-test.py b/webui/tests/selenium/webui-selenium-test.py index 3b218d7850b..6e08acea8e1 100755 --- a/webui/tests/selenium/webui-selenium-test.py +++ b/webui/tests/selenium/webui-selenium-test.py @@ -4,8 +4,13 @@ # selenium.common.exceptions.ElementNotInteractableException: requires >= selenium-3.4.0 -import logging, os, sys, unittest from datetime import datetime +import logging +import os +import sys +from time import sleep +import unittest + from selenium import webdriver from selenium.common.exceptions import * from selenium.webdriver.common.by import By @@ -13,7 +18,18 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import Select, WebDriverWait -from time import sleep + +# +# try to import the SauceClient, +# required for builds inside https://travis-ci.org, +# but not available on all platforms. +# +try: + from sauceclient import SauceClient +except ImportError: + pass + + class BadJobException(Exception): '''Raise when a started job doesn't result in ID''' @@ -23,10 +39,10 @@ def __init__(self, msg=None): class ClientStatusException(Exception): '''Raise when a client does not have the expected status''' - def __init__(self,client, status, msg=None): - if status=='enabled': + def __init__(self, client, status, msg=None): + if status == 'enabled': msg = '%s is enabled and cannot be enabled again.' % client - if status=='disabled': + if status == 'disabled': msg = '%s is disabled and cannot be disabled again.' % client super(ClientStatusException, self).__init__(msg) @@ -66,21 +82,22 @@ def __init__(self, value): class LocaleException(Exception): '''Raise when wait_and_click fails''' def __init__(self, dirCounter, langCounter): - if dirCounter!=langCounter: + if dirCounter != langCounter: msg = 'The available languages in login did not meet expectations.\n Expected '+str(dirCounter)+' languages but got '+str(langCounter)+'.' else: - msg = 'The available languages in login did not meet expectations.\n' + msg = 'The available languages in login did not meet expectations.\n' super(LocaleException, self).__init__(msg) class WrongCredentialsException(Exception): '''Raise when wait_and_click fails''' def __init__(self, username, password): - msg = 'Username "%s" or password "%s" is wrong.' % (username,password) + msg = 'Username "%s" or password "%s" is wrong.' % (username, password) super(WrongCredentialsException, self).__init__(msg) -class SeleniumTest(unittest.TestCase): +class SeleniumTest(unittest.TestCase): + browser = 'firefox' base_url = 'http://127.0.0.1/bareos-webui' username = 'admin' @@ -95,47 +112,84 @@ class SeleniumTest(unittest.TestCase): maxwait = 10 # time to wait before trying again waittime = 0.1 + # Travis SauceLab integration + travis = False + sauce_username = None + access_key = None + + + def __get_dict_from_env(self): + result = {} + + for key in [ + 'TRAVIS_BRANCH', + 'TRAVIS_BUILD_NUMBER', + 'TRAVIS_BUILD_WEB_URL', + 'TRAVIS_COMMIT', + 'TRAVIS_COMMIT_MESSAGE', + 'TRAVIS_JOB_NUMBER', + 'TRAVIS_JOB_WEB_URL', + 'TRAVIS_PULL_REQUEST', + 'TRAVIS_PULL_REQUEST_BRANCH', + 'TRAVIS_PULL_REQUEST_SHA', + 'TRAVIS_PULL_REQUEST_SLUG', + 'TRAVIS_REPO_SLUG', + 'TRAVIS_TAG' + ]: + result[key] = os.environ.get(key) + result['GIT_BRANCH_URL'] = 'https://github.com/bareos/bareos/tree/' + os.environ.get('TRAVIS_BRANCH') + result['GIT_COMMIT_URL'] = 'https://github.com/bareos/bareos/tree/' + os.environ.get('TRAVIS_COMMIT') + + return result + + + def __setUpTravis(self): + self.desired_capabilities = {} + buildnumber = os.environ['TRAVIS_BUILD_NUMBER'] + jobnumber = os.environ['TRAVIS_JOB_NUMBER'] + self.desired_capabilities['tunnel-identifier'] = jobnumber + self.desired_capabilities['build'] = "{} {}".format(os.environ.get('TRAVIS_REPO_SLUG'), buildnumber) + #self.desired_capabilities['name'] = "Travis Build Nr. {}: {}".format(buildnumber, self.__get_name_of_test()) + self.desired_capabilities['name'] = "{}: {}".format(os.environ.get('TRAVIS_BRANCH'), self.__get_name_of_test()) + self.desired_capabilities['tags'] = [os.environ.get('TRAVIS_BRANCH')] + self.desired_capabilities['custom-data'] = self.__get_dict_from_env() + self.desired_capabilities['platform'] = "macOS 10.13" + self.desired_capabilities['browserName'] = "chrome" + self.desired_capabilities['version'] = "latest" + self.desired_capabilities['captureHtml'] = True + self.desired_capabilities['extendedDebugging'] = True + sauce_url = "http://%s:%s@localhost:4445/wd/hub" + self.driver = webdriver.Remote( + desired_capabilities=self.desired_capabilities, + command_executor=sauce_url % (self.sauce_username, self.access_key) + ) + def setUp(self): # Configure the logger, for information about the timings set it to INFO # Selenium driver itself will write additional debug messages when set to DEBUG logging.basicConfig( - filename='%s/webui-selenium-test.log' % (self.logpath), - format='%(levelname)s %(module)s.%(funcName)s: %(message)s', - level=logging.INFO + filename='%s/webui-selenium-test.log' % (self.logpath), + format='%(levelname)s %(module)s.%(funcName)s: %(message)s', + level=logging.INFO ) self.logger = logging.getLogger() - if(os.environ.get('TRAVIS') == 'true'): - from sauceclient import SauceClient - from selenium.webdriver.remote.remote_connection import RemoteConnection - self.desired_capabilities = {} - self.desired_capabilities['name'] = (self.id().split('.'))[2]+": Travis Build Nr. %s" % os.environ['TRAVIS_BUILD_NUMBER'] - jobnumber = os.environ['TRAVIS_JOB_NUMBER'] - if jobnumber: - self.desired_capabilities['tunnel-identifier'] = jobnumber - buildnumber = os.environ['TRAVIS_BUILD_NUMBER'] - if buildnumber: - self.desired_capabilities['build'] = buildnumber - self.desired_capabilities['platform'] = "macOS 10.13" - self.desired_capabilities['browserName'] = "chrome" - self.desired_capabilities['version'] = "latest" - sauce_url = "http://%s:%s@localhost:4445/wd/hub" - self.driver = webdriver.Remote( - desired_capabilities=self.desired_capabilities, - command_executor=sauce_url % (self.sauce_username, self.access_key) - ) + if self.travis: + self.__setUpTravis() else: if self.browser == 'chrome': chromedriverpath = self.getChromedriverpath() self.driver = webdriver.Chrome(chromedriverpath) - - if self.browser == "firefox": + elif self.browser == "firefox": d = DesiredCapabilities.FIREFOX - d['loggingPrefs'] = { 'browser':'ALL' } + d['loggingPrefs'] = {'browser': 'ALL'} fp = webdriver.FirefoxProfile() fp.set_preference('webdriver.log.file', self.logpath + '/firefox_console.log') self.driver = webdriver.Firefox(capabilities=d, firefox_profile=fp) + else: + raise RuntimeError('Browser {} not found.'.format(str(self.browser))) + # used as timeout for selenium.webdriver.support.expected_conditions (EC) self.wait = WebDriverWait(self.driver, self.maxwait) @@ -145,7 +199,13 @@ def setUp(self): self.verificationErrors = [] self.logger.info("===================== TESTING =====================") -# Tests + # + # Tests + # + def test_login(self): + self.login() + self.logout() + def test_client_disabling(self): # This test navigates to clients, ensures client is enabled, # disables it, closes a possible modal, goes to dashboard and reenables client. @@ -166,17 +226,17 @@ def test_client_disabling(self): self.wait_and_click(By.LINK_TEXT, self.client) self.wait_and_click(By.ID, 'menu-topnavbar-client') # Checks if client is enabled - if self.client_status(self.client)=='Enabled': + if self.client_status(self.client) == 'Enabled': # Disables client self.wait_and_click(By.XPATH, '//tr[contains(td[1], "%s")]/td[5]/a[@title="Disable"]' % self.client) # Switches to dashboard, if prevented by open modal: close modal - self.wait_and_click(By.ID, 'menu-topnavbar-dashboard',By.CSS_SELECTOR, 'div.modal-footer > button.btn.btn-default') + self.wait_and_click(By.ID, 'menu-topnavbar-dashboard', By.CSS_SELECTOR, 'div.modal-footer > button.btn.btn-default') # Throw exception if client is already disabled else: raise ClientStatusException(self.client, 'disabled') self.wait_and_click(By.ID, 'menu-topnavbar-client') # Checks if client is disabled so that it can be enabled - if self.client_status(self.client)=='Disabled': + if self.client_status(self.client) == 'Disabled': # Enables client self.wait_and_click(By.XPATH, '//tr[contains(td[1], "%s")]/td[5]/a[@title="Enable"]' % self.client) # Switches to dashboard, if prevented by open modal: close modal @@ -187,25 +247,20 @@ def test_client_disabling(self): self.logout() def disabled_test_job_canceling(self): - driver = self.driver self.login() job_id = self.job_start_configured() self.job_cancel(job_id) self.logout() - def test_login(self): - self.login() - self.logout() - def test_languages(self): # Goes to login page, matches found languages against predefined list, throws exception if no match driver = self.driver driver.get(self.base_url + '/auth/login') self.wait_and_click(By.XPATH, '//button[@data-id="locale"]') - expected_elements = {'Chinese','Czech','Dutch/Belgium','English','French','German','Italian','Russian','Slovak','Spanish','Turkish'} + expected_elements = ['Chinese', 'Czech', 'Dutch/Belgium', 'English', 'French', 'German', 'Italian', 'Russian', 'Slovak', 'Spanish', 'Turkish'] found_elements = [] for element in self.driver.find_elements_by_xpath('//ul[@aria-expanded="true"]/li[@data-original-index>"0"]/a/span[@class="text"]'): - found_elements.append(element.text) + found_elements.append(element.text) # Compare the counted languages against the counted directories for element in expected_elements: if element not in found_elements: @@ -237,7 +292,7 @@ def test_rerun_job(self): def test_restore(self): # Login self.login() - self.wait_for_url_and_click('/restore/') + self.wait_and_click(By.ID, 'menu-topnavbar-restore') # Click on client dropdown menue and close the possible modal self.wait_and_click(By.XPATH, '(//button[@data-id="client"])', By.XPATH, '//div[@id="modal-001"]//button[.="Close"]') # Select correct client @@ -276,15 +331,17 @@ def test_run_default_job(self): self.wait_and_click(By.ID, 'menu-topnavbar-dashboard') self.logout() -# Methods used for testing + # + # Methods used for testing + # def client_status(self, client): # Wait until site and status element are loaded, check client, if not found raise exception - self.wait.until(EC.presence_of_element_located((By.XPATH, '//tr[contains(td[1], "%s")]/td[4]/span' % self.client))) + self.wait.until(EC.presence_of_element_located((By.XPATH, '//tr[contains(td[1], "%s")]/td[4]/span' % client))) try: - status = self.driver.find_element(By.XPATH, '//tr[contains(td[1], "%s")]/td[4]/span' % self.client).text + status = self.driver.find_element(By.XPATH, '//tr[contains(td[1], "%s")]/td[4]/span' % client).text except NoSuchElementException: - raise ClientNotFoundException(self.client) + raise ClientNotFoundException(client) return status def job_cancel(self, id): @@ -339,17 +396,16 @@ def logout(self): self.wait_and_click(By.LINK_TEXT, 'Logout') sleep(self.sleeptime) -# Methods used for waiting and clicking + # + # Methods used for waiting and clicking + # def getChromedriverpath(self): # On OS X: Chromedriver path is 'usr/local/lib/chromium-browser/chromedriver' - for chromedriverpath in {'/usr/lib/chromium-browser/chromedriver', '/usr/local/lib/chromium-browser/chromedriver'}: - try: - os.path.isfile(chromedriverpath) - except FileNotFoundError: - pass - else: + for chromedriverpath in ['/usr/lib/chromium-browser/chromedriver', '/usr/local/lib/chromium-browser/chromedriver']: + if os.path.isfile(chromedriverpath): return chromedriverpath + raise IOError('Chrome Driver file not found.') def wait_and_click(self, by, value, modal_by=None, modal_value=None): logger = logging.getLogger() @@ -387,7 +443,7 @@ def wait_for_element(self, by, value): except TimeoutException: self.driver.save_screenshot('screenshot.png') raise ElementTimeoutException(value) - if element==None: + if element is None: try: self.driver.find_element(by, value) except NoSuchElementException: @@ -412,33 +468,41 @@ def close_alert_and_get_its_text(self, accept=True): alert = self.driver.switch_to_alert() alert_text = alert.text except NoAlertPresentException: - return + return None if accept: alert.accept() else: alert.dismiss() - logger.debug( 'alert message: %s' % (alert_text)) + logger.debug('alert message: %s' % (alert_text)) return alert_text def tearDown(self): - if(os.environ.get('TRAVIS') == 'true'): - print("Link to job : https://saucelabs.com/jobs/%s" % self.driver.session_id) + logger = logging.getLogger() + if self.travis: + print("Link to test {}: https://app.saucelabs.com/jobs/{}".format(self.__get_name_of_test(), self.driver.session_id)) sauce_client = SauceClient(self.sauce_username, self.access_key) - try: - if sys.exc_info() == (None, None, None): - sauce_client.jobs.update_job(self.driver.session_id, passed=True) - else: - sauce_client.jobs.update_job(self.driver.session_id, passed=False) - finally: - self.driver.quit() - self.assertEqual([], self.verificationErrors) - else: + if sys.exc_info() == (None, None, None): + sauce_client.jobs.update_job(self.driver.session_id, passed=True) + else: + sauce_client.jobs.update_job(self.driver.session_id, passed=False) + try: self.driver.quit() - self.assertEqual([], self.verificationErrors) + except WebDriverException as e: + logger.warn('{}: ignored'.format(str(e))) + + self.assertEqual([], self.verificationErrors) + + def __get_name_of_test(self): + return self.id().split('.', 1)[1] + + def get_env(): - # Get attributes as environment variables, - # if not available or set use defaults. + ''' + Get attributes as environment variables, + if not available or set use defaults. + ''' + chromedriverpath = os.environ.get('BAREOS_CHROMEDRIVER_PATH') if chromedriverpath: SeleniumTest.chromedriverpath = chromedriverpath @@ -466,9 +530,12 @@ def get_env(): sleeptime = os.environ.get('BAREOS_DELAY') if sleeptime: SeleniumTest.sleeptime = float(sleeptime) - if(os.environ.get('TRAVIS') == 'true'): - SeleniumTest.sauce_username = os.environ.get('SAUCE_USERNAME') - SeleniumTest.access_key = os.environ.get('SAUCE_ACCESS_KEY') + if os.environ.get('TRAVIS_COMMIT'): + SeleniumTest.travis = True + SeleniumTest.sauce_username = os.environ.get('SAUCE_USERNAME') + SeleniumTest.access_key = os.environ.get('SAUCE_ACCESS_KEY') + + if __name__ == '__main__': get_env()