Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add E2E Tests for Wok #292

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions tests/run_tests.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,14 @@ HAVE_UNITTEST=@HAVE_PYMOD_UNITTEST@
PYTHON_VER=@PYTHON_VERSION@

if [ "$1" = "-v" ]; then
OPTS="-v"
# p ./test*.py means the pattern for
# discover is local file starting with test
OPTS="-v -p ./test*.py"
shift
else
OPTS=""
fi

if [ $# -ne 0 ]; then
ARGS="$@"
else
ARGS=`find -name "test_*.py" | xargs -I @ basename @ .py`
fi

CMD="python3 -m unittest"

PYTHONPATH=../src:../ $CMD $OPTS $ARGS
PYTHONPATH=../src:../ $CMD $OPTS
52 changes: 52 additions & 0 deletions tests/ui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Wok E2E Tests

The tests are located in `tests/ui`. You should go to the directory to start them
```
$ cd tests/ui
```

## How to run

First you need to install all dependencies to run the tests

### Optional: install a virtual environment

```
$ python3 -m venv .env
$ source .env/bin/activate
```

### Install deps
```
$ pip install -r requirements.txt
```

### Run in headless mode
The script expect some environment variables to run kimchi-project tests, which are:

```
Expect environment variables:
USERNAME: username for the host default: root
PASSWORD: password for the host
HOST: host for wok default: localhost
PORT: port for wok default: 8001
```

So, if you are running against a remote host:

```
$ HOST=<HOST> ./run_tests.sh
Type password for host USER@HOST

```

### Run in debug mode
If you use the command above, the browser will no be visible for you.

To see the browser action, add the variable `DEBUG`

```
$ HOST=<HOST> DEBUG=true ./run_tests.sh
Type password for host USER@HOST

```
72 changes: 72 additions & 0 deletions tests/ui/pages/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import logging as log
import os
import utils
import pytest
from selenium.common.exceptions import TimeoutException

logging = log.getLogger(__name__)

# locators by ID
USERNAME = "username"
PASSWORD = "password"
LOGIN_BUTTON = "btn-login"
LOGIN_BAR = "user-login"

# environment variables
ENV_USER = "USERNAME"
ENV_PASS = "PASSWORD"
ENV_PORT = "PORT"
ENV_HOST = "HOST"


class KimchiLoginPage():
"""
Page object to Login

Expect environment variables:
KIMCHI_USERNAME: username for the host
KIMCHI_PASSWORD: password for the host
KIMCHI_HOST: host for kimchi
KIMCHI_PORT: port for kimchi
"""

def __init__(self, browser):
self.browser = browser

# assert envs
for var in [ENV_USER, ENV_PASS, ENV_PORT, ENV_HOST]:
assert var in os.environ, f"{var} is a required environment var"

# get values
self.host = os.environ.get(ENV_HOST)
self.port = os.environ.get(ENV_PORT)
self.user = os.environ.get(ENV_USER)
self.password = os.environ.get(ENV_PASS)

def login(self):
try:
url = f"https://{self.host}:{self.port}/login.html"
self.browser.get(url)
except TimeoutException as e:
logging.error(f"Cannot reach kimchi at {url}")
return False

# fill user and password
logging.info(f"Loging in {url}")
utils.fillTextIfElementIsVisibleById(self.browser,
USERNAME,
self.user)
utils.fillTextIfElementIsVisibleById(self.browser,
PASSWORD,
self.password)

# press login
utils.clickIfElementIsVisibleById(self.browser, LOGIN_BUTTON)

# login bar not found: return error
if utils.waitElementIsVisibleById(self.browser, LOGIN_BAR) == False:
logging.error(f"Invalid credentials")
return False

logging.info(f"Logged in {url}")
return True
4 changes: 4 additions & 0 deletions tests/ui/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pytest]
verbose = True
log_cli = True
log_cli_level = INFO
28 changes: 28 additions & 0 deletions tests/ui/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
appnope==0.1.0
attrs==19.3.0
backcall==0.1.0
chromedriver-binary==78.0.3904.105.0
decorator==4.4.1
importlib-metadata==1.3.0
ipdb==0.12.3
ipython==7.11.0
ipython-genutils==0.2.0
jedi==0.15.2
more-itertools==8.0.2
packaging==19.2
parso==0.5.2
pexpect==4.7.0
pickleshare==0.7.5
pluggy==0.13.1
prompt-toolkit==3.0.2
ptyprocess==0.6.0
py==1.8.1
Pygments==2.5.2
pyparsing==2.4.6
pytest==5.3.2
selenium==3.141.0
six==1.13.0
traitlets==4.3.3
urllib3==1.25.7
wcwidth==0.1.7
zipp==0.6.0
13 changes: 13 additions & 0 deletions tests/ui/run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

HOST=${HOST:-localhost}
PORT=${PORT:-8001}
USERNAME=${USERNAME:-root}

# ask for password if not passed
if [ -z $PASSWORD ]; then
echo "Type password for host ${USERNAME}@${HOST}"
read -s PASSWORD
fi

HOST=${HOST} PASSWORD=${PASSWORD} USERNAME=${USERNAME} PORT=${PORT} python3 -m pytest
13 changes: 13 additions & 0 deletions tests/ui/test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest

from pages.login import KimchiLoginPage
from utils import getBrowser

@pytest.fixture
def browser():
browser = getBrowser()
yield browser
browser.quit()

def test_login(browser):
assert KimchiLoginPage(browser).login(), "Cannot login to Kimchi"
105 changes: 105 additions & 0 deletions tests/ui/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support.wait import TimeoutException
from selenium.webdriver.support import expected_conditions as EC

import chromedriver_binary
import logging as log
import os

logging = log.getLogger(__name__)

WAIT = 10

def getBrowser(headless=True):
if os.environ.get("DEBUG") is not None:
logging.info("Headless mode deactivated")
headless = False

options = Options()
if headless is True:
options.add_argument('--headless')
options.add_argument('--no-sandbox')
options.add_argument('--disable-gpu')

driver = webdriver.Chrome(options=options)
driver.set_page_load_timeout(WAIT * 2)
return driver

def waitElementByCondition(browser, condition, searchMethod, searchString, errorMessage, time=WAIT):
try:
element = WebDriverWait(browser, time).until(
condition((searchMethod, searchString))
)
except TimeoutException as e:
logging.error(f"Element {searchString} {errorMessage}")
return False
return True


def waitElementIsVisibleById(browser, elementId, time=WAIT):
return waitElementByCondition(browser,
EC.visibility_of_element_located,
By.ID,
elementId,
"is not visibile",
time)

def waitElementIsVisibleByXpath(browser, xpath):
return waitElementByCondition(browser,
EC.visibility_of_element_located,
By.XPATH,
xpath,
"is not visibile")

def waitElementIsClickableById(browser, elementId):
return waitElementByCondition(browser,
EC.element_to_be_clickable,
By.ID,
elementId,
"is not clickable")

def waitElementIsClickableByXpath(browser, xpath):
return waitElementByCondition(browser,
EC.element_to_be_clickable,
By.XPATH,
xpath,
"is not clickable")

def clickIfElementIsVisibleByXpath(browser, xpath):
try:
assert(waitElementIsVisibleByXpath(browser, xpath))
assert(waitElementIsClickableByXpath(browser, xpath))
browser.find_element_by_xpath(xpath).click()

except Exception as e:
logging.error(f"Cannot click on element {xpath}: {e}")
return False

return True

def clickIfElementIsVisibleById(browser, elementId):
try:
assert(waitElementIsVisibleById(browser, elementId))
assert(waitElementIsClickableById(browser, elementId))
browser.find_element_by_id(elementId).click()

except Exception as e:
logging.error(f"Cannot click on element {elementId}: {e}")
return False

return True

def fillTextIfElementIsVisibleById(browser, elementId, text):
try:
assert(waitElementIsVisibleById(browser, elementId))
browser.find_element_by_id(elementId).send_keys(text)

except Exception as e:
logging.error(f"Cannot type {text} on element {elementId}: {e}")
return False

return True