Skip to content

Commit 10486ef

Browse files
Add first frontend test (#1364)
* Add auto login api * Add first test in frontend (#392) * Add frontend tests to github actions Setup identity before starting the tests * Install launchpadlib as it is dependency for poetry * Install keyring as it is dependency for poetry * Update frontend.yml * Update frontend.yml * Update frontend.yml * Update frontend.yml * Update base_tests.py * Update frontend.yml * Update frontend.yml * Update frontend tests * Update frontend tests * Update frontend tests * Update frontend.yml * Update frontend.yml * Update frontend tests * Update frontend tests * Update frontend.yml * Update frontend.yml * Update frontend.yml * Use ip instead of localhost * Update workflow * Update frontend.yml * Update frontend.yml * Update workflow * Update workflow * Update workflow * Update base_tests.py * Update workflow * Update workflow * Update workflow * Update workflow * Update workflow * Update workflow * Update workflow * Cleanup * Update frontend workflow to run only nightly * Comment for how to use head mode browser * Add selenium to workflow
1 parent 08f26b0 commit 10486ef

File tree

11 files changed

+235
-1
lines changed

11 files changed

+235
-1
lines changed

.github/workflows/frontend.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Frontend
2+
3+
on:
4+
schedule:
5+
- cron: '0 0 * * *'
6+
7+
jobs:
8+
build:
9+
runs-on: ubuntu-20.04
10+
steps:
11+
- uses: actions/checkout@v2
12+
with:
13+
ref: development
14+
- uses: actions/setup-python@v2
15+
with:
16+
python-version: '3.8'
17+
- uses: dschep/[email protected]
18+
- uses: nanasess/setup-chromedriver@master
19+
- name: Gathering deps
20+
run: |
21+
sudo apt-get update
22+
sudo apt-get install -y git tmux redis restic nginx cron unzip
23+
sudo service redis stop
24+
sudo service nginx stop
25+
poetry run pip3 install pytest selenium
26+
27+
- name: Install js-sdk
28+
run: |
29+
poetry install
30+
31+
- name: Run tests
32+
env:
33+
TNAME: ${{ secrets.TNAME }}
34+
EMAIL: ${{ secrets.EMAIL }}
35+
WORDS: ${{ secrets.WORDS }}
36+
run: |
37+
sudo setcap CAP_NET_BIND_SERVICE=+eip $(which nginx)
38+
poetry run pytest -s tests/frontend/tests

.github/workflows/js-sdk.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
sudo service redis stop
2222
sudo service nginx stop
2323
sudo pip3 install poetry
24-
sudo poetry run pip3 install pytest
24+
sudo poetry run pip3 install pytest selenium
2525
- name: Install
2626
run: |
2727
sudo poetry install

jumpscale/packages/auth/bottle/auth.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,22 @@ def access_denied():
178178
return env.get_template("access_denied.html").render(email=email, next_url=next_url)
179179

180180

181+
@app.route("/auto_login")
182+
def auto_login():
183+
"""Auto login is used for testing for skipping login and use the current user's info instead.
184+
185+
Returns:
186+
Redirect to the admin dashboard.
187+
"""
188+
if j.core.config.get("AUTO_LOGIN"):
189+
session = request.environ.get("beaker.session", {})
190+
session["username"] = j.core.identity.me.tname
191+
session["email"] = j.core.identity.me.email
192+
session["authorized"] = True
193+
194+
return redirect("/admin")
195+
196+
181197
def get_user_info():
182198
"""Parse user information from the session object
183199

tests/frontend/__init__.py

Whitespace-only changes.

tests/frontend/pages/__init__.py

Whitespace-only changes.

tests/frontend/pages/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Base:
2+
base_url = "https://localhost"
3+
4+
def __init__(self, *args, **kwargs):
5+
pass

tests/frontend/pages/wallets/__init__.py

Whitespace-only changes.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from tests.frontend.pages.base import Base
2+
from urllib.parse import urljoin
3+
from selenium.webdriver.common.by import By
4+
from selenium.webdriver.support.ui import WebDriverWait
5+
from selenium.webdriver.support import expected_conditions as EC
6+
7+
8+
class Wallets(Base):
9+
def __init__(self, driver, *args, **kwargs):
10+
super().__init__(self, *args, **kwargs)
11+
self.driver = driver
12+
self.endpoint = "/admin/#/wallets"
13+
14+
def load(self):
15+
url = urljoin(self.base_url, self.endpoint)
16+
self.driver.get(url)
17+
18+
def create(self, name):
19+
buttons = self.driver.find_elements_by_class_name("v-btn")
20+
create_button = [button for button in buttons if button.text == "CREATE"][0]
21+
create_button.click()
22+
wallet_name_box = self.driver.find_element_by_class_name("v-text-field__slot")
23+
wallet_name_input = wallet_name_box.find_element_by_tag_name("input")
24+
wallet_name_input.send_keys(name)
25+
buttons = self.driver.find_elements_by_class_name("v-btn")
26+
submit_button = [button for button in buttons if button.text == "SUBMIT"][0]
27+
submit_button.click()
28+
wait = WebDriverWait(self.driver, 60)
29+
wait.until(EC.invisibility_of_element_located((By.CLASS_NAME, "v-dialog")))
30+
31+
def list(self):
32+
wallets = {}
33+
wallets_class = self.driver.find_element_by_class_name("row")
34+
wallets_cards = wallets_class.find_elements_by_class_name("v-card")
35+
for wallet_card in wallets_cards:
36+
wallet_card_name = wallet_card.find_element_by_class_name("v-card__title")
37+
wallet_name = wallet_card_name.text
38+
wallets[wallet_name] = wallet_card
39+
40+
return wallets
41+
42+
def delete(self, name):
43+
wallets = self.list()
44+
for wallet in wallets.keys():
45+
if wallet == name:
46+
wallet_card = wallets[name]
47+
delete_icon = wallet_card.find_element_by_class_name("v-btn")
48+
delete_icon.click()
49+
break
50+
else:
51+
return
52+
buttons = self.driver.find_elements_by_class_name("v-btn")
53+
submit_button = [button for button in buttons if button.text == "SUBMIT"][0]
54+
submit_button.click()

tests/frontend/tests/__init__.py

Whitespace-only changes.

tests/frontend/tests/base_tests.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import os
2+
import time
3+
from unittest import TestCase
4+
from urllib.parse import urljoin
5+
6+
from selenium import webdriver
7+
from selenium.webdriver.chrome.options import Options
8+
9+
from jumpscale.core.base import StoredFactory
10+
from jumpscale.loader import j
11+
from jumpscale.packages.admin.bottle.models import UserEntry
12+
from tests.frontend.pages.base import Base
13+
14+
15+
class BaseTest(TestCase):
16+
@classmethod
17+
def setUpClass(cls):
18+
# Set auto login config to disable 3bot connect login.
19+
j.core.config.set("AUTO_LOGIN", True)
20+
21+
# Get environment variables to create identity.
22+
cls.tname = os.environ.get("TNAME")
23+
cls.email = os.environ.get("EMAIL")
24+
cls.words = os.environ.get("WORDS")
25+
cls.explorer_url = "https://explorer.testnet.grid.tf/api/v1"
26+
if not (cls.tname and cls.email and cls.words):
27+
raise Exception("Please add (TNAME, EMAIL, WORDS) of your 3bot identity as environment variables")
28+
29+
# Check if there is identity registered to set it back after the tests are finished.
30+
cls.me = None
31+
if j.core.identity.list_all() and hasattr(j.core.identity, "me"):
32+
cls.me = j.core.identity.me
33+
34+
# Accept T&C for testing identity.
35+
user_factory = StoredFactory(UserEntry)
36+
user_entry = user_factory.get(f"{cls.tname.replace('.3bot', '')}")
37+
user_entry.has_agreed = True
38+
user_entry.tname = cls.tname
39+
user_entry.save()
40+
41+
# Configure test identity and start threebot server.
42+
cls.identity_name = j.data.random_names.random_name()
43+
identity = j.core.identity.new(
44+
cls.identity_name, tname=cls.tname, email=cls.email, words=cls.words, explorer_url=cls.explorer_url
45+
)
46+
identity.register()
47+
j.core.identity.set_default(cls.identity_name)
48+
cls.server = j.servers.threebot.get("default")
49+
cls.server.start()
50+
51+
@classmethod
52+
def tearDownClass(cls):
53+
# Disable auto login after the tests are finished.
54+
j.core.config.set("AUTO_LOGIN", False)
55+
56+
# Stop threebot server and the testing identity.
57+
cls.server.stop()
58+
j.core.identity.delete(cls.identity_name)
59+
60+
# Restore the user identity
61+
if cls.me:
62+
j.core.identity.set_default(cls.me.instance_name)
63+
64+
def setUp(self):
65+
# Configure chrome driver and go to the entrypoint.
66+
options = Options()
67+
options.add_argument("--no-sandbox")
68+
# For browser's head mode comment the next 3 lines
69+
options.add_argument("headless")
70+
options.add_argument("--disable-gpu")
71+
options.add_argument("--disable-dev-shm-usage")
72+
options.add_argument("ignore-certificate-errors")
73+
self.driver = webdriver.Chrome(options=options)
74+
self.driver.implicitly_wait(10)
75+
self.driver.maximize_window()
76+
self.login_endpoint = "/auth/auto_login"
77+
self.driver.get(urljoin(Base.base_url, self.login_endpoint))
78+
79+
def tearDown(self):
80+
# Take screenshot for failure test.
81+
if self._outcome.errors:
82+
self.driver.save_screenshot(f"{self._testMethodName}.png")
83+
self.driver.quit()
84+
85+
def info(self, msg):
86+
j.logger.info(msg)
87+
88+
def random_str(self):
89+
return j.data.random_names.random_name()

0 commit comments

Comments
 (0)