From a28cc29bd80280b956f83b68d1648b045b613dbf Mon Sep 17 00:00:00 2001 From: Nico Schedel Date: Thu, 14 Nov 2019 12:09:34 +0100 Subject: [PATCH] TT#70901 initial commit for ngcp-csc-ui selenium tests ngcp-csc-ui selenium tests will be created here using python3 and nose2 for running tests. firefox only for now, chrome can be added later. this test will create a subscriber, login to csc panel and delete subscriber when it is done Change-Id: Ie10c369b878885aa38892488aea58353d43e74e1 --- t/selenium/Dockerfile | 78 ++++++++++++++++++++++++++ t/selenium/functions/Collections.py | 86 +++++++++++++++++++++++++++++ t/selenium/jenkins_docker_run | 12 ++++ t/selenium/nose2cfg/jenkinstest.cfg | 9 +++ t/selenium/nose2cfg/localtest.cfg | 12 ++++ t/selenium/test_login.py | 76 +++++++++++++++++++++++++ t/selenium/testrunner | 77 ++++++++++++++++++++++++++ 7 files changed, 350 insertions(+) create mode 100644 t/selenium/Dockerfile create mode 100644 t/selenium/functions/Collections.py create mode 100755 t/selenium/jenkins_docker_run create mode 100644 t/selenium/nose2cfg/jenkinstest.cfg create mode 100644 t/selenium/nose2cfg/localtest.cfg create mode 100644 t/selenium/test_login.py create mode 100755 t/selenium/testrunner diff --git a/t/selenium/Dockerfile b/t/selenium/Dockerfile new file mode 100644 index 00000000..bf9a0539 --- /dev/null +++ b/t/selenium/Dockerfile @@ -0,0 +1,78 @@ +# DOCKER_NAME=ngcp-csc-ui-selenium-test-buster +FROM docker.mgm.sipwise.com/sipwise-buster:latest + +# Important! Update this no-op ENV variable when this Dockerfile +# is updated with the current date. It will force refresh of all +# of the base images and things like `apt-get update` won't be using +# old cached versions when the Dockerfile is built. +ENV REFRESHED_AT 2019-11-21 + +RUN apt-get update +RUN apt-get install --assume-yes \ + curl \ + devscripts \ + fluxbox \ + git \ + libdbus-glib-1-2 \ + libgtk-3-0 \ + lsof \ + net-tools \ + procps \ + python3 \ + python3-pip \ + vim-tiny \ + tigervnc-standalone-server \ + wget \ + xterm + +RUN adduser --disabled-password --gecos=Selenium-User selenium + +WORKDIR /home/selenium/ + +# installing geckodriver (0.26) +RUN wget http://deb.sipwise.com/files/geckodriver-v0.26.0-linux64.tar.gz && \ + tar xvf geckodriver-v0.26.0-linux64.tar.gz && \ + ln -s /home/selenium/geckodriver /usr/bin/geckodriver && \ + rm -f geckodriver-v0.26.0-linux64.tar.gz + +# installing firefox (lang=en_US) (70.0.1) +RUN wget http://deb.sipwise.com/files/firefox-70.0.1.tar.bz2 && \ + tar xvf firefox-70.0.1.tar.bz2 && \ + ln -s /home/selenium/firefox/firefox /usr/bin/firefox && \ + rm -f firefox-70.0.1.tar.bz2 + +# installing nose2 (0.9.1), coverage (4.5.4) and selenium (3.141.0) via pip +# considering to package latest version of python3-nose2/selenium/coverage as .deb package +# installing coverage first to satisfy dependency for nose2 +RUN wget http://deb.sipwise.com/files/nose2-0.9.1.tar.gz && \ + wget http://deb.sipwise.com/files/selenium-3.141.0.tar.gz && \ + wget http://deb.sipwise.com/files/coverage-4.5.4.tar.gz && \ + pip3 install coverage-4.5.4.tar.gz && \ + pip3 install nose2-0.9.1.tar.gz selenium-3.141.0.tar.gz && \ + rm -f nose2-0.9.1.tar.gz selenium-3.141.0.tar.gz coverage-4.5.4.tar.gz + +RUN echo "cd /code && ./t/selenium/testrunner 1.2.3.4 /code/ tap" >/home/selenium/.bash_history +RUN echo "su - selenium" >/root/.bash_history +EXPOSE 5999 + +COPY t/selenium/jenkins_docker_run /home/selenium/ + +################################################################################ +# Instructions for usage +# ---------------------- +# When you want to build the base image from scratch (jump to the next section if you don't want to build yourself!): +# NOTE: run the following command from root folder of git repository: +# % docker build --tag="ngcp-csc-ui-selenium-test-buster" -f t/selenium/Dockerfile . +# % docker run --rm -p 5999:5999 --shm-size 4g -i -t -v ~/Dokumente/ngcp-csc-ui:/code:rw ngcp-csc-ui-selenium-test-buster bash +# +# Use the existing docker image: +# % docker pull docker.mgm.sipwise.com/ngcp-csc-ui-selenium-test-buster +# % docker run --rm -p 5999:5999 --shm-size 4g -i -t -v $(pwd):/code:rw docker.mgm.sipwise.com/ngcp-csc-ui-selenium-test-buster:latest bash +# +# Inside docker: +# su - selenium +# cd /code && ./t/selenium/testrunner $IP_OF_NGCP_SYSTEM # IP_OF_NGCP_SYSTEM can be e.g. IP of a `vagrant up ce-trunk` system (eth1) +# +# On host (where vncviewer can be e.g. from Debian package tigervnc-viewer): +# % vncviewer geometry=1280x1024x16 localhost:5999 +################################################################################ diff --git a/t/selenium/functions/Collections.py b/t/selenium/functions/Collections.py new file mode 100644 index 00000000..46d44f30 --- /dev/null +++ b/t/selenium/functions/Collections.py @@ -0,0 +1,86 @@ +import os +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By + + +def create_subscriber(driver): + driver.get(os.environ['CATALYST_SERVER'] + ":1443/logout") + driver.get(os.environ['CATALYST_SERVER'] + ":1443") + driver.find_element_by_xpath( + '//*[@id="username"]').send_keys('administrator') + driver.find_element_by_xpath( + '//*[@id="password"]').send_keys('administrator') + driver.find_element_by_xpath('//*[@id="submit"]').click() + driver.find_element_by_xpath( + '//*[@id="main-nav"]//*[contains(text(),"Settings")]').click() + driver.find_element_by_link_text('Customers').click() + hoverclick = ActionChains(driver) + hoverclick.move_to_element(driver.find_element_by_xpath( + '//*[@id="Customer_table"]//tr[1]')) + hoverclick.click(driver.find_element_by_xpath( + '//*[@id="Customer_table"]//tr[1]//td//a[contains(text(), ' + '"Details")]') + ) + hoverclick.perform() + driver.find_element_by_link_text('Expand Groups').click() + driver.execute_script( + 'arguments[0].scrollIntoView();', + driver.find_element_by_link_text('Subscribers') + ) + driver.find_element_by_link_text("Create Subscriber").click() + WebDriverWait(driver, 10).until(EC.element_to_be_clickable( + (By.XPATH, '//*[@id="domainidtable_paginate"]/a[4]'))) + driver.find_element_by_xpath( + '//*[@id="domainidtable_paginate"]/a[4]').click() + driver.find_element_by_xpath( + '//*[@id="domainidtable"]//tr[1]//td//input[@type="checkbox"]' + ).click() + driver.find_element_by_xpath( + '//*[@id="webusername"]').send_keys('testuser') + driver.find_element_by_xpath( + '//*[@id="webpassword"]').send_keys('testpasswd') + driver.find_element_by_xpath( + '//*[@id="username"]').send_keys('testuser') + driver.find_element_by_xpath( + '//*[@id="password"]').send_keys('testpasswd') + driver.find_element_by_xpath('//*[@id="save"]').click() + + +def delete_subscriber(driver): + driver.get(os.environ['CATALYST_SERVER'] + ":1443/logout") + driver.get(os.environ['CATALYST_SERVER'] + ":1443") + driver.find_element_by_xpath( + '//*[@id="username"]').send_keys('administrator') + driver.find_element_by_xpath( + '//*[@id="password"]').send_keys('administrator') + driver.find_element_by_xpath('//*[@id="submit"]').click() + driver.find_element_by_xpath( + '//*[@id="main-nav"]//*[contains(text(),"Settings")]').click() + driver.find_element_by_link_text('Customers').click() + hoverclick = ActionChains(driver) + hoverclick.move_to_element(driver.find_element_by_xpath( + '//*[@id="Customer_table"]//tr[1]')) + hoverclick.click(driver.find_element_by_xpath( + '//*[@id="Customer_table"]//tr[1]//td//a[contains(text(), ' + '"Details")]') + ) + hoverclick.perform() + driver.find_element_by_link_text('Expand Groups').click() + driver.execute_script( + 'arguments[0].scrollIntoView();', + driver.find_element_by_link_text('Subscribers') + ) + WebDriverWait(driver, 1) + hoverclick = ActionChains(driver) + hoverclick.move_to_element(driver.find_element_by_xpath( + '//*[@id="subscribers_table"]//tr[1]')) + hoverclick.click(driver.find_element_by_xpath( + '//*[@id="subscribers_table"]//tr[1]//td//a[contains(text(), ' + '"Terminate")]') + ) + hoverclick.perform() + driver.find_element_by_xpath('//*[@id="dataConfirmOK"]').click() diff --git a/t/selenium/jenkins_docker_run b/t/selenium/jenkins_docker_run new file mode 100755 index 00000000..ad864147 --- /dev/null +++ b/t/selenium/jenkins_docker_run @@ -0,0 +1,12 @@ +#!/bin/bash +# This script is used for running the tests with +# proper arguments from Jenkins under user 'selenium' + +set -e + +su - selenium -c "cd /code && ./t/selenium/testrunner $*" || RC=$? + +## Jenkins will detect errors in produced artifacts if any +## as a result the Jenkins job will be "yellow" instead of "red". +[ -n "${RC}" ] && echo "Masking non-zero exit code '${RC}'" +exit 0 # ${RC:-0} diff --git a/t/selenium/nose2cfg/jenkinstest.cfg b/t/selenium/nose2cfg/jenkinstest.cfg new file mode 100644 index 00000000..f0f3e702 --- /dev/null +++ b/t/selenium/nose2cfg/jenkinstest.cfg @@ -0,0 +1,9 @@ +[unittest] +plugins = nose2.plugins.junitxml +exclude-plugins = nose2.plugins.failfast + +[junit-xml] +always-on = True +keep_restricted = False +path = /results/selenium.xml +test_fullname = True diff --git a/t/selenium/nose2cfg/localtest.cfg b/t/selenium/nose2cfg/localtest.cfg new file mode 100644 index 00000000..998cc61e --- /dev/null +++ b/t/selenium/nose2cfg/localtest.cfg @@ -0,0 +1,12 @@ +[unittest] +plugins = nose2.plugins.junitxml +exclude-plugins = nose2.plugins.failfast + +[junit-xml] +always-on = False +keep_restricted = False +path = selenium.xml +test_fullname = True + +[pretty-assert] +always-on = True diff --git a/t/selenium/test_login.py b/t/selenium/test_login.py new file mode 100644 index 00000000..932b6a5f --- /dev/null +++ b/t/selenium/test_login.py @@ -0,0 +1,76 @@ +import unittest +import os +import nose2 +import functions.Collections as Collections +from selenium import webdriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By + + +class test_login(unittest.TestCase): + def setUp(self): + profile = webdriver.FirefoxProfile() + profile.accept_untrusted_certs = True + caps = DesiredCapabilities().FIREFOX + caps["pageLoadStrategy"] = "normal" + self.driver = webdriver.Firefox( + capabilities=caps, firefox_profile=profile, log_path='/dev/null') + self.driver.implicitly_wait(10) + self.driver.set_page_load_timeout(10) + + def test_login(self): + driver = self.driver + Collections.create_subscriber(driver) + driver.find_element_by_link_text('Expand Groups').click() + domainname = driver.find_element_by_xpath( + '//*[@id="subscribers_table"]//tr[1]/td[3]').text + driver.get(os.environ['CATALYST_SERVER']) + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="text"]' + ).send_keys('invalid') + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="password"]' + ).send_keys('user') + driver.find_element_by_xpath( + '//*[@id="csc-login"]//div//button').click() + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="text"]' + ).send_keys(Keys.CONTROL + "a") + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="text"]' + ).send_keys(Keys.DELETE) + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="text"]' + ).send_keys('testuser@' + domainname) + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="password"]' + ).send_keys(Keys.CONTROL + "a") + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="password"]' + ).send_keys(Keys.DELETE) + driver.find_element_by_xpath( + '//*[@id="csc-login-form"]//div//input[@type="password"]' + ).send_keys('testpasswd') + driver.find_element_by_xpath( + '//*[@id="csc-login"]//div//button').click() + self.assertEqual("testuser", (driver.find_element_by_xpath( + '//*[@id="csc-header-toolbar"]//div//span[contains(text(), ' + '"testuser")]').text) + ) + WebDriverWait(driver, 1) + + def tearDown(self): + driver = self.driver + try: + Collections.delete_subscriber(driver) + driver.close() + except Exception: + driver.close() + + +if __name__ == '__main__': + nose2.main() diff --git a/t/selenium/testrunner b/t/selenium/testrunner new file mode 100755 index 00000000..ff196992 --- /dev/null +++ b/t/selenium/testrunner @@ -0,0 +1,77 @@ +#!/bin/bash + +if ! [ -f /.dockerenv ] && ! grep -q 'devices:/docker' /proc/1/cgroup ; then + echo "Not running inside docker, exiting to avoid data damage." >&2 + exit 1 +fi + +set -e +set -u + +PASSWORD="selenium" + +if [ -z "${1:-}" ] ; then + echo "Usage: $0 [] [junit] [test_plan]" >&2 + echo + echo "Usage examples: + + $0 1.2.3.4 + $0 1.2.3.4 /code/ tap" + exit 1 +fi + +SERVER="${1}" +OUTPUT_DIRECTORY="${2:-/code/}" +OUTPUT_TYPE=${3:-} +shift; shift; shift # remove first three argv to use ${@} below. + +# vnc +echo "Setting VNC password" +printf '%s\n%s\n\n' "${PASSWORD}" "${PASSWORD}" | vncpasswd >/dev/null +PASSWORD_ENCODED=$(hexdump -v -e '"\\""x" 1/1 "%02X"' < "${HOME}/.vnc/passwd") +xvnc_process=$(pgrep -f 'Xtigervnc :99' || true) +export DISPLAY=":99" +if [ -n "${xvnc_process:-}" ] ; then + echo "Warning: existing VNC server found, not restarting Xtigervnc process (PID: $xvnc_process)." +else + echo "Starting VNCserver on display :99" + vncserver -localhost no -geometry 1280x1024 :99 +fi + +echo "Firefox version:" +firefox --version + +# ensure we don't leave any process behind causing problems with re-execution +pkill -f 'geckodriver' || true +DISPLAY=:99 ~/geckodriver -p 4444 --log fatal & +#DISPLAY=:99 ~/geckodriver -p 5555 --log fatal & +#DISPLAY=:99 ~/geckodriver -p 6666 --log fatal & +#DISPLAY=:99 ~/geckodriver -p 7777 --log fatal & + +echo "################################################################################" +echo "Finished main setup, now running tests ..." +echo "Selenium server log file available at /home/selenium/selenium.log" +echo "Watch at test runs by connecting via VNC (password: '${PASSWORD}'):" +echo "echo -e '$PASSWORD_ENCODED' >/tmp/vncpasswd ; vncviewer geometry=1280x1024x16 passwd=/tmp/vncpasswd localhost:5999" + +RC=0 +export CATALYST_SERVER="https://${SERVER}" +cd t/selenium + +if [ "${OUTPUT_TYPE:-}" = "junit" ] ; then + export JENKINS=1 + nose2 -v -c nose2cfg/jenkinstest.cfg | \ + tee -a "${OUTPUT_DIRECTORY}/selenium.xml" + RC="${PIPESTATUS[0]}" +else + nose2 -v -c nose2cfg/localtest.cfg || RC=$? +fi + +echo "Finished test execution, test execution returned with exit code ${RC}." +if [ -f "${OUTPUT_DIRECTORY}/selenium.xml" ] ; then + echo "Test results available at '${OUTPUT_DIRECTORY}/selenium.xml'" +fi + +echo "################################################################################" + +exit "${RC}"