Skip to content

Commit

Permalink
Automate login callback test using selenium
Browse files Browse the repository at this point in the history
  • Loading branch information
ggediminass authored Apr 15, 2024
1 parent a1a5e98 commit 8ad07a2
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 0 deletions.
82 changes: 82 additions & 0 deletions test/qa/lib/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,91 @@
import os

import sh
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.expected_conditions import presence_of_element_located
from selenium.webdriver.support.ui import WebDriverWait

from . import logging, ssh

# Path where we save screenshots from browser, of Selenium tests, if they fail
BROWSER_LOGS_PATH = f"{os.environ['WORKDIR']}/dist/logs/" #browser_tests/"

LOGIN_MSG_SUCCESS = "Welcome to NordVPN! You can now connect to VPN by using 'nordvpn connect'."
LOGOUT_MSG_SUCCESS = "You are logged out."

# environment variable, which we will use to specify location to temporary folder for Firefox
ENV_FF_TMP = "TMPDIR"

# location to store temporary Firefox files, in case Firefox was installed from Snap (prevents profile error)
SNAP_FF_TMP_DIR_PATH = os.path.expanduser("~") + "/ff_tmp"

# XPath used in Selenium tests, to determine locations of elements on NordAccount
NA_USERNAME_PAGE_TEXTBOX_XPATH = '/html/body/div/div/div[1]/main/form/fieldset/div/span/input'
NA_USERNAME_PAGE_BUTTON_XPATH = '/html/body/div/div/div[1]/main/form/fieldset/button'

NA_PASSWORD_PAGE_TEXTBOX_XPATH = '/html/body/div/div/div[1]/main/form/fieldset/div[3]/span/input'
NA_PASSWORD_PAGE_BUTTON_XPATH = NA_USERNAME_PAGE_BUTTON_XPATH

NA_CONTINUE_PAGE_LINK_BUTTON = '/html/body/div/div/div[1]/main/div/a'

# used with Selenium login tests
LOGIN_FLAG = ["", "--nordaccount"]


class SeleniumBrowser:
def __init__(self, preferences:list=None):
self.options = webdriver.FirefoxOptions()
self.options.binary_location = str(sh.which("firefox"))
self.options.add_argument('--headless')
self.options.add_argument('--no-sandbox')

if preferences is not None:
for preference, value in preferences:
self.options.set_preference(preference, value)

if os.path.exists("/snap/firefox") and os.environ.get(ENV_FF_TMP) is None:
os.mkdir(SNAP_FF_TMP_DIR_PATH)
os.environ[ENV_FF_TMP] = SNAP_FF_TMP_DIR_PATH

service = webdriver.FirefoxService(executable_path="/usr/bin/geckodriver") #, log_output=BROWSER_LOGS_PATH + "geckodriver.log")
self.browser = webdriver.Firefox(options=self.options, service=service)

def browser_get(self) -> webdriver.Firefox:
return self.browser

def browser_kill(self) -> None:
""" Quits browser, and deletes temporary folder created for Firefox. """
self.browser.quit()

# Cleanup, if we were working with Firefox from snap
if os.environ.get(ENV_FF_TMP) is not None:
os.environ.pop(ENV_FF_TMP)
os.removedirs(SNAP_FF_TMP_DIR_PATH)

def browser_element_interact(self, xpath: str, write:str=None, return_attribute:str=None) -> None | str:
"""
Clicks element on website, specified by `xpath`.
If `write` parameter value is set, also writes the set value to the element.
If `return_attribute` parameter value is set, also returns specified attribute value of element.
"""

# Defines how long will we wait for elements, that we are looking for to appear in pages
wait = WebDriverWait(self.browser, 10)

website_element = wait.until(presence_of_element_located((By.XPATH, xpath)))
website_element.click()

if write is not None:
website_element.send_keys(write)

if return_attribute is not None:
return website_element.get_attribute(return_attribute)
else:
return None


def get_default_credentials():
"""Returns tuple[username,token]."""
Expand Down
103 changes: 103 additions & 0 deletions test/qa/test_login_nordaccount.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import os

import pytest
import sh
import timeout_decorator

import lib
from lib import (
daemon,
info,
logging,
login,
)


def setup_function(function): # noqa: ARG001
daemon.start()

logging.log()


def teardown_function(function): # noqa: ARG001
logging.log(data=info.collect())
logging.log()

sh.nordvpn.set.defaults()
daemon.stop()


@pytest.mark.skip("Does not work on Docker")
@pytest.mark.parametrize("login_flag", login.LOGIN_FLAG)
@timeout_decorator.timeout(60)
def test_selenium_login(login_flag):
preferences = [
["network.protocol-handler.expose.nordvpn", True],
["network.protocol-handler.external.nordvpn", True],
["network.protocol-handler.app.nordvpn", "/usr/bin/nordvpn"]
]

selenium = login.SeleniumBrowser(preferences)
browser = selenium.browser_get()

with lib.Defer(selenium.browser_kill):
# Get login link from NordVPN app, trim all spaces & chars after link itself
login_link = sh.nordvpn.login(login_flag, _tty_out=False).strip().split(": ")[1]

# Open login link from NordVPN app
browser.get(login_link)

# User credentials, that we will use in order to log in to NordAccount
user_info = os.environ.get("DEFAULT_LOGIN_USERNAME") + ":" + os.environ.get("DEFAULT_LOGIN_PASSWORD")

try:
# Username page
selenium.browser_element_interact(login.NA_USERNAME_PAGE_TEXTBOX_XPATH, user_info.split(':')[0])
selenium.browser_element_interact(login.NA_USERNAME_PAGE_BUTTON_XPATH)

# Password page
selenium.browser_element_interact(login.NA_PASSWORD_PAGE_TEXTBOX_XPATH, user_info.split(':')[1])
selenium.browser_element_interact(login.NA_PASSWORD_PAGE_BUTTON_XPATH)

# Continue to app page
selenium.browser_element_interact(login.NA_CONTINUE_PAGE_LINK_BUTTON)
except: # noqa: E722
browser.save_screenshot(login.BROWSER_LOGS_PATH + "Screenshot.png")
pytest.fail()

assert login.LOGOUT_MSG_SUCCESS in sh.nordvpn.logout()


@pytest.mark.parametrize("login_flag", login.LOGIN_FLAG)
@timeout_decorator.timeout(60)
def test_selenium_login_callback(login_flag):
selenium = login.SeleniumBrowser()
browser = selenium.browser_get()

with lib.Defer(selenium.browser_kill):
# Get login link from NordVPN app, trim all spaces & chars after link itself
login_link = sh.nordvpn.login(login_flag, _tty_out=False).strip().split(": ")[1]

# Open login link from NordVPN app
browser.get(login_link)

# User credentials, that we will use in order to log in to NordAccount
user_info = os.environ.get("DEFAULT_LOGIN_USERNAME") + ":" + os.environ.get("DEFAULT_LOGIN_PASSWORD")

try:
# Username page
selenium.browser_element_interact(login.NA_USERNAME_PAGE_TEXTBOX_XPATH, user_info.split(':')[0])
selenium.browser_element_interact(login.NA_USERNAME_PAGE_BUTTON_XPATH)

# Password page
selenium.browser_element_interact(login.NA_PASSWORD_PAGE_TEXTBOX_XPATH, user_info.split(':')[1])
selenium.browser_element_interact(login.NA_PASSWORD_PAGE_BUTTON_XPATH)

# Continue to app page
# preferences not set in constructor, so when we click link it does not redirect us to app.
callback_link = selenium.browser_element_interact(login.NA_CONTINUE_PAGE_LINK_BUTTON, return_attribute='href')
except: # noqa: E722
browser.save_screenshot(login.BROWSER_LOGS_PATH + "Screenshot.png")
pytest.fail()

assert login.LOGIN_MSG_SUCCESS in sh.nordvpn.login("--callback", callback_link)

0 comments on commit 8ad07a2

Please sign in to comment.