Commit 5cafac00 authored by Marc Egger's avatar Marc Egger
Browse files
parent b3b2d9b3
Pipeline #2837 failed with stages
in 3 minutes and 17 seconds
...@@ -53,6 +53,7 @@ Assumes you have run qfq in docker as explained above. ...@@ -53,6 +53,7 @@ Assumes you have run qfq in docker as explained above.
Assumes you have run qfq in docker as explained above. Assumes you have run qfq in docker as explained above.
0. Install selenium for python: ```python3 -m pip install --user selenium```
1. again from the docker directory run ```./run_selenium_tests_local.sh``` 1. again from the docker directory run ```./run_selenium_tests_local.sh```
## Run a single selenium test file on local machine ## Run a single selenium test file on local machine
......
This diff is collapsed.
This diff is collapsed.
...@@ -73,7 +73,7 @@ until mysql -u root --password=${MYSQL_ROOT_PASSWORD} --port=${DB_PORT} -h 127.0 ...@@ -73,7 +73,7 @@ until mysql -u root --password=${MYSQL_ROOT_PASSWORD} --port=${DB_PORT} -h 127.0
-e "CREATE DATABASE ${T3_DATABASE}; CREATE DATABASE ${QFQ_DATABASE}" -e "CREATE DATABASE ${T3_DATABASE}; CREATE DATABASE ${QFQ_DATABASE}"
do do
tries=$((tries+1)) tries=$((tries+1))
if [[ "${tries}" -gt 30 ]]; then if [[ "${tries}" -gt 60 ]]; then
echo "Timeout: could not connect to database." echo "Timeout: could not connect to database."
exit 1; exit 1;
fi fi
......
#!/bin/bash -ex #!/bin/bash
source run_qfq_docker.output source run_qfq_docker.output
# download chromedriver # this function prints a separator
if [ ! -f chromedriver ]; then function print_separator {
wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip # prints the separator
unzip /tmp/chromedriver.zip chromedriver echo -e "----------------------------------------------------------------------"
chmod +x chromedriver }
# prints the starting separator
print_separator
# checks that a file named geckodriver doesn't already exist
if [ ! -f "geckodriver" ]; then
# stores the current version of the driver
gecko_version=$(curl --silent "https://api.github.com/repos/mozilla/geckodriver/releases/latest" | grep -Po '"tag_name": "\K.*?(?=")') &> /dev/null
# downloads the geckodriver from github
wget -O /tmp/geckodriver.tar.gz https://github.com/mozilla/geckodriver/releases/download/${gecko_version}/geckodriver-${gecko_version}-linux64.tar.gz &> /dev/null
# unzips the downloaded geckodriver
tar xzf /tmp/geckodriver.tar.gz geckodriver &> /dev/null
# makes the geckodriver executable
chmod +x geckodriver &> /dev/null
# prints a success output
echo -e "Successfully downloaded geckodriver"
fi
# checks that a file named chromedriver doesn't already exist
if [ ! -f "chromedriver" ]; then
# downloads the newest version of the chromedriver from google
wget -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip &> /dev/null
# unzips the downloaded chromedriver
unzip /tmp/chromedriver.zip chromedriver &> /dev/null
# makes the chromedriver executable
chmod +x chromedriver &> /dev/null
# prints a success output
echo -e "Successfully downloaded chromedriver"
fi
# reads the url to test from the input variable
SELENIUM_URL=$1
# checks if the selenium url is not given
if [ -z $SELENIUM_URL ]; then
SELENIUM_URL="http://127.0.0.1:${T3_PORT}"
fi
# defines the url to be used during testing
export SELENIUM_URL=$SELENIUM_URL
# stores the default engine
DEFAULT_ENGINE="chrome"
# reads the engine from the 2nd input variable
ENGINE=$2
# checks if an engine is not specified
if [ -z $ENGINE ]; then
# defines the default engine to use during tests
export BROWSER=$DEFAULT_ENGINE
# defines the path to the drivers of the engine
export DRIVER_PATH="${PWD}/${DEFAULT_ENGINE}driver"
else
# defines the engine to use during tests
export BROWSER=$ENGINE
# defines the path to the drivers of the engine
export DRIVER_PATH="${PWD}/${ENGINE}driver"
fi
# stores the default headless option
DEFAULT_HEADLESS="no"
# reads the headless option from the 3rd input variable
HEADLESS=$3
# checks if the headless parameter is not specified
if [ -z $HEADLESS ]; then
# defines if the browser gui should open
export SELENIUM_HEADLESS=$DEFAULT_HEADLESS
else
# defines if the browser gui should open
export SELENIUM_HEADLESS=$HEADLESS
fi
# stores the default slowdown
DEFAULT_SLOWDOWN="0"
# reads the slowdown from the 4th input variable
SLOWDOWN=$4
# checks if the slowdown parameter is not specified
if [ -z $SLOWDOWN ]; then
# defines what slowdown should be applied
export SELENIUM_SLOWDOWN=$DEFAULT_SLOWDOWN
else
# defines what slowdown should be applied
export SELENIUM_SLOWDOWN=$SLOWDOWN
fi fi
export CHROMEDRIVER_PATH="${PWD}/chromedriver"
# run tests
cd ../extension/Tests/selenium cd ../extension/Tests/selenium
export SELENIUM_URL="http://127.0.0.1:${T3_PORT}"
export SELENIUM_HEADLESS="no" # prints running tests
python -m unittest discover echo -e -n "Running tests: "
\ No newline at end of file
# runs the python tests
python3 -W ignore -m unittest discover
# checks if the tests were successful
if [ $? -eq 0 ]; then
print_separator
# prints a success message
echo -e "Successfully tested ${SELENIUM_URL}"
else
print_separator
# prints a success message
echo -e "Failed while testing ${SELENIUM_URL}"
fi
# prints the trailing separator
print_separator
# exits the program
exit 0
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from selenium import webdriver from selenium import webdriver
from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.options import Options
from selenium.webdriver.firefox.options import Options as FFOptions
from selenium.webdriver.support.ui import Select from selenium.webdriver.support.ui import Select
from selenium.common.exceptions import StaleElementReferenceException, NoSuchElementException, WebDriverException from selenium.common.exceptions import StaleElementReferenceException, NoSuchElementException, WebDriverException
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
import unittest import unittest
import os import os
import sys import sys
import glob
import time import time
import codecs import codecs
import re import re
import socket import socket
import random
import string
def urlJoin(url_list): def urlJoin(url_list):
return '/'.join(s.strip('/') for s in url_list) return '/'.join(s.strip('/') for s in url_list)
def urlFromHostname(hostname, port=80): def urlFromHostname(hostname, port=80):
try: """
return 'http://' + socket.gethostbyname(hostname) + ':' + str(port) this function returns a url from a given hostname and a given port
except socket.gaierror as e: """
return 'unknown' try:
return 'http://' + socket.gethostbyname(hostname) + ':' + str(port)
except socket.gaierror as e:
return 'unknown'
class QfqSeleniumTestCase(unittest.TestCase): class QfqSeleniumTestCase(unittest.TestCase):
"""initialize selenium and add custom commands and asserts""" """
this class initializes selenium and adds custom commands and asserts.
"""
max_number_of_log_files_to_keep = 100 max_number_of_log_files_to_keep = 100
gecko_browser_name = "gecko"
gecko_driver_path = "geckodriver"
chrome_browser_name = "chrome"
chrome_driver_path = "chromedriver"
# These variables can be overwritten by environment variables of the same name # these variables can be overwritten by environment variables of the same name
CHROMEDRIVER_PATH = "chromedriver" BROWSER = chrome_browser_name
DRIVER_PATH = ""
if BROWSER == chrome_browser_name:
DRIVER_PATH = chrome_driver_path
elif BROWSER == gecko_browser_name:
DRIVER_PATH = gecko_driver_path
SELENIUM_LOGS_PATH = os.getcwd() SELENIUM_LOGS_PATH = os.getcwd()
SELENIUM_HEADLESS = 'yes' # set environment variable to 'no' to turn off SELENIUM_HEADLESS = 'yes' # set environment variable to 'no' to turn off
SELENIUM_URL = urlFromHostname('typo3container') SELENIUM_URL = urlFromHostname('typo3container')
SELENIUM_SLOWDOWN = 0
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
"""executed by unittest once before any tests are executed""" """
executed by unittest once before any tests are executed.
"""
# read environment variables # reads the environment variables
cls.CHROMEDRIVER_PATH = os.environ.get('CHROMEDRIVER_PATH', cls.CHROMEDRIVER_PATH) cls.BROWSER = os.environ.get('BROWSER', cls.BROWSER)
cls.DRIVER_PATH = os.environ.get('DRIVER_PATH', cls.DRIVER_PATH)
cls.SELENIUM_LOGS_PATH = os.environ.get('SELENIUM_LOGS_PATH', cls.SELENIUM_LOGS_PATH) cls.SELENIUM_LOGS_PATH = os.environ.get('SELENIUM_LOGS_PATH', cls.SELENIUM_LOGS_PATH)
cls.SELENIUM_HEADLESS = os.environ.get('SELENIUM_HEADLESS', cls.SELENIUM_HEADLESS) cls.SELENIUM_HEADLESS = os.environ.get('SELENIUM_HEADLESS', cls.SELENIUM_HEADLESS)
cls.SELENIUM_URL = os.environ.get('SELENIUM_URL', cls.SELENIUM_URL) cls.SELENIUM_URL = os.environ.get('SELENIUM_URL', cls.SELENIUM_URL)
cls.SELENIUM_SLOWDOWN = float(os.environ.get('SELENIUM_SLOWDOWN', cls.SELENIUM_SLOWDOWN))
# setup log directory, delete very old log files # setup log directory, delete very old log files
cls.selenium_logs_dir = 'selenium_logs' cls.selenium_logs_dir = 'selenium_logs'
...@@ -52,42 +79,58 @@ class QfqSeleniumTestCase(unittest.TestCase): ...@@ -52,42 +79,58 @@ class QfqSeleniumTestCase(unittest.TestCase):
log_file_pattern = re.compile('[0-9]{8}-{1}[0-9]{6}.*') log_file_pattern = re.compile('[0-9]{8}-{1}[0-9]{6}.*')
for filename in log_files_to_delete: for filename in log_files_to_delete:
if not log_file_pattern.match(filename): if not log_file_pattern.match(filename):
print 'non log file in log directory found: ', filename print('non log file in log directory found: ', filename)
continue continue
file_path = os.path.join(cls.selenium_logs_dir_path, filename) file_path = os.path.join(cls.selenium_logs_dir_path, filename)
os.remove(file_path) os.remove(file_path)
# initialize webdriver # initializes webdriver
chrome_options = Options() if cls.BROWSER == cls.chrome_browser_name:
if cls.SELENIUM_HEADLESS != 'no': chrome_options = Options()
chrome_options.add_argument("--headless") if cls.SELENIUM_HEADLESS != 'no':
chrome_options.add_argument("--window-size=1920,1080") chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-dev-shm-usage") chrome_options.add_argument("--window-size=1920,1080")
chrome_options.add_argument("--no-sandbox") chrome_options.add_argument("--disable-dev-shm-usage")
desired_capabilities = {'UNEXPECTED_ALERT_BEHAVIOUR': 'ignore'} chrome_options.add_argument("--no-sandbox")
cls.driver = webdriver.Chrome(chrome_options=chrome_options, desired_capabilities = {'UNEXPECTED_ALERT_BEHAVIOUR': 'ignore'}
executable_path=cls.CHROMEDRIVER_PATH, cls.driver = webdriver.Chrome(chrome_options=chrome_options,
desired_capabilities=desired_capabilities) executable_path=cls.DRIVER_PATH,
desired_capabilities=desired_capabilities)
elif cls.BROWSER == cls.gecko_browser_name:
firefox_options = FFOptions()
if cls.SELENIUM_HEADLESS != 'no':
firefox_options.headless = True
desired_capabilities = webdriver.DesiredCapabilities().FIREFOX
cls.driver = webdriver.Firefox(firefox_options=firefox_options,
executable_path=cls.DRIVER_PATH,
capabilities=desired_capabilities)
cls.driver.set_window_size(1920, 1080)
cls.driver.implicitly_wait(30) cls.driver.implicitly_wait(30)
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
"""executed by unittest after all test cases were executed""" """
executed by unittest after all test cases were executed
"""
cls.driver.quit() cls.driver.quit()
def tearDown(self): def tearDown(self):
"""executed by unittest after every single test case""" """
executed by unittest after every single test case
"""
# save website state on failure # save website state on failure
if sys.exc_info()[0]: if sys.exc_info()[0]:
filename = time.strftime("%Y%m%d-%H%M%S") + '_' + self._testMethodName filename = time.strftime("%Y%m%d-%H%M%S") + '_' + self._testMethodName
screenshot_file_path = self.qfq_save_screenshot(filename) screenshot_file_path = self.qfq_save_screenshot(filename)
html_file_path = self.qfq_save_html(filename) html_file_path = self.qfq_save_html(filename)
print 'Test failed.' print('Test failed.')
print 'Webpage screenshot saved to ' + screenshot_file_path print('Webpage screenshot saved to ' + screenshot_file_path)
print 'Webpage Html saved to ' + html_file_path print('Webpage Html saved to ' + html_file_path)
print '!!! ATTENTION !!!: If you get the error "unexpected alert open",' \ print('!!! ATTENTION !!!: If you get the error "unexpected alert open",' \
+ 'there must be another error above which actually triggers the test failure.' + 'there must be another error above which actually triggers the test failure.')
def _prepare_log_file_path(self, filename, suffix=''): def _prepare_log_file_path(self, filename, suffix=''):
file_path = os.path.join(self.selenium_logs_dir_path, filename + suffix) file_path = os.path.join(self.selenium_logs_dir_path, filename + suffix)
...@@ -101,49 +144,300 @@ class QfqSeleniumTestCase(unittest.TestCase): ...@@ -101,49 +144,300 @@ class QfqSeleniumTestCase(unittest.TestCase):
except (StaleElementReferenceException, NoSuchElementException, WebDriverException): except (StaleElementReferenceException, NoSuchElementException, WebDriverException):
retries -= 1 retries -= 1
def _slow_available(func):
"""
adds a globally specified slowdown to a function
"""
def f(*args, **kwargs):
self = args[0]
self.qfq_wait(self.SELENIUM_SLOWDOWN)
func(*args, **kwargs)
return f
# ----- DATA SAVING ----- #
def qfq_save_screenshot(self, filename): def qfq_save_screenshot(self, filename):
"""
saves a screenshot of the page to a logfile with a given name
"""
screenshot_file_path = self._prepare_log_file_path(filename, '.png') screenshot_file_path = self._prepare_log_file_path(filename, '.png')
self.driver.save_screenshot(screenshot_file_path) self.driver.save_screenshot(screenshot_file_path)
return screenshot_file_path return screenshot_file_path
def qfq_save_html(self, filename): def qfq_save_html(self, filename):
"""
saves the html source to a logfile with a given name
"""
html_file_path = self._prepare_log_file_path(filename, '.html') html_file_path = self._prepare_log_file_path(filename, '.html')
with codecs.open(html_file_path, "w", "utf-8") as f: with codecs.open(html_file_path, "w", "utf-8") as f:
f.write(self.driver.page_source) f.write(self.driver.page_source)
return html_file_path return html_file_path
def qfq_goto(self, web_path):
url = urlJoin([self.SELENIUM_URL, web_path])
self.driver.get(url)
def qfq_wait(self, seconds):
time.sleep(seconds)
# ----- ASSERTIONS ----- #
def qfq_assert_text_exists(self, text): def qfq_assert_text_exists(self, text):
self.assertTrue(text in self.driver.page_source) """
asserts if a given text exists on the webpage
"""
self.assertTrue(str(text) in self.driver.page_source)
def qfq_assert_text_absent(self, text): def qfq_assert_text_absent(self, text):
self.assertTrue(text not in self.driver.page_source) """
asserts if a given text is absent on the webpage
"""
self.assertTrue(str(text) not in self.driver.page_source)
def qfq_assert_true(self, bool):
"""
asserts if a given bool is true
"""
self.assertTrue(bool)
def qfq_assert_false(self, bool):
"""
asserts if a given bool is false
"""
self.assertFalse(bool)
# ----- SELECTORS ----- #
def qfq_get_element_by_css_selector(self, selector, select_invisibles=True):
"""
this funtion returns the first element to match a given css selector
"""
element = WebDriverWait(self.driver, 1).until(EC.presence_of_element_located((By.CSS_SELECTOR, selector)))
if self.qfq_element_is_visible(element) or select_invisibles:
return element
else:
raise ValueError("The element with the given selector is not visible on screen")
def qfq_find_by_data_ref(self, data_reference): def qfq_get_element_by_id(self, id, select_invisibles=True):
return self.driver.find_element_by_css_selector("[data-reference='{}']".format(data_reference)) """
returns an element which is identified by a given id
"""
id_occurences = self.driver.page_source.count(" id=\"" + id + "\"")
if id_occurences == 0 or id_occurences > 1:
raise ValueError(
"No element could be identified with the given id. The page source contains more than one or no element with the given id."
)
return self.qfq_get_element_by_css_selector(
"#" + str(id),
select_invisibles
)
def qfq_get_element_by_data_ref(self, data_reference, select_invisibles=True):
"""
returns an element which is identified by a given data reference.
If there is no such data reference it throws an exception.
"""
return self.qfq_get_element_by_css_selector(
"[data-reference='" + data_reference + "']",
select_invisibles
)
def qfq_get_element_by_xpath(self, xpath, select_invisibles=True):
"""
this function returns an element identified by a given xpath
"""
element = WebDriverWait(self.driver, 1).until(EC.presence_of_element_located((By.XPATH, xpath)))
if self.qfq_element_is_visible(element) or select_invisibles:
return element
else:
raise ValueError("The element with the given selector is not visible on screen")
# ----- ACTIONS ----- #
@_slow_available
def qfq_goto_page(self, web_path):
"""
goes to a sub page of the given path
"""
url = urlJoin([self.SELENIUM_URL, str(web_path)])
self.driver.get(url)
@_slow_available
def qfq_fill_textfield(self, data_reference, text): def qfq_fill_textfield(self, data_reference, text):
self.qfq_find_by_data_ref(data_reference).send_keys(text) """
fills a given string into a text field which
is identified by the given data reference
"""
self.qfq_get_element_by_data_ref(data_reference).send_keys(str(text))
@_slow_available
def qfq_clear_textfield(self, data_reference):
"""
clears a textfield with a given data reference
"""
self.qfq_get_element_by_data_ref(data_reference).clear()
@_slow_available
def qfq_dropdown_select(self, data_reference, option): def qfq_dropdown_select(self, data_reference, option):
Select(self.qfq_find_by_data_ref(data_reference)).select_by_visible_text(option) """
selects an option (with the option's text)
from a dropdown list by data reference
"""
Select(
self.qfq_get_element_by_data_ref(data_reference)
).select_by_visible_text(str(option))
@_slow_available
def qfq_radio_select(self, text):
"""
selects a radio button in a radio button set
"""
self.qfq_click_element_with_xpath(
"//label[text()='" + str(text) + "']"
)
@_slow_available
def qfq_checkbox_select(self, text):
"""
selects a checkbox in a checkbox set
"""
# TODO doesn't work currently because qfq uses div instead of label
# self.qfq_click_element_with_xpath(
# "//label[text()='" + str(text) + "']"
# )
@_slow_available
def qfq_open_pill(self, name):
"""
this function opens a pill by name
"""
self.qfq_click_element_with_xpath(
"//*[@id='qfqTabs']//a[text()='" + str(name) + "']"
)
@_slow_available
def qfq_upload_file(self, data_reference, file_name, suffix, size):
"""
this function uploads a generated file from the given
arguments size and suffix. This file is uploaded to the
input with the given data reference.
"""
# stores the tmp dir name
tmp_dir = "tmp"
# checks if the tmp dir doesn't already exist
if not os.path.exists(tmp_dir):
# creates tmp dir if it doesn't exist
os.makedirs(tmp_dir)
# loops through all files in the tmp directory
for f in glob.glob(tmp_dir + "/*." + suffix):
# removes each file (old test files)
os.remove(f)
# stores the path for the file
file_path = os.getcwd() + "/" + tmp_dir + "/" + file_name + "." + suffix
# opens the file from the file path (creates the file)
with open(file_path, "w") as f:
# writes some random text to the file with the given size
f.write(self.qfq_generate_random_string(size))
# uploads the created file
self.qfq_get_element_by_data_ref(data_reference).send_keys(file_path)
# ----- BUTTONS ----- #
@_slow_available
def qfq_click_new_form_button(self): def qfq_click_new_form_button(self):
self.qfq_find_by_data_ref('newForm').click() """
clicks the button to create a new form
"""
self.qfq_click_element_with_data_ref('newForm')
@_slow_available
def qfq_click_close_form_button(self): def qfq_click_close_form_button(self):
def f(): self.driver.find_element_by_id('close-button').click() """