mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-09-03 20:52:57 -05:00
Add feature test framework, using selenium and chromedriver for UI testing.
Written by both George and Atira at Pivotal.
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import signal
|
||||
|
||||
import random
|
||||
|
||||
class AppStarter:
|
||||
"""
|
||||
Helper for starting the full pgadmin4 app and loading the page via selenium
|
||||
"""
|
||||
|
||||
def __init__(self, driver, app_config):
|
||||
self.driver = driver
|
||||
self.app_config = app_config
|
||||
|
||||
def start_app(self):
|
||||
random_server_port = str(random.randint(10000, 65535))
|
||||
env = {"PGADMIN_PORT": random_server_port}
|
||||
env.update(os.environ)
|
||||
|
||||
self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py", "magic-portal", random_server_port],
|
||||
shell=False,
|
||||
preexec_fn=os.setsid,
|
||||
stderr=open(os.devnull, 'w'),
|
||||
env=env)
|
||||
|
||||
self.driver.set_window_size(1024, 1024)
|
||||
print("opening browser")
|
||||
self.driver.get("http://" + self.app_config.DEFAULT_SERVER + ":" + random_server_port)
|
||||
|
||||
def stop_app(self):
|
||||
self.driver.close()
|
||||
os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
|
||||
@@ -0,0 +1,26 @@
|
||||
from selenium import webdriver
|
||||
|
||||
import config as app_config
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression.feature_utils.app_starter import AppStarter
|
||||
from regression.feature_utils.pgadmin_page import PgadminPage
|
||||
|
||||
|
||||
class BaseFeatureTest(BaseTestGenerator):
|
||||
def setUp(self):
|
||||
if app_config.SERVER_MODE:
|
||||
self.skipTest("Currently, config is set to start pgadmin in server mode. "
|
||||
"This test doesn't know username and password so doesn't work in server mode")
|
||||
|
||||
driver = webdriver.Chrome()
|
||||
self.app_starter = AppStarter(driver, app_config)
|
||||
self.page = PgadminPage(driver, app_config)
|
||||
self.app_starter.start_app()
|
||||
self.page.wait_for_app()
|
||||
|
||||
def failureException(self, *args, **kwargs):
|
||||
self.page.driver.save_screenshot('/tmp/feature_test_failure.png')
|
||||
return AssertionError(*args, **kwargs)
|
||||
|
||||
def runTest(self):
|
||||
pass
|
||||
@@ -0,0 +1,124 @@
|
||||
import time
|
||||
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver import ActionChains
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
|
||||
|
||||
class PgadminPage:
|
||||
"""
|
||||
Helper class for interacting with the page, given a selenium driver
|
||||
"""
|
||||
def __init__(self, driver, app_config):
|
||||
self.driver = driver
|
||||
self.app_config = app_config
|
||||
|
||||
def add_server(self, server_config):
|
||||
self.wait_for_spinner_to_disappear()
|
||||
|
||||
self.find_by_xpath("//*[@class='aciTreeText' and contains(.,'Servers')]").click()
|
||||
self.driver.find_element_by_link_text("Object").click()
|
||||
ActionChains(self.driver) \
|
||||
.move_to_element(self.driver.find_element_by_link_text("Create")) \
|
||||
.perform()
|
||||
self.find_by_partial_link_text("Server...").click()
|
||||
|
||||
self.fill_input_by_field_name("name", server_config['name'])
|
||||
self.find_by_partial_link_text("Connection").click()
|
||||
self.fill_input_by_field_name("host", server_config['host'])
|
||||
self.fill_input_by_field_name("port", server_config['port'])
|
||||
self.fill_input_by_field_name("username", server_config['username'])
|
||||
self.fill_input_by_field_name("password", server_config['db_password'])
|
||||
self.find_by_xpath("//button[contains(.,'Save')]").click()
|
||||
|
||||
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "']")
|
||||
|
||||
def remove_server(self, server_config):
|
||||
self.find_by_xpath("//*[@id='tree']//*[.='" + server_config['name'] + "' and @class='aciTreeItem']").click()
|
||||
self.find_by_partial_link_text("Object").click()
|
||||
self.find_by_partial_link_text("Delete/Drop").click()
|
||||
time.sleep(0.5)
|
||||
self.find_by_xpath("//button[contains(.,'OK')]").click()
|
||||
|
||||
def toggle_open_tree_item(self, tree_item_text):
|
||||
self.find_by_xpath("//*[@id='tree']//*[.='" + tree_item_text + "']/../*[@class='aciTreeButton']").click()
|
||||
|
||||
def find_by_xpath(self, xpath):
|
||||
return self.wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
|
||||
|
||||
def find_by_id(self, element_id):
|
||||
return self.wait_for_element(lambda: self.driver.find_element_by_id(element_id))
|
||||
|
||||
def find_by_partial_link_text(self, link_text):
|
||||
return self.wait_for_element(lambda: self.driver.find_element_by_partial_link_text(link_text))
|
||||
|
||||
def fill_input_by_field_name(self, field_name, field_content):
|
||||
field = self.find_by_xpath("//input[@name='" + field_name + "']")
|
||||
backspaces = [Keys.BACKSPACE]*len(field.get_attribute('value'))
|
||||
|
||||
field.click()
|
||||
field.send_keys(backspaces)
|
||||
field.send_keys(str(field_content))
|
||||
self.wait_for_input_field_content(field_name, field_content)
|
||||
|
||||
def fill_codemirror_area_with(self, field_content):
|
||||
self.find_by_xpath(
|
||||
"//pre[contains(@class,'CodeMirror-line')]/../../../*[contains(@class,'CodeMirror-code')]").click()
|
||||
ActionChains(self.driver).send_keys(field_content).perform()
|
||||
|
||||
def click_tab(self, tab_name):
|
||||
self.find_by_xpath("//*[contains(@class,'wcPanelTab') and contains(.,'" + tab_name + "')]").click()
|
||||
|
||||
def wait_for_input_field_content(self, field_name, content):
|
||||
def input_field_has_content():
|
||||
element = self.driver.find_element_by_xpath(
|
||||
"//input[@name='" + field_name + "']")
|
||||
|
||||
return str(content) == element.get_attribute('value')
|
||||
|
||||
return self._wait_for("field to contain '" + str(content) + "'", input_field_has_content)
|
||||
|
||||
def wait_for_element(self, find_method_with_args):
|
||||
def element_if_it_exists():
|
||||
try:
|
||||
element = find_method_with_args()
|
||||
if element.is_displayed() & element.is_enabled():
|
||||
return element
|
||||
except NoSuchElementException:
|
||||
return False
|
||||
|
||||
return self._wait_for("element to exist", element_if_it_exists)
|
||||
|
||||
def wait_for_spinner_to_disappear(self):
|
||||
def spinner_has_disappeared():
|
||||
try:
|
||||
self.driver.find_element_by_id("pg-spinner")
|
||||
return False
|
||||
except NoSuchElementException:
|
||||
return True
|
||||
|
||||
self._wait_for("spinner to disappear", spinner_has_disappeared)
|
||||
|
||||
def wait_for_app(self):
|
||||
def page_shows_app():
|
||||
if self.driver.title == self.app_config.APP_NAME:
|
||||
return True
|
||||
else:
|
||||
self.driver.refresh()
|
||||
return False
|
||||
|
||||
self._wait_for("app to start", page_shows_app)
|
||||
|
||||
def _wait_for(self, waiting_for_message, condition_met_function):
|
||||
timeout = 10
|
||||
time_waited = 0
|
||||
sleep_time = 0.01
|
||||
|
||||
while time_waited < timeout:
|
||||
result = condition_met_function()
|
||||
if result:
|
||||
return result
|
||||
time_waited += sleep_time
|
||||
time.sleep(sleep_time)
|
||||
|
||||
raise AssertionError("timed out waiting for " + waiting_for_message)
|
||||
Reference in New Issue
Block a user