public inbox for [email protected]  
help / color / mirror / Atom feed
Re: Acceptance Tests against a browser (WIP)
30+ messages / 3 participants
[nested] [flat]

* Re: Acceptance Tests against a browser (WIP)
@ 2017-01-17 16:09 Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-01-17 16:09 UTC (permalink / raw)
  To: pgadmin-hackers; +Cc: George Gelashvili <[email protected]>

Thanks for your feedback, Dave!

We can put the tests under the regression directory. I think that makes
sense.
I'm not picturing these tests being module specific, but we may want to
enable running it as a separate suite of tests.

Thanks for the callout about the port and title. We'll make sure those are
pulled from config or that the pgAdmin server is spun up by the test with
specific values.

I have a couple ideas about why the test might not have been running for
you. I think the patch we attached didn't spin up its own pgAdmin yet and
it definitely doesn't fill in username/password if your app is running that
way. That's part of the WIP-ness :-P

-Tira

Hi

On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
<ggelashvili(at)pivotal(dot)io> wrote:
> here's the patch we forgot to attach. Also, you can see work on our branch
> at:
> https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/acceptance-tests
>
> On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili <ggelashvili(at)pivotal(dot)io>
> wrote:
>>
>> Hi there,
>>
>> We are working on browser-automation-based acceptance tests that exercise
>> pgAdmin4 the way a user might.

Nice!

>> The first "connect to database" test works, but at the moment depends on
>> Chrome and chromedriver. We would appreciate feedback on any possible
>> license or code style issues at this point, as well as any thoughts on
>> adding this sort of test to the codebase.

A few thoughts:

- If these tests are to run as part of the regression suite, the
framework for them should live under that directory.

- Are any of the tests likely to be module-specific? If so, they
should really be part of the relevant module as the regression tests
are. If they're more general/less tightly coupled, then I don't see a
problem with them residing where they are.

- Please take care not to include changes to .gitgnore files that
aren't relevant to the rest of us.

- The port number is hard-coded in the test.

- You've hard-coded the string "pgAdmin 4". We've tried to keep that
title as a config option in config.py, so you should pull the string
from there rather than hard-coding it.

- The connect test fails for me (Mac, Python 2.7). I have a suspicion
that this may be because when the test starts chromedriver, OS X
prompts the user about whether a listening port should be opened, but
the tests don't wait (though, I tested with 3 servers configured and
it failed with the same error on the second and third as well, long
after I clicked OK on the prompt):

Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_database.py",
line 32, in runTest
    self.assertEqual("pgAdmin 4", self.driver.title)
AssertionError: 'pgAdmin 4' != u'localhost'

- Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).

- It looks like running a single test won't work yet (because of
TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
arguments['pkg']))

Thanks!

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-01-19 22:07 ` George Gelashvili <[email protected]>
  2017-01-19 23:15   ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 2 replies; 30+ messages in thread

From: George Gelashvili @ 2017-01-19 22:07 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: pgadmin-hackers

Here is an updated patch which starts the server up when the test starts
and uses the values from config.py for server name etc. It still requires
installing chromedriver before running. Should we add something to the
readme about that?

On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]> wrote:

> Thanks for your feedback, Dave!
>
> We can put the tests under the regression directory. I think that makes
> sense.
> I'm not picturing these tests being module specific, but we may want to
> enable running it as a separate suite of tests.
>
> Thanks for the callout about the port and title. We'll make sure those are
> pulled from config or that the pgAdmin server is spun up by the test with
> specific values.
>
> I have a couple ideas about why the test might not have been running for
> you. I think the patch we attached didn't spin up its own pgAdmin yet and
> it definitely doesn't fill in username/password if your app is running that
> way. That's part of the WIP-ness :-P
>
> -Tira
>
> Hi
>
> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
> <ggelashvili(at)pivotal(dot)io> wrote:
> > here's the patch we forgot to attach. Also, you can see work on our branch
> > at:
> > https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/acceptance-tests
> >
> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili <ggelashvili(at)pivotal(dot)io>
> > wrote:
> >>
> >> Hi there,
> >>
> >> We are working on browser-automation-based acceptance tests that exercise
> >> pgAdmin4 the way a user might.
>
> Nice!
>
> >> The first "connect to database" test works, but at the moment depends on
> >> Chrome and chromedriver. We would appreciate feedback on any possible
> >> license or code style issues at this point, as well as any thoughts on
> >> adding this sort of test to the codebase.
>
> A few thoughts:
>
> - If these tests are to run as part of the regression suite, the
> framework for them should live under that directory.
>
> - Are any of the tests likely to be module-specific? If so, they
> should really be part of the relevant module as the regression tests
> are. If they're more general/less tightly coupled, then I don't see a
> problem with them residing where they are.
>
> - Please take care not to include changes to .gitgnore files that
> aren't relevant to the rest of us.
>
> - The port number is hard-coded in the test.
>
> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
> title as a config option in config.py, so you should pull the string
> from there rather than hard-coding it.
>
> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
> that this may be because when the test starts chromedriver, OS X
> prompts the user about whether a listening port should be opened, but
> the tests don't wait (though, I tested with 3 servers configured and
> it failed with the same error on the second and third as well, long
> after I clicked OK on the prompt):
>
> Traceback (most recent call last):
>   File "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_database.py",
> line 32, in runTest
>     self.assertEqual("pgAdmin 4", self.driver.title)
> AssertionError: 'pgAdmin 4' != u'localhost'
>
> - Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).
>
> - It looks like running a single test won't work yet (because of
> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
> arguments['pkg']))
>
> 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 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..ccbcf9f5
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,108 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if (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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid)
+
+        print("waiting for server to start")
+        time.sleep(10)
+
+        print("opening browser")
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+        print(config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self.driver.get("http://"; + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _wait_for_element(self, find_method_with_args):
+        timeout = 5
+        time_waited = 0
+        sleep_time = 0.01
+
+        while time_waited < timeout:
+            try:
+                element = find_method_with_args()
+                if element.is_displayed() & element.is_enabled():
+                    return element
+            except NoSuchElementException:
+                time_waited += sleep_time
+                time.sleep(sleep_time)
+                pass
+
+        self.fail("Timed out waiting for element")
+
+    def _wait_for_spinner_to_disappear(self):
+        try:
+            while True:
+                self.driver.find_element_by_id("pg-spinner")
+                time.sleep(0.1)
+        except NoSuchElementException:
+            pass
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-server-start.diff (6.6K, 3-acceptance-tests-with-server-start.diff)
  download | inline diff:
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..ccbcf9f5
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,108 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if (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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid)
+
+        print("waiting for server to start")
+        time.sleep(10)
+
+        print("opening browser")
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+        print(config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self.driver.get("http://" + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _wait_for_element(self, find_method_with_args):
+        timeout = 5
+        time_waited = 0
+        sleep_time = 0.01
+
+        while time_waited < timeout:
+            try:
+                element = find_method_with_args()
+                if element.is_displayed() & element.is_enabled():
+                    return element
+            except NoSuchElementException:
+                time_waited += sleep_time
+                time.sleep(sleep_time)
+                pass
+
+        self.fail("Timed out waiting for element")
+
+    def _wait_for_spinner_to_disappear(self):
+        try:
+            while True:
+                self.driver.find_element_by_id("pg-spinner")
+                time.sleep(0.1)
+        except NoSuchElementException:
+            pass
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-19 23:15   ` George Gelashvili <[email protected]>
  2017-01-20 15:38     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  1 sibling, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-19 23:15 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: pgadmin-hackers

Here's an updated patch which polls to wait for the app to start instead of
sleeping for 10 seconds.

On Thu, Jan 19, 2017 at 5:07 PM, George Gelashvili <[email protected]>
wrote:

>
> Here is an updated patch which starts the server up when the test starts
> and uses the values from config.py for server name etc. It still requires
> installing chromedriver before running. Should we add something to the
> readme about that?
>
> On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]> wrote:
>
>> Thanks for your feedback, Dave!
>>
>> We can put the tests under the regression directory. I think that makes
>> sense.
>> I'm not picturing these tests being module specific, but we may want to
>> enable running it as a separate suite of tests.
>>
>> Thanks for the callout about the port and title. We'll make sure those
>> are pulled from config or that the pgAdmin server is spun up by the test
>> with specific values.
>>
>> I have a couple ideas about why the test might not have been running for
>> you. I think the patch we attached didn't spin up its own pgAdmin yet and
>> it definitely doesn't fill in username/password if your app is running that
>> way. That's part of the WIP-ness :-P
>>
>> -Tira
>>
>> Hi
>>
>> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
>> <ggelashvili(at)pivotal(dot)io> wrote:
>> > here's the patch we forgot to attach. Also, you can see work on our branch
>> > at:
>> > https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/acceptance-tests
>> >
>> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili <ggelashvili(at)pivotal(dot)io>
>> > wrote:
>> >>
>> >> Hi there,
>> >>
>> >> We are working on browser-automation-based acceptance tests that exercise
>> >> pgAdmin4 the way a user might.
>>
>> Nice!
>>
>> >> The first "connect to database" test works, but at the moment depends on
>> >> Chrome and chromedriver. We would appreciate feedback on any possible
>> >> license or code style issues at this point, as well as any thoughts on
>> >> adding this sort of test to the codebase.
>>
>> A few thoughts:
>>
>> - If these tests are to run as part of the regression suite, the
>> framework for them should live under that directory.
>>
>> - Are any of the tests likely to be module-specific? If so, they
>> should really be part of the relevant module as the regression tests
>> are. If they're more general/less tightly coupled, then I don't see a
>> problem with them residing where they are.
>>
>> - Please take care not to include changes to .gitgnore files that
>> aren't relevant to the rest of us.
>>
>> - The port number is hard-coded in the test.
>>
>> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
>> title as a config option in config.py, so you should pull the string
>> from there rather than hard-coding it.
>>
>> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
>> that this may be because when the test starts chromedriver, OS X
>> prompts the user about whether a listening port should be opened, but
>> the tests don't wait (though, I tested with 3 servers configured and
>> it failed with the same error on the second and third as well, long
>> after I clicked OK on the prompt):
>>
>> Traceback (most recent call last):
>>   File "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_database.py",
>> line 32, in runTest
>>     self.assertEqual("pgAdmin 4", self.driver.title)
>> AssertionError: 'pgAdmin 4' != u'localhost'
>>
>> - Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).
>>
>> - It looks like running a single test won't work yet (because of
>> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
>> arguments['pkg']))
>>
>> 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 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..2c4f85c4
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,123 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if (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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid, stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://"; + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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():
+            self.driver.refresh()
+            return self.driver.title == config.APP_NAME
+
+        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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-server-start-and-polling.diff (7.1K, 3-acceptance-tests-with-server-start-and-polling.diff)
  download | inline diff:
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..2c4f85c4
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,123 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if (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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid, stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://" + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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():
+            self.driver.refresh()
+            return self.driver.title == config.APP_NAME
+
+        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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-19 23:15   ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-20 15:38     ` Dave Page <[email protected]>
  0 siblings, 0 replies; 30+ messages in thread

From: Dave Page @ 2017-01-20 15:38 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

Hi

On Thu, Jan 19, 2017 at 11:15 PM, George Gelashvili
<[email protected]> wrote:
> Here's an updated patch which polls to wait for the app to start instead of
> sleeping for 10 seconds.

I see the browser opening now, and it immediately loads pgAdmin. Then,
it prompts me for a reload whenever the test is run, which fails no
matter how quickly I hit the prompt from what I can see:

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.test_connects_to_database.ConnectsToDatabase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/test_connects_to_database.py",
line 41, in setUp
    self._wait_for_app()
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/test_connects_to_database.py",
line 109, in _wait_for_app
    self.__wait_for("app to start", page_shows_app)
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/test_connects_to_database.py",
line 117, in __wait_for
    result = condition_met_function()
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/test_connects_to_database.py",
line 107, in page_shows_app
    return self.driver.title == config.APP_NAME
  File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py",
line 257, in title
    resp = self.execute(Command.GET_TITLE)
  File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/selenium/webdriver/remote/webdriver.py",
line 236, in execute
    self.error_handler.check_response(response)
  File "/Users/dpage/.virtualenvs/pgadmin4/lib/python2.7/site-packages/selenium/webdriver/remote/errorhandler.py",
line 192, in check_response
    raise exception_class(message, screen, stacktrace)
UnexpectedAlertPresentException: Alert Text: None
Message: unexpected alert open: {Alert text : Are you sure you wish to
close the pgAdmin 4 browser?}
  (Session info: chrome=55.0.2883.95)
  (Driver info: chromedriver=2.27.440174
(e97a722caafc2d3a8b807ee115bfb307f7d2cfd9),platform=Mac OS X 10.12.1
x86_64)

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-20 15:38   ` Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  1 sibling, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-20 15:38 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

On Thu, Jan 19, 2017 at 10:07 PM, George Gelashvili
<[email protected]> wrote:
>
> Here is an updated patch which starts the server up when the test starts and
> uses the values from config.py for server name etc. It still requires
> installing chromedriver before running. Should we add something to the
> readme about that?

Yes, we definitely should (including download site URL)

> On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]> wrote:
>>
>> Thanks for your feedback, Dave!
>>
>> We can put the tests under the regression directory. I think that makes
>> sense.
>> I'm not picturing these tests being module specific, but we may want to
>> enable running it as a separate suite of tests.
>>
>> Thanks for the callout about the port and title. We'll make sure those are
>> pulled from config or that the pgAdmin server is spun up by the test with
>> specific values.
>>
>> I have a couple ideas about why the test might not have been running for
>> you. I think the patch we attached didn't spin up its own pgAdmin yet and it
>> definitely doesn't fill in username/password if your app is running that
>> way. That's part of the WIP-ness :-P
>>
>> -Tira
>>
>> Hi
>>
>> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
>> <ggelashvili(at)pivotal(dot)io> wrote:
>> > here's the patch we forgot to attach. Also, you can see work on our
>> > branch
>> > at:
>> >
>> > https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/acceptance-tests
>> >
>> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili
>> > <ggelashvili(at)pivotal(dot)io>
>> > wrote:
>> >>
>> >> Hi there,
>> >>
>> >> We are working on browser-automation-based acceptance tests that
>> >> exercise
>> >> pgAdmin4 the way a user might.
>>
>> Nice!
>>
>> >> The first "connect to database" test works, but at the moment depends
>> >> on
>> >> Chrome and chromedriver. We would appreciate feedback on any possible
>> >> license or code style issues at this point, as well as any thoughts on
>> >> adding this sort of test to the codebase.
>>
>> A few thoughts:
>>
>> - If these tests are to run as part of the regression suite, the
>> framework for them should live under that directory.
>>
>> - Are any of the tests likely to be module-specific? If so, they
>> should really be part of the relevant module as the regression tests
>> are. If they're more general/less tightly coupled, then I don't see a
>> problem with them residing where they are.
>>
>> - Please take care not to include changes to .gitgnore files that
>> aren't relevant to the rest of us.
>>
>> - The port number is hard-coded in the test.
>>
>> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
>> title as a config option in config.py, so you should pull the string
>> from there rather than hard-coding it.
>>
>> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
>> that this may be because when the test starts chromedriver, OS X
>> prompts the user about whether a listening port should be opened, but
>> the tests don't wait (though, I tested with 3 servers configured and
>> it failed with the same error on the second and third as well, long
>> after I clicked OK on the prompt):
>>
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_database.py",
>> line 32, in runTest
>>     self.assertEqual("pgAdmin 4", self.driver.title)
>> AssertionError: 'pgAdmin 4' != u'localhost'
>>
>> - Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).
>>
>> - It looks like running a single test won't work yet (because of
>> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
>> arguments['pkg']))
>>
>> Thanks!
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>>
>
>
>
> --
> Sent via pgadmin-hackers mailing list ([email protected])
> To make changes to your subscription:
> http://www.postgresql.org/mailpref/pgadmin-hackers
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-20 17:33     ` George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-20 17:33 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

Thanks for bringing that to our attention! Here's the latest patch

On Fri, Jan 20, 2017 at 10:38 AM, Dave Page <[email protected]> wrote:

> On Thu, Jan 19, 2017 at 10:07 PM, George Gelashvili
> <[email protected]> wrote:
> >
> > Here is an updated patch which starts the server up when the test starts
> and
> > uses the values from config.py for server name etc. It still requires
> > installing chromedriver before running. Should we add something to the
> > readme about that?
>
> Yes, we definitely should (including download site URL)
>
> > On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]>
> wrote:
> >>
> >> Thanks for your feedback, Dave!
> >>
> >> We can put the tests under the regression directory. I think that makes
> >> sense.
> >> I'm not picturing these tests being module specific, but we may want to
> >> enable running it as a separate suite of tests.
> >>
> >> Thanks for the callout about the port and title. We'll make sure those
> are
> >> pulled from config or that the pgAdmin server is spun up by the test
> with
> >> specific values.
> >>
> >> I have a couple ideas about why the test might not have been running for
> >> you. I think the patch we attached didn't spin up its own pgAdmin yet
> and it
> >> definitely doesn't fill in username/password if your app is running that
> >> way. That's part of the WIP-ness :-P
> >>
> >> -Tira
> >>
> >> Hi
> >>
> >> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
> >> <ggelashvili(at)pivotal(dot)io> wrote:
> >> > here's the patch we forgot to attach. Also, you can see work on our
> >> > branch
> >> > at:
> >> >
> >> > https://github.com/pivotalsoftware/pgadmin4/tree/
> pivotal/acceptance-tests
> >> >
> >> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili
> >> > <ggelashvili(at)pivotal(dot)io>
> >> > wrote:
> >> >>
> >> >> Hi there,
> >> >>
> >> >> We are working on browser-automation-based acceptance tests that
> >> >> exercise
> >> >> pgAdmin4 the way a user might.
> >>
> >> Nice!
> >>
> >> >> The first "connect to database" test works, but at the moment depends
> >> >> on
> >> >> Chrome and chromedriver. We would appreciate feedback on any possible
> >> >> license or code style issues at this point, as well as any thoughts
> on
> >> >> adding this sort of test to the codebase.
> >>
> >> A few thoughts:
> >>
> >> - If these tests are to run as part of the regression suite, the
> >> framework for them should live under that directory.
> >>
> >> - Are any of the tests likely to be module-specific? If so, they
> >> should really be part of the relevant module as the regression tests
> >> are. If they're more general/less tightly coupled, then I don't see a
> >> problem with them residing where they are.
> >>
> >> - Please take care not to include changes to .gitgnore files that
> >> aren't relevant to the rest of us.
> >>
> >> - The port number is hard-coded in the test.
> >>
> >> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
> >> title as a config option in config.py, so you should pull the string
> >> from there rather than hard-coding it.
> >>
> >> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
> >> that this may be because when the test starts chromedriver, OS X
> >> prompts the user about whether a listening port should be opened, but
> >> the tests don't wait (though, I tested with 3 servers configured and
> >> it failed with the same error on the second and third as well, long
> >> after I clicked OK on the prompt):
> >>
> >> Traceback (most recent call last):
> >>   File
> >> "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_
> to_database.py",
> >> line 32, in runTest
> >>     self.assertEqual("pgAdmin 4", self.driver.title)
> >> AssertionError: 'pgAdmin 4' != u'localhost'
> >>
> >> - Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).
> >>
> >> - It looks like running a single test won't work yet (because of
> >> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
> >> arguments['pkg']))
> >>
> >> Thanks!
> >>
> >> --
> >> Dave Page
> >> Blog: http://pgsnake.blogspot.com
> >> Twitter: @pgsnake
> >>
> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> The Enterprise PostgreSQL Company
> >>
> >>
> >
> >
> >
> > --
> > Sent via pgadmin-hackers mailing list ([email protected])
> > To make changes to your subscription:
> > http://www.postgresql.org/mailpref/pgadmin-hackers
> >
>
>
>
> --
> 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 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..c35cd0fd
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,127 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if 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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://"; + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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 == 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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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
diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index a2d2f5cd..1f9f0522 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -69,7 +69,7 @@ def get_config_data():
     """This function reads the server data from config_data"""
     server_data = []
     for srv in test_setup.config_data['server_credentials']:
-        if (not srv.has_key('enabled')) or srv['enabled'] == True:
+        if (not 'enabled' in srv) or srv['enabled']:
             data = {"name": srv['name'],
                     "comment": srv['comment'],
                     "host": srv['host'],


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-server-start-and-polling.diff (8.5K, 3-acceptance-tests-with-server-start-and-polling.diff)
  download | inline diff:
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..c35cd0fd
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,127 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if 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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://" + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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 == 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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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
diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index a2d2f5cd..1f9f0522 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -69,7 +69,7 @@ def get_config_data():
     """This function reads the server data from config_data"""
     server_data = []
     for srv in test_setup.config_data['server_credentials']:
-        if (not srv.has_key('enabled')) or srv['enabled'] == True:
+        if (not 'enabled' in srv) or srv['enabled']:
             data = {"name": srv['name'],
                     "comment": srv['comment'],
                     "host": srv['host'],


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-24 09:43       ` Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-24 09:43 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

On Fri, Jan 20, 2017 at 5:33 PM, George Gelashvili
<[email protected]> wrote:
> Thanks for bringing that to our attention! Here's the latest patch

piranha:pgadmin4 dpage$ git apply
~/Downloads/acceptance-tests-with-server-start-and-polling.diff
error: patch failed: web/regression/test_utils.py:69
error: web/regression/test_utils.py: patch does not apply

:-(

> On Fri, Jan 20, 2017 at 10:38 AM, Dave Page <[email protected]> wrote:
>>
>> On Thu, Jan 19, 2017 at 10:07 PM, George Gelashvili
>> <[email protected]> wrote:
>> >
>> > Here is an updated patch which starts the server up when the test starts
>> > and
>> > uses the values from config.py for server name etc. It still requires
>> > installing chromedriver before running. Should we add something to the
>> > readme about that?
>>
>> Yes, we definitely should (including download site URL)
>>
>> > On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]>
>> > wrote:
>> >>
>> >> Thanks for your feedback, Dave!
>> >>
>> >> We can put the tests under the regression directory. I think that makes
>> >> sense.
>> >> I'm not picturing these tests being module specific, but we may want to
>> >> enable running it as a separate suite of tests.
>> >>
>> >> Thanks for the callout about the port and title. We'll make sure those
>> >> are
>> >> pulled from config or that the pgAdmin server is spun up by the test
>> >> with
>> >> specific values.
>> >>
>> >> I have a couple ideas about why the test might not have been running
>> >> for
>> >> you. I think the patch we attached didn't spin up its own pgAdmin yet
>> >> and it
>> >> definitely doesn't fill in username/password if your app is running
>> >> that
>> >> way. That's part of the WIP-ness :-P
>> >>
>> >> -Tira
>> >>
>> >> Hi
>> >>
>> >> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
>> >> <ggelashvili(at)pivotal(dot)io> wrote:
>> >> > here's the patch we forgot to attach. Also, you can see work on our
>> >> > branch
>> >> > at:
>> >> >
>> >> >
>> >> > https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/acceptance-tests
>> >> >
>> >> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili
>> >> > <ggelashvili(at)pivotal(dot)io>
>> >> > wrote:
>> >> >>
>> >> >> Hi there,
>> >> >>
>> >> >> We are working on browser-automation-based acceptance tests that
>> >> >> exercise
>> >> >> pgAdmin4 the way a user might.
>> >>
>> >> Nice!
>> >>
>> >> >> The first "connect to database" test works, but at the moment
>> >> >> depends
>> >> >> on
>> >> >> Chrome and chromedriver. We would appreciate feedback on any
>> >> >> possible
>> >> >> license or code style issues at this point, as well as any thoughts
>> >> >> on
>> >> >> adding this sort of test to the codebase.
>> >>
>> >> A few thoughts:
>> >>
>> >> - If these tests are to run as part of the regression suite, the
>> >> framework for them should live under that directory.
>> >>
>> >> - Are any of the tests likely to be module-specific? If so, they
>> >> should really be part of the relevant module as the regression tests
>> >> are. If they're more general/less tightly coupled, then I don't see a
>> >> problem with them residing where they are.
>> >>
>> >> - Please take care not to include changes to .gitgnore files that
>> >> aren't relevant to the rest of us.
>> >>
>> >> - The port number is hard-coded in the test.
>> >>
>> >> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
>> >> title as a config option in config.py, so you should pull the string
>> >> from there rather than hard-coding it.
>> >>
>> >> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
>> >> that this may be because when the test starts chromedriver, OS X
>> >> prompts the user about whether a listening port should be opened, but
>> >> the tests don't wait (though, I tested with 3 servers configured and
>> >> it failed with the same error on the second and third as well, long
>> >> after I clicked OK on the prompt):
>> >>
>> >> Traceback (most recent call last):
>> >>   File
>> >>
>> >> "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_database.py",
>> >> line 32, in runTest
>> >>     self.assertEqual("pgAdmin 4", self.driver.title)
>> >> AssertionError: 'pgAdmin 4' != u'localhost'
>> >>
>> >> - Please keep tests in the pgadmin. namespace (pgadmin.acceptance.??).
>> >>
>> >> - It looks like running a single test won't work yet (because of
>> >> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
>> >> arguments['pkg']))
>> >>
>> >> Thanks!
>> >>
>> >> --
>> >> Dave Page
>> >> Blog: http://pgsnake.blogspot.com
>> >> Twitter: @pgsnake
>> >>
>> >> EnterpriseDB UK: http://www.enterprisedb.com
>> >> The Enterprise PostgreSQL Company
>> >>
>> >>
>> >
>> >
>> >
>> > --
>> > Sent via pgadmin-hackers mailing list ([email protected])
>> > To make changes to your subscription:
>> > http://www.postgresql.org/mailpref/pgadmin-hackers
>> >
>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-25 23:31         ` George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-25 23:31 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

ah
That diff was generated before the python 3 patch was applied. This should
work against master

Cheers,
George

On Tue, Jan 24, 2017 at 4:43 AM, Dave Page <[email protected]> wrote:

> On Fri, Jan 20, 2017 at 5:33 PM, George Gelashvili
> <[email protected]> wrote:
> > Thanks for bringing that to our attention! Here's the latest patch
>
> piranha:pgadmin4 dpage$ git apply
> ~/Downloads/acceptance-tests-with-server-start-and-polling.diff
> error: patch failed: web/regression/test_utils.py:69
> error: web/regression/test_utils.py: patch does not apply
>
> :-(
>
> > On Fri, Jan 20, 2017 at 10:38 AM, Dave Page <[email protected]> wrote:
> >>
> >> On Thu, Jan 19, 2017 at 10:07 PM, George Gelashvili
> >> <[email protected]> wrote:
> >> >
> >> > Here is an updated patch which starts the server up when the test
> starts
> >> > and
> >> > uses the values from config.py for server name etc. It still requires
> >> > installing chromedriver before running. Should we add something to the
> >> > readme about that?
> >>
> >> Yes, we definitely should (including download site URL)
> >>
> >> > On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]>
> >> > wrote:
> >> >>
> >> >> Thanks for your feedback, Dave!
> >> >>
> >> >> We can put the tests under the regression directory. I think that
> makes
> >> >> sense.
> >> >> I'm not picturing these tests being module specific, but we may want
> to
> >> >> enable running it as a separate suite of tests.
> >> >>
> >> >> Thanks for the callout about the port and title. We'll make sure
> those
> >> >> are
> >> >> pulled from config or that the pgAdmin server is spun up by the test
> >> >> with
> >> >> specific values.
> >> >>
> >> >> I have a couple ideas about why the test might not have been running
> >> >> for
> >> >> you. I think the patch we attached didn't spin up its own pgAdmin yet
> >> >> and it
> >> >> definitely doesn't fill in username/password if your app is running
> >> >> that
> >> >> way. That's part of the WIP-ness :-P
> >> >>
> >> >> -Tira
> >> >>
> >> >> Hi
> >> >>
> >> >> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
> >> >> <ggelashvili(at)pivotal(dot)io> wrote:
> >> >> > here's the patch we forgot to attach. Also, you can see work on our
> >> >> > branch
> >> >> > at:
> >> >> >
> >> >> >
> >> >> > https://github.com/pivotalsoftware/pgadmin4/tree/
> pivotal/acceptance-tests
> >> >> >
> >> >> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili
> >> >> > <ggelashvili(at)pivotal(dot)io>
> >> >> > wrote:
> >> >> >>
> >> >> >> Hi there,
> >> >> >>
> >> >> >> We are working on browser-automation-based acceptance tests that
> >> >> >> exercise
> >> >> >> pgAdmin4 the way a user might.
> >> >>
> >> >> Nice!
> >> >>
> >> >> >> The first "connect to database" test works, but at the moment
> >> >> >> depends
> >> >> >> on
> >> >> >> Chrome and chromedriver. We would appreciate feedback on any
> >> >> >> possible
> >> >> >> license or code style issues at this point, as well as any
> thoughts
> >> >> >> on
> >> >> >> adding this sort of test to the codebase.
> >> >>
> >> >> A few thoughts:
> >> >>
> >> >> - If these tests are to run as part of the regression suite, the
> >> >> framework for them should live under that directory.
> >> >>
> >> >> - Are any of the tests likely to be module-specific? If so, they
> >> >> should really be part of the relevant module as the regression tests
> >> >> are. If they're more general/less tightly coupled, then I don't see a
> >> >> problem with them residing where they are.
> >> >>
> >> >> - Please take care not to include changes to .gitgnore files that
> >> >> aren't relevant to the rest of us.
> >> >>
> >> >> - The port number is hard-coded in the test.
> >> >>
> >> >> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
> >> >> title as a config option in config.py, so you should pull the string
> >> >> from there rather than hard-coding it.
> >> >>
> >> >> - The connect test fails for me (Mac, Python 2.7). I have a suspicion
> >> >> that this may be because when the test starts chromedriver, OS X
> >> >> prompts the user about whether a listening port should be opened, but
> >> >> the tests don't wait (though, I tested with 3 servers configured and
> >> >> it failed with the same error on the second and third as well, long
> >> >> after I clicked OK on the prompt):
> >> >>
> >> >> Traceback (most recent call last):
> >> >>   File
> >> >>
> >> >> "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_
> to_database.py",
> >> >> line 32, in runTest
> >> >>     self.assertEqual("pgAdmin 4", self.driver.title)
> >> >> AssertionError: 'pgAdmin 4' != u'localhost'
> >> >>
> >> >> - Please keep tests in the pgadmin. namespace
> (pgadmin.acceptance.??).
> >> >>
> >> >> - It looks like running a single test won't work yet (because of
> >> >> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
> >> >> arguments['pkg']))
> >> >>
> >> >> Thanks!
> >> >>
> >> >> --
> >> >> Dave Page
> >> >> Blog: http://pgsnake.blogspot.com
> >> >> Twitter: @pgsnake
> >> >>
> >> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> >> The Enterprise PostgreSQL Company
> >> >>
> >> >>
> >> >
> >> >
> >> >
> >> > --
> >> > Sent via pgadmin-hackers mailing list ([email protected]
> )
> >> > To make changes to your subscription:
> >> > http://www.postgresql.org/mailpref/pgadmin-hackers
> >> >
> >>
> >>
> >>
> >> --
> >> Dave Page
> >> Blog: http://pgsnake.blogspot.com
> >> Twitter: @pgsnake
> >>
> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> The Enterprise PostgreSQL Company
> >
> >
>
>
>
> --
> 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 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..c35cd0fd
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,127 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if 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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://"; + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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 == 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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
index f501d3d0..99fa00c7 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
@@ -1673,8 +1673,8 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
             return make_json_response(
                 success=1,
-                info=gettext("Trigger(s) have been enabled") if is_enable
-                else gettext("Trigger(s) have been disabled"),
+                info=gettext("Trigger(s) has been enabled") if is_enable
+                else gettext("Trigger(s) has been disabled"),
                 data={
                     'id': tid,
                     'scid': scid
@@ -1706,7 +1706,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
             return make_json_response(
                 success=1,
-                info=gettext("Table statistics have been reset"),
+                info=gettext("Table statistics has been reset"),
                 data={
                     'id': tid,
                     'scid': scid
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
index fe7d9349..6b7f52b0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
@@ -816,7 +816,7 @@ class ExclusionConstraintView(PGChildNodeView):
             sql = render_template("/".join([self.template_path, 'create.sql']),
                                   data=data, conn=self.conn)
 
-        return sql, data['name']
+        return sql, data['name'] if 'name' in data else old_data['name']
 
     @check_precondition
     def sql(self, gid, sid, did, scid, tid, exid=None):
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
index e7bd905c..cfe15195 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
@@ -7,11 +7,10 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
     defaults: {
       column: undefined,
       oper_class: undefined,
-      order: false,
-      nulls_order: false,
+      order: undefined,
+      nulls_order: undefined,
       operator:undefined,
-      col_type:undefined,
-      is_sort_nulls_applicable: true
+      col_type:undefined
     },
     toJSON: function () {
       var d = pgBrowser.Node.Model.prototype.toJSON.apply(this, arguments);
@@ -24,9 +23,26 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
       },{
         id: 'oper_class', label:'{{ _('Operator class') }}', type:'text',
         node: 'table', url: 'get_oper_class', first_empty: true,
-        editable: true,
+        editable: function(m) {
+          if (m instanceof Backbone.Collection) {
+            return true;
+          }
+          if ((_.has(m.collection, 'handler') &&
+                !_.isUndefined(m.collection.handler) &&
+                !_.isUndefined(m.collection.handler.get('oid')))) {
+            return false;
+          }
+
+          if (m.collection) {
+            var indexType = m.collection.handler.get('amname')
+            return (indexType == 'btree' || _.isUndefined(indexType) ||
+              _.isNull(indexType) || indexType == '');
+          } else {
+            return true;
+          }
+        },
         select2: {
-          allowClear: true, width: 'style', tags: true,
+          allowClear: true, width: 'style',
           placeholder: '{{ _("Select the operator class") }}'
         }, cell: Backgrid.Extension.Select2Cell.extend({
           initialize: function () {
@@ -39,12 +55,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
 
             if (url && (indextype == 'btree' || _.isUndefined(indextype) ||
                 _.isNull(indextype) || indextype == '')) {
-              // Set sort_order and nulls to true if access method is btree
-              setTimeout(function() {
-                m.set('order', true);
-                m.set('nulls_order', true);
-              }, 10);
-
               var node = this.column.get('schema_node'),
                   eventHandler = m.top || m,
                   node_info = this.column.get('node_info'),
@@ -98,14 +108,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
           if (m instanceof Backbone.Collection) {
             return true;
           }
-          else {
-            if (m.top.get('amname') === 'btree') {
-              m.set('is_sort_nulls_applicable', true);
-              return true;
-            }
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
           if ((_.has(m.collection, 'handler') &&
                 !_.isUndefined(m.collection.handler) &&
                 !_.isUndefined(m.collection.handler.get('oid')))) {
@@ -122,15 +124,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
           if (m instanceof Backbone.Collection) {
             return true;
           }
-          else {
-            if (m.top.get('amname') === 'btree') {
-              m.set('is_sort_nulls_applicable', true);
-              return true;
-            }
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
-
           if ((_.has(m.collection, 'handler') &&
                 !_.isUndefined(m.collection.handler) &&
                 !_.isUndefined(m.collection.handler.get('oid')))) {
@@ -905,15 +898,8 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
         }],
         validate: function() {
           this.errorModel.clear();
-          var columns = this.get('columns'),
-              name = this.get('name');
-
-          if ((_.isUndefined(name) || _.isNull(name) || name.length < 1)) {
-            var msg = '{{ _('Please specify name for exclusion constraint.') }}';
-            this.errorModel.set('name', msg);
-            return msg;
-          }
-          else  if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
+          var columns = this.get('columns');
+          if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
             var msg = '{{ _('Please specify columns for exclusion constraint.') }}';
             this.errorModel.set('columns', msg);
             return msg;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
index 6f462652..6531ba5e 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
@@ -51,8 +51,7 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           collspcname: undefined,
           op_class: undefined,
           sort_order: false,
-          nulls: false,
-          is_sort_nulls_applicable: true
+          nulls: false
         },
         schema: [
           {
@@ -78,7 +77,7 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
             control: 'node-ajax-options', url: 'get_collations', node: 'index'
           },{
             id: 'op_class', label:'{{ _('Operator class') }}',
-            cell: NodeAjaxOptionsDepsCell, tags: true,
+            cell: NodeAjaxOptionsDepsCell,
             type: 'text', disabled: 'checkAccessMethod',
             editable: function(m) {
                 // Header cell then skip
@@ -109,19 +108,13 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           },{
             id: 'sort_order', label:'{{ _('Sort order') }}',
             cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
+            disabled: 'checkAccessMethod',
             editable: function(m) {
-              // Header cell then skip
-              if (m instanceof Backbone.Collection) {
-                  return false;
-              }
-              else {
-                if (m.top.get('amname') === 'btree') {
-                  m.set('is_sort_nulls_applicable', true);
-                  return true;
+                // Header cell then skip
+                if (m instanceof Backbone.Collection) {
+                    return false;
                 }
-                m.set('is_sort_nulls_applicable', false);
-                return false;
-              }
+                return !(m.checkAccessMethod.apply(this, arguments));
             },
             deps: ['amname'],
             options: {
@@ -132,18 +125,13 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           },{
             id: 'nulls', label:'{{ _('NULLs') }}',
             cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
+            disabled: 'checkAccessMethod',
             editable: function(m) {
-              // Header cell then skip
-              if (m instanceof Backbone.Collection) {
-                  return true;
-              } else {
-                  if (m.top.get('amname') === 'btree') {
-                    m.set('is_sort_nulls_applicable', true);
+                // Header cell then skip
+                if (m instanceof Backbone.Collection) {
                     return true;
-                  }
-                  m.set('is_sort_nulls_applicable', false);
-                  return false;
-              }
+                }
+                return !(m.checkAccessMethod.apply(this, arguments));
             },
             deps: ['amname', 'sort_order'],
             options: {
@@ -196,11 +184,9 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
             if(m.get('sort_order') == true && m.previous('sort_order') ==  false) {
                setTimeout(function() { m.set('nulls', true) }, 10);
             }
+            return false;
           }
-          else {
-            m.set('is_sort_nulls_applicable', false);
-          }
-          return false;
+          return true;
         },
     });
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
index b7bfa527..33af1973 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
@@ -3,7 +3,7 @@ CREATE {% if data.indisunique %}UNIQUE {% endif %}INDEX {% if data.isconcurrent
 
 {% if mode == 'create' %}
     ({% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.colname)}}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.op_class %}
- {{c.op_class}}{% endif %}{% if data.amname is defined %}{% if c.sort_order is defined and c.is_sort_nulls_applicable %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined and c.is_sort_nulls_applicable %} NULLS {% if c.nulls %}
+ {{c.op_class}}{% endif %}{% if data.amname is defined and data.amname not in ['gist', 'gin'] %}{% if c.sort_order is defined %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined %} NULLS {% if c.nulls %}
 FIRST{% else %}LAST{% endif %}{% endif %}{% endif %}{% endfor %})
 {% else %}
 {## We will get indented data from postgres for column ##}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
index c87a2930..2d006090 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
@@ -198,54 +198,51 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
          });
        },
        reset_table_stats: function(args) {
-          var input = args || {},
-            obj = this,
-            t = pgBrowser.tree,
-            i = input.item || t.selected(),
-            d = i && i.length == 1 ? t.itemData(i) : undefined;
+          var input = args || {};
+          obj = this,
+          t = pgBrowser.tree,
+          i = input.item || t.selected(),
+          d = i && i.length == 1 ? t.itemData(i) : undefined;
 
           if (!d)
             return false;
 
           alertify.confirm(
-            '{{ _('Reset statistics') }}',
-            S('{{ _('Are you sure you want to reset the statistics for table %%s?') }}').sprintf(d._label).value(),
+            S('{{ _('Are you sure you want to reset table statistics for %s?') }}').sprintf(d.label).value(),
             function (e) {
-              if (e) {
-                var data = d;
-                $.ajax({
-                  url: obj.generate_url(i, 'reset' , d, true),
-                  type:'DELETE',
-                  success: function(res) {
-                    if (res.success == 1) {
-                      alertify.success("{{ _('" + res.info + "') }}");
-                      t.removeIcon(i);
-                      data.icon = 'icon-table';
-                      t.addIcon(i, {icon: data.icon});
-                      t.unload(i);
-                      t.setInode(i);
-                      t.deselect(i);
-                      // Fetch updated data from server
-                      setTimeout(function() {
-                        t.select(i);
-                      }, 10);
-                    }
-                  },
-                  error: function(xhr, status, error) {
-                    try {
-                      var err = $.parseJSON(xhr.responseText);
-                      if (err.success == 0) {
-                        msg = S('{{ _(' + err.errormsg + ')}}').value();
-                        alertify.error("{{ _('" + err.errormsg + "') }}");
-                      }
-                    } catch (e) {}
+            if (e) {
+              var data = d;
+              $.ajax({
+                url: obj.generate_url(i, 'reset' , d, true),
+                type:'DELETE',
+                success: function(res) {
+                  if (res.success == 1) {
+                    alertify.success("{{ _('" + res.info + "') }}");
+                    t.removeIcon(i);
+                    data.icon = 'icon-table';
+                    t.addIcon(i, {icon: data.icon});
                     t.unload(i);
+                    t.setInode(i);
+                    t.deselect(i);
+                    // Fetch updated data from server
+                    setTimeout(function() {
+                      t.select(i);
+                    }, 10);
                   }
-                });
-              }
-            },
-            function() {}
-          );
+                },
+                error: function(xhr, status, error) {
+                  try {
+                    var err = $.parseJSON(xhr.responseText);
+                    if (err.success == 0) {
+                      msg = S('{{ _(' + err.errormsg + ')}}').value();
+                      alertify.error("{{ _('" + err.errormsg + "') }}");
+                    }
+                  } catch (e) {}
+                  t.unload(i);
+                }
+              });
+            }
+         });
        }
       },
       model: pgBrowser.Node.Model.extend({
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-server-start-and-polling.diff (29.1K, 3-acceptance-tests-with-server-start-and-polling.diff)
  download | inline diff:
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 51170a45..de167121 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 f68db7a8..9565a6e4 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/test_connects_to_database.py b/web/pgadmin/acceptance/tests/test_connects_to_database.py
new file mode 100644
index 00000000..c35cd0fd
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/test_connects_to_database.py
@@ -0,0 +1,127 @@
+#############################################################
+#
+# 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
+
+
+class ConnectsToDatabase(BaseTestGenerator):
+    """
+    Tests that a database connection can be created from the UI
+    """
+
+    def setUp(self):
+        if 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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        self.driver = webdriver.Chrome()
+        self.server_config = self.server
+
+        print("opening browser")
+        self.driver.get("http://" + config.DEFAULT_SERVER + ":" + str(config.DEFAULT_SERVER_PORT))
+        self._wait_for_app()
+
+    def runTest(self):
+        self.assertEqual(config.APP_NAME, self.driver.title)
+        self._wait_for_spinner_to_disappear()
+
+        self._find_by_xpath("//*[@class='aciTreeText' and .='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_xpath("name", self.server_config['name'])
+        self._find_by_partial_link_text("Connection").click()
+        self._fill_input_by_xpath("host", self.server_config['host'])
+        self._fill_input_by_xpath("port", self.server_config['port'])
+        self._fill_input_by_xpath("username", self.server_config['username'])
+        self._fill_input_by_xpath("password", self.server_config['db_password'])
+        self._find_by_xpath("//button[contains(.,'Save')]").click()
+
+        self._find_by_xpath("//*[@id='tree']//*[.='" + self.server_config['name'] + "']")
+
+    def tearDown(self):
+        self.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
+
+    def _find_by_xpath(self, xpath):
+        return self._wait_for_element(lambda: self.driver.find_element_by_xpath(xpath))
+
+    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_xpath(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 _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 == 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)
+
+        self.fail("Timed out waiting for " + waiting_for_message)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
index f501d3d0..99fa00c7 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/__init__.py
@@ -1673,8 +1673,8 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
             return make_json_response(
                 success=1,
-                info=gettext("Trigger(s) have been enabled") if is_enable
-                else gettext("Trigger(s) have been disabled"),
+                info=gettext("Trigger(s) has been enabled") if is_enable
+                else gettext("Trigger(s) has been disabled"),
                 data={
                     'id': tid,
                     'scid': scid
@@ -1706,7 +1706,7 @@ class TableView(PGChildNodeView, DataTypeReader, VacuumSettings):
 
             return make_json_response(
                 success=1,
-                info=gettext("Table statistics have been reset"),
+                info=gettext("Table statistics has been reset"),
                 data={
                     'id': tid,
                     'scid': scid
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
index fe7d9349..6b7f52b0 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/__init__.py
@@ -816,7 +816,7 @@ class ExclusionConstraintView(PGChildNodeView):
             sql = render_template("/".join([self.template_path, 'create.sql']),
                                   data=data, conn=self.conn)
 
-        return sql, data['name']
+        return sql, data['name'] if 'name' in data else old_data['name']
 
     @check_precondition
     def sql(self, gid, sid, did, scid, tid, exid=None):
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
index e7bd905c..cfe15195 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/exclusion_constraint/templates/exclusion_constraint/js/exclusion_constraint.js
@@ -7,11 +7,10 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
     defaults: {
       column: undefined,
       oper_class: undefined,
-      order: false,
-      nulls_order: false,
+      order: undefined,
+      nulls_order: undefined,
       operator:undefined,
-      col_type:undefined,
-      is_sort_nulls_applicable: true
+      col_type:undefined
     },
     toJSON: function () {
       var d = pgBrowser.Node.Model.prototype.toJSON.apply(this, arguments);
@@ -24,9 +23,26 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
       },{
         id: 'oper_class', label:'{{ _('Operator class') }}', type:'text',
         node: 'table', url: 'get_oper_class', first_empty: true,
-        editable: true,
+        editable: function(m) {
+          if (m instanceof Backbone.Collection) {
+            return true;
+          }
+          if ((_.has(m.collection, 'handler') &&
+                !_.isUndefined(m.collection.handler) &&
+                !_.isUndefined(m.collection.handler.get('oid')))) {
+            return false;
+          }
+
+          if (m.collection) {
+            var indexType = m.collection.handler.get('amname')
+            return (indexType == 'btree' || _.isUndefined(indexType) ||
+              _.isNull(indexType) || indexType == '');
+          } else {
+            return true;
+          }
+        },
         select2: {
-          allowClear: true, width: 'style', tags: true,
+          allowClear: true, width: 'style',
           placeholder: '{{ _("Select the operator class") }}'
         }, cell: Backgrid.Extension.Select2Cell.extend({
           initialize: function () {
@@ -39,12 +55,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
 
             if (url && (indextype == 'btree' || _.isUndefined(indextype) ||
                 _.isNull(indextype) || indextype == '')) {
-              // Set sort_order and nulls to true if access method is btree
-              setTimeout(function() {
-                m.set('order', true);
-                m.set('nulls_order', true);
-              }, 10);
-
               var node = this.column.get('schema_node'),
                   eventHandler = m.top || m,
                   node_info = this.column.get('node_info'),
@@ -98,14 +108,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
           if (m instanceof Backbone.Collection) {
             return true;
           }
-          else {
-            if (m.top.get('amname') === 'btree') {
-              m.set('is_sort_nulls_applicable', true);
-              return true;
-            }
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
           if ((_.has(m.collection, 'handler') &&
                 !_.isUndefined(m.collection.handler) &&
                 !_.isUndefined(m.collection.handler.get('oid')))) {
@@ -122,15 +124,6 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
           if (m instanceof Backbone.Collection) {
             return true;
           }
-          else {
-            if (m.top.get('amname') === 'btree') {
-              m.set('is_sort_nulls_applicable', true);
-              return true;
-            }
-            m.set('is_sort_nulls_applicable', false);
-            return false;
-          }
-
           if ((_.has(m.collection, 'handler') &&
                 !_.isUndefined(m.collection.handler) &&
                 !_.isUndefined(m.collection.handler.get('oid')))) {
@@ -905,15 +898,8 @@ function($, _, S, pgAdmin, pgBrowser, Alertify) {
         }],
         validate: function() {
           this.errorModel.clear();
-          var columns = this.get('columns'),
-              name = this.get('name');
-
-          if ((_.isUndefined(name) || _.isNull(name) || name.length < 1)) {
-            var msg = '{{ _('Please specify name for exclusion constraint.') }}';
-            this.errorModel.set('name', msg);
-            return msg;
-          }
-          else  if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
+          var columns = this.get('columns');
+          if ((_.isUndefined(columns) || _.isNull(columns) || columns.length < 1)) {
             var msg = '{{ _('Please specify columns for exclusion constraint.') }}';
             this.errorModel.set('columns', msg);
             return msg;
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
index 6f462652..6531ba5e 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/indexes/templates/index/js/index.js
@@ -51,8 +51,7 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           collspcname: undefined,
           op_class: undefined,
           sort_order: false,
-          nulls: false,
-          is_sort_nulls_applicable: true
+          nulls: false
         },
         schema: [
           {
@@ -78,7 +77,7 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
             control: 'node-ajax-options', url: 'get_collations', node: 'index'
           },{
             id: 'op_class', label:'{{ _('Operator class') }}',
-            cell: NodeAjaxOptionsDepsCell, tags: true,
+            cell: NodeAjaxOptionsDepsCell,
             type: 'text', disabled: 'checkAccessMethod',
             editable: function(m) {
                 // Header cell then skip
@@ -109,19 +108,13 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           },{
             id: 'sort_order', label:'{{ _('Sort order') }}',
             cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
+            disabled: 'checkAccessMethod',
             editable: function(m) {
-              // Header cell then skip
-              if (m instanceof Backbone.Collection) {
-                  return false;
-              }
-              else {
-                if (m.top.get('amname') === 'btree') {
-                  m.set('is_sort_nulls_applicable', true);
-                  return true;
+                // Header cell then skip
+                if (m instanceof Backbone.Collection) {
+                    return false;
                 }
-                m.set('is_sort_nulls_applicable', false);
-                return false;
-              }
+                return !(m.checkAccessMethod.apply(this, arguments));
             },
             deps: ['amname'],
             options: {
@@ -132,18 +125,13 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
           },{
             id: 'nulls', label:'{{ _('NULLs') }}',
             cell: Backgrid.Extension.TableChildSwitchCell, type: 'switch',
+            disabled: 'checkAccessMethod',
             editable: function(m) {
-              // Header cell then skip
-              if (m instanceof Backbone.Collection) {
-                  return true;
-              } else {
-                  if (m.top.get('amname') === 'btree') {
-                    m.set('is_sort_nulls_applicable', true);
+                // Header cell then skip
+                if (m instanceof Backbone.Collection) {
                     return true;
-                  }
-                  m.set('is_sort_nulls_applicable', false);
-                  return false;
-              }
+                }
+                return !(m.checkAccessMethod.apply(this, arguments));
             },
             deps: ['amname', 'sort_order'],
             options: {
@@ -196,11 +184,9 @@ function($, _, S, pgAdmin, pgBrowser, Backform, alertify) {
             if(m.get('sort_order') == true && m.previous('sort_order') ==  false) {
                setTimeout(function() { m.set('nulls', true) }, 10);
             }
+            return false;
           }
-          else {
-            m.set('is_sort_nulls_applicable', false);
-          }
-          return false;
+          return true;
         },
     });
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.1_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.2_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
index 6d0bd1be..db29048b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/exclusion_constraint/sql/9.6_plus/create.sql
@@ -1,7 +1,7 @@
 ALTER TABLE {{ conn|qtIdent(data.schema, data.table) }}
     ADD{% if data.name %} CONSTRAINT {{ conn|qtIdent(data.name) }}{% endif%} EXCLUDE {% if data.amname and data.amname != '' %}USING {{data.amname}}{% endif %} (
     {% for col in data.columns %}{% if loop.index != 1 %},
-    {% endif %}{{ conn|qtIdent(col.column)}}{% if col.oper_class and col.oper_class != '' %} {{col.oper_class}}{% endif%}{% if col.order is defined and col.is_sort_nulls_applicable %}{% if col.order %} ASC{% else %} DESC{% endif %} NULLS{% endif %} {% if col.nulls_order is defined and col.is_sort_nulls_applicable %}{% if col.nulls_order %}FIRST {% else %}LAST {% endif %}{% endif %}WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
+    {% endif %}{{ conn|qtIdent(col.column)}} {% if col.oper_class and col.oper_class != '' %}{{col.oper_class}} {% endif%}{% if col.order %}ASC{% else %}DESC{% endif %} NULLS {% if col.nulls_order %}FIRST{% else %}LAST{% endif %} WITH {{col.operator}}{% endfor %}){% if data.fillfactor %}
     WITH (FILLFACTOR={{data.fillfactor}}){% endif %}{% if data.spcname and data.spcname != "pg_default" %}
 
     USING INDEX TABLESPACE {{ conn|qtIdent(data.spcname) }}{% endif %}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
index b7bfa527..33af1973 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/index/sql/9.1_plus/create.sql
@@ -3,7 +3,7 @@ CREATE {% if data.indisunique %}UNIQUE {% endif %}INDEX {% if data.isconcurrent
 
 {% if mode == 'create' %}
     ({% for c in data.columns %}{% if loop.index != 1 %}, {% endif %}{{conn|qtIdent(c.colname)}}{% if c.collspcname %} COLLATE {{c.collspcname}}{% endif %}{% if c.op_class %}
- {{c.op_class}}{% endif %}{% if data.amname is defined %}{% if c.sort_order is defined and c.is_sort_nulls_applicable %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined and c.is_sort_nulls_applicable %} NULLS {% if c.nulls %}
+ {{c.op_class}}{% endif %}{% if data.amname is defined and data.amname not in ['gist', 'gin'] %}{% if c.sort_order is defined %}{% if c.sort_order %} DESC{% else %} ASC{% endif %}{% endif %}{% if c.nulls is defined %} NULLS {% if c.nulls %}
 FIRST{% else %}LAST{% endif %}{% endif %}{% endif %}{% endfor %})
 {% else %}
 {## We will get indented data from postgres for column ##}
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
index c87a2930..2d006090 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/templates/table/js/table.js
@@ -198,54 +198,51 @@ function($, _, S, pgAdmin, pgBrowser, alertify) {
          });
        },
        reset_table_stats: function(args) {
-          var input = args || {},
-            obj = this,
-            t = pgBrowser.tree,
-            i = input.item || t.selected(),
-            d = i && i.length == 1 ? t.itemData(i) : undefined;
+          var input = args || {};
+          obj = this,
+          t = pgBrowser.tree,
+          i = input.item || t.selected(),
+          d = i && i.length == 1 ? t.itemData(i) : undefined;
 
           if (!d)
             return false;
 
           alertify.confirm(
-            '{{ _('Reset statistics') }}',
-            S('{{ _('Are you sure you want to reset the statistics for table %%s?') }}').sprintf(d._label).value(),
+            S('{{ _('Are you sure you want to reset table statistics for %s?') }}').sprintf(d.label).value(),
             function (e) {
-              if (e) {
-                var data = d;
-                $.ajax({
-                  url: obj.generate_url(i, 'reset' , d, true),
-                  type:'DELETE',
-                  success: function(res) {
-                    if (res.success == 1) {
-                      alertify.success("{{ _('" + res.info + "') }}");
-                      t.removeIcon(i);
-                      data.icon = 'icon-table';
-                      t.addIcon(i, {icon: data.icon});
-                      t.unload(i);
-                      t.setInode(i);
-                      t.deselect(i);
-                      // Fetch updated data from server
-                      setTimeout(function() {
-                        t.select(i);
-                      }, 10);
-                    }
-                  },
-                  error: function(xhr, status, error) {
-                    try {
-                      var err = $.parseJSON(xhr.responseText);
-                      if (err.success == 0) {
-                        msg = S('{{ _(' + err.errormsg + ')}}').value();
-                        alertify.error("{{ _('" + err.errormsg + "') }}");
-                      }
-                    } catch (e) {}
+            if (e) {
+              var data = d;
+              $.ajax({
+                url: obj.generate_url(i, 'reset' , d, true),
+                type:'DELETE',
+                success: function(res) {
+                  if (res.success == 1) {
+                    alertify.success("{{ _('" + res.info + "') }}");
+                    t.removeIcon(i);
+                    data.icon = 'icon-table';
+                    t.addIcon(i, {icon: data.icon});
                     t.unload(i);
+                    t.setInode(i);
+                    t.deselect(i);
+                    // Fetch updated data from server
+                    setTimeout(function() {
+                      t.select(i);
+                    }, 10);
                   }
-                });
-              }
-            },
-            function() {}
-          );
+                },
+                error: function(xhr, status, error) {
+                  try {
+                    var err = $.parseJSON(xhr.responseText);
+                    if (err.success == 0) {
+                      msg = S('{{ _(' + err.errormsg + ')}}').value();
+                      alertify.error("{{ _('" + err.errormsg + "') }}");
+                    }
+                  } catch (e) {}
+                  t.unload(i);
+                }
+              });
+            }
+         });
        }
       },
       model: pgBrowser.Node.Model.extend({
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-26 22:40           ` George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-26 22:40 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

instead of that patch, please use this no-zombies version that kills the
started process group instead of pid-only.

On Wed, Jan 25, 2017 at 6:31 PM, George Gelashvili <[email protected]>
wrote:

> ah
> That diff was generated before the python 3 patch was applied. This should
> work against master
>
> Cheers,
> George
>
> On Tue, Jan 24, 2017 at 4:43 AM, Dave Page <[email protected]> wrote:
>
>> On Fri, Jan 20, 2017 at 5:33 PM, George Gelashvili
>> <[email protected]> wrote:
>> > Thanks for bringing that to our attention! Here's the latest patch
>>
>> piranha:pgadmin4 dpage$ git apply
>> ~/Downloads/acceptance-tests-with-server-start-and-polling.diff
>> error: patch failed: web/regression/test_utils.py:69
>> error: web/regression/test_utils.py: patch does not apply
>>
>> :-(
>>
>> > On Fri, Jan 20, 2017 at 10:38 AM, Dave Page <[email protected]> wrote:
>> >>
>> >> On Thu, Jan 19, 2017 at 10:07 PM, George Gelashvili
>> >> <[email protected]> wrote:
>> >> >
>> >> > Here is an updated patch which starts the server up when the test
>> starts
>> >> > and
>> >> > uses the values from config.py for server name etc. It still requires
>> >> > installing chromedriver before running. Should we add something to
>> the
>> >> > readme about that?
>> >>
>> >> Yes, we definitely should (including download site URL)
>> >>
>> >> > On Tue, Jan 17, 2017 at 11:09 AM, Atira Odhner <[email protected]>
>> >> > wrote:
>> >> >>
>> >> >> Thanks for your feedback, Dave!
>> >> >>
>> >> >> We can put the tests under the regression directory. I think that
>> makes
>> >> >> sense.
>> >> >> I'm not picturing these tests being module specific, but we may
>> want to
>> >> >> enable running it as a separate suite of tests.
>> >> >>
>> >> >> Thanks for the callout about the port and title. We'll make sure
>> those
>> >> >> are
>> >> >> pulled from config or that the pgAdmin server is spun up by the test
>> >> >> with
>> >> >> specific values.
>> >> >>
>> >> >> I have a couple ideas about why the test might not have been running
>> >> >> for
>> >> >> you. I think the patch we attached didn't spin up its own pgAdmin
>> yet
>> >> >> and it
>> >> >> definitely doesn't fill in username/password if your app is running
>> >> >> that
>> >> >> way. That's part of the WIP-ness :-P
>> >> >>
>> >> >> -Tira
>> >> >>
>> >> >> Hi
>> >> >>
>> >> >> On Thu, Jan 12, 2017 at 10:41 PM, George Gelashvili
>> >> >> <ggelashvili(at)pivotal(dot)io> wrote:
>> >> >> > here's the patch we forgot to attach. Also, you can see work on
>> our
>> >> >> > branch
>> >> >> > at:
>> >> >> >
>> >> >> >
>> >> >> > https://github.com/pivotalsoftware/pgadmin4/tree/pivotal/
>> acceptance-tests
>> >> >> >
>> >> >> > On Thu, Jan 12, 2017 at 5:26 PM, George Gelashvili
>> >> >> > <ggelashvili(at)pivotal(dot)io>
>> >> >> > wrote:
>> >> >> >>
>> >> >> >> Hi there,
>> >> >> >>
>> >> >> >> We are working on browser-automation-based acceptance tests that
>> >> >> >> exercise
>> >> >> >> pgAdmin4 the way a user might.
>> >> >>
>> >> >> Nice!
>> >> >>
>> >> >> >> The first "connect to database" test works, but at the moment
>> >> >> >> depends
>> >> >> >> on
>> >> >> >> Chrome and chromedriver. We would appreciate feedback on any
>> >> >> >> possible
>> >> >> >> license or code style issues at this point, as well as any
>> thoughts
>> >> >> >> on
>> >> >> >> adding this sort of test to the codebase.
>> >> >>
>> >> >> A few thoughts:
>> >> >>
>> >> >> - If these tests are to run as part of the regression suite, the
>> >> >> framework for them should live under that directory.
>> >> >>
>> >> >> - Are any of the tests likely to be module-specific? If so, they
>> >> >> should really be part of the relevant module as the regression tests
>> >> >> are. If they're more general/less tightly coupled, then I don't see
>> a
>> >> >> problem with them residing where they are.
>> >> >>
>> >> >> - Please take care not to include changes to .gitgnore files that
>> >> >> aren't relevant to the rest of us.
>> >> >>
>> >> >> - The port number is hard-coded in the test.
>> >> >>
>> >> >> - You've hard-coded the string "pgAdmin 4". We've tried to keep that
>> >> >> title as a config option in config.py, so you should pull the string
>> >> >> from there rather than hard-coding it.
>> >> >>
>> >> >> - The connect test fails for me (Mac, Python 2.7). I have a
>> suspicion
>> >> >> that this may be because when the test starts chromedriver, OS X
>> >> >> prompts the user about whether a listening port should be opened,
>> but
>> >> >> the tests don't wait (though, I tested with 3 servers configured and
>> >> >> it failed with the same error on the second and third as well, long
>> >> >> after I clicked OK on the prompt):
>> >> >>
>> >> >> Traceback (most recent call last):
>> >> >>   File
>> >> >>
>> >> >> "/Users/dpage/git/pgadmin4/web/acceptance/test_connects_to_
>> database.py",
>> >> >> line 32, in runTest
>> >> >>     self.assertEqual("pgAdmin 4", self.driver.title)
>> >> >> AssertionError: 'pgAdmin 4' != u'localhost'
>> >> >>
>> >> >> - Please keep tests in the pgadmin. namespace
>> (pgadmin.acceptance.??).
>> >> >>
>> >> >> - It looks like running a single test won't work yet (because of
>> >> >> TestsGeneratorRegistry.load_generators('pgadmin.%s.tests' %
>> >> >> arguments['pkg']))
>> >> >>
>> >> >> Thanks!
>> >> >>
>> >> >> --
>> >> >> Dave Page
>> >> >> Blog: http://pgsnake.blogspot.com
>> >> >> Twitter: @pgsnake
>> >> >>
>> >> >> EnterpriseDB UK: http://www.enterprisedb.com
>> >> >> The Enterprise PostgreSQL Company
>> >> >>
>> >> >>
>> >> >
>> >> >
>> >> >
>> >> > --
>> >> > Sent via pgadmin-hackers mailing list ([email protected]
>> g)
>> >> > To make changes to your subscription:
>> >> > http://www.postgresql.org/mailpref/pgadmin-hackers
>> >> >
>> >>
>> >>
>> >>
>> >> --
>> >> Dave Page
>> >> Blog: http://pgsnake.blogspot.com
>> >> Twitter: @pgsnake
>> >>
>> >> EnterpriseDB UK: http://www.enterprisedb.com
>> >> The Enterprise PostgreSQL Company
>> >
>> >
>>
>>
>>
>> --
>> 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 51170a45..de167121 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 f68db7a8..9565a6e4 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_database_feature_test.py b/web/pgadmin/acceptance/tests/connect_to_database_feature_test.py
new file mode 100644
index 00000000..a3baa45c
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_database_feature_test.py
@@ -0,0 +1,78 @@
+#############################################################
+#
+# 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 ConnectsToDatabaseFeatureTest(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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        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()
+
+
+    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.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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
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/__init__.py b/web/regression/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..c093f046
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -0,0 +1,24 @@
+import os
+import subprocess
+
+import signal
+
+
+class AppStarter:
+
+    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..1c8857f2
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,103 @@
+import time
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver import ActionChains
+
+
+class PgadminPage:
+    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 .='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'] + "']").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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-test-no-zombies.diff (13.0K, 3-acceptance-test-no-zombies.diff)
  download | inline diff:
diff --git a/requirements_py2.txt b/requirements_py2.txt
index 51170a45..de167121 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 f68db7a8..9565a6e4 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_database_feature_test.py b/web/pgadmin/acceptance/tests/connect_to_database_feature_test.py
new file mode 100644
index 00000000..a3baa45c
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_database_feature_test.py
@@ -0,0 +1,78 @@
+#############################################################
+#
+# 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 ConnectsToDatabaseFeatureTest(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")
+
+        self.pgadmin_process = subprocess.Popen(["python", "pgAdmin4.py"], shell=False, preexec_fn=os.setsid,
+                                                stderr=open(os.devnull, 'w'))
+
+        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()
+
+
+    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.driver.close()
+        os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_test_screenshot.png')
+        return AssertionError(*args, **kwargs)
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index f18d2c18..fed26a0f 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -54,20 +54,25 @@ class TestsGeneratorRegistry(ABCMeta):
         ABCMeta.__init__(cls, name, bases, d)
 
     @classmethod
-    def load_generators(cls, pkg):
+    def load_generators(cls, *pkgs):
 
         cls.registry = dict()
 
+        all_modules = []
+
+        for pkg in pkgs:
+            all_modules += find_modules(pkg, False, True)
+
+
         # Check for SERVER mode
-        if config.SERVER_MODE:
-            for module_name in find_modules(pkg, False, True):
+        for module_name in all_modules:
+            if config.SERVER_MODE:
                 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):
+            else:
                 try:
                     # Exclude the test cases in browser node if SERVER_MODE
                     # is False
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..ae5d268d 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
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/__init__.py b/web/regression/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..c093f046
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -0,0 +1,24 @@
+import os
+import subprocess
+
+import signal
+
+
+class AppStarter:
+
+    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..1c8857f2
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,103 @@
+import time
+from selenium.common.exceptions import NoSuchElementException
+from selenium.webdriver import ActionChains
+
+
+class PgadminPage:
+    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 .='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'] + "']").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)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-27 16:11             ` Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-27 16:11 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

On Thu, Jan 26, 2017 at 10:40 PM, George Gelashvili
<[email protected]> 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.

Thanks!

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-27 16:28               ` Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-27 16:28 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

On Fri, Jan 27, 2017 at 4:11 PM, Dave Page <[email protected]> wrote:
> On Thu, Jan 26, 2017 at 10:40 PM, George Gelashvili
> <[email protected]> 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


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-30 19:28                 ` George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-30 19:28 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

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 <[email protected]> wrote:

> On Fri, Jan 27, 2017 at 4:11 PM, Dave Page <[email protected]> wrote:
> > On Thu, Jan 26, 2017 at 10:40 PM, George Gelashvili
> > <[email protected]> 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..47d077d5
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,72 @@
+#############################################################
+#
+# 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.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..807dff5a
--- /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)
+        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")
+        self.app_starter.stop_app()
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..dce257c5
--- /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 .='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'] + "']").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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-exclude.diff (18.3K, 3-acceptance-tests-with-exclude.diff)
  download | inline diff:
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..47d077d5
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,72 @@
+#############################################################
+#
+# 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.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..807dff5a
--- /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)
+        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")
+        self.app_starter.stop_app()
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..dce257c5
--- /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 .='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'] + "']").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)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-30 21:23                   ` Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-01-30 21:23 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Dave Page <[email protected]>; pgadmin-hackers

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 <[email protected]>
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 <[email protected]> wrote:
>
>> On Fri, Jan 27, 2017 at 4:11 PM, Dave Page <[email protected]> wrote:
>> > On Thu, Jan 26, 2017 at 10:40 PM, George Gelashvili
>> > <[email protected]> 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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-with-exclude-and-connection-cleanup.diff (18.4K, 3-acceptance-tests-with-exclude-and-connection-cleanup.diff)
  download | inline diff:
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)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-01-31 14:41                     ` Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-31 14:41 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi

On Mon, Jan 30, 2017 at 9:23 PM, Atira Odhner <[email protected]> wrote:
> Here's the patch with one more fix -- cleaning up the connections that get
> created in pgAdmin.

Hmm, I had trouble with this one. I noticed a few issues:

- The tests started pgAdmin listening on the default port (5050),
however, I already had an instance running on there;
    a) It should have detected that something else was running on the port
    b) Shouldn't we just use a random, unused port?

- Errors were given because I already had an acceptance_test_db on a
number of servers, and that contained the test table. Obviously the
code now cleans up after itself, but I think we should use a random
database name as the main regression tests do (they append a random
number to the name iirc).

- Some of the tests just seemed to time out. I *think* this might be
because the test browser window opens quite narrowly, and it looks
like the tests are probably trying to do things with nodes that aren't
actually visible.

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
line 69, in tearDown
    self.app_starter.stop_app()
  File "/Users/dpage/git/pgadmin4/web/regression/utils/app_starter.py",
line 27, in stop_app
    os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
OSError: [Errno 3] No such process

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
line 37, in runTest
    self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText'
and .='Trigger Functions']").click()
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 45, in find_by_xpath
    return self.wait_for_element(lambda:
self.driver.find_element_by_xpath(xpath))
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 72, in wait_for_element
    return self._wait_for("element to exist", element_if_it_exists)
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 106, in _wait_for
    raise RuntimeError("timed out waiting for " + waiting_for_message)
RuntimeError: timed out waiting for element to exist

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
line 60, in tearDown
    self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 45, in find_by_xpath
    return self.wait_for_element(lambda:
self.driver.find_element_by_xpath(xpath))
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 72, in wait_for_element
    return self._wait_for("element to exist", element_if_it_exists)
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 106, in _wait_for
    raise RuntimeError("timed out waiting for " + waiting_for_message)
RuntimeError: timed out waiting for element to exist

----------------------------------------------------------------------

Thanks.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-31 14:54                       ` George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: George Gelashvili @ 2017-01-31 14:54 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

Hi Dave,

We agree that a random port would be a nice addition. We think having
randomized test database names can lead to polluting with lots of extra
databases left around in the event that cleanup fails for whatever reason
(e.g. a test errors out).  We see this happen already with the randomized
test databases you mention. We agree that there should probably be one
strategy across the test suite. We could use randomized names and have a
more general cleanup step that removes all databases of the form "test_...".

Dave, are those errors you saw when you shut down your application on :5050
and did a fresh run of the tests? If not, could you please do a clean run?
It's possible that the second error could be related to viewport size as
you suggested, but the first error just looks like a problem with the test
not being able to spin up its own server.

Thanks,
George & Tira

On Tue, Jan 31, 2017 at 9:41 AM, Dave Page <[email protected]> wrote:

> Hi
>
> On Mon, Jan 30, 2017 at 9:23 PM, Atira Odhner <[email protected]> wrote:
> > Here's the patch with one more fix -- cleaning up the connections that
> get
> > created in pgAdmin.
>
> Hmm, I had trouble with this one. I noticed a few issues:
>
> - The tests started pgAdmin listening on the default port (5050),
> however, I already had an instance running on there;
>     a) It should have detected that something else was running on the port
>     b) Shouldn't we just use a random, unused port?
>
> - Errors were given because I already had an acceptance_test_db on a
> number of servers, and that contained the test table. Obviously the
> code now cleans up after itself, but I think we should use a random
> database name as the main regression tests do (they append a random
> number to the name iirc).
>
> - Some of the tests just seemed to time out. I *think* this might be
> because the test browser window opens quite narrowly, and it looks
> like the tests are probably trying to do things with nodes that aren't
> actually visible.
>
> ======================================================================
> ERROR: runTest (pgadmin.acceptance.tests.connect_to_server_feature_test.
> ConnectsToServerFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> connect_to_server_feature_test.py",
> line 69, in tearDown
>     self.app_starter.stop_app()
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/app_starter.py",
> line 27, in stop_app
>     os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
> OSError: [Errno 3] No such process
>
> ======================================================================
> ERROR: runTest (pgadmin.acceptance.tests.sql_template_selection_by_
> postgres_version_works_feature_test.SQLTemplateSelectionByPostgres
> VersionWorksFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> sql_template_selection_by_postgres_version_works_feature_test.py",
> line 37, in runTest
>     self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText'
> and .='Trigger Functions']").click()
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 45, in find_by_xpath
>     return self.wait_for_element(lambda:
> self.driver.find_element_by_xpath(xpath))
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 72, in wait_for_element
>     return self._wait_for("element to exist", element_if_it_exists)
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 106, in _wait_for
>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> RuntimeError: timed out waiting for element to exist
>
> ======================================================================
> ERROR: runTest (pgadmin.acceptance.tests.sql_template_selection_by_
> postgres_version_works_feature_test.SQLTemplateSelectionByPostgres
> VersionWorksFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> sql_template_selection_by_postgres_version_works_feature_test.py",
> line 60, in tearDown
>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 45, in find_by_xpath
>     return self.wait_for_element(lambda:
> self.driver.find_element_by_xpath(xpath))
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 72, in wait_for_element
>     return self._wait_for("element to exist", element_if_it_exists)
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 106, in _wait_for
>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> RuntimeError: timed out waiting for element to exist
>
> ----------------------------------------------------------------------
>
> Thanks.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
@ 2017-01-31 15:10                         ` Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-31 15:10 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

Hi

On Tue, Jan 31, 2017 at 2:54 PM, George Gelashvili
<[email protected]> wrote:
> Hi Dave,
>
> We agree that a random port would be a nice addition. We think having
> randomized test database names can lead to polluting with lots of extra
> databases left around in the event that cleanup fails for whatever reason
> (e.g. a test errors out).  We see this happen already with the randomized
> test databases you mention. We agree that there should probably be one
> strategy across the test suite. We could use randomized names and have a
> more general cleanup step that removes all databases of the form "test_...".

I'm very wary about doing things like that. We had an early version of
the suite that managed to delete all databases :-/. Maybe we could use
a patterned name, but only delete databases that also have a comment
with some text in it that we can verify?

> Dave, are those errors you saw when you shut down your application on :5050
> and did a fresh run of the tests? If not, could you please do a clean run?
> It's possible that the second error could be related to viewport size as you
> suggested, but the first error just looks like a problem with the test not
> being able to spin up its own server.

That was on a second run of the tests, yes. I just did a careful
cleanup of left-over test databases, double-checked my server wasn't
running and re-ran the tests - I got the same results.

>
> Thanks,
> George & Tira
>
> On Tue, Jan 31, 2017 at 9:41 AM, Dave Page <[email protected]> wrote:
>>
>> Hi
>>
>> On Mon, Jan 30, 2017 at 9:23 PM, Atira Odhner <[email protected]> wrote:
>> > Here's the patch with one more fix -- cleaning up the connections that
>> > get
>> > created in pgAdmin.
>>
>> Hmm, I had trouble with this one. I noticed a few issues:
>>
>> - The tests started pgAdmin listening on the default port (5050),
>> however, I already had an instance running on there;
>>     a) It should have detected that something else was running on the port
>>     b) Shouldn't we just use a random, unused port?
>>
>> - Errors were given because I already had an acceptance_test_db on a
>> number of servers, and that contained the test table. Obviously the
>> code now cleans up after itself, but I think we should use a random
>> database name as the main regression tests do (they append a random
>> number to the name iirc).
>>
>> - Some of the tests just seemed to time out. I *think* this might be
>> because the test browser window opens quite narrowly, and it looks
>> like the tests are probably trying to do things with nodes that aren't
>> actually visible.
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
>> line 69, in tearDown
>>     self.app_starter.stop_app()
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/app_starter.py",
>> line 27, in stop_app
>>     os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
>> OSError: [Errno 3] No such process
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
>> line 37, in runTest
>>     self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText'
>> and .='Trigger Functions']").click()
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 45, in find_by_xpath
>>     return self.wait_for_element(lambda:
>> self.driver.find_element_by_xpath(xpath))
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 72, in wait_for_element
>>     return self._wait_for("element to exist", element_if_it_exists)
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 106, in _wait_for
>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
>> RuntimeError: timed out waiting for element to exist
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
>> line 60, in tearDown
>>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 45, in find_by_xpath
>>     return self.wait_for_element(lambda:
>> self.driver.find_element_by_xpath(xpath))
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 72, in wait_for_element
>>     return self._wait_for("element to exist", element_if_it_exists)
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 106, in _wait_for
>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
>> RuntimeError: timed out waiting for element to exist
>>
>> ----------------------------------------------------------------------
>>
>> Thanks.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-01-31 16:25                           ` Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-01-31 16:25 UTC (permalink / raw)
  To: George Gelashvili <[email protected]>; +Cc: Atira Odhner <[email protected]>; pgadmin-hackers

Hi George,

I just tried to do some debugging of pgAdmin, and found that I
couldn't start it. On further investigation, I found that I had an
instance running in the background on my system. I'm assuming this was
started by the acceptance tests, but not shutdown. I killed it off,
and re-ran the tests only to see failures because the database and
table used in the acceptance tests were still present. When the tests
completed, pgAdmin was again left running in the background.

I've just re-run the tests, having first killed the backgrounded
pgAdmin and then manually cleaned up the test objects. This time I do
indeed only get the two errors below when it tests the first of 3
servers I have configured. The second and third servers get three
errors each, and pgAdmin is left running in the background again.

So, you were right that I had another instance of pgAdmin running...
but it was tests that caused it :-p



On Tue, Jan 31, 2017 at 3:10 PM, Dave Page <[email protected]> wrote:
> Hi
>
> On Tue, Jan 31, 2017 at 2:54 PM, George Gelashvili
> <[email protected]> wrote:
>> Hi Dave,
>>
>> We agree that a random port would be a nice addition. We think having
>> randomized test database names can lead to polluting with lots of extra
>> databases left around in the event that cleanup fails for whatever reason
>> (e.g. a test errors out).  We see this happen already with the randomized
>> test databases you mention. We agree that there should probably be one
>> strategy across the test suite. We could use randomized names and have a
>> more general cleanup step that removes all databases of the form "test_...".
>
> I'm very wary about doing things like that. We had an early version of
> the suite that managed to delete all databases :-/. Maybe we could use
> a patterned name, but only delete databases that also have a comment
> with some text in it that we can verify?
>
>> Dave, are those errors you saw when you shut down your application on :5050
>> and did a fresh run of the tests? If not, could you please do a clean run?
>> It's possible that the second error could be related to viewport size as you
>> suggested, but the first error just looks like a problem with the test not
>> being able to spin up its own server.
>
> That was on a second run of the tests, yes. I just did a careful
> cleanup of left-over test databases, double-checked my server wasn't
> running and re-ran the tests - I got the same results.
>
>>
>> Thanks,
>> George & Tira
>>
>> On Tue, Jan 31, 2017 at 9:41 AM, Dave Page <[email protected]> wrote:
>>>
>>> Hi
>>>
>>> On Mon, Jan 30, 2017 at 9:23 PM, Atira Odhner <[email protected]> wrote:
>>> > Here's the patch with one more fix -- cleaning up the connections that
>>> > get
>>> > created in pgAdmin.
>>>
>>> Hmm, I had trouble with this one. I noticed a few issues:
>>>
>>> - The tests started pgAdmin listening on the default port (5050),
>>> however, I already had an instance running on there;
>>>     a) It should have detected that something else was running on the port
>>>     b) Shouldn't we just use a random, unused port?
>>>
>>> - Errors were given because I already had an acceptance_test_db on a
>>> number of servers, and that contained the test table. Obviously the
>>> code now cleans up after itself, but I think we should use a random
>>> database name as the main regression tests do (they append a random
>>> number to the name iirc).
>>>
>>> - Some of the tests just seemed to time out. I *think* this might be
>>> because the test browser window opens quite narrowly, and it looks
>>> like the tests are probably trying to do things with nodes that aren't
>>> actually visible.
>>>
>>> ======================================================================
>>> ERROR: runTest
>>> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
>>> ----------------------------------------------------------------------
>>> Traceback (most recent call last):
>>>   File
>>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
>>> line 69, in tearDown
>>>     self.app_starter.stop_app()
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/app_starter.py",
>>> line 27, in stop_app
>>>     os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
>>> OSError: [Errno 3] No such process
>>>
>>> ======================================================================
>>> ERROR: runTest
>>> (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
>>> ----------------------------------------------------------------------
>>> Traceback (most recent call last):
>>>   File
>>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
>>> line 37, in runTest
>>>     self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText'
>>> and .='Trigger Functions']").click()
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 45, in find_by_xpath
>>>     return self.wait_for_element(lambda:
>>> self.driver.find_element_by_xpath(xpath))
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 72, in wait_for_element
>>>     return self._wait_for("element to exist", element_if_it_exists)
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 106, in _wait_for
>>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
>>> RuntimeError: timed out waiting for element to exist
>>>
>>> ======================================================================
>>> ERROR: runTest
>>> (pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest)
>>> ----------------------------------------------------------------------
>>> Traceback (most recent call last):
>>>   File
>>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py",
>>> line 60, in tearDown
>>>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 45, in find_by_xpath
>>>     return self.wait_for_element(lambda:
>>> self.driver.find_element_by_xpath(xpath))
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 72, in wait_for_element
>>>     return self._wait_for("element to exist", element_if_it_exists)
>>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>>> line 106, in _wait_for
>>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
>>> RuntimeError: timed out waiting for element to exist
>>>
>>> ----------------------------------------------------------------------
>>>
>>> Thanks.
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>
>>
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-03 21:56                             ` Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-03 21:56 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi Dave,

Here is a new patch which includes the following:
- randomized ports
- delete the acceptance_test_db database in setup in case a prior run failed
- fixed size browser window

Cheers,
Tira & George

On Tue, Jan 31, 2017 at 11:25 AM, Dave Page <[email protected]> wrote:

> Hi George,
>
> I just tried to do some debugging of pgAdmin, and found that I
> couldn't start it. On further investigation, I found that I had an
> instance running in the background on my system. I'm assuming this was
> started by the acceptance tests, but not shutdown. I killed it off,
> and re-ran the tests only to see failures because the database and
> table used in the acceptance tests were still present. When the tests
> completed, pgAdmin was again left running in the background.
>
> I've just re-run the tests, having first killed the backgrounded
> pgAdmin and then manually cleaned up the test objects. This time I do
> indeed only get the two errors below when it tests the first of 3
> servers I have configured. The second and third servers get three
> errors each, and pgAdmin is left running in the background again.
>
> So, you were right that I had another instance of pgAdmin running...
> but it was tests that caused it :-p
>
>
>
> On Tue, Jan 31, 2017 at 3:10 PM, Dave Page <[email protected]> wrote:
> > Hi
> >
> > On Tue, Jan 31, 2017 at 2:54 PM, George Gelashvili
> > <[email protected]> wrote:
> >> Hi Dave,
> >>
> >> We agree that a random port would be a nice addition. We think having
> >> randomized test database names can lead to polluting with lots of extra
> >> databases left around in the event that cleanup fails for whatever
> reason
> >> (e.g. a test errors out).  We see this happen already with the
> randomized
> >> test databases you mention. We agree that there should probably be one
> >> strategy across the test suite. We could use randomized names and have a
> >> more general cleanup step that removes all databases of the form
> "test_...".
> >
> > I'm very wary about doing things like that. We had an early version of
> > the suite that managed to delete all databases :-/. Maybe we could use
> > a patterned name, but only delete databases that also have a comment
> > with some text in it that we can verify?
> >
> >> Dave, are those errors you saw when you shut down your application on
> :5050
> >> and did a fresh run of the tests? If not, could you please do a clean
> run?
> >> It's possible that the second error could be related to viewport size
> as you
> >> suggested, but the first error just looks like a problem with the test
> not
> >> being able to spin up its own server.
> >
> > That was on a second run of the tests, yes. I just did a careful
> > cleanup of left-over test databases, double-checked my server wasn't
> > running and re-ran the tests - I got the same results.
> >
> >>
> >> Thanks,
> >> George & Tira
> >>
> >> On Tue, Jan 31, 2017 at 9:41 AM, Dave Page <[email protected]> wrote:
> >>>
> >>> Hi
> >>>
> >>> On Mon, Jan 30, 2017 at 9:23 PM, Atira Odhner <[email protected]>
> wrote:
> >>> > Here's the patch with one more fix -- cleaning up the connections
> that
> >>> > get
> >>> > created in pgAdmin.
> >>>
> >>> Hmm, I had trouble with this one. I noticed a few issues:
> >>>
> >>> - The tests started pgAdmin listening on the default port (5050),
> >>> however, I already had an instance running on there;
> >>>     a) It should have detected that something else was running on the
> port
> >>>     b) Shouldn't we just use a random, unused port?
> >>>
> >>> - Errors were given because I already had an acceptance_test_db on a
> >>> number of servers, and that contained the test table. Obviously the
> >>> code now cleans up after itself, but I think we should use a random
> >>> database name as the main regression tests do (they append a random
> >>> number to the name iirc).
> >>>
> >>> - Some of the tests just seemed to time out. I *think* this might be
> >>> because the test browser window opens quite narrowly, and it looks
> >>> like the tests are probably trying to do things with nodes that aren't
> >>> actually visible.
> >>>
> >>> ======================================================================
> >>> ERROR: runTest
> >>> (pgadmin.acceptance.tests.connect_to_server_feature_test.
> ConnectsToServerFeatureTest)
> >>> ----------------------------------------------------------------------
> >>> Traceback (most recent call last):
> >>>   File
> >>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> connect_to_server_feature_test.py",
> >>> line 69, in tearDown
> >>>     self.app_starter.stop_app()
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/app_
> starter.py",
> >>> line 27, in stop_app
> >>>     os.killpg(os.getpgid(self.pgadmin_process.pid), signal.SIGTERM)
> >>> OSError: [Errno 3] No such process
> >>>
> >>> ======================================================================
> >>> ERROR: runTest
> >>> (pgadmin.acceptance.tests.sql_template_selection_by_
> postgres_version_works_feature_test.SQLTemplateSelectionByPostgres
> VersionWorksFeatureTest)
> >>> ----------------------------------------------------------------------
> >>> Traceback (most recent call last):
> >>>   File
> >>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> sql_template_selection_by_postgres_version_works_feature_test.py",
> >>> line 37, in runTest
> >>>     self.page.find_by_xpath("//*[@id='tree']//*[@class='aciTreeText'
> >>> and .='Trigger Functions']").click()
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 45, in find_by_xpath
> >>>     return self.wait_for_element(lambda:
> >>> self.driver.find_element_by_xpath(xpath))
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 72, in wait_for_element
> >>>     return self._wait_for("element to exist", element_if_it_exists)
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 106, in _wait_for
> >>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> >>> RuntimeError: timed out waiting for element to exist
> >>>
> >>> ======================================================================
> >>> ERROR: runTest
> >>> (pgadmin.acceptance.tests.sql_template_selection_by_
> postgres_version_works_feature_test.SQLTemplateSelectionByPostgres
> VersionWorksFeatureTest)
> >>> ----------------------------------------------------------------------
> >>> Traceback (most recent call last):
> >>>   File
> >>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/
> sql_template_selection_by_postgres_version_works_feature_test.py",
> >>> line 60, in tearDown
> >>>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 45, in find_by_xpath
> >>>     return self.wait_for_element(lambda:
> >>> self.driver.find_element_by_xpath(xpath))
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 72, in wait_for_element
> >>>     return self._wait_for("element to exist", element_if_it_exists)
> >>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_
> page.py",
> >>> line 106, in _wait_for
> >>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> >>> RuntimeError: timed out waiting for element to exist
> >>>
> >>> ----------------------------------------------------------------------
> >>>
> >>> Thanks.
> >>>
> >>> --
> >>> Dave Page
> >>> Blog: http://pgsnake.blogspot.com
> >>> Twitter: @pgsnake
> >>>
> >>> EnterpriseDB UK: http://www.enterprisedb.com
> >>> The Enterprise PostgreSQL Company
> >>
> >>
> >
> >
> >
> > --
> > Dave Page
> > Blog: http://pgsnake.blogspot.com
> > Twitter: @pgsnake
> >
> > EnterpriseDB UK: http://www.enterprisedb.com
> > The Enterprise PostgreSQL Company
>
>
>
> --
> 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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..b54696f6
--- /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_connect_to_server_test_failure.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..65b96eae
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py
@@ -0,0 +1,78 @@
+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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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 272e3802..7b1bf543 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -138,12 +138,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()
@@ -164,6 +172,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..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance_with_random_ports.diff (19.8K, 3-acceptance_with_random_ports.diff)
  download | inline diff:
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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..b54696f6
--- /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_connect_to_server_test_failure.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..65b96eae
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/sql_template_selection_by_postgres_version_works_feature_test.py
@@ -0,0 +1,78 @@
+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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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 272e3802..7b1bf543 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -138,12 +138,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()
@@ -164,6 +172,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..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
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)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-06 10:32                               ` Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-02-06 10:32 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi

On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]> wrote:
> Hi Dave,
>
> Here is a new patch which includes the following:
> - randomized ports
> - delete the acceptance_test_db database in setup in case a prior run failed
> - fixed size browser window

Definitely getting there :-). A couple of thoughts/questions:

- Now there are 2 tests in there, it's clear that both the Python
server and browser session are restarted for each test. Can this be
avoided? It'll really slow down test execution as more and more are
added.

- We've got a new monster name:
pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
(which on disk is
sql_template_select_by_postgres_version_works_feature_test.py). Names
like that really must be shortened to something more sane and
manageable.

- I'm a little confused by why the tests cannot be run in server mode.
The error says it's because the username/password is unknown -
however, both the pgAdmin and database server usernames and passwords
are in test_config.json.

Thanks!

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-06 14:54                                 ` Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-06 14:54 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

I agree that we should rename the test. We've renamed it to
"template_selection_feature_test".
Your other suggestions are captured in our backlog as future improvements.
We definitely can and should do those things but I think it would be
valuable to go ahead and get this suite in and give other devs a chance to
use and iterate on this work.

Thanks,

Tira & George

On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:

> Hi
>
> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]> wrote:
> > Hi Dave,
> >
> > Here is a new patch which includes the following:
> > - randomized ports
> > - delete the acceptance_test_db database in setup in case a prior run
> failed
> > - fixed size browser window
>
> Definitely getting there :-). A couple of thoughts/questions:
>
> - Now there are 2 tests in there, it's clear that both the Python
> server and browser session are restarted for each test. Can this be
> avoided? It'll really slow down test execution as more and more are
> added.
>
> - We've got a new monster name:
> pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_
> feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
> (which on disk is
> sql_template_select_by_postgres_version_works_feature_test.py). Names
> like that really must be shortened to something more sane and
> manageable.
>
> - I'm a little confused by why the tests cannot be run in server mode.
> The error says it's because the username/password is unknown -
> however, both the pgAdmin and database server usernames and passwords
> are in test_config.json.
>
> 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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..b54696f6
--- /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_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+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 TemplateSelectionFeatureTest(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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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 272e3802..7b1bf543 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -138,12 +138,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()
@@ -164,6 +172,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..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
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 ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance_test_with_randomized_ports_renamed.diff (19.7K, 3-acceptance_test_with_randomized_ports_renamed.diff)
  download | inline diff:
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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..b54696f6
--- /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_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+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 TemplateSelectionFeatureTest(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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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 272e3802..7b1bf543 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -138,12 +138,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()
@@ -164,6 +172,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..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
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)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-08 22:15                                   ` Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-08 22:15 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hey Dave,

We re-used one of the test helpers for the 'fix-greenplum-show-tables.diff'
patch, so here is an updated patch which does not include adding that test
helper in case you apply the show-tables patch first. Also, we saw some
strange test behavior yesterday where form fields weren't being filled in
correctly so we changed the way that input fields get filled to be more
reliable.

In short these need to be applied in this order:

> git apply fix-greenplum-show-tables.diff

git apply acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff


We also moved the --exclude flag changes out to a separate patch.

On our side we are still dealing with these as 20 separate commits. What is
the best way for us to send you these patches? Do you prefer having them
all squashed down to a single patch or to have smaller patches?



On Mon, Feb 6, 2017 at 9:54 AM, Atira Odhner <[email protected]> wrote:

> I agree that we should rename the test. We've renamed it to
> "template_selection_feature_test".
> Your other suggestions are captured in our backlog as future improvements.
> We definitely can and should do those things but I think it would be
> valuable to go ahead and get this suite in and give other devs a chance to
> use and iterate on this work.
>
> Thanks,
>
> Tira & George
>
> On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:
>
>> Hi
>>
>> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]> wrote:
>> > Hi Dave,
>> >
>> > Here is a new patch which includes the following:
>> > - randomized ports
>> > - delete the acceptance_test_db database in setup in case a prior run
>> failed
>> > - fixed size browser window
>>
>> Definitely getting there :-). A couple of thoughts/questions:
>>
>> - Now there are 2 tests in there, it's clear that both the Python
>> server and browser session are restarted for each test. Can this be
>> avoided? It'll really slow down test execution as more and more are
>> added.
>>
>> - We've got a new monster name:
>> pgadmin.acceptance.tests.sql_template_selection_by_postgres_
>> version_works_feature_test.SQLTemplateSelectionByPostgresVer
>> sionWorksFeatureTest
>> (which on disk is
>> sql_template_select_by_postgres_version_works_feature_test.py). Names
>> like that really must be shortened to something more sane and
>> manageable.
>>
>> - I'm a little confused by why the tests cannot be run in server mode.
>> The error says it's because the username/password is unknown -
>> however, both the pgAdmin and database server usernames and passwords
>> are in test_config.json.
>>
>> Thanks!
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>

commit f340178c3f2d779ea5368f48a36f47112e2fa316
Author: George Gelashvili and Tira Odhner <[email protected]>
Date:   Wed Feb 8 14:52:32 2017 -0500

    ACCEPTANCE TEST SQUASH
    
    Based on Work around input validation by sending backspace to clear

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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..c3bee4e6
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,92 @@
+#############################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##############################################################
+
+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 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)
+
+        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")
+        test_utils.create_database(self.server, "acceptance_test_db")
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+
+        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._connects_to_server()
+        self._tables_node_expandable()
+
+    def tearDown(self):
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
+
+    def _connects_to_server(self):
+        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()
+
+    def _tables_node_expandable(self):
+        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.toggle_open_tree_item('Tables')
+        self.page.toggle_open_tree_item('test_table')
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+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 TemplateSelectionFeatureTest(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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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..ae5d268d 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
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
diff --git a/web/regression/utils/pgadmin_page.py b/web/regression/utils/pgadmin_page.py
new file mode 100644
index 00000000..d6a5836c
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,120 @@
+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 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 RuntimeError("timed out waiting for " + waiting_for_message)


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [text/plain] acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff (17.1K, 3-acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff)
  download | inline diff:
commit f340178c3f2d779ea5368f48a36f47112e2fa316
Author: George Gelashvili and Tira Odhner <[email protected]>
Date:   Wed Feb 8 14:52:32 2017 -0500

    ACCEPTANCE TEST SQUASH
    
    Based on Work around input validation by sending backspace to clear

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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..c3bee4e6
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,92 @@
+#############################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##############################################################
+
+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 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)
+
+        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")
+        test_utils.create_database(self.server, "acceptance_test_db")
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+
+        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._connects_to_server()
+        self._tables_node_expandable()
+
+    def tearDown(self):
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
+
+    def _connects_to_server(self):
+        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()
+
+    def _tables_node_expandable(self):
+        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.toggle_open_tree_item('Tables')
+        self.page.toggle_open_tree_item('test_table')
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+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 TemplateSelectionFeatureTest(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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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..ae5d268d 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
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
diff --git a/web/regression/utils/pgadmin_page.py b/web/regression/utils/pgadmin_page.py
new file mode 100644
index 00000000..d6a5836c
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,120 @@
+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 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 RuntimeError("timed out waiting for " + waiting_for_message)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-09 12:47                                     ` Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-02-09 12:47 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi

I get the following crash when running with Python 3.4 or 3.5:

(pgadmin4-py34) piranha:pgadmin4 dpage$ python web/regression/runtests.py
pgAdmin 4 - Application Initialisation
======================================


The configuration database - '/Users/dpage/.pgadmin/test_pgadmin4.db'
does not exist.
Entering initial setup mode...
NOTE: Configuring authentication for DESKTOP mode.

The configuration database has been created at
/Users/dpage/.pgadmin/test_pgadmin4.db

=============Running the test cases for 'Regression - PG 9.4'=============
runTest (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
... Traceback (most recent call last):
  File "web/regression/runtests.py", line 276, in <module>
    verbosity=2).run(suite)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/runner.py",
line 168, in run
    test(result)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
line 84, in __call__
    return self.run(*args, **kwds)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
line 122, in run
    test(result)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
line 628, in __call__
    return self.run(*args, **kwds)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
line 588, in run
    self._feedErrorsToResult(result, outcome.errors)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
line 515, in _feedErrorsToResult
    if issubclass(exc_info[0], self.failureException):
TypeError: issubclass() arg 2 must be a class or tuple of classes

With Python 2.7, it initially opens Chrome with the URL "data:,"
(without the quotes), and then spits out:

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
line 41, in setUp
    test_utils.create_table(self.server, "acceptance_test_db", "test_table")
AttributeError: 'module' object has no attribute 'create_table'

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
line 36, in runTest
    test_utils.create_table(self.server, "acceptance_test_db", "test_table")
AttributeError: 'module' object has no attribute 'create_table'

======================================================================
ERROR: runTest (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
line 66, in tearDown
    self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 46, in find_by_xpath
    return self.wait_for_element(lambda:
self.driver.find_element_by_xpath(xpath))
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 86, in wait_for_element
    return self._wait_for("element to exist", element_if_it_exists)
  File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
line 120, in _wait_for
    raise RuntimeError("timed out waiting for " + waiting_for_message)
RuntimeError: timed out waiting for element to exist

----------------------------------------------------------------------
Ran 149 tests in 59.258s

FAILED (errors=3, skipped=12)


On Wed, Feb 8, 2017 at 10:15 PM, Atira Odhner <[email protected]> wrote:
> Hey Dave,
>
> We re-used one of the test helpers for the 'fix-greenplum-show-tables.diff'
> patch, so here is an updated patch which does not include adding that test
> helper in case you apply the show-tables patch first. Also, we saw some
> strange test behavior yesterday where form fields weren't being filled in
> correctly so we changed the way that input fields get filled to be more
> reliable.
>
> In short these need to be applied in this order:
>>
>> git apply fix-greenplum-show-tables.diff
>>
>> git apply
>> acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff
>
>
> We also moved the --exclude flag changes out to a separate patch.
>
> On our side we are still dealing with these as 20 separate commits. What is
> the best way for us to send you these patches? Do you prefer having them all
> squashed down to a single patch or to have smaller patches?
>
>
>
> On Mon, Feb 6, 2017 at 9:54 AM, Atira Odhner <[email protected]> wrote:
>>
>> I agree that we should rename the test. We've renamed it to
>> "template_selection_feature_test".
>> Your other suggestions are captured in our backlog as future improvements.
>> We definitely can and should do those things but I think it would be
>> valuable to go ahead and get this suite in and give other devs a chance to
>> use and iterate on this work.
>>
>> Thanks,
>>
>> Tira & George
>>
>> On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:
>>>
>>> Hi
>>>
>>> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]> wrote:
>>> > Hi Dave,
>>> >
>>> > Here is a new patch which includes the following:
>>> > - randomized ports
>>> > - delete the acceptance_test_db database in setup in case a prior run
>>> > failed
>>> > - fixed size browser window
>>>
>>> Definitely getting there :-). A couple of thoughts/questions:
>>>
>>> - Now there are 2 tests in there, it's clear that both the Python
>>> server and browser session are restarted for each test. Can this be
>>> avoided? It'll really slow down test execution as more and more are
>>> added.
>>>
>>> - We've got a new monster name:
>>>
>>> pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
>>> (which on disk is
>>> sql_template_select_by_postgres_version_works_feature_test.py). Names
>>> like that really must be shortened to something more sane and
>>> manageable.
>>>
>>> - I'm a little confused by why the tests cannot be run in server mode.
>>> The error says it's because the username/password is unknown -
>>> however, both the pgAdmin and database server usernames and passwords
>>> are in test_config.json.
>>>
>>> Thanks!
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EnterpriseDB UK: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>
>>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-09 13:15                                       ` Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-09 13:15 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

create_table is the change we pulled into the other patch which would need
to be applied first.

On Thu, Feb 9, 2017, 7:47 AM Dave Page <[email protected]> wrote:

> Hi
>
> I get the following crash when running with Python 3.4 or 3.5:
>
> (pgadmin4-py34) piranha:pgadmin4 dpage$ python web/regression/runtests.py
> pgAdmin 4 - Application Initialisation
> ======================================
>
>
> The configuration database - '/Users/dpage/.pgadmin/test_pgadmin4.db'
> does not exist.
> Entering initial setup mode...
> NOTE: Configuring authentication for DESKTOP mode.
>
> The configuration database has been created at
> /Users/dpage/.pgadmin/test_pgadmin4.db
>
> =============Running the test cases for 'Regression - PG 9.4'=============
> runTest
> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
> ... Traceback (most recent call last):
>   File "web/regression/runtests.py", line 276, in <module>
>     verbosity=2).run(suite)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/runner.py",
> line 168, in run
>     test(result)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
> line 84, in __call__
>     return self.run(*args, **kwds)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
> line 122, in run
>     test(result)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> line 628, in __call__
>     return self.run(*args, **kwds)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> line 588, in run
>     self._feedErrorsToResult(result, outcome.errors)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> line 515, in _feedErrorsToResult
>     if issubclass(exc_info[0], self.failureException):
> TypeError: issubclass() arg 2 must be a class or tuple of classes
>
> With Python 2.7, it initially opens Chrome with the URL "data:,"
> (without the quotes), and then spits out:
>
> ======================================================================
> ERROR: runTest
> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
> line 41, in setUp
>     test_utils.create_table(self.server, "acceptance_test_db",
> "test_table")
> AttributeError: 'module' object has no attribute 'create_table'
>
> ======================================================================
> ERROR: runTest
> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
> line 36, in runTest
>     test_utils.create_table(self.server, "acceptance_test_db",
> "test_table")
> AttributeError: 'module' object has no attribute 'create_table'
>
> ======================================================================
> ERROR: runTest
> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
> line 66, in tearDown
>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 46, in find_by_xpath
>     return self.wait_for_element(lambda:
> self.driver.find_element_by_xpath(xpath))
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 86, in wait_for_element
>     return self._wait_for("element to exist", element_if_it_exists)
>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> line 120, in _wait_for
>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> RuntimeError: timed out waiting for element to exist
>
> ----------------------------------------------------------------------
> Ran 149 tests in 59.258s
>
> FAILED (errors=3, skipped=12)
>
>
> On Wed, Feb 8, 2017 at 10:15 PM, Atira Odhner <[email protected]> wrote:
> > Hey Dave,
> >
> > We re-used one of the test helpers for the
> 'fix-greenplum-show-tables.diff'
> > patch, so here is an updated patch which does not include adding that
> test
> > helper in case you apply the show-tables patch first. Also, we saw some
> > strange test behavior yesterday where form fields weren't being filled in
> > correctly so we changed the way that input fields get filled to be more
> > reliable.
> >
> > In short these need to be applied in this order:
> >>
> >> git apply fix-greenplum-show-tables.diff
> >>
> >> git apply
> >> acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff
> >
> >
> > We also moved the --exclude flag changes out to a separate patch.
> >
> > On our side we are still dealing with these as 20 separate commits. What
> is
> > the best way for us to send you these patches? Do you prefer having them
> all
> > squashed down to a single patch or to have smaller patches?
> >
> >
> >
> > On Mon, Feb 6, 2017 at 9:54 AM, Atira Odhner <[email protected]> wrote:
> >>
> >> I agree that we should rename the test. We've renamed it to
> >> "template_selection_feature_test".
> >> Your other suggestions are captured in our backlog as future
> improvements.
> >> We definitely can and should do those things but I think it would be
> >> valuable to go ahead and get this suite in and give other devs a chance
> to
> >> use and iterate on this work.
> >>
> >> Thanks,
> >>
> >> Tira & George
> >>
> >> On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:
> >>>
> >>> Hi
> >>>
> >>> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]>
> wrote:
> >>> > Hi Dave,
> >>> >
> >>> > Here is a new patch which includes the following:
> >>> > - randomized ports
> >>> > - delete the acceptance_test_db database in setup in case a prior run
> >>> > failed
> >>> > - fixed size browser window
> >>>
> >>> Definitely getting there :-). A couple of thoughts/questions:
> >>>
> >>> - Now there are 2 tests in there, it's clear that both the Python
> >>> server and browser session are restarted for each test. Can this be
> >>> avoided? It'll really slow down test execution as more and more are
> >>> added.
> >>>
> >>> - We've got a new monster name:
> >>>
> >>>
> pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
> >>> (which on disk is
> >>> sql_template_select_by_postgres_version_works_feature_test.py). Names
> >>> like that really must be shortened to something more sane and
> >>> manageable.
> >>>
> >>> - I'm a little confused by why the tests cannot be run in server mode.
> >>> The error says it's because the username/password is unknown -
> >>> however, both the pgAdmin and database server usernames and passwords
> >>> are in test_config.json.
> >>>
> >>> Thanks!
> >>>
> >>> --
> >>> Dave Page
> >>> Blog: http://pgsnake.blogspot.com
> >>> Twitter: @pgsnake
> >>>
> >>> EnterpriseDB UK: http://www.enterprisedb.com
> >>> The Enterprise PostgreSQL Company
> >>
> >>
> >
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-09 13:26                                         ` Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-02-09 13:26 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

OK, well that one was sent back with feedback as well, so please
resubmit when the relevant updates have been made to either and
they've been retested. Given the amount of work you're doing at the
moment, it would be helpful if you could note when one patch is
dependent on another. It's hard to keep track when you're this
productive!

Thanks.

On Thu, Feb 9, 2017 at 1:15 PM, Atira Odhner <[email protected]> wrote:
> create_table is the change we pulled into the other patch which would need
> to be applied first.
>
>
> On Thu, Feb 9, 2017, 7:47 AM Dave Page <[email protected]> wrote:
>>
>> Hi
>>
>> I get the following crash when running with Python 3.4 or 3.5:
>>
>> (pgadmin4-py34) piranha:pgadmin4 dpage$ python web/regression/runtests.py
>> pgAdmin 4 - Application Initialisation
>> ======================================
>>
>>
>> The configuration database - '/Users/dpage/.pgadmin/test_pgadmin4.db'
>> does not exist.
>> Entering initial setup mode...
>> NOTE: Configuring authentication for DESKTOP mode.
>>
>> The configuration database has been created at
>> /Users/dpage/.pgadmin/test_pgadmin4.db
>>
>> =============Running the test cases for 'Regression - PG 9.4'=============
>> runTest
>> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
>> ... Traceback (most recent call last):
>>   File "web/regression/runtests.py", line 276, in <module>
>>     verbosity=2).run(suite)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/runner.py",
>> line 168, in run
>>     test(result)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
>> line 84, in __call__
>>     return self.run(*args, **kwds)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
>> line 122, in run
>>     test(result)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
>> line 628, in __call__
>>     return self.run(*args, **kwds)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
>> line 588, in run
>>     self._feedErrorsToResult(result, outcome.errors)
>>   File
>> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
>> line 515, in _feedErrorsToResult
>>     if issubclass(exc_info[0], self.failureException):
>> TypeError: issubclass() arg 2 must be a class or tuple of classes
>>
>> With Python 2.7, it initially opens Chrome with the URL "data:,"
>> (without the quotes), and then spits out:
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
>> line 41, in setUp
>>     test_utils.create_table(self.server, "acceptance_test_db",
>> "test_table")
>> AttributeError: 'module' object has no attribute 'create_table'
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
>> line 36, in runTest
>>     test_utils.create_table(self.server, "acceptance_test_db",
>> "test_table")
>> AttributeError: 'module' object has no attribute 'create_table'
>>
>> ======================================================================
>> ERROR: runTest
>> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
>> ----------------------------------------------------------------------
>> Traceback (most recent call last):
>>   File
>> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
>> line 66, in tearDown
>>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 46, in find_by_xpath
>>     return self.wait_for_element(lambda:
>> self.driver.find_element_by_xpath(xpath))
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 86, in wait_for_element
>>     return self._wait_for("element to exist", element_if_it_exists)
>>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
>> line 120, in _wait_for
>>     raise RuntimeError("timed out waiting for " + waiting_for_message)
>> RuntimeError: timed out waiting for element to exist
>>
>> ----------------------------------------------------------------------
>> Ran 149 tests in 59.258s
>>
>> FAILED (errors=3, skipped=12)
>>
>>
>> On Wed, Feb 8, 2017 at 10:15 PM, Atira Odhner <[email protected]> wrote:
>> > Hey Dave,
>> >
>> > We re-used one of the test helpers for the
>> > 'fix-greenplum-show-tables.diff'
>> > patch, so here is an updated patch which does not include adding that
>> > test
>> > helper in case you apply the show-tables patch first. Also, we saw some
>> > strange test behavior yesterday where form fields weren't being filled
>> > in
>> > correctly so we changed the way that input fields get filled to be more
>> > reliable.
>> >
>> > In short these need to be applied in this order:
>> >>
>> >> git apply fix-greenplum-show-tables.diff
>> >>
>> >> git apply
>> >> acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff
>> >
>> >
>> > We also moved the --exclude flag changes out to a separate patch.
>> >
>> > On our side we are still dealing with these as 20 separate commits. What
>> > is
>> > the best way for us to send you these patches? Do you prefer having them
>> > all
>> > squashed down to a single patch or to have smaller patches?
>> >
>> >
>> >
>> > On Mon, Feb 6, 2017 at 9:54 AM, Atira Odhner <[email protected]> wrote:
>> >>
>> >> I agree that we should rename the test. We've renamed it to
>> >> "template_selection_feature_test".
>> >> Your other suggestions are captured in our backlog as future
>> >> improvements.
>> >> We definitely can and should do those things but I think it would be
>> >> valuable to go ahead and get this suite in and give other devs a chance
>> >> to
>> >> use and iterate on this work.
>> >>
>> >> Thanks,
>> >>
>> >> Tira & George
>> >>
>> >> On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:
>> >>>
>> >>> Hi
>> >>>
>> >>> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]>
>> >>> wrote:
>> >>> > Hi Dave,
>> >>> >
>> >>> > Here is a new patch which includes the following:
>> >>> > - randomized ports
>> >>> > - delete the acceptance_test_db database in setup in case a prior
>> >>> > run
>> >>> > failed
>> >>> > - fixed size browser window
>> >>>
>> >>> Definitely getting there :-). A couple of thoughts/questions:
>> >>>
>> >>> - Now there are 2 tests in there, it's clear that both the Python
>> >>> server and browser session are restarted for each test. Can this be
>> >>> avoided? It'll really slow down test execution as more and more are
>> >>> added.
>> >>>
>> >>> - We've got a new monster name:
>> >>>
>> >>>
>> >>> pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
>> >>> (which on disk is
>> >>> sql_template_select_by_postgres_version_works_feature_test.py). Names
>> >>> like that really must be shortened to something more sane and
>> >>> manageable.
>> >>>
>> >>> - I'm a little confused by why the tests cannot be run in server mode.
>> >>> The error says it's because the username/password is unknown -
>> >>> however, both the pgAdmin and database server usernames and passwords
>> >>> are in test_config.json.
>> >>>
>> >>> Thanks!
>> >>>
>> >>> --
>> >>> Dave Page
>> >>> Blog: http://pgsnake.blogspot.com
>> >>> Twitter: @pgsnake
>> >>>
>> >>> EnterpriseDB UK: http://www.enterprisedb.com
>> >>> The Enterprise PostgreSQL Company
>> >>
>> >>
>> >
>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-09 14:20                                           ` Atira Odhner <[email protected]>
  2017-02-09 14:28                                             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-09 14:20 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Certainly.  We did mention the dependency in the email. Would it be better
to mention it in the patch name? Is there a better way for us to manage
these changes? On other open source projects, I've seen github mirrors set
up so that changes can be pulled in like branches rather then as patch
applies. That would have avoided this situation since the parent commit
would be pulled in with the same SHA from either pull request branch and
git would not see it as a conflict.

I'm rather new to dealing with patch files like this so I would love some
tips.

Thanks,
Tira

On Thu, Feb 9, 2017, 8:27 AM Dave Page <[email protected]> wrote:

> OK, well that one was sent back with feedback as well, so please
> resubmit when the relevant updates have been made to either and
> they've been retested. Given the amount of work you're doing at the
> moment, it would be helpful if you could note when one patch is
> dependent on another. It's hard to keep track when you're this
> productive!
>
> Thanks.
>
> On Thu, Feb 9, 2017 at 1:15 PM, Atira Odhner <[email protected]> wrote:
> > create_table is the change we pulled into the other patch which would
> need
> > to be applied first.
> >
> >
> > On Thu, Feb 9, 2017, 7:47 AM Dave Page <[email protected]> wrote:
> >>
> >> Hi
> >>
> >> I get the following crash when running with Python 3.4 or 3.5:
> >>
> >> (pgadmin4-py34) piranha:pgadmin4 dpage$ python
> web/regression/runtests.py
> >> pgAdmin 4 - Application Initialisation
> >> ======================================
> >>
> >>
> >> The configuration database - '/Users/dpage/.pgadmin/test_pgadmin4.db'
> >> does not exist.
> >> Entering initial setup mode...
> >> NOTE: Configuring authentication for DESKTOP mode.
> >>
> >> The configuration database has been created at
> >> /Users/dpage/.pgadmin/test_pgadmin4.db
> >>
> >> =============Running the test cases for 'Regression - PG
> 9.4'=============
> >> runTest
> >>
> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
> >> ... Traceback (most recent call last):
> >>   File "web/regression/runtests.py", line 276, in <module>
> >>     verbosity=2).run(suite)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/runner.py",
> >> line 168, in run
> >>     test(result)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
> >> line 84, in __call__
> >>     return self.run(*args, **kwds)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/suite.py",
> >> line 122, in run
> >>     test(result)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> >> line 628, in __call__
> >>     return self.run(*args, **kwds)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> >> line 588, in run
> >>     self._feedErrorsToResult(result, outcome.errors)
> >>   File
> >>
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/unittest/case.py",
> >> line 515, in _feedErrorsToResult
> >>     if issubclass(exc_info[0], self.failureException):
> >> TypeError: issubclass() arg 2 must be a class or tuple of classes
> >>
> >> With Python 2.7, it initially opens Chrome with the URL "data:,"
> >> (without the quotes), and then spits out:
> >>
> >> ======================================================================
> >> ERROR: runTest
> >>
> (pgadmin.acceptance.tests.connect_to_server_feature_test.ConnectsToServerFeatureTest)
> >> ----------------------------------------------------------------------
> >> Traceback (most recent call last):
> >>   File
> >>
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py",
> >> line 41, in setUp
> >>     test_utils.create_table(self.server, "acceptance_test_db",
> >> "test_table")
> >> AttributeError: 'module' object has no attribute 'create_table'
> >>
> >> ======================================================================
> >> ERROR: runTest
> >>
> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
> >> ----------------------------------------------------------------------
> >> Traceback (most recent call last):
> >>   File
> >>
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
> >> line 36, in runTest
> >>     test_utils.create_table(self.server, "acceptance_test_db",
> >> "test_table")
> >> AttributeError: 'module' object has no attribute 'create_table'
> >>
> >> ======================================================================
> >> ERROR: runTest
> >>
> (pgadmin.acceptance.tests.template_selection_feature_test.TemplateSelectionFeatureTest)
> >> ----------------------------------------------------------------------
> >> Traceback (most recent call last):
> >>   File
> >>
> "/Users/dpage/git/pgadmin4/web/pgadmin/acceptance/tests/template_selection_feature_test.py",
> >> line 66, in tearDown
> >>     self.page.find_by_xpath("//button[contains(.,'Cancel')]").click()
> >>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> >> line 46, in find_by_xpath
> >>     return self.wait_for_element(lambda:
> >> self.driver.find_element_by_xpath(xpath))
> >>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> >> line 86, in wait_for_element
> >>     return self._wait_for("element to exist", element_if_it_exists)
> >>   File "/Users/dpage/git/pgadmin4/web/regression/utils/pgadmin_page.py",
> >> line 120, in _wait_for
> >>     raise RuntimeError("timed out waiting for " + waiting_for_message)
> >> RuntimeError: timed out waiting for element to exist
> >>
> >> ----------------------------------------------------------------------
> >> Ran 149 tests in 59.258s
> >>
> >> FAILED (errors=3, skipped=12)
> >>
> >>
> >> On Wed, Feb 8, 2017 at 10:15 PM, Atira Odhner <[email protected]>
> wrote:
> >> > Hey Dave,
> >> >
> >> > We re-used one of the test helpers for the
> >> > 'fix-greenplum-show-tables.diff'
> >> > patch, so here is an updated patch which does not include adding that
> >> > test
> >> > helper in case you apply the show-tables patch first. Also, we saw
> some
> >> > strange test behavior yesterday where form fields weren't being filled
> >> > in
> >> > correctly so we changed the way that input fields get filled to be
> more
> >> > reliable.
> >> >
> >> > In short these need to be applied in this order:
> >> >>
> >> >> git apply fix-greenplum-show-tables.diff
> >> >>
> >> >> git apply
> >> >> acceptance-tests-minus-create-table-helper-with-fixed-inputs.diff
> >> >
> >> >
> >> > We also moved the --exclude flag changes out to a separate patch.
> >> >
> >> > On our side we are still dealing with these as 20 separate commits.
> What
> >> > is
> >> > the best way for us to send you these patches? Do you prefer having
> them
> >> > all
> >> > squashed down to a single patch or to have smaller patches?
> >> >
> >> >
> >> >
> >> > On Mon, Feb 6, 2017 at 9:54 AM, Atira Odhner <[email protected]>
> wrote:
> >> >>
> >> >> I agree that we should rename the test. We've renamed it to
> >> >> "template_selection_feature_test".
> >> >> Your other suggestions are captured in our backlog as future
> >> >> improvements.
> >> >> We definitely can and should do those things but I think it would be
> >> >> valuable to go ahead and get this suite in and give other devs a
> chance
> >> >> to
> >> >> use and iterate on this work.
> >> >>
> >> >> Thanks,
> >> >>
> >> >> Tira & George
> >> >>
> >> >> On Mon, Feb 6, 2017 at 5:32 AM, Dave Page <[email protected]> wrote:
> >> >>>
> >> >>> Hi
> >> >>>
> >> >>> On Fri, Feb 3, 2017 at 9:56 PM, Atira Odhner <[email protected]>
> >> >>> wrote:
> >> >>> > Hi Dave,
> >> >>> >
> >> >>> > Here is a new patch which includes the following:
> >> >>> > - randomized ports
> >> >>> > - delete the acceptance_test_db database in setup in case a prior
> >> >>> > run
> >> >>> > failed
> >> >>> > - fixed size browser window
> >> >>>
> >> >>> Definitely getting there :-). A couple of thoughts/questions:
> >> >>>
> >> >>> - Now there are 2 tests in there, it's clear that both the Python
> >> >>> server and browser session are restarted for each test. Can this be
> >> >>> avoided? It'll really slow down test execution as more and more are
> >> >>> added.
> >> >>>
> >> >>> - We've got a new monster name:
> >> >>>
> >> >>>
> >> >>>
> pgadmin.acceptance.tests.sql_template_selection_by_postgres_version_works_feature_test.SQLTemplateSelectionByPostgresVersionWorksFeatureTest
> >> >>> (which on disk is
> >> >>> sql_template_select_by_postgres_version_works_feature_test.py).
> Names
> >> >>> like that really must be shortened to something more sane and
> >> >>> manageable.
> >> >>>
> >> >>> - I'm a little confused by why the tests cannot be run in server
> mode.
> >> >>> The error says it's because the username/password is unknown -
> >> >>> however, both the pgAdmin and database server usernames and
> passwords
> >> >>> are in test_config.json.
> >> >>>
> >> >>> Thanks!
> >> >>>
> >> >>> --
> >> >>> Dave Page
> >> >>> Blog: http://pgsnake.blogspot.com
> >> >>> Twitter: @pgsnake
> >> >>>
> >> >>> EnterpriseDB UK: http://www.enterprisedb.com
> >> >>> The Enterprise PostgreSQL Company
> >> >>
> >> >>
> >> >
> >>
> >>
> >>
> >> --
> >> Dave Page
> >> Blog: http://pgsnake.blogspot.com
> >> Twitter: @pgsnake
> >>
> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> The Enterprise PostgreSQL Company
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-09 14:28                                             ` Dave Page <[email protected]>
  2017-02-09 16:17                                               ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-02-09 14:28 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi

On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]> wrote:
> Certainly.  We did mention the dependency in the email. Would it be better
> to mention it in the patch name?

I think the problem was that the way you phrased it, it sounded
optional ("an updated patch which does not include adding that test
helper in case you apply the show-tables patch first"). I think a
clear "This patch is dependent on patch Foo" would suffice.

> Is there a better way for us to manage
> these changes? On other open source projects, I've seen github mirrors set
> up so that changes can be pulled in like branches rather then as patch
> applies. That would have avoided this situation since the parent commit
> would be pulled in with the same SHA from either pull request branch and git
> would not see it as a conflict.
>
> I'm rather new to dealing with patch files like this so I would love some
> tips.

The Postgres project in general is quite conservative and stuck in
it's ways about how things are done (which is usually a good thing
considering you trust your data to the resulting code). We're used to
dealing with larger patchsets via the mailing list - typically as long
as you're clear about any dependencies, it shouldn't be a problem.
Some of us use tools like PyCharms for handling patches and helping
with reviews etc. which I guess replaces most, if not all of the
GitHub functionality over plain git.

-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 14:28                                             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-09 16:17                                               ` Atira Odhner <[email protected]>
  2017-02-13 14:36                                                 ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-09 16:17 UTC (permalink / raw)
  To: Dave Page <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi Dave,

 I think the problem was that the way you phrased it,


You're right, we totally messed that up. We were talking about making 3
patches and ended up making only 2 and forgot to reword that bit.
Sorry about that.

Here are the two patches for this change that resolves the AttributeError you
were seeing. The first patch is identical to the patch of the same name in
the other email thread.

We're used to
> dealing with larger patchsets via the mailing list - typically as long
> as you're clear about any dependencies, it shouldn't be a problem.
>

Great! We'll try sending patchsets from now on and hopefully that resolves
some of the issues we were seeing.

Tira & George

On Thu, Feb 9, 2017 at 9:28 AM, Dave Page <[email protected]> wrote:

> Hi
>
> On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]> wrote:
> > Certainly.  We did mention the dependency in the email. Would it be
> better
> > to mention it in the patch name?
>
> I think the problem was that the way you phrased it, it sounded
> optional ("an updated patch which does not include adding that test
> helper in case you apply the show-tables patch first"). I think a
> clear "This patch is dependent on patch Foo" would suffice.
>
> > Is there a better way for us to manage
> > these changes? On other open source projects, I've seen github mirrors
> set
> > up so that changes can be pulled in like branches rather then as patch
> > applies. That would have avoided this situation since the parent commit
> > would be pulled in with the same SHA from either pull request branch and
> git
> > would not see it as a conflict.
> >
> > I'm rather new to dealing with patch files like this so I would love some
> > tips.
>
> The Postgres project in general is quite conservative and stuck in
> it's ways about how things are done (which is usually a good thing
> considering you trust your data to the resulting code). We're used to
> dealing with larger patchsets via the mailing list - typically as long
> as you're clear about any dependencies, it shouldn't be a problem.
> Some of us use tools like PyCharms for handling patches and helping
> with reviews etc. which I guess replaces most, if not all of the
> GitHub functionality over plain git.
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] 0001-Add-create_table-to-test_utils.patch (1.6K, 3-0001-Add-create_table-to-test_utils.patch)
  download | inline diff:
From 75980f10b4cfe7cbabe62893a6ccf3ccc26949ff Mon Sep 17 00:00:00 2001
From: George Gelashvili and Tira Odhner <[email protected]>
Date: Wed, 8 Feb 2017 09:45:49 -0500
Subject: [PATCH 1/3] Add create_table to test_utils

---
 web/regression/test_utils.py | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index 1f9f0522..2dbf47bb 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -134,6 +134,25 @@ 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"]:
-- 
2.11.0



  [application/octet-stream] 0002-Acceptance-tests-with-input-fix.patch (17.1K, 4-0002-Acceptance-tests-with-input-fix.patch)
  download | inline diff:
commit f340178c3f2d779ea5368f48a36f47112e2fa316
Author: George Gelashvili and Tira Odhner <[email protected]>
Date:   Wed Feb 8 14:52:32 2017 -0500

    ACCEPTANCE TEST SQUASH
    
    Based on Work around input validation by sending backspace to clear

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/pgAdmin4.py b/web/pgAdmin4.py
index 68848c00..95e675b7 100644
--- a/web/pgAdmin4.py
+++ b/web/pgAdmin4.py
@@ -59,6 +59,12 @@ if 'PGADMIN_PORT' in globals():
                      globals()['PGADMIN_PORT'])
     server_port = int(globals()['PGADMIN_PORT'])
     PGADMIN_RUNTIME = True
+elif 'PGADMIN_PORT' in os.environ:
+    port = os.environ['PGADMIN_PORT']
+    app.logger.debug(
+        'Not running under the desktop runtime, port: %s',
+        port)
+    server_port = int(port)
 else:
     app.logger.debug(
         'Not running under the desktop runtime, port: %s',
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..c3bee4e6
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
@@ -0,0 +1,92 @@
+#############################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2017, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##############################################################
+
+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 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)
+
+        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")
+        test_utils.create_database(self.server, "acceptance_test_db")
+        test_utils.create_table(self.server, "acceptance_test_db", "test_table")
+
+        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._connects_to_server()
+        self._tables_node_expandable()
+
+    def tearDown(self):
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_connect_to_server_test_failure.png')
+        return AssertionError(*args, **kwargs)
+
+    def _connects_to_server(self):
+        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()
+
+    def _tables_node_expandable(self):
+        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.toggle_open_tree_item('Tables')
+        self.page.toggle_open_tree_item('test_table')
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
new file mode 100644
index 00000000..b7405d56
--- /dev/null
+++ b/web/pgadmin/acceptance/tests/template_selection_feature_test.py
@@ -0,0 +1,78 @@
+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 TemplateSelectionFeatureTest(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)
+
+        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")
+
+        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")
+
+    def failureException(self, *args, **kwargs):
+        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
+        return AssertionError(*args, **kwargs)
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..ae5d268d 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
diff --git a/web/regression/utils/app_starter.py b/web/regression/utils/app_starter.py
new file mode 100644
index 00000000..b297bd6d
--- /dev/null
+++ b/web/regression/utils/app_starter.py
@@ -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)
diff --git a/web/regression/utils/pgadmin_page.py b/web/regression/utils/pgadmin_page.py
new file mode 100644
index 00000000..d6a5836c
--- /dev/null
+++ b/web/regression/utils/pgadmin_page.py
@@ -0,0 +1,120 @@
+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 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 RuntimeError("timed out waiting for " + waiting_for_message)


^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 14:28                                             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 16:17                                               ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-13 14:36                                                 ` Dave Page <[email protected]>
  2017-02-21 22:12                                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Dave Page @ 2017-02-13 14:36 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: George Gelashvili <[email protected]>; pgadmin-hackers

Hi,

I've been playing with this for the last couple of hours, and I just
can't get it to work reliably;

- A good percentage of the time the browser opens with a URL of
"data:," and does nothing more. This appears to happen if tests fail,
which still leaves server processes running in the background.

- The connect_to_server test usually seems to work.

- The template_selection_feature test usually does *not* work. I can't
see an obvious reason, but I suspect it's a race condition. What seems
to happen is that the function definition is entered, but not
registered by the UI, so the mSQL panel just ends up saying
"incomplete definition". Manually checking what was input proves that
everything is correct - and indeed, returning the SQL tab shows the
expected SQL.

Other issues I noted:

- The template_selection_feature test should just enter BEGIN/END.
What it currently enters is an entire function definition, when only
the body content is expected. E.g.

        self.page.fill_codemirror_area_with(
"""BEGIN

END;
"""
        )

- Screenshots are being taken of failed tests:
  1) I've never actually seen any get saved
  2) They should be saved to the same directory as the test log, not /tmp
  3) They should have guaranteed unique names, and be mentioned in the
test output so the user can reference the image to the failure.

The reason the last two items are important is that I've now got a
test server running the test suite with every supported version of
Python, for every supported database (well, almost, pending a couple
of fixes). I have separate workspaces for each Python version, and a
single test run might run every test 10 times, once for each database
server.

- Please wrap the README at < 80 chars.



On Thu, Feb 9, 2017 at 4:17 PM, Atira Odhner <[email protected]> wrote:
> Hi Dave,
>
>>  I think the problem was that the way you phrased it,
>
>
> You're right, we totally messed that up. We were talking about making 3
> patches and ended up making only 2 and forgot to reword that bit.
> Sorry about that.
>
> Here are the two patches for this change that resolves the AttributeError
> you were seeing. The first patch is identical to the patch of the same name
> in the other email thread.
>
>> We're used to
>> dealing with larger patchsets via the mailing list - typically as long
>> as you're clear about any dependencies, it shouldn't be a problem.
>
>
> Great! We'll try sending patchsets from now on and hopefully that resolves
> some of the issues we were seeing.
>
> Tira & George
>
> On Thu, Feb 9, 2017 at 9:28 AM, Dave Page <[email protected]> wrote:
>>
>> Hi
>>
>> On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]> wrote:
>> > Certainly.  We did mention the dependency in the email. Would it be
>> > better
>> > to mention it in the patch name?
>>
>> I think the problem was that the way you phrased it, it sounded
>> optional ("an updated patch which does not include adding that test
>> helper in case you apply the show-tables patch first"). I think a
>> clear "This patch is dependent on patch Foo" would suffice.
>>
>> > Is there a better way for us to manage
>> > these changes? On other open source projects, I've seen github mirrors
>> > set
>> > up so that changes can be pulled in like branches rather then as patch
>> > applies. That would have avoided this situation since the parent commit
>> > would be pulled in with the same SHA from either pull request branch and
>> > git
>> > would not see it as a conflict.
>> >
>> > I'm rather new to dealing with patch files like this so I would love
>> > some
>> > tips.
>>
>> The Postgres project in general is quite conservative and stuck in
>> it's ways about how things are done (which is usually a good thing
>> considering you trust your data to the resulting code). We're used to
>> dealing with larger patchsets via the mailing list - typically as long
>> as you're clear about any dependencies, it shouldn't be a problem.
>> Some of us use tools like PyCharms for handling patches and helping
>> with reviews etc. which I guess replaces most, if not all of the
>> GitHub functionality over plain git.
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 14:28                                             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 16:17                                               ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-13 14:36                                                 ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
@ 2017-02-21 22:12                                                   ` Atira Odhner <[email protected]>
  2017-02-22 12:42                                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  0 siblings, 1 reply; 30+ messages in thread

From: Atira Odhner @ 2017-02-21 22:12 UTC (permalink / raw)
  To: Dave Page <[email protected]>; pgadmin-hackers; +Cc: Sarah McAlear <[email protected]>

Hi Dave,

We fixed the flakiness issues that we saw (hopefully they are the same ones
you were seeing.) by tearing down connections to the acceptance_test_db
before attempting to drop it at the beginning of the test. Once we have
access to the CI pipeline we can help out there to ensure the flakiness is
gone.

We wrapped the README at 80 characters, and removed the misleading function
definition from the test.

As far as the screenshots go, I'm more inclined to remove the
screenshotting than to work on improving it. It currently only works when
the failure is due to an AssertionError since that's what failureException
relies on.

We also renamed acceptance to feature_tests since 'acceptance' seemed
ambiguous/redundant with 'regression'.

Tira & Sara


On Mon, Feb 13, 2017 at 9:36 AM, Dave Page <[email protected]> wrote:

> Hi,
>
> I've been playing with this for the last couple of hours, and I just
> can't get it to work reliably;
>
> - A good percentage of the time the browser opens with a URL of
> "data:," and does nothing more. This appears to happen if tests fail,
> which still leaves server processes running in the background.
>
> - The connect_to_server test usually seems to work.
>
> - The template_selection_feature test usually does *not* work. I can't
> see an obvious reason, but I suspect it's a race condition. What seems
> to happen is that the function definition is entered, but not
> registered by the UI, so the mSQL panel just ends up saying
> "incomplete definition". Manually checking what was input proves that
> everything is correct - and indeed, returning the SQL tab shows the
> expected SQL.
>
> Other issues I noted:
>
> - The template_selection_feature test should just enter BEGIN/END.
> What it currently enters is an entire function definition, when only
> the body content is expected. E.g.
>
>         self.page.fill_codemirror_area_with(
> """BEGIN
>
> END;
> """
>         )
>
> - Screenshots are being taken of failed tests:
>   1) I've never actually seen any get saved
>   2) They should be saved to the same directory as the test log, not /tmp
>   3) They should have guaranteed unique names, and be mentioned in the
> test output so the user can reference the image to the failure.
>
> The reason the last two items are important is that I've now got a
> test server running the test suite with every supported version of
> Python, for every supported database (well, almost, pending a couple
> of fixes). I have separate workspaces for each Python version, and a
> single test run might run every test 10 times, once for each database
> server.
>
> - Please wrap the README at < 80 chars.
>
>
>
> On Thu, Feb 9, 2017 at 4:17 PM, Atira Odhner <[email protected]> wrote:
> > Hi Dave,
> >
> >>  I think the problem was that the way you phrased it,
> >
> >
> > You're right, we totally messed that up. We were talking about making 3
> > patches and ended up making only 2 and forgot to reword that bit.
> > Sorry about that.
> >
> > Here are the two patches for this change that resolves the AttributeError
> > you were seeing. The first patch is identical to the patch of the same
> name
> > in the other email thread.
> >
> >> We're used to
> >> dealing with larger patchsets via the mailing list - typically as long
> >> as you're clear about any dependencies, it shouldn't be a problem.
> >
> >
> > Great! We'll try sending patchsets from now on and hopefully that
> resolves
> > some of the issues we were seeing.
> >
> > Tira & George
> >
> > On Thu, Feb 9, 2017 at 9:28 AM, Dave Page <[email protected]> wrote:
> >>
> >> Hi
> >>
> >> On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]>
> wrote:
> >> > Certainly.  We did mention the dependency in the email. Would it be
> >> > better
> >> > to mention it in the patch name?
> >>
> >> I think the problem was that the way you phrased it, it sounded
> >> optional ("an updated patch which does not include adding that test
> >> helper in case you apply the show-tables patch first"). I think a
> >> clear "This patch is dependent on patch Foo" would suffice.
> >>
> >> > Is there a better way for us to manage
> >> > these changes? On other open source projects, I've seen github mirrors
> >> > set
> >> > up so that changes can be pulled in like branches rather then as patch
> >> > applies. That would have avoided this situation since the parent
> commit
> >> > would be pulled in with the same SHA from either pull request branch
> and
> >> > git
> >> > would not see it as a conflict.
> >> >
> >> > I'm rather new to dealing with patch files like this so I would love
> >> > some
> >> > tips.
> >>
> >> The Postgres project in general is quite conservative and stuck in
> >> it's ways about how things are done (which is usually a good thing
> >> considering you trust your data to the resulting code). We're used to
> >> dealing with larger patchsets via the mailing list - typically as long
> >> as you're clear about any dependencies, it shouldn't be a problem.
> >> Some of us use tools like PyCharms for handling patches and helping
> >> with reviews etc. which I guess replaces most, if not all of the
> >> GitHub functionality over plain git.
> >>
> >> --
> >> Dave Page
> >> Blog: http://pgsnake.blogspot.com
> >> Twitter: @pgsnake
> >>
> >> EnterpriseDB UK: http://www.enterprisedb.com
> >> The Enterprise PostgreSQL Company
> >
> >
>
>
>
> --
> Dave Page
> Blog: http://pgsnake.blogspot.com
> Twitter: @pgsnake
>
> EnterpriseDB UK: http://www.enterprisedb.com
> The Enterprise PostgreSQL Company
>


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers


Attachments:

  [application/octet-stream] 0003-Rename-acceptance-feature_tests-and-make-tests-less-.patch (12.5K, 3-0003-Rename-acceptance-feature_tests-and-make-tests-less-.patch)
  download | inline diff:
From 52bc460dffa6497a2074178d74aafeeeae0be292 Mon Sep 17 00:00:00 2001
From: "George Gelashvili, Sarah McAlear and Tira Odhner"
 <[email protected]>
Date: Tue, 21 Feb 2017 11:25:36 -0500
Subject: [PATCH 1/2] Rename acceptance -> feature_tests and make tests less
 flaky by tearing down the database connection before running the test

---
 .../{acceptance => feature_tests}/__init__.py      |  0
 .../connect_to_server_feature_test.py              | 22 +++---------------
 .../template_selection_feature_test.py             | 24 ++++----------------
 web/pgadmin/utils/route.py                         |  2 +-
 web/regression/README                              |  6 ++---
 .../tests => regression/feature_utils}/__init__.py |  0
 .../{utils => feature_utils}/app_starter.py        |  0
 web/regression/feature_utils/base_feature_test.py  | 26 ++++++++++++++++++++++
 .../{utils => feature_utils}/pgadmin_page.py       |  6 ++++-
 web/regression/test_utils.py                       |  5 +++++
 web/regression/utils/__init__.py                   |  0
 11 files changed, 47 insertions(+), 44 deletions(-)
 rename web/pgadmin/{acceptance => feature_tests}/__init__.py (100%)
 rename web/pgadmin/{acceptance/tests => feature_tests}/connect_to_server_feature_test.py (78%)
 rename web/pgadmin/{acceptance/tests => feature_tests}/template_selection_feature_test.py (74%)
 rename web/{pgadmin/acceptance/tests => regression/feature_utils}/__init__.py (100%)
 rename web/regression/{utils => feature_utils}/app_starter.py (100%)
 create mode 100644 web/regression/feature_utils/base_feature_test.py
 rename web/regression/{utils => feature_utils}/pgadmin_page.py (95%)
 delete mode 100644 web/regression/utils/__init__.py

diff --git a/web/pgadmin/acceptance/__init__.py b/web/pgadmin/feature_tests/__init__.py
similarity index 100%
rename from web/pgadmin/acceptance/__init__.py
rename to web/pgadmin/feature_tests/__init__.py
diff --git a/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py b/web/pgadmin/feature_tests/connect_to_server_feature_test.py
similarity index 78%
rename from web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
rename to web/pgadmin/feature_tests/connect_to_server_feature_test.py
index c3bee4e6..2a7f638e 100644
--- a/web/pgadmin/acceptance/tests/connect_to_server_feature_test.py
+++ b/web/pgadmin/feature_tests/connect_to_server_feature_test.py
@@ -7,29 +7,20 @@
 #
 ##############################################################
 
-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
+from regression.feature_utils.base_feature_test import BaseFeatureTest
 
 
-class ConnectsToServerFeatureTest(BaseTestGenerator):
+class ConnectsToServerFeatureTest(BaseFeatureTest):
     """
     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)
+        super(ConnectsToServerFeatureTest, self).setUp()
 
         connection = test_utils.get_db_connection(self.server['db'],
                                                   self.server['username'],
@@ -40,9 +31,6 @@ class ConnectsToServerFeatureTest(BaseTestGenerator):
         test_utils.create_database(self.server, "acceptance_test_db")
         test_utils.create_table(self.server, "acceptance_test_db", "test_table")
 
-        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()
@@ -61,10 +49,6 @@ class ConnectsToServerFeatureTest(BaseTestGenerator):
                                                   self.server['port'])
         test_utils.drop_database(connection, "acceptance_test_db")
 
-    def failureException(self, *args, **kwargs):
-        self.page.driver.save_screenshot('/tmp/pgadmin_connect_to_server_test_failure.png')
-        return AssertionError(*args, **kwargs)
-
     def _connects_to_server(self):
         self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click()
         self.page.driver.find_element_by_link_text("Object").click()
diff --git a/web/pgadmin/acceptance/tests/template_selection_feature_test.py b/web/pgadmin/feature_tests/template_selection_feature_test.py
similarity index 74%
rename from web/pgadmin/acceptance/tests/template_selection_feature_test.py
rename to web/pgadmin/feature_tests/template_selection_feature_test.py
index b7405d56..858950f4 100644
--- a/web/pgadmin/acceptance/tests/template_selection_feature_test.py
+++ b/web/pgadmin/feature_tests/template_selection_feature_test.py
@@ -1,22 +1,13 @@
 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
+from regression.feature_utils.base_feature_test import BaseFeatureTest
 
 
-class TemplateSelectionFeatureTest(BaseTestGenerator):
+class TemplateSelectionFeatureTest(BaseFeatureTest):
     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)
+        super(TemplateSelectionFeatureTest, self).setUp()
 
         connection = test_utils.get_db_connection(self.server['db'],
                                                   self.server['username'],
@@ -27,9 +18,6 @@ class TemplateSelectionFeatureTest(BaseTestGenerator):
 
         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):
@@ -71,8 +59,4 @@ $BODY$
                                                   self.server['db_password'],
                                                   self.server['host'],
                                                   self.server['port'])
-        test_utils.drop_database(connection, "acceptance_test_db")
-
-    def failureException(self, *args, **kwargs):
-        self.page.driver.save_screenshot('/tmp/pgadmin_sql_template_selection_failure.png')
-        return AssertionError(*args, **kwargs)
+        test_utils.drop_database(connection, "acceptance_test_db")
\ No newline at end of file
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index 996892a6..2dea25d9 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -48,7 +48,7 @@ class TestsGeneratorRegistry(ABCMeta):
         # Register this type of module, based on the module name
         # Avoid registering the BaseDriver itself
 
-        if name != 'BaseTestGenerator':
+        if name != 'BaseTestGenerator' and name != 'BaseFeatureTest':
             TestsGeneratorRegistry.registry[d['__module__']] = cls
 
         ABCMeta.__init__(cls, name, bases, d)
diff --git a/web/regression/README b/web/regression/README
index 7101eb75..d16111b6 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -103,7 +103,7 @@ Test Data Details
 Execution:
 -----------
 
-- For acceptance tests to run as part of the entire test suite, Chrome and chromedriver need to be installed:
+- For feature 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
 
@@ -137,8 +137,8 @@ Execution:
 
 - Exclude a package and its subpackages when running tests:
 
-    Example: exclude acceptance tests but run all others:
-    run 'python runtests.py --exclude acceptance'
+    Example: exclude feature tests but run all others:
+    run 'python runtests.py --exclude feature_tests'
 
     Example: exclude multiple packages:
     run 'python runtests.py --exclude browser.server_groups.servers.databases,browser.server_groups.servers.tablespaces'
diff --git a/web/pgadmin/acceptance/tests/__init__.py b/web/regression/feature_utils/__init__.py
similarity index 100%
rename from web/pgadmin/acceptance/tests/__init__.py
rename to web/regression/feature_utils/__init__.py
diff --git a/web/regression/utils/app_starter.py b/web/regression/feature_utils/app_starter.py
similarity index 100%
rename from web/regression/utils/app_starter.py
rename to web/regression/feature_utils/app_starter.py
diff --git a/web/regression/feature_utils/base_feature_test.py b/web/regression/feature_utils/base_feature_test.py
new file mode 100644
index 00000000..62d3bb36
--- /dev/null
+++ b/web/regression/feature_utils/base_feature_test.py
@@ -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
\ No newline at end of file
diff --git a/web/regression/utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
similarity index 95%
rename from web/regression/utils/pgadmin_page.py
rename to web/regression/feature_utils/pgadmin_page.py
index d6a5836c..8d2843f6 100644
--- a/web/regression/utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -1,4 +1,5 @@
 import time
+
 from selenium.common.exceptions import NoSuchElementException
 from selenium.webdriver import ActionChains
 from selenium.webdriver.common.keys import Keys
@@ -65,6 +66,9 @@ class PgadminPage:
             "//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(
@@ -117,4 +121,4 @@ class PgadminPage:
             time_waited += sleep_time
             time.sleep(sleep_time)
 
-        raise RuntimeError("timed out waiting for " + waiting_for_message)
+        raise AssertionError("timed out waiting for " + waiting_for_message)
diff --git a/web/regression/test_utils.py b/web/regression/test_utils.py
index b4445d7d..1c2d244d 100644
--- a/web/regression/test_utils.py
+++ b/web/regression/test_utils.py
@@ -157,6 +157,11 @@ def drop_database(connection, database_name):
     """This function used to drop the database"""
     if database_name not in ["postgres", "template1", "template0"]:
         pg_cursor = connection.cursor()
+
+        pg_cursor.execute(
+            "SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity "
+            "WHERE pg_stat_activity.datname ='%s' and pid <> pg_backend_pid();" % database_name
+                          )
         pg_cursor.execute("SELECT * FROM pg_database db WHERE"
                           " db.datname='%s'" % database_name)
         if pg_cursor.fetchall():
diff --git a/web/regression/utils/__init__.py b/web/regression/utils/__init__.py
deleted file mode 100644
index e69de29b..00000000
-- 
2.11.0



  [application/octet-stream] 0004-wrap-README-in-80-characters-and-simplify-TemplateSe.patch (3.2K, 4-0004-wrap-README-in-80-characters-and-simplify-TemplateSe.patch)
  download | inline diff:
From 186e6fda2557e23346a14340364187bd0fc8dcee Mon Sep 17 00:00:00 2001
From: Sarah McAlear and Tira Odhner <[email protected]>
Date: Tue, 21 Feb 2017 15:42:45 -0500
Subject: [PATCH 2/2] wrap README in 80 characters and simplify
 TemplateSelectionFeatureTest

---
 README                                                      | 13 +++++++------
 .../feature_tests/template_selection_feature_test.py        | 11 +----------
 2 files changed, 8 insertions(+), 16 deletions(-)

diff --git a/README b/README
index c4a533e2..ed010174 100644
--- a/README
+++ b/README
@@ -2,7 +2,7 @@ pgAdmin 4
 =========
 
 pgAdmin 4 is a rewrite of the popular pgAdmin3 management tool for the
-PostgreSQL (http://www.postgresql.org) database. 
+PostgreSQL (http://www.postgresql.org) database.
 
 In the following documentation and examples, "$PGADMIN4_SRC/" is used to denote
 the top-level directory of a copy of the pgAdmin source tree, either from a
@@ -53,9 +53,10 @@ By default, the runtime application will be built in release mode.
 On Linux, an executable called 'pgAdmin4' will be built, and on Mac OS X, an
 app bundle called pgAdmin4.app will be created.
 
-To build the runtime on a Windows system, export PYTHON_HOME and PYTHON_VERSION 
-variables in the System environment. Specify the PYTHON_VERSION with the major 
-and minor number. Do not specify micro level version.
+To build the runtime on a Windows system, export PYTHON_HOME and
+PYTHON_VERSION variables in the System environment. Specify the
+PYTHON_VERSION with the major and minor number. Do not specify micro level
+version.
 
 For example, given a Python version of A.B.C; A - Major number, B - Minor
 number, C - Micro level (Bug fix releases).
@@ -100,7 +101,7 @@ process is fairly simple - adapt as required for your distribution:
    pg_config can be found for building psycopg2), and install the required
    packages:
 
-    (pgadmin4) $ PATH=$PATH:/usr/local/pgsql/bin pip install -r $PGADMIN4_SRC/requirements_py2.txt
+   $ PATH=$PATH:/usr/local/pgsql/bin pip install -r $PGADMIN4_SRC/requirements_py2.txt
 
    If you are using Python 3, use the requirements_py3.txt file instead.
 
@@ -302,6 +303,6 @@ pgAdmin Hackers mailing list:
 
 [email protected]
 
--- 
+--
 Dave Page
 pgAdmin Project Lead
diff --git a/web/pgadmin/feature_tests/template_selection_feature_test.py b/web/pgadmin/feature_tests/template_selection_feature_test.py
index 858950f4..ceb12478 100644
--- a/web/pgadmin/feature_tests/template_selection_feature_test.py
+++ b/web/pgadmin/feature_tests/template_selection_feature_test.py
@@ -36,16 +36,7 @@ class TemplateSelectionFeatureTest(BaseFeatureTest):
         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.fill_codemirror_area_with("some-trigger-function-content")
         self.page.find_by_partial_link_text("SQL").click()
 
         self.page.find_by_xpath("//*[contains(@class,'CodeMirror-lines') and contains(.,'LEAKPROOF')]")
-- 
2.11.0



^ permalink  raw  reply  [nested|flat] 30+ messages in thread

* Re: Acceptance Tests against a browser (WIP)
  2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-19 22:07 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-20 15:38   ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-20 17:33     ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-24 09:43       ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-25 23:31         ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-26 22:40           ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-27 16:11             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-27 16:28               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-30 19:28                 ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-30 21:23                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-01-31 14:41                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 14:54                       ` Re: Acceptance Tests against a browser (WIP) George Gelashvili <[email protected]>
  2017-01-31 15:10                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-01-31 16:25                           ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-03 21:56                             ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-06 10:32                               ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-06 14:54                                 ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-08 22:15                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 12:47                                     ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 13:15                                       ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 13:26                                         ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 14:20                                           ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-09 14:28                                             ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-09 16:17                                               ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
  2017-02-13 14:36                                                 ` Re: Acceptance Tests against a browser (WIP) Dave Page <[email protected]>
  2017-02-21 22:12                                                   ` Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
@ 2017-02-22 12:42                                                     ` Dave Page <[email protected]>
  0 siblings, 0 replies; 30+ messages in thread

From: Dave Page @ 2017-02-22 12:42 UTC (permalink / raw)
  To: Atira Odhner <[email protected]>; +Cc: pgadmin-hackers; Sarah McAlear <[email protected]>

Thanks, patch applied!

On Tue, Feb 21, 2017 at 10:12 PM, Atira Odhner <[email protected]> wrote:
> Hi Dave,
>
> We fixed the flakiness issues that we saw (hopefully they are the same ones
> you were seeing.) by tearing down connections to the acceptance_test_db
> before attempting to drop it at the beginning of the test. Once we have
> access to the CI pipeline we can help out there to ensure the flakiness is
> gone.
>
> We wrapped the README at 80 characters, and removed the misleading function
> definition from the test.
>
> As far as the screenshots go, I'm more inclined to remove the screenshotting
> than to work on improving it. It currently only works when the failure is
> due to an AssertionError since that's what failureException relies on.
>
> We also renamed acceptance to feature_tests since 'acceptance' seemed
> ambiguous/redundant with 'regression'.
>
> Tira & Sara
>
>
> On Mon, Feb 13, 2017 at 9:36 AM, Dave Page <[email protected]> wrote:
>>
>> Hi,
>>
>> I've been playing with this for the last couple of hours, and I just
>> can't get it to work reliably;
>>
>> - A good percentage of the time the browser opens with a URL of
>> "data:," and does nothing more. This appears to happen if tests fail,
>> which still leaves server processes running in the background.
>>
>> - The connect_to_server test usually seems to work.
>>
>> - The template_selection_feature test usually does *not* work. I can't
>> see an obvious reason, but I suspect it's a race condition. What seems
>> to happen is that the function definition is entered, but not
>> registered by the UI, so the mSQL panel just ends up saying
>> "incomplete definition". Manually checking what was input proves that
>> everything is correct - and indeed, returning the SQL tab shows the
>> expected SQL.
>>
>> Other issues I noted:
>>
>> - The template_selection_feature test should just enter BEGIN/END.
>> What it currently enters is an entire function definition, when only
>> the body content is expected. E.g.
>>
>>         self.page.fill_codemirror_area_with(
>> """BEGIN
>>
>> END;
>> """
>>         )
>>
>> - Screenshots are being taken of failed tests:
>>   1) I've never actually seen any get saved
>>   2) They should be saved to the same directory as the test log, not /tmp
>>   3) They should have guaranteed unique names, and be mentioned in the
>> test output so the user can reference the image to the failure.
>>
>> The reason the last two items are important is that I've now got a
>> test server running the test suite with every supported version of
>> Python, for every supported database (well, almost, pending a couple
>> of fixes). I have separate workspaces for each Python version, and a
>> single test run might run every test 10 times, once for each database
>> server.
>>
>> - Please wrap the README at < 80 chars.
>>
>>
>>
>> On Thu, Feb 9, 2017 at 4:17 PM, Atira Odhner <[email protected]> wrote:
>> > Hi Dave,
>> >
>> >>  I think the problem was that the way you phrased it,
>> >
>> >
>> > You're right, we totally messed that up. We were talking about making 3
>> > patches and ended up making only 2 and forgot to reword that bit.
>> > Sorry about that.
>> >
>> > Here are the two patches for this change that resolves the
>> > AttributeError
>> > you were seeing. The first patch is identical to the patch of the same
>> > name
>> > in the other email thread.
>> >
>> >> We're used to
>> >> dealing with larger patchsets via the mailing list - typically as long
>> >> as you're clear about any dependencies, it shouldn't be a problem.
>> >
>> >
>> > Great! We'll try sending patchsets from now on and hopefully that
>> > resolves
>> > some of the issues we were seeing.
>> >
>> > Tira & George
>> >
>> > On Thu, Feb 9, 2017 at 9:28 AM, Dave Page <[email protected]> wrote:
>> >>
>> >> Hi
>> >>
>> >> On Thu, Feb 9, 2017 at 2:20 PM, Atira Odhner <[email protected]>
>> >> wrote:
>> >> > Certainly.  We did mention the dependency in the email. Would it be
>> >> > better
>> >> > to mention it in the patch name?
>> >>
>> >> I think the problem was that the way you phrased it, it sounded
>> >> optional ("an updated patch which does not include adding that test
>> >> helper in case you apply the show-tables patch first"). I think a
>> >> clear "This patch is dependent on patch Foo" would suffice.
>> >>
>> >> > Is there a better way for us to manage
>> >> > these changes? On other open source projects, I've seen github
>> >> > mirrors
>> >> > set
>> >> > up so that changes can be pulled in like branches rather then as
>> >> > patch
>> >> > applies. That would have avoided this situation since the parent
>> >> > commit
>> >> > would be pulled in with the same SHA from either pull request branch
>> >> > and
>> >> > git
>> >> > would not see it as a conflict.
>> >> >
>> >> > I'm rather new to dealing with patch files like this so I would love
>> >> > some
>> >> > tips.
>> >>
>> >> The Postgres project in general is quite conservative and stuck in
>> >> it's ways about how things are done (which is usually a good thing
>> >> considering you trust your data to the resulting code). We're used to
>> >> dealing with larger patchsets via the mailing list - typically as long
>> >> as you're clear about any dependencies, it shouldn't be a problem.
>> >> Some of us use tools like PyCharms for handling patches and helping
>> >> with reviews etc. which I guess replaces most, if not all of the
>> >> GitHub functionality over plain git.
>> >>
>> >> --
>> >> Dave Page
>> >> Blog: http://pgsnake.blogspot.com
>> >> Twitter: @pgsnake
>> >>
>> >> EnterpriseDB UK: http://www.enterprisedb.com
>> >> The Enterprise PostgreSQL Company
>> >
>> >
>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>
>



-- 
Dave Page
Blog: http://pgsnake.blogspot.com
Twitter: @pgsnake

EnterpriseDB UK: http://www.enterprisedb.com
The Enterprise PostgreSQL Company


-- 
Sent via pgadmin-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgadmin-hackers




^ permalink  raw  reply  [nested|flat] 30+ messages in thread


end of thread, other threads:[~2017-02-22 12:42 UTC | newest]

Thread overview: 30+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2017-01-17 16:09 Re: Acceptance Tests against a browser (WIP) Atira Odhner <[email protected]>
2017-01-19 22:07 ` George Gelashvili <[email protected]>
2017-01-19 23:15   ` George Gelashvili <[email protected]>
2017-01-20 15:38     ` Dave Page <[email protected]>
2017-01-20 15:38   ` Dave Page <[email protected]>
2017-01-20 17:33     ` George Gelashvili <[email protected]>
2017-01-24 09:43       ` Dave Page <[email protected]>
2017-01-25 23:31         ` George Gelashvili <[email protected]>
2017-01-26 22:40           ` George Gelashvili <[email protected]>
2017-01-27 16:11             ` Dave Page <[email protected]>
2017-01-27 16:28               ` Dave Page <[email protected]>
2017-01-30 19:28                 ` George Gelashvili <[email protected]>
2017-01-30 21:23                   ` Atira Odhner <[email protected]>
2017-01-31 14:41                     ` Dave Page <[email protected]>
2017-01-31 14:54                       ` George Gelashvili <[email protected]>
2017-01-31 15:10                         ` Dave Page <[email protected]>
2017-01-31 16:25                           ` Dave Page <[email protected]>
2017-02-03 21:56                             ` Atira Odhner <[email protected]>
2017-02-06 10:32                               ` Dave Page <[email protected]>
2017-02-06 14:54                                 ` Atira Odhner <[email protected]>
2017-02-08 22:15                                   ` Atira Odhner <[email protected]>
2017-02-09 12:47                                     ` Dave Page <[email protected]>
2017-02-09 13:15                                       ` Atira Odhner <[email protected]>
2017-02-09 13:26                                         ` Dave Page <[email protected]>
2017-02-09 14:20                                           ` Atira Odhner <[email protected]>
2017-02-09 14:28                                             ` Dave Page <[email protected]>
2017-02-09 16:17                                               ` Atira Odhner <[email protected]>
2017-02-13 14:36                                                 ` Dave Page <[email protected]>
2017-02-21 22:12                                                   ` Atira Odhner <[email protected]>
2017-02-22 12:42                                                     ` Dave Page <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox