Here's the patch with one more fix -- cleaning up the connections that get
created in pgAdmin.


On Mon, Jan 30, 2017 at 2:28 PM, George Gelashvili <ggelashv...@pivotal.io>
wrote:

> so, it sounds like you're saying our accaptable_test_db is unacceptable :-P
>
> here's a patch that takes an "--exclude" flag (see README) and doesn't
> create dbs that don't get cleaned up afterwards
>
> On Fri, Jan 27, 2017 at 11:28 AM, Dave Page <dp...@pgadmin.org> wrote:
>
>> On Fri, Jan 27, 2017 at 4:11 PM, Dave Page <dp...@pgadmin.org> wrote:
>> > On Thu, Jan 26, 2017 at 10:40 PM, George Gelashvili
>> > <ggelashv...@pivotal.io> wrote:
>> >> instead of that patch, please use this no-zombies version that kills
>> the
>> >> started process group instead of pid-only.
>> >
>> > Very cool :-). The only minor annoyance for me is that my Mac pops up
>> > a message asking me if I want pgAdmin to accept connections, but
>> > there's nothing you can do about that of course.
>> >
>> > At this point I think there are a couple of things left to do;
>> >
>> > - Add more tests!
>> >
>> > - Add command line options to runtests.py to allow users to run either
>> > the existing tests or the acceptance tests (or both, which should be
>> > the default). Of course, it should still be possible to just run any
>> > single test.
>>
>> Please add:
>>
>> - Proper cleanup. I just noticed the tests have left an
>> "acceptable_test_db" database behind.
>>
>> Thanks.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 4fb05891..998cdabf 100644
--- a/requirements_py2.txt
+++ b/requirements_py2.txt
@@ -36,6 +36,7 @@ testscenarios==0.5.0
 testtools==2.0.0
 traceback2==1.4.0
 unittest2==1.1.0
+selenium==3.0.2
 Werkzeug==0.9.6
 WTForms==2.0.2
 sqlparse==0.1.19
diff --git a/requirements_py3.txt b/requirements_py3.txt
index c4490f52..2239de63 100644
--- a/requirements_py3.txt
+++ b/requirements_py3.txt
@@ -35,6 +35,7 @@ testscenarios==0.5.0
 testtools==2.0.0
 traceback2==1.4.0
 unittest2==1.1.0
+selenium==3.0.2
 Werkzeug==0.9.6
 WTForms==2.0.2
 sqlparse==0.1.19
diff --git a/web/pgadmin/acceptance/__init__.py 
b/web/pgadmin/acceptance/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/acceptance/tests/__init__.py 
b/web/pgadmin/acceptance/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py 
b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
new file mode 100644
index 00000000..08e92154
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,73 @@
+#############################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##############################################################
+
+import time
+
+from selenium import webdriver
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver import ActionChains
+
+from pgadmin.utils.route import BaseTestGenerator
+
+import subprocess
+import os
+import signal
+import config as app_config
+from regression import test_utils
+from regression.utils.app_starter import AppStarter
+from regression.utils.pgadmin_page import PgadminPage
+
+
+class ConnectsToServerFeatureTest(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    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 runTest(self):
+        self.assertEqual(app_config.APP_NAME, self.page.driver.title)
+        self.page.wait_for_spinner_to_disappear()
+
+        self.page.find_by_xpath("//*[@class='aciTreeText' and 
.='Servers']").click()
+        self.page.driver.find_element_by_link_text("Object").click()
+        ActionChains(self.page.driver) \
+            
.move_to_element(self.page.driver.find_element_by_link_text("Create")) \
+            .perform()
+        self.page.find_by_partial_link_text("Server...").click()
+
+        server_config = self.server
+        self.page.fill_input_by_field_name("name", server_config['name'])
+        self.page.find_by_partial_link_text("Connection").click()
+        self.page.fill_input_by_field_name("host", server_config['host'])
+        self.page.fill_input_by_field_name("port", server_config['port'])
+        self.page.fill_input_by_field_name("username", 
server_config['username'])
+        self.page.fill_input_by_field_name("password", 
server_config['db_password'])
+        self.page.find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self.page.find_by_xpath("//*[@id='tree']//*[.='" + 
server_config['name'] + "']")
+
+    def tearDown(self):
+        self.page.remove_server(self.server)
+        self.app_starter.stop_app()
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
diff --git 
a/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py
 
b/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py
new file mode 100644
index 00000000..ed6c0d6b
--- /dev/null
+++ 
b/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py
@@ -0,0 +1,68 @@
+import time
+from selenium import webdriver
+from selenium.webdriver import ActionChains
+
+import config as app_config
+from pgadmin.utils.route import BaseTestGenerator
+from regression import test_utils
+from regression.utils.app_starter import AppStarter
+from regression.utils.pgadmin_page import PgadminPage
+
+
+class SQLTemplateSelectionByPostgresVersionWorksFeatureTest(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)
+
+        test_utils.create_database(self.server, "acceptance_test_db")
+
+        self.app_starter.start_app()
+        self.page.wait_for_app()
+
+        self.page.add_server(self.server)
+
+    def runTest(self):
+        test_utils.create_table(self.server, "acceptance_test_db", 
"test_table")
+
+        self.page.toggle_open_tree_item(self.server['name'])
+        self.page.toggle_open_tree_item('Databases')
+        self.page.toggle_open_tree_item('acceptance_test_db')
+        self.page.toggle_open_tree_item('Schemas')
+        self.page.toggle_open_tree_item('public')
+        self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText' and 
.='Trigger Functions']").click()
+        self.page.find_by_partial_link_text("Object").click()
+        ActionChains(self.page.driver) \
+            
.move_to_element(self.page.driver.find_element_by_link_text("Create")) \
+            .perform()
+        self.page.find_by_partial_link_text("Trigger function...").click()
+        self.page.fill_input_by_field_name("name", "test-trigger-function")
+        self.page.find_by_partial_link_text("Definition").click()
+        self.page.fill_codemirror_area_with(
+"""CREATE OR REPLACE FUNCTION log_last_name_changes()
+RETURNS TRIGGER AS
+$BODY$
+BEGIN
+
+END;
+$BODY$
+"""
+        )
+        self.page.find_by_partial_link_text("SQL").click()
+
+        self.page.find_by_xpath("//*[contains(@class,'CodeMirror-lines') and 
contains(.,'LEAKPROOF')]")
+
+    def tearDown(self):
+        self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
+        self.page.remove_server(self.server)
+        self.app_starter.stop_app()
+        connection = test_utils.get_db_connection(self.server['db'],
+                                                  self.server['username'],
+                                                  self.server['db_password'],
+                                                  self.server['host'],
+                                                  self.server['port'])
+        test_utils.drop_database(connection, "acceptance_test_db")
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..996892a6 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,27 +54,23 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, pkg_root, exclude_pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        all_modules += find_modules(pkg_root, False, True)
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
-                try:
-                    if "tests." in str(module_name):
-                        import_module(module_name)
-                except ImportError:
-                    traceback.print_exc(file=sys.stderr)
-        else:
-            for module_name in find_modules(pkg, False, True):
-                try:
-                    # Exclude the test cases in browser node if SERVER_MODE
-                    # is False
-                    if "pgadmin.browser.tests" not in module_name:
-                        import_module(module_name)
-                except ImportError:
-                    traceback.print_exc(file=sys.stderr)
+        for module_name in all_modules:
+            try:
+                if "tests." in str(module_name) and not any(
+                        str(module_name).startswith('pgadmin.' + 
str(exclude_pkg)) for exclude_pkg in exclude_pkgs
+                ):
+                    import_module(module_name)
+            except ImportError:
+                traceback.print_exc(file=sys.stderr)
 
 
 import six
diff --git a/web/regression/.gitignore b/web/regression/.gitignore
index 0581810b..723fce7e 100644
--- a/web/regression/.gitignore
+++ b/web/regression/.gitignore
@@ -1,4 +1,5 @@
 parent_id.pkl
 regression.log
+test_greenplum_config.json
 test_advanced_config.json
 test_config.json
diff --git a/web/regression/README b/web/regression/README
index 8cc29987..5b077d81 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -103,6 +103,10 @@ Test Data Details
 Execution:
 -----------
 
+- For acceptance tests to run as part of the entire test suite, Chrome and 
chromedriver need to be installed:
+  get chromedriver from 
https://sites.google.com/a/chromium.org/chromedriver/downloads or a package 
manager
+  and make sure it is on the PATH
+
 - The test framework is modular and pluggable and dynamically locates tests
   for modules which are discovered at runtime. All test cases are found
   and registered automatically by its module name in
@@ -122,3 +126,8 @@ Execution:
 
      Example 2) Run test framework for 'database' node
      run 'python runtests.py --pkg browser.server_groups.servers.databases'
+
+- Exclude a package and its subpackages when running tests:
+
+    Example: exclude acceptance tests but run all others:
+    run 'python runtests.py --exclude acceptance'
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index 8d2a886a..cd372b4e 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -133,12 +133,20 @@ def get_test_modules(arguments):
 
     from pgadmin.utils.route import TestsGeneratorRegistry
 
+    exclude_pkgs = []
+
+    if not config.SERVER_MODE:
+        exclude_pkgs.append("browser.tests")
+    if 'exclude' in arguments:
+        exclude_pkgs.append(arguments['exclude'])
+
     # Load the test modules which are in given package(i.e. in arguments.pkg)
     if arguments['pkg'] is None or arguments['pkg'] == "all":
-        TestsGeneratorRegistry.load_generators('pgadmin')
+        TestsGeneratorRegistry.load_generators('pgadmin', exclude_pkgs)
     else:
         TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
-                                               arguments['pkg'])
+                                               arguments['pkg'],
+                                               exclude_pkgs)
 
     # Sort module list so that test suite executes the test cases sequentially
     module_list = TestsGeneratorRegistry.registry.items()
@@ -159,6 +167,8 @@ def add_arguments():
     parser = argparse.ArgumentParser(description='Test suite for pgAdmin4')
     parser.add_argument('--pkg', help='Executes the test cases of particular'
                                       ' package')
+    parser.add_argument('--exclude', help='Skips execution of the test '
+                                          'cases of particular package')
     arg = parser.parse_args()
 
     return arg
diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index 1f9f0522..68f36cbc 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -134,6 +134,24 @@ def create_database(server, db_name):
         traceback.print_exc(file=sys.stderr)
 
 
+def create_table(server, db_name, table_name):
+    try:
+        connection = get_db_connection(db_name,
+                                       server['username'],
+                                       server['db_password'],
+                                       server['host'],
+                                       server['port'])
+        old_isolation_level = connection.isolation_level
+        connection.set_isolation_level(0)
+        pg_cursor = connection.cursor()
+        pg_cursor.execute('''CREATE TABLE "%s" (name VARCHAR, value 
NUMERIC)''' % table_name)
+        pg_cursor.execute('''INSERT INTO "%s" VALUES ('Some-Name', 6)''' % 
table_name)
+        connection.set_isolation_level(old_isolation_level)
+        connection.commit()
+
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+
 def drop_database(connection, database_name):
     """This function used to drop the database"""
     if database_name not in ["postgres", "template1", "template0"]:
diff --git a/web/regression/utils/app_starter.py 
b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..fffd9526
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -0,0 +1,27 @@
+import os
+import subprocess
+
+import signal
+
+
+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):
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"],
+                                                shell=False,
+                                                preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        print("opening browser")
+        self.driver.get("http://"; + self.app_config.DEFAULT_SERVER + ":" + 
str(self.app_config.DEFAULT_SERVER_PORT))
+
+    def stop_app(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
diff --git a/web/regression/utils/pgadmin_page.py 
b/web/regression/utils/pgadmin_page.py
new file mode 100644
index 00000000..4e334e80
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,106 @@
+import time
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver import ActionChains
+
+
+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):
+        self.find_by_xpath("//input[@name='" + field_name + "']").clear()
+        self.find_by_xpath("//input[@name='" + field_name + "']").send_keys(
+            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 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 = 5
+        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 RuntimeError("timed out waiting for " + waiting_for_message)
-- 
Sent via pgadmin-hackers mailing list (pgadmin-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers

Reply via email to