Tests - update end to end tests and ci config
This commit is contained in:
parent
7f88d995ed
commit
d9482c5976
9
.github/workflows/.tests-python.yml
vendored
9
.github/workflows/.tests-python.yml
vendored
@ -68,6 +68,14 @@ jobs:
|
|||||||
--health-retries 5
|
--health-retries 5
|
||||||
selenium:
|
selenium:
|
||||||
image: selenium/standalone-firefox
|
image: selenium/standalone-firefox
|
||||||
|
mailhog:
|
||||||
|
image: mailhog/mailhog:latest
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
env:
|
||||||
|
APP_SETTINGS: fittrackee.config.End2EndTestingConfig
|
||||||
|
EMAIL_URL: "smtp://mailhog:1025"
|
||||||
|
REDIS_URL: "redis://redis:6379"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Install Poetry and Dependencies
|
- name: Install Poetry and Dependencies
|
||||||
@ -83,4 +91,5 @@ jobs:
|
|||||||
setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 &
|
setsid nohup flask run --with-threads -h 0.0.0.0 -p 5000 >> nohup.out 2>&1 &
|
||||||
export TEST_APP_URL=http://$(hostname --ip-address):5000
|
export TEST_APP_URL=http://$(hostname --ip-address):5000
|
||||||
sleep 5
|
sleep 5
|
||||||
|
nohup flask worker --processes=1 >> nohup.out 2>&1 &
|
||||||
pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444
|
pytest e2e --driver Remote --capability browserName firefox --host selenium --port 4444
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
from .utils import (
|
from .utils import TEST_URL, login_valid_user, register_valid_user_and_logout
|
||||||
TEST_URL,
|
|
||||||
assert_navbar,
|
|
||||||
login_valid_user,
|
|
||||||
register_valid_user_and_logout,
|
|
||||||
)
|
|
||||||
|
|
||||||
URL = f'{TEST_URL}/login'
|
URL = f'{TEST_URL}/login'
|
||||||
|
|
||||||
@ -34,10 +29,20 @@ class TestLogin:
|
|||||||
assert 'Register' in links[0].text
|
assert 'Register' in links[0].text
|
||||||
assert links[1].tag_name == 'a'
|
assert links[1].tag_name == 'a'
|
||||||
assert 'Forgot password?' in links[1].text
|
assert 'Forgot password?' in links[1].text
|
||||||
|
assert links[2].tag_name == 'a'
|
||||||
|
assert "Didn't received instructions?" in links[2].text
|
||||||
|
|
||||||
def test_user_can_log_in(self, selenium):
|
def test_user_can_log_in(self, selenium):
|
||||||
user = register_valid_user_and_logout(selenium)
|
user = register_valid_user_and_logout(selenium)
|
||||||
|
|
||||||
login_valid_user(selenium, user)
|
login_valid_user(selenium, user)
|
||||||
|
|
||||||
assert_navbar(selenium, user)
|
nav = selenium.find_element_by_id('nav').text
|
||||||
|
assert 'Register' not in nav
|
||||||
|
assert 'Login' not in nav
|
||||||
|
assert 'Dashboard' in nav
|
||||||
|
assert 'Workouts' in nav
|
||||||
|
assert 'Statistics' in nav
|
||||||
|
assert 'Add a workout' in nav
|
||||||
|
assert user['username'] in nav
|
||||||
|
assert 'Logout' in nav
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
from .utils import TEST_URL, register_valid_user
|
from .utils import register_valid_user
|
||||||
|
|
||||||
URL = f'{TEST_URL}/profile'
|
|
||||||
|
|
||||||
|
|
||||||
class TestProfile:
|
class TestProfile:
|
||||||
def test_it_displays_user_profile(self, selenium):
|
def test_it_displays_user_profile(self, selenium):
|
||||||
user = register_valid_user(selenium)
|
user = register_valid_user(selenium)
|
||||||
|
|
||||||
selenium.get(URL)
|
app_menu = selenium.find_element_by_class_name('nav-items-user-menu')
|
||||||
|
profile_link = app_menu.find_elements_by_class_name('nav-item')[1]
|
||||||
|
profile_link.click()
|
||||||
|
selenium.implicitly_wait(1)
|
||||||
|
|
||||||
user_header = selenium.find_element_by_class_name('user-header')
|
user_header = selenium.find_element_by_class_name('user-header')
|
||||||
assert user['username'] in user_header.text
|
assert user['username'] in user_header.text
|
||||||
assert '0\nworkouts' in user_header.text
|
assert '0\nworkouts' in user_header.text
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
from .utils import (
|
from .utils import (
|
||||||
TEST_URL,
|
TEST_URL,
|
||||||
assert_navbar,
|
|
||||||
random_string,
|
random_string,
|
||||||
register,
|
register,
|
||||||
register_valid_user,
|
|
||||||
register_valid_user_and_logout,
|
register_valid_user_and_logout,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,18 +22,39 @@ class TestRegistration:
|
|||||||
assert inputs[2].get_attribute('id') == 'password'
|
assert inputs[2].get_attribute('id') == 'password'
|
||||||
assert inputs[2].get_attribute('type') == 'password'
|
assert inputs[2].get_attribute('type') == 'password'
|
||||||
|
|
||||||
|
form_infos = selenium.find_elements_by_class_name('form-info')
|
||||||
|
assert len(form_infos) == 3
|
||||||
|
assert form_infos[0].text == (
|
||||||
|
'3 to 30 characters required, only alphanumeric characters and '
|
||||||
|
'the underscore character "_" allowed.'
|
||||||
|
)
|
||||||
|
assert form_infos[1].text == 'Enter a valid email address.'
|
||||||
|
assert form_infos[2].text == 'At least 8 characters required.'
|
||||||
|
|
||||||
button = selenium.find_element_by_tag_name('button')
|
button = selenium.find_element_by_tag_name('button')
|
||||||
assert button.get_attribute('type') == 'submit'
|
assert button.get_attribute('type') == 'submit'
|
||||||
assert 'Register' in button.text
|
assert 'Register' in button.text
|
||||||
|
|
||||||
link = selenium.find_element_by_class_name('links')
|
links = selenium.find_elements_by_class_name('links')
|
||||||
assert link.tag_name == 'a'
|
assert links[0].tag_name == 'a'
|
||||||
assert 'Login' in link.text
|
assert 'Login' in links[0].text
|
||||||
|
assert links[1].tag_name == 'a'
|
||||||
|
assert "Didn't received instructions?" in links[1].text
|
||||||
|
|
||||||
def test_user_can_register(self, selenium):
|
def test_user_can_register(self, selenium):
|
||||||
user = register_valid_user(selenium)
|
user = {
|
||||||
|
'username': random_string(),
|
||||||
|
'email': f'{random_string()}@example.com',
|
||||||
|
'password': 'p@ssw0rd',
|
||||||
|
}
|
||||||
|
|
||||||
assert_navbar(selenium, user)
|
register(selenium, user)
|
||||||
|
|
||||||
|
message = selenium.find_element_by_class_name('success-message').text
|
||||||
|
assert (
|
||||||
|
'A link to activate your account has been '
|
||||||
|
'emailed to the address provided.'
|
||||||
|
) in message
|
||||||
|
|
||||||
def test_user_can_not_register_with_invalid_email(self, selenium):
|
def test_user_can_not_register_with_invalid_email(self, selenium):
|
||||||
user_name = random_string()
|
user_name = random_string()
|
||||||
@ -62,14 +81,19 @@ class TestRegistration:
|
|||||||
|
|
||||||
assert selenium.current_url == URL
|
assert selenium.current_url == URL
|
||||||
errors = selenium.find_element_by_class_name('error-message').text
|
errors = selenium.find_element_by_class_name('error-message').text
|
||||||
assert 'Sorry, that user already exists.' in errors
|
assert 'Sorry, that username is already taken.' in errors
|
||||||
|
|
||||||
def test_user_can_not_register_if_email_is_already_taken(self, selenium):
|
def test_user_does_not_return_error_if_email_is_already_taken(
|
||||||
|
self, selenium
|
||||||
|
):
|
||||||
user = register_valid_user_and_logout(selenium)
|
user = register_valid_user_and_logout(selenium)
|
||||||
user['username'] = random_string()
|
user['username'] = random_string()
|
||||||
|
|
||||||
register(selenium, user)
|
register(selenium, user)
|
||||||
|
|
||||||
assert selenium.current_url == URL
|
assert selenium.current_url == f'{TEST_URL}/login'
|
||||||
errors = selenium.find_element_by_class_name('error-message').text
|
message = selenium.find_element_by_class_name('success-message').text
|
||||||
assert 'Sorry, that user already exists.' in errors
|
assert (
|
||||||
|
'A link to activate your account has been '
|
||||||
|
'emailed to the address provided.'
|
||||||
|
) in message
|
||||||
|
49
e2e/utils.py
49
e2e/utils.py
@ -1,18 +1,25 @@
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import string
|
import string
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
from selenium.webdriver.support.ui import WebDriverWait
|
||||||
|
from urllib3.util import parse_url
|
||||||
|
|
||||||
TEST_APP_URL = os.getenv('TEST_APP_URL')
|
TEST_APP_URL = os.getenv('TEST_APP_URL')
|
||||||
TEST_CLIENT_URL = os.getenv('TEST_CLIENT_URL')
|
TEST_CLIENT_URL = os.getenv('TEST_CLIENT_URL')
|
||||||
E2E_ARGS = os.getenv('E2E_ARGS')
|
E2E_ARGS = os.getenv('E2E_ARGS')
|
||||||
TEST_URL = TEST_CLIENT_URL if E2E_ARGS == 'client' else TEST_APP_URL
|
TEST_URL = TEST_CLIENT_URL if E2E_ARGS == 'client' else TEST_APP_URL
|
||||||
|
EMAIL_URL = os.getenv('EMAIL_URL', 'smtp://none:none@0.0.0.0:1025')
|
||||||
|
parsed_email_url = parse_url(EMAIL_URL)
|
||||||
|
EMAIL_API_URL = f'http://{parsed_email_url.host}:8025'
|
||||||
|
|
||||||
|
|
||||||
def random_string(length=8):
|
def random_string(length=8):
|
||||||
return ''.join(random.choice(string.ascii_letters) for x in range(length))
|
return ''.join(random.choice(string.ascii_letters) for _ in range(length))
|
||||||
|
|
||||||
|
|
||||||
def register(selenium, user):
|
def register(selenium, user):
|
||||||
@ -47,40 +54,34 @@ def register_valid_user(selenium):
|
|||||||
'password': 'p@ssw0rd',
|
'password': 'p@ssw0rd',
|
||||||
}
|
}
|
||||||
register(selenium, user)
|
register(selenium, user)
|
||||||
WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register"))
|
WebDriverWait(selenium, 30).until(EC.url_changes(f"{TEST_URL}/register"))
|
||||||
|
confirm_account(selenium, user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def register_valid_user_and_logout(selenium):
|
def register_valid_user_and_logout(selenium):
|
||||||
user_name = random_string()
|
user = register_valid_user(selenium)
|
||||||
user = {
|
|
||||||
'username': user_name,
|
|
||||||
'email': f'{user_name}@example.com',
|
|
||||||
'password': 'p@ssw0rd',
|
|
||||||
}
|
|
||||||
register(selenium, user)
|
|
||||||
WebDriverWait(selenium, 15).until(EC.url_changes(f"{TEST_URL}/register"))
|
|
||||||
|
|
||||||
user_menu = selenium.find_element_by_class_name('nav-items-user-menu')
|
user_menu = selenium.find_element_by_class_name('nav-items-user-menu')
|
||||||
logout_link = user_menu.find_elements_by_class_name('nav-item')[2]
|
logout_link = user_menu.find_elements_by_class_name('nav-item')[2]
|
||||||
logout_link.click()
|
logout_link.click()
|
||||||
selenium.implicitly_wait(1)
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def confirm_account(selenium, user):
|
||||||
|
time.sleep(1)
|
||||||
|
response = requests.get(
|
||||||
|
f"{EMAIL_API_URL}/api/v2/search?kind=to&query={user['email']}"
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
results = response.json()
|
||||||
|
message = results['items'][0]['Content']['Body']
|
||||||
|
link = re.search(r'Verify your email: (.+?)\r\n', message).groups()[0]
|
||||||
|
link = link.replace('http://0.0.0.0:5000', TEST_URL)
|
||||||
|
selenium.get(link)
|
||||||
|
WebDriverWait(selenium, 15).until(EC.url_changes(link))
|
||||||
|
|
||||||
|
|
||||||
def login_valid_user(selenium, user):
|
def login_valid_user(selenium, user):
|
||||||
login(selenium, user)
|
login(selenium, user)
|
||||||
WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login"))
|
WebDriverWait(selenium, 10).until(EC.url_changes(f"{TEST_URL}/login"))
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def assert_navbar(selenium, user):
|
|
||||||
nav = selenium.find_element_by_id('nav').text
|
|
||||||
assert 'Register' not in nav
|
|
||||||
assert 'Login' not in nav
|
|
||||||
assert 'Dashboard' in nav
|
|
||||||
assert 'Workouts' in nav
|
|
||||||
assert 'Statistics' in nav
|
|
||||||
assert 'Add a workout' in nav
|
|
||||||
assert user['username'] in nav
|
|
||||||
assert 'Logout' in nav
|
|
||||||
|
@ -12,7 +12,6 @@ else:
|
|||||||
|
|
||||||
|
|
||||||
class BaseConfig:
|
class BaseConfig:
|
||||||
"""Base configuration"""
|
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
TESTING = False
|
TESTING = False
|
||||||
@ -49,8 +48,6 @@ class BaseConfig:
|
|||||||
|
|
||||||
|
|
||||||
class DevelopmentConfig(BaseConfig):
|
class DevelopmentConfig(BaseConfig):
|
||||||
"""Development configuration"""
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||||
SECRET_KEY = 'development key'
|
SECRET_KEY = 'development key'
|
||||||
@ -59,8 +56,6 @@ class DevelopmentConfig(BaseConfig):
|
|||||||
|
|
||||||
|
|
||||||
class TestingConfig(BaseConfig):
|
class TestingConfig(BaseConfig):
|
||||||
"""Testing configuration"""
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
TESTING = True
|
TESTING = True
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_TEST_URL')
|
||||||
@ -74,9 +69,11 @@ class TestingConfig(BaseConfig):
|
|||||||
SENDER_EMAIL = 'fittrackee@example.com'
|
SENDER_EMAIL = 'fittrackee@example.com'
|
||||||
|
|
||||||
|
|
||||||
class ProductionConfig(BaseConfig):
|
class End2EndTestingConfig(TestingConfig):
|
||||||
"""Production configuration"""
|
DRAMATIQ_BROKER_URL = os.getenv('REDIS_URL', 'redis://')
|
||||||
|
|
||||||
|
|
||||||
|
class ProductionConfig(BaseConfig):
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
# https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork # noqa
|
# https://docs.sqlalchemy.org/en/13/core/pooling.html#using-connection-pools-with-multiprocessing-or-os-fork # noqa
|
||||||
SQLALCHEMY_ENGINE_OPTIONS = (
|
SQLALCHEMY_ENGINE_OPTIONS = (
|
||||||
|
Loading…
Reference in New Issue
Block a user