Skip to content

Commit 2341f32

Browse files
author
Ramon Medeiros
committed
Add E2E Tests for Wok
1 parent b2e1815 commit 2341f32

7 files changed

Lines changed: 289 additions & 0 deletions

File tree

tests/ui/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Wok E2E Tests
2+
3+
The tests are located in `tests/ui`. You should go to the directory to start them
4+
```
5+
$ cd tests/ui
6+
```
7+
8+
## How to run
9+
10+
First you need to install all dependencies to run the tests
11+
12+
### Optional: install a virtual environment
13+
14+
```
15+
$ python3 -m venv .env
16+
$ source .env/bin/activate
17+
```
18+
19+
### Install deps
20+
```
21+
$ pip install -r requirements.txt
22+
```
23+
24+
### Run in headless mode
25+
The script expect some environment variables to run kimchi-project tests, which are:
26+
27+
```
28+
Expect environment variables:
29+
USERNAME: username for the host default: root
30+
PASSWORD: password for the host
31+
HOST: host for wok default: localhost
32+
PORT: port for wok default: 8001
33+
```
34+
35+
So, if you are running against a remote host:
36+
37+
```
38+
$ HOST=<HOST> ./run_tests.sh
39+
Type password for host USER@HOST
40+
41+
```
42+
43+
### Run in debug mode
44+
If you use the command above, the browser will no be visible for you.
45+
46+
To see the browser action, add the variable `DEBUG`
47+
48+
```
49+
$ HOST=<HOST> DEBUG=true ./run_tests.sh
50+
Type password for host USER@HOST
51+
52+
```

tests/ui/pages/login.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import logging as log
2+
import os
3+
import utils
4+
import pytest
5+
from selenium.common.exceptions import TimeoutException
6+
7+
logging = log.getLogger(__name__)
8+
9+
# locators by ID
10+
USERNAME = "username"
11+
PASSWORD = "password"
12+
LOGIN_BUTTON = "btn-login"
13+
LOGIN_BAR = "user-login"
14+
15+
# environment variables
16+
ENV_USER = "USERNAME"
17+
ENV_PASS = "PASSWORD"
18+
ENV_PORT = "PORT"
19+
ENV_HOST = "HOST"
20+
21+
22+
class KimchiLoginPage():
23+
"""
24+
Page object to Login
25+
26+
Expect environment variables:
27+
KIMCHI_USERNAME: username for the host
28+
KIMCHI_PASSWORD: password for the host
29+
KIMCHI_HOST: host for kimchi
30+
KIMCHI_PORT: port for kimchi
31+
"""
32+
33+
def __init__(self, browser):
34+
self.browser = browser
35+
36+
# assert envs
37+
for var in [ENV_USER, ENV_PASS, ENV_PORT, ENV_HOST]:
38+
assert var in os.environ, f"{var} is a required environment var"
39+
40+
# get values
41+
self.host = os.environ.get(ENV_HOST)
42+
self.port = os.environ.get(ENV_PORT)
43+
self.user = os.environ.get(ENV_USER)
44+
self.password = os.environ.get(ENV_PASS)
45+
46+
def login(self):
47+
try:
48+
url = f"https://{self.host}:{self.port}/login.html"
49+
self.browser.get(url)
50+
except TimeoutException as e:
51+
logging.error(f"Cannot reach kimchi at {url}")
52+
return False
53+
54+
# fill user and password
55+
logging.info(f"Loging in {url}")
56+
utils.fillTextIfElementIsVisibleById(self.browser,
57+
USERNAME,
58+
self.user)
59+
utils.fillTextIfElementIsVisibleById(self.browser,
60+
PASSWORD,
61+
self.password)
62+
63+
# press login
64+
utils.clickIfElementIsVisibleById(self.browser, LOGIN_BUTTON)
65+
66+
# login bar not found: return error
67+
if utils.waitElementIsVisibleById(self.browser, LOGIN_BAR) == False:
68+
logging.error(f"Invalid credentials")
69+
return False
70+
71+
logging.info(f"Logged in {url}")
72+
return True

tests/ui/pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
verbose = True
3+
log_cli = True
4+
log_cli_level = INFO

tests/ui/requirements.txt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
appnope==0.1.0
2+
attrs==19.3.0
3+
backcall==0.1.0
4+
chromedriver-binary==78.0.3904.105.0
5+
decorator==4.4.1
6+
importlib-metadata==1.3.0
7+
ipdb==0.12.3
8+
ipython==7.11.0
9+
ipython-genutils==0.2.0
10+
jedi==0.15.2
11+
more-itertools==8.0.2
12+
packaging==19.2
13+
parso==0.5.2
14+
pexpect==4.7.0
15+
pickleshare==0.7.5
16+
pluggy==0.13.1
17+
prompt-toolkit==3.0.2
18+
ptyprocess==0.6.0
19+
py==1.8.1
20+
Pygments==2.5.2
21+
pyparsing==2.4.6
22+
pytest==5.3.2
23+
selenium==3.141.0
24+
six==1.13.0
25+
traitlets==4.3.3
26+
urllib3==1.25.7
27+
wcwidth==0.1.7
28+
zipp==0.6.0

tests/ui/run_tests.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#!/bin/bash
2+
3+
HOST=${HOST:-localhost}
4+
PORT=${PORT:-8001}
5+
USERNAME=${USERNAME:-root}
6+
7+
# ask for password if not passed
8+
if [ -z $PASSWORD ]; then
9+
echo "Type password for host ${USERNAME}@${HOST}"
10+
read -s PASSWORD
11+
fi
12+
13+
HOST=${HOST} PASSWORD=${PASSWORD} USERNAME=${USERNAME} PORT=${PORT} python3 -m pytest

tests/ui/test_login.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import utils
2+
from pages.login import KimchiLoginPage
3+
4+
import logging as log
5+
6+
class TestWokLogin():
7+
8+
def setup(self):
9+
self.browser = utils.getBrowser()
10+
11+
def test_login(self):
12+
assert KimchiLoginPage(self.browser).login(), "Cannot login to Kimchi"
13+
14+
def tearDown(self):
15+
self.browser.close()

tests/ui/utils.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
from selenium import webdriver
2+
from selenium.webdriver.chrome.options import Options
3+
from selenium.webdriver.common.by import By
4+
from selenium.webdriver.support.ui import WebDriverWait
5+
from selenium.webdriver.support.wait import TimeoutException
6+
from selenium.webdriver.support import expected_conditions as EC
7+
8+
import chromedriver_binary
9+
import logging as log
10+
import os
11+
12+
logging = log.getLogger(__name__)
13+
14+
WAIT = 10
15+
16+
def getBrowser(headless=True):
17+
if os.environ.get("DEBUG") is not None:
18+
logging.info("Headless mode deactivated")
19+
headless = False
20+
21+
options = Options()
22+
if headless is True:
23+
options.add_argument('--headless')
24+
options.add_argument('--no-sandbox')
25+
options.add_argument('--disable-gpu')
26+
27+
driver = webdriver.Chrome(options=options)
28+
driver.set_page_load_timeout(WAIT * 2)
29+
return driver
30+
31+
def waitElementByCondition(browser, condition, searchMethod, searchString, errorMessage, time=WAIT):
32+
try:
33+
element = WebDriverWait(browser, time).until(
34+
condition((searchMethod, searchString))
35+
)
36+
except TimeoutException as e:
37+
logging.error(f"Element {searchString} {errorMessage}")
38+
return False
39+
return True
40+
41+
42+
def waitElementIsVisibleById(browser, elementId, time=WAIT):
43+
return waitElementByCondition(browser,
44+
EC.visibility_of_element_located,
45+
By.ID,
46+
elementId,
47+
"is not visibile",
48+
time)
49+
50+
def waitElementIsVisibleByXpath(browser, xpath):
51+
return waitElementByCondition(browser,
52+
EC.visibility_of_element_located,
53+
By.XPATH,
54+
xpath,
55+
"is not visibile")
56+
57+
def waitElementIsClickableById(browser, elementId):
58+
return waitElementByCondition(browser,
59+
EC.element_to_be_clickable,
60+
By.ID,
61+
elementId,
62+
"is not clickable")
63+
64+
def waitElementIsClickableByXpath(browser, xpath):
65+
return waitElementByCondition(browser,
66+
EC.element_to_be_clickable,
67+
By.XPATH,
68+
xpath,
69+
"is not clickable")
70+
71+
def clickIfElementIsVisibleByXpath(browser, xpath):
72+
try:
73+
assert(waitElementIsVisibleByXpath(browser, xpath))
74+
assert(waitElementIsClickableByXpath(browser, xpath))
75+
browser.find_element_by_xpath(xpath).click()
76+
77+
except Exception as e:
78+
logging.error(f"Cannot click on element {xpath}: {e}")
79+
return False
80+
81+
return True
82+
83+
def clickIfElementIsVisibleById(browser, elementId):
84+
try:
85+
assert(waitElementIsVisibleById(browser, elementId))
86+
assert(waitElementIsClickableById(browser, elementId))
87+
browser.find_element_by_id(elementId).click()
88+
89+
except Exception as e:
90+
logging.error(f"Cannot click on element {elementId}: {e}")
91+
return False
92+
93+
return True
94+
95+
def fillTextIfElementIsVisibleById(browser, elementId, text):
96+
try:
97+
assert(waitElementIsVisibleById(browser, elementId))
98+
browser.find_element_by_id(elementId).send_keys(text)
99+
100+
except Exception as e:
101+
logging.error(f"Cannot type {text} on element {elementId}: {e}")
102+
return False
103+
104+
return True
105+

0 commit comments

Comments
 (0)