public inbox for [email protected]  
help / color / mirror / Atom feed
From: Yogesh Mahajan <[email protected]>
To: pgadmin-hackers <[email protected]>
Subject: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
Date: Mon, 13 Apr 2020 14:40:02 +0530
Message-ID: <CAMa=N=NokBUU=gRybJzctGKu4N-H1GWu_peamTHqqTNm+v9CPw@mail.gmail.com> (raw)

Hi,

Please find the attached patch for running *features tests* using
solenoid(selenium grid + docker).
KIndly review.
To sun feature tests in parallel, required prerequisites can be checked in
'~/web/regression/README' file.
Also detailed instructions are added in the same file.
After applying the patch, any existing process for execution of
API/Features tests remains the same.


Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


Attachments:

  [application/octet-stream] Selenium_grid_implementation_v1.0 .patch (82.5K, 3-Selenium_grid_implementation_v1.0%20.patch)
  download | inline diff:
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
index 3cd6b3b86..5b24e6e8f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
@@ -43,31 +43,30 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
          dict(url='/browser/unique_constraint/obj/', data=unique_key_data))
     ]
 
-    @classmethod
-    def setUpClass(cls):
-        cls.db_name = parent_node_dict["database"][-1]["db_name"]
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
-        cls.server_id = schema_info["server_id"]
-        cls.db_id = schema_info["db_id"]
-        db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
-                                                 cls.server_id, cls.db_id)
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
         if not db_con['data']["connected"]:
             raise Exception("Could not connect to database to add a "
                             "index constraint(primary key or unique key).")
-        cls.schema_id = schema_info["schema_id"]
-        cls.schema_name = schema_info["schema_name"]
-        schema_response = schema_utils.verify_schemas(cls.server,
-                                                      cls.db_name,
-                                                      cls.schema_name)
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
         if not schema_response:
             raise Exception("Could not find the schema to add a index "
                             "constraint(primary key or unique key).")
-        cls.table_name = "table_indexconstraint_%s" % \
-                         (str(uuid.uuid4())[1:8])
-        cls.table_id = tables_utils.create_table(cls.server,
-                                                 cls.db_name,
-                                                 cls.schema_name,
-                                                 cls.table_name)
+        self.table_name = "table_indexconstraint_%s" % \
+                          (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server,
+                                                  self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
 
     def runTest(self):
         """This function will add index constraint(primary key or unique key)
@@ -80,7 +79,6 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
             content_type='html/json')
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         # Disconnect the database
-        database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
index 7eea5e9bb..f35a00b4a 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
@@ -38,31 +38,30 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator):
               type="UNIQUE"))
     ]
 
-    @classmethod
-    def setUpClass(cls):
-        cls.db_name = parent_node_dict["database"][-1]["db_name"]
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
-        cls.server_id = schema_info["server_id"]
-        cls.db_id = schema_info["db_id"]
-        db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
-                                                 cls.server_id, cls.db_id)
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
         if not db_con['data']["connected"]:
             raise Exception("Could not connect to database to add a "
                             "index constraint(primary key or unique key).")
-        cls.schema_id = schema_info["schema_id"]
-        cls.schema_name = schema_info["schema_name"]
-        schema_response = schema_utils.verify_schemas(cls.server,
-                                                      cls.db_name,
-                                                      cls.schema_name)
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
         if not schema_response:
             raise Exception("Could not find the schema to add a index "
                             "constraint(primary key or unique key).")
-        cls.table_name = "table_indexconstraint_%s" % \
-                         (str(uuid.uuid4())[1:8])
-        cls.table_id = tables_utils.create_table(cls.server,
-                                                 cls.db_name,
-                                                 cls.schema_name,
-                                                 cls.table_name)
+        self.table_name = "table_indexconstraint_%s" % \
+                          (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server,
+                                                  self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
 
     def runTest(self):
         """This function will delete index constraint(primary key or
@@ -81,7 +80,6 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator):
         )
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         # Disconnect the database
-        database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
index c08c2acb3..f838a2f73 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
@@ -38,31 +38,30 @@ class IndexConstraintGetTestCase(BaseTestGenerator):
               type="UNIQUE"))
     ]
 
-    @classmethod
-    def setUpClass(cls):
-        cls.db_name = parent_node_dict["database"][-1]["db_name"]
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
-        cls.server_id = schema_info["server_id"]
-        cls.db_id = schema_info["db_id"]
-        db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
-                                                 cls.server_id, cls.db_id)
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
         if not db_con['data']["connected"]:
             raise Exception("Could not connect to database to add a "
                             "index constraint(primary key or unique key).")
-        cls.schema_id = schema_info["schema_id"]
-        cls.schema_name = schema_info["schema_name"]
-        schema_response = schema_utils.verify_schemas(cls.server,
-                                                      cls.db_name,
-                                                      cls.schema_name)
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
         if not schema_response:
             raise Exception("Could not find the schema to add a index "
                             "constraint(primary key or unique key).")
-        cls.table_name = "table_indexconstraint_%s" % \
-                         (str(uuid.uuid4())[1:8])
-        cls.table_id = tables_utils.create_table(cls.server,
-                                                 cls.db_name,
-                                                 cls.schema_name,
-                                                 cls.table_name)
+        self.table_name = "table_indexconstraint_%s" % \
+                          (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server,
+                                                  self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
 
     def runTest(self):
         """This function will fetch the index constraint(primary key or
@@ -81,7 +80,6 @@ class IndexConstraintGetTestCase(BaseTestGenerator):
         )
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         # Disconnect the database
-        database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
index 87289a620..6bdf70308 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
@@ -40,31 +40,30 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator):
               type="UNIQUE", data=data))
     ]
 
-    @classmethod
-    def setUpClass(cls):
-        cls.db_name = parent_node_dict["database"][-1]["db_name"]
+    def setUp(self):
+        self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
-        cls.server_id = schema_info["server_id"]
-        cls.db_id = schema_info["db_id"]
-        db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
-                                                 cls.server_id, cls.db_id)
+        self.server_id = schema_info["server_id"]
+        self.db_id = schema_info["db_id"]
+        db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+                                                 self.server_id, self.db_id)
         if not db_con['data']["connected"]:
             raise Exception("Could not connect to database to add a "
                             "index constraint(primary key or unique key).")
-        cls.schema_id = schema_info["schema_id"]
-        cls.schema_name = schema_info["schema_name"]
-        schema_response = schema_utils.verify_schemas(cls.server,
-                                                      cls.db_name,
-                                                      cls.schema_name)
+        self.schema_id = schema_info["schema_id"]
+        self.schema_name = schema_info["schema_name"]
+        schema_response = schema_utils.verify_schemas(self.server,
+                                                      self.db_name,
+                                                      self.schema_name)
         if not schema_response:
             raise Exception("Could not find the schema to add a index "
                             "constraint(primary key or unique key).")
-        cls.table_name = "table_indexconstraint_%s" % \
-                         (str(uuid.uuid4())[1:8])
-        cls.table_id = tables_utils.create_table(cls.server,
-                                                 cls.db_name,
-                                                 cls.schema_name,
-                                                 cls.table_name)
+        self.table_name = "table_indexconstraint_%s" % \
+                          (str(uuid.uuid4())[1:8])
+        self.table_id = tables_utils.create_table(self.server,
+                                                  self.db_name,
+                                                  self.schema_name,
+                                                  self.table_name)
 
     def runTest(self):
         """This function will update index constraint(primary key or
@@ -84,7 +83,6 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator):
             follow_redirects=True)
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(cls):
+    def tearDown(self):
         # Disconnect the database
-        database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+        database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
index bbed26998..02203d2f5 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
@@ -46,8 +46,9 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
          )
     ]
 
-    @classmethod
-    def setUpClass(self):
+    table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8])
+
+    def setUp(self):
         self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
         self.server_id = schema_info["server_id"]
@@ -63,12 +64,14 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
                                                       self.schema_name)
         if not schema_response:
             raise Exception("Could not find the schema to add a table.")
-        self.table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8])
 
-        self.table_id = tables_utils.create_table(
-            self.server, self.db_name,
-            self.schema_name,
-            self.table_name)
+        self.table_id = tables_utils.get_table_id(self.server, self.db_name,
+                                                  self.table_name)
+        if self.table_id is None:
+            self.table_id = tables_utils.create_table(
+                self.server, self.db_name,
+                self.schema_name,
+                self.table_name)
 
     def runTest(self):
         """This function will fetch added table under schema node."""
@@ -130,7 +133,6 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
                                    follow_redirects=True)
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(self):
+    def tearDown(self):
         # Disconnect the database
         database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
index 0a2963aab..8e5f665f3 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
@@ -483,3 +483,26 @@ def get_hash_partitions_data(data):
           }]
     data['partition_keys'] = \
         [{'key_type': 'column', 'pt_column': 'empno'}]
+
+
+def get_table_id(server, db_name, table_name):
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        pg_cursor = connection.cursor()
+        pg_cursor.execute("select oid from pg_class where relname='%s'" %
+                          table_name)
+        table = pg_cursor.fetchone()
+        if table:
+            table_id = table[0]
+        else:
+            table_id = None
+        connection.close()
+        return table_id
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
index c84276ef6..1f63ef564 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
@@ -45,8 +45,9 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
          )
     ]
 
-    @classmethod
-    def setUpClass(self):
+    m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8])
+
+    def setUp(self):
         self.db_name = parent_node_dict["database"][-1]["db_name"]
         schema_info = parent_node_dict["schema"][-1]
         self.server_id = schema_info["server_id"]
@@ -70,17 +71,19 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
         if not schema_response:
             raise Exception("Could not find the schema to update a mview.")
 
-        self.m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8])
-        m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE pg_default " \
-                     "AS SELECT 'test_pgadmin' WITH NO DATA;ALTER TABLE " \
-                     "%s.%s OWNER TO %s"
-
-        self.m_view_id = views_utils.create_view(self.server,
-                                                 self.db_name,
-                                                 self.schema_name,
-                                                 m_view_sql,
+        self.m_view_id = views_utils.get_view_id(self.server, self.db_name,
                                                  self.m_view_name)
 
+        if self.m_view_id is None:
+            m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE " \
+                         "pg_default AS SELECT 'test_pgadmin' WITH NO " \
+                         "DATA;ALTER TABLE %s.%s OWNER TO %s"
+            self.m_view_id = views_utils.create_view(self.server,
+                                                     self.db_name,
+                                                     self.schema_name,
+                                                     m_view_sql,
+                                                     self.m_view_name)
+
     def runTest(self):
         """This function will update the view/mview under schema node."""
         mview_response = views_utils.verify_view(self.server, self.db_name,
@@ -141,7 +144,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
                                    follow_redirects=True)
         self.assertEquals(response.status_code, 200)
 
-    @classmethod
-    def tearDownClass(self):
+    def tearDown(self):
         # Disconnect the database
         database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
index d6180c4fc..7619ee673 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
@@ -86,3 +86,28 @@ def verify_view(server, db_name, view_name):
     except Exception:
         traceback.print_exc(file=sys.stderr)
         raise
+
+
+def get_view_id(server, db_name, view_name):
+    try:
+        connection = utils.get_db_connection(db_name,
+                                             server['username'],
+                                             server['db_password'],
+                                             server['host'],
+                                             server['port'],
+                                             server['sslmode'])
+        old_isolation_level = connection.isolation_level
+        connection.set_isolation_level(0)
+        pg_cursor = connection.cursor()
+        # Get 'oid' from newly created view
+        pg_cursor.execute("select oid from pg_class where relname='%s'" %
+                          view_name)
+        view = pg_cursor.fetchone()
+        view_id = None
+        if view:
+            view_id = view[0]
+        connection.close()
+        return view_id
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        raise
diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
index 7091ffa42..7475970ab 100644
--- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
+++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
@@ -8,7 +8,6 @@
 ##########################################################################
 
 from __future__ import print_function
-import pyperclip
 import random
 
 from selenium.webdriver import ActionChains
@@ -61,7 +60,6 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self._copies_rows_with_header()
 
     def _copies_rows(self):
-        pyperclip.copy("old clipboard contents")
         first_row = self.page.find_by_xpath(
             QueryToolLocators.output_row_xpath.format(1))
         first_row.click()
@@ -70,14 +68,23 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
+        scratch_pad_ele.clear()
 
     def _copies_rows_with_header(self):
         self.page.find_by_css_selector('#btn-copy-row-dropdown').click()
         self.page.find_by_css_selector('a#btn-copy-with-header').click()
 
-        pyperclip.copy("old clipboard contents")
         select_all = self.page.find_by_xpath(
             QueryToolLocators.select_all_column)
         select_all.click()
@@ -86,13 +93,22 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual("""\"some_column"\t"value"\t"details"
 \"Some-Name"\t6\t"some info"
 \"Some-Other-Name"\t22\t"some other info"
-\"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste())
+\"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text)
+        scratch_pad_ele.clear()
 
     def _copies_columns(self):
-        pyperclip.copy("old clipboard contents")
         column = self.page.find_by_css_selector(
             QueryToolLocators.output_column_header_css.format('some_column'))
         column.click()
@@ -101,14 +117,23 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
+        scratch_pad_ele.clear()
 
     def _copies_row_using_keyboard_shortcut(self):
-        pyperclip.copy("old clipboard contents")
         first_row = self.page.find_by_xpath(
             QueryToolLocators.output_row_xpath.format(1))
         first_row.click()
@@ -116,27 +141,43 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
+        scratch_pad_ele.clear()
 
     def _copies_column_using_keyboard_shortcut(self):
-        pyperclip.copy("old clipboard contents")
         column = self.page.find_by_css_selector(
             QueryToolLocators.output_column_header_css.format('some_column'))
         column.click()
 
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
 
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
+        scratch_pad_ele.clear()
 
     def _copies_rectangular_selection(self):
-        pyperclip.copy("old clipboard contents")
-
         top_left_cell = \
             self.page.find_by_xpath(
                 QueryToolLocators.output_column_data_xpath.
@@ -154,12 +195,20 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             self.page.driver
         ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual(
-            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
+        scratch_pad_ele.clear()
 
     def _shift_resizes_rectangular_selection(self):
-        pyperclip.copy("old clipboard contents")
-
         top_left_cell = self.page.find_by_xpath(
             QueryToolLocators.output_column_data_xpath.
             format('Some-Other-Name')
@@ -180,12 +229,20 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             Keys.CONTROL
         ).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual("""\"Some-Other-Name"\t22\t"some other info"
-"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste())
+"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text)
+        scratch_pad_ele.clear()
 
     def _shift_resizes_column_selection(self):
-        pyperclip.copy("old clipboard contents")
-
         column = self.page.find_by_css_selector(
             QueryToolLocators.output_column_header_css.format('value')
         )
@@ -197,13 +254,21 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual(
             '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
-            pyperclip.paste())
+            clipboard_text)
+        scratch_pad_ele.clear()
 
     def _mouseup_outside_grid_still_makes_a_selection(self):
-        pyperclip.copy("old clipboard contents")
-
         bottom_right_cell = self.page.find_by_xpath(
             QueryToolLocators.output_column_data_xpath.format('cool info')
         )
@@ -218,7 +283,17 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
-        self.assertIn('"cool info"', pyperclip.paste())
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
+        self.assertIn('"cool info"', clipboard_text)
+        scratch_pad_ele.clear()
 
     def after(self):
         self.page.close_query_tool()
diff --git a/web/pgadmin/feature_tests/file_manager_test.py b/web/pgadmin/feature_tests/file_manager_test.py
index 6d012869b..33f86ad86 100644
--- a/web/pgadmin/feature_tests/file_manager_test.py
+++ b/web/pgadmin/feature_tests/file_manager_test.py
@@ -38,7 +38,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
 
         self.page.add_server(self.server)
         self.wait = WebDriverWait(self.page.driver, 10)
-        self.XSS_FILE = '/tmp/<img src=x onmouseover=alert("1")>.sql'
+        self.XSS_FILE = '/tmp/<img src=x ' + self.server['name'][:13] \
+                        + '=alert("1")>.sql'
         # Remove any previous file
         if os.path.isfile(self.XSS_FILE):
             os.remove(self.XSS_FILE)
@@ -67,7 +68,7 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
         self.page.open_query_tool()
 
     def _create_new_file(self):
-        self.page.find_by_css_selector(QueryToolLocators.btn_save_file)\
+        self.page.find_by_css_selector(QueryToolLocators.btn_save_file) \
             .click()
         # Set the XSS value in input
         self.page.find_by_css_selector('.change_file_types')
@@ -112,8 +113,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
         self.page.wait_for_query_tool_loading_indicator_to_disappear()
         self._check_escaped_characters(
             contents,
-            '&lt;img src=x onmouseover=alert("1")&gt;.sql',
-            'File manager'
+            '&lt;img src=x ' + self.server['name'][:13] +
+            '=alert("1")&gt;.sql', 'File manager'
         )
 
     def _check_escaped_characters(self, source_code, string_to_find, source):
diff --git a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py
index 1d6d7a43e..98b8a38b8 100644
--- a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py
+++ b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py
@@ -18,6 +18,7 @@ from regression.python_test_utils import test_utils
 from regression.python_test_utils import test_gui_helper
 from regression.feature_utils.locators import NavMenuLocators
 from regression.feature_utils.tree_area_locators import TreeAreaLocators
+from selenium.webdriver import ActionChains
 
 
 class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
@@ -56,6 +57,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
             self.server['sslmode']
         )
         test_utils.drop_database(connection, self.database_name)
+        self._update_preferences()
         db_id = test_utils.create_database(self.server, self.database_name)
         if not db_id:
             self.assertTrue(False, "Database {} is not "
@@ -130,7 +132,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
             self._check_detailed_window_for_xss('Backup')
         else:
             command = self.page.find_by_css_selector(
-                NavMenuLocators.process_watcher_detailed_command_canvas_css).\
+                NavMenuLocators.process_watcher_detailed_command_canvas_css). \
                 text
 
             self.assertIn(self.server['name'], str(command))
@@ -199,7 +201,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
             self._check_detailed_window_for_xss('Restore')
         else:
             command = self.page.find_by_css_selector(
-                NavMenuLocators.process_watcher_detailed_command_canvas_css).\
+                NavMenuLocators.process_watcher_detailed_command_canvas_css). \
                 text
 
             self.assertIn(self.server['name'], str(command))
@@ -242,3 +244,71 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest):
         # For XSS we need to search against element's html code
         assert source_code.find(string_to_find) != - \
             1, "{0} might be vulnerable to XSS ".format(source)
+
+    def _update_preferences(self):
+        file_menu = self.page.find_by_css_selector(
+            NavMenuLocators.file_menu_css)
+        file_menu.click()
+
+        pref_menu_item = self.page.find_by_css_selector(
+            NavMenuLocators.preference_menu_item_css)
+        pref_menu_item.click()
+
+        wait = WebDriverWait(self.page.driver, 10)
+
+        # Wait till the preference dialogue box is displayed by checking the
+        # visibility of Show System Object label
+        wait.until(EC.presence_of_element_located(
+            (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
+        )
+
+        maximize_button = self.page.find_by_css_selector(
+            NavMenuLocators.maximize_pref_dialogue_css)
+        maximize_button.click()
+
+        path = self.page.find_by_xpath(
+            NavMenuLocators.specified_preference_tree_node.format('Paths'))
+        if self.page.find_by_xpath(
+            NavMenuLocators.specified_pref_node_exp_status.format('Paths')). \
+                get_attribute('aria-expanded') == 'false':
+            ActionChains(self.driver).double_click(path).perform()
+
+        binary_path = self.page.find_by_xpath(
+            NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
+                'Paths', 'Binary paths'))
+        binary_path.click()
+
+        default_binary_path = self.server['default_binary_paths']
+        if default_binary_path is not None:
+            server_types = default_binary_path.keys()
+            for serv in server_types:
+                if serv == 'pg':
+                    path_input = self.page.find_by_xpath(
+                        "//label[text()='PostgreSQL Binary "
+                        "Path']/following-sibling::div//input")
+                    path_input.clear()
+                    path_input.click()
+                    path_input.send_keys(default_binary_path['pg'])
+                elif serv == 'gpdb':
+                    path_input = self.page.find_by_xpath(
+                        "//label[text()='Greenplum Database Binary "
+                        "Path']/following-sibling::div//input")
+                    path_input.clear()
+                    path_input.click()
+                    path_input.send_keys(default_binary_path['gpdb'])
+                elif serv == 'ppas':
+                    path_input = self.page.find_by_xpath(
+                        "//label[text()='EDB Advanced Server Binary "
+                        "Path']/following-sibling::div//input")
+                    path_input.clear()
+                    path_input.click()
+                    path_input.send_keys(default_binary_path['ppas'])
+                else:
+                    print('Binary path Key is Incorrect')
+
+        # save and close the preference dialog.
+        self.page.click_modal('Save')
+
+        self.page.wait_for_element_to_disappear(
+            lambda driver: driver.find_element_by_css_selector(".ajs-modal")
+        )
diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py
index 2d7c4ef98..ddb4e3caf 100644
--- a/web/pgadmin/feature_tests/query_tool_journey_test.py
+++ b/web/pgadmin/feature_tests/query_tool_journey_test.py
@@ -9,7 +9,6 @@
 
 from __future__ import print_function
 import sys
-import pyperclip
 import random
 
 from selenium.webdriver import ActionChains
@@ -90,7 +89,6 @@ class QueryToolJourneyTest(BaseFeatureTest):
         print(" OK.", file=sys.stderr)
 
     def _test_copies_rows(self):
-        pyperclip.copy("old clipboard contents")
         self.page.driver.switch_to.default_content()
         self.page.driver.switch_to_frame(
             self.page.driver.find_element_by_tag_name("iframe"))
@@ -103,12 +101,21 @@ class QueryToolJourneyTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_row.click()
 
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
-    def _test_copies_columns(self):
-        pyperclip.copy("old clipboard contents")
+        scratch_pad_ele.clear()
 
+    def _test_copies_columns(self):
         self.page.driver.switch_to.default_content()
         self.page.driver.switch_to_frame(
             self.page.driver.find_element_by_tag_name("iframe"))
@@ -121,9 +128,20 @@ class QueryToolJourneyTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_btn.click()
 
-        self.assertTrue('"Some-Name"' in pyperclip.paste())
-        self.assertTrue('"Some-Other-Name"' in pyperclip.paste())
-        self.assertTrue('"Yet-Another-Name"' in pyperclip.paste())
+        self.page.driver.switch_to.default_content()
+        self.page.driver.switch_to_frame(
+            self.page.driver.find_element_by_tag_name("iframe"))
+
+        scratch_pad_ele = self.page.find_by_css_selector(
+            QueryToolLocators.scratch_pad_css)
+        self.page.paste_values(scratch_pad_ele)
+
+        clipboard_text = scratch_pad_ele.get_attribute("value")
+
+        self.assertTrue('"Some-Name"' in clipboard_text)
+        self.assertTrue('"Some-Other-Name"' in clipboard_text)
+        self.assertTrue('"Yet-Another-Name"' in clipboard_text)
+        scratch_pad_ele.clear()
 
     def _test_history_tab(self):
         self.page.clear_query_tool()
@@ -370,10 +388,10 @@ class QueryToolJourneyTest(BaseFeatureTest):
             self.page.find_by_css_selector(
                 QueryToolLocators.btn_clear_dropdown)
         )
-        ActionChains(self.driver)\
+        ActionChains(self.driver) \
             .move_to_element(
-                self.page.find_by_css_selector(
-                    QueryToolLocators.btn_clear_history)).perform()
+            self.page.find_by_css_selector(
+                QueryToolLocators.btn_clear_history)).perform()
         self.page.click_element(
             self.page.find_by_css_selector(QueryToolLocators.btn_clear_history)
         )
diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py
index 0155cdc32..db788bf24 100644
--- a/web/pgadmin/feature_tests/view_data_dml_queries.py
+++ b/web/pgadmin/feature_tests/view_data_dml_queries.py
@@ -131,13 +131,15 @@ CREATE TABLE public.nonintpkey
                                           self.test_db, 'public')
 
         self._load_config_data('table_insert_update_cases')
+        data_local = config_data
         # iterate on both tables
         for cnt in (1, 2):
-            self._perform_test_for_table('defaults_{0}'.format(str(cnt)))
-
+            self._perform_test_for_table('defaults_{0}'.format(str(cnt)),
+                                         data_local)
         # test nonint pkey table
         self._load_config_data('table_insert_update_nonint')
-        self._perform_test_for_table('nonintpkey')
+        data_local = config_data
+        self._perform_test_for_table('nonintpkey', data_local)
 
     def after(self):
         self.page.remove_server(self.server)
@@ -167,7 +169,7 @@ CREATE TABLE public.nonintpkey
         global config_data
         config_data = config_data_json[config_key]
 
-    def _perform_test_for_table(self, table_name):
+    def _perform_test_for_table(self, table_name, config_data_local):
         self.page.click_a_tree_node(
             table_name,
             TreeAreaLocators.sub_nodes_of_tables_node)
@@ -176,19 +178,20 @@ CREATE TABLE public.nonintpkey
 
         self.page.wait_for_query_tool_loading_indicator_to_disappear()
         # Run test to insert a new row in table with default values
-        self._add_row()
-        self._verify_row_data(True, config_data['add'])
+        self._add_row(config_data_local)
+        self._verify_row_data(True, config_data_local['add'])
 
         # Run test to copy/paste a row
-        self._copy_paste_row()
+        self._copy_paste_row(config_data_local)
 
-        self._update_row()
+        self._update_row(config_data_local)
         self.page.click_tab("Messages")
         self._verify_messsages("")
         self.page.click_tab("Data Output")
         updated_row_data = {
-            i: config_data['update'][i] if i in config_data['update'] else val
-            for i, val in config_data['add'].items()
+            i: config_data_local['update'][i] if i in config_data_local[
+                'update'] else val
+            for i, val in config_data_local['add'].items()
         }
         self._verify_row_data(False, updated_row_data)
 
@@ -219,7 +222,6 @@ CREATE TABLE public.nonintpkey
         Returns: None
 
         """
-
         self.wait.until(EC.visibility_of_element_located(
             (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
         )
@@ -236,7 +238,7 @@ CREATE TABLE public.nonintpkey
             if value == 'clear':
                 cell_el.find_element_by_css_selector('input').clear()
             else:
-                ActionChains(self.driver).send_keys(value).\
+                ActionChains(self.driver).send_keys(value). \
                     send_keys(Keys.ENTER).perform()
         elif cell_type in ['text', 'json', 'text[]', 'boolean[]']:
             text_area_ele = self.page.find_by_css_selector(
@@ -288,7 +290,7 @@ CREATE TABLE public.nonintpkey
             self.page.driver.find_element_by_tag_name('iframe')
         )
 
-    def _copy_paste_row(self):
+    def _copy_paste_row(self, config_data_l):
         row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath("r0", 1)
 
         self.page.find_by_xpath(row0_cell0_xpath).click()
@@ -298,12 +300,12 @@ CREATE TABLE public.nonintpkey
             QueryToolLocators.paste_button_css).click()
 
         # Update primary key of copied cell
-        self._add_update_save_row(config_data['copy'], row=2)
+        self._add_update_save_row(config_data_l['copy'], row=2)
 
         # Verify row 1 and row 2 data
         updated_row_data = {
-            i: config_data['copy'][i] if i in config_data['copy'] else val
-            for i, val in config_data['add'].items()
+            i: config_data_l['copy'][i] if i in config_data_l['copy'] else val
+            for i, val in config_data_l['add'].items()
         }
         self._verify_row_data(False, updated_row_data)
 
@@ -326,11 +328,11 @@ CREATE TABLE public.nonintpkey
         # save ajax is completed.
         time.sleep(2)
 
-    def _add_row(self):
-        self._add_update_save_row(config_data['add'], 1)
+    def _add_row(self, config_data_l):
+        self._add_update_save_row(config_data_l['add'], 1)
 
-    def _update_row(self):
-        self._add_update_save_row(config_data['update'], 1)
+    def _update_row(self, config_data_l):
+        self._add_update_save_row(config_data_l['update'], 1)
 
     def _verify_messsages(self, text):
         messages_ele = self.page.find_by_css_selector(
diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
index 9efad8a36..69c3cba80 100644
--- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
+++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py
@@ -211,17 +211,23 @@ class CheckForXssFeatureTest(BaseFeatureTest):
             "Query tool (History Entry)"
         )
 
-        # Check for history details message
-        history_ele = self.driver\
-            .find_element_by_css_selector(".query-detail .content-value")
-
-        source_code = history_ele.get_attribute('innerHTML')
+        retry = 2
+        while retry > 0:
+            try:
+                history_ele = self.driver \
+                    .find_element_by_css_selector(
+                        ".query-detail .content-value")
+                source_code = history_ele.get_attribute('innerHTML')
+                break
+            except StaleElementReferenceException:
+                retry -= 1
 
         self._check_escaped_characters(
             source_code,
             '&lt;script&gt;alert(1)&lt;/script&gt;',
             "Query tool (History Details-Message)"
         )
+
         retry = 2
         while retry > 0:
             try:
diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py
index 35493e95d..98be31328 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -118,9 +118,8 @@ class BaseTestGenerator(unittest.TestCase):
                     self.skipTest('cannot run in: %s' %
                                   server_con['data']['type'])
 
-    @classmethod
-    def setTestServer(cls, server):
-        cls.server = server
+    def setTestServer(self, server):
+        self.server = server
 
     @abstractmethod
     def runTest(self):
@@ -135,17 +134,14 @@ class BaseTestGenerator(unittest.TestCase):
     def setTestClient(cls, test_client):
         cls.tester = test_client
 
-    @classmethod
-    def setDriver(cls, driver):
-        cls.driver = driver
+    def setDriver(self, driver):
+        self.driver = driver
 
-    @classmethod
-    def setServerInformation(cls, server_information):
-        cls.server_information = server_information
+    def setServerInformation(self, server_information):
+        self.server_information = server_information
 
-    @classmethod
-    def setTestDatabaseName(cls, database_name):
-        cls.test_db = database_name
+    def setTestDatabaseName(self, database_name):
+        self.test_db = database_name
 
     @classmethod
     def setReSQLModuleList(cls, module_list):
diff --git a/web/regression/README b/web/regression/README
index a4591acb2..314e58da9 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,28 @@ Python Tests:
   and registered automatically by its module name in
   'pgadmin4/web/pgadmin/utils/test.py' file.
 
+- To run Feature Tests in parallel using selenoid(grid + docker), selenoid
+  need to be installed. Steps to install selenoid -
+  - Install & Start docker
+    $yum -y install docker docker-registry
+    $vi /etc/sysconfig/docker   # in OPTIONS add ‘--selinux-enabled=false’
+    $systemctl enable docker.service
+    $systemctl start docker.service
+    $systemctl status docker.service
+  - Install & Start Selenoid
+    $curl -s https://aerokube.com/cm/bash | bash
+    $./cm selenoid start --vnc -g -limit 3 -cpu 1.5
+    $./cm selenoid-ui start
+    Check selenoid status -
+    http://<IP address of Selenoid Installed machine>:4444/status
+            - Should show json with browsers details
+    http://<IP address of Selenoid Installed machine>:8080/#/
+            - Capabilities shows available browser
+  - Update config_local.py if exists else create new with 'DEFAULT_SERVER' value
+    DEFAULT_SERVER = '<Your System IP>'
+  - Update 'test_config.json' with selenoid related info like selenoid url
+    & browser details where tests need to be executed
+
 - Change to the regression test directory:
      run 'cd web/regression'
 
@@ -190,6 +212,9 @@ Python Tests:
      Example 2)  Execute only reverse engineered SQL test framework for some modules
          run 'python runtests.py --pkg resql --modules sequences,functions'
 
+- Execute ui selenium tests in parallel using selenoid(selenium grid + docker)
+     Example : --pkg feature_tests --parallel
+
 Code Coverage:
 ---------------
 
diff --git a/web/regression/feature_utils/app_starter.py b/web/regression/feature_utils/app_starter.py
index fe4c441a1..7de10b25c 100644
--- a/web/regression/feature_utils/app_starter.py
+++ b/web/regression/feature_utils/app_starter.py
@@ -61,11 +61,16 @@ class AppStarter:
                     raise Exception('Unable to start python server even after '
                                     'retrying 60 times.')
 
-        launch_browser(0)
+        if self.driver is not None:
+            launch_browser(0)
+        else:
+            return "http://" + self.app_config.DEFAULT_SERVER + ":" \
+                   + random_server_port
 
     def stop_app(self):
         """ This function stop the started app by killing process """
-        self.driver.quit()
+        if self.driver is not None:
+            self.driver.quit()
         # os.killpg supported in Mac and Unix as this function not supported in
         # Windows
         try:
diff --git a/web/regression/feature_utils/locators.py b/web/regression/feature_utils/locators.py
index b8169fa59..0df8e61dd 100644
--- a/web/regression/feature_utils/locators.py
+++ b/web/regression/feature_utils/locators.py
@@ -172,6 +172,8 @@ class QueryToolLocators:
 
     new_row_xpath = "//div[contains(@class, 'new-row')]"
 
+    scratch_pad_css = ".sql-scratch > textarea"
+
     copy_button_css = "#btn-copy-row"
 
     paste_button_css = "#btn-paste-row"
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index aef031fd0..4d6f15e50 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -88,11 +88,17 @@ class PgadminPage:
             (By.CSS_SELECTOR, "button[type='save'].btn.btn-primary")))
         self.find_by_css_selector("button[type='save'].btn.btn-primary").\
             click()
-
-        WebDriverWait(self.driver, 10).until(
-            EC.visibility_of_element_located(
-                (By.XPATH,
-                 "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
+        try:
+            WebDriverWait(self.driver, 10).until(
+                EC.visibility_of_element_located(
+                    (By.XPATH,
+                     "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
+        except TimeoutException:
+            self.toggle_open_servers_group()
+            WebDriverWait(self.driver, 10).until(
+                EC.visibility_of_element_located(
+                    (By.XPATH,
+                     "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
 
     def open_query_tool(self):
         self.driver.find_element_by_link_text("Tools").click()
@@ -910,7 +916,11 @@ class PgadminPage:
                     return element
             except (NoSuchElementException, WebDriverException):
                 return False
-
+        time.sleep(1)
+        self.driver.switch_to.default_content()
+        self.driver.switch_to_frame(
+            self.driver.find_element_by_tag_name("iframe"))
+        self.find_by_xpath("//a[text()='Query Editor']").click()
         codemirror_ele = WebDriverWait(
             self.driver, timeout=self.timeout, poll_frequency=0.01)\
             .until(find_codemirror,
@@ -1161,3 +1171,12 @@ class PgadminPage:
             except Exception:
                 attempt += 1
         return click_status
+
+    def paste_values(self, el=None):
+        actions = ActionChains(self.driver)
+        if el:
+            el.click()
+            actions.key_down(Keys.SHIFT)
+            actions.send_keys(Keys.INSERT)
+            actions.key_up(Keys.SHIFT)
+            actions.perform()
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc2403..71b75df7b 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -17,6 +17,13 @@ import sqlite3
 import shutil
 from functools import partial
 from testtools.testcase import clone_test_with_new_id
+import re
+import time
+from selenium.common.exceptions import WebDriverException
+import urllib.request as urllib
+import json
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
 
 import config
 import regression
@@ -1216,3 +1223,144 @@ def create_expected_output(parameters, actual_data):
             actual_data.remove(value)
             break
     return expected_output
+
+
+def is_parallel_ui_tests(args):
+    """
+    This function checks for coverage args exists in command line args
+    :return: boolean
+    """
+    if "parallel" in args and args["parallel"]:
+        return True
+    return False
+
+
+def get_selenium_grid_status_and_browser_list(selrnoid_url):
+    """
+    This function checks selenoid status for given url
+    :param selrnoid_url:
+    :return: status of selenoid & list of browsers available with selenoid if
+    status is up
+    """
+    selenoid_status = False
+    browser_list = []
+    try:
+        selenoid_status = urllib.urlopen(
+            "http://" + re.split('/', (re.split('//', selrnoid_url, 1)[1]))[
+                0] + "/status", timeout=10)
+        selenoid_status = json.load(selenoid_status)
+        if isinstance(selenoid_status, dict):
+            available_browsers = selenoid_status["browsers"]
+            selenoid_status = True
+
+        list_of_browsers = test_setup.config_data['selenoid_info'][
+            'cross_Browsers']
+
+        for browser in list_of_browsers:
+            if browser["name"].lower() in available_browsers.keys():
+                versions = available_browsers[(browser["name"].lower())]
+                if browser["version"] in versions.keys():
+                    browser_list.append(browser)
+                else:
+                    print("Available {0} versions {1}".format(browser["name"],
+                                                              versions.keys()))
+                    print("Specified Version = {0}".format(browser["version"]))
+            else:
+                print("{0} is NOT available".format(browser["name"]))
+    except Exception as e:
+        print(str(e))
+        print("Unable to find Selenoid Status")
+
+    return selenoid_status, browser_list
+
+
+def is_feature_test_included(arguments):
+    """
+    :param arguments: his is command line arguments for module name to
+    which test suite will run
+    :return: boolean value whether to execute feature tests or NOT &
+    browser name if feature_test_tobe_included = True
+    """
+    exclude_pkgs = []
+    if arguments['exclude'] is not None:
+        exclude_pkgs += arguments['exclude'].split(',')
+
+    feature_test_tobe_included = 'feature_tests' not in exclude_pkgs and \
+                                 (arguments['pkg'] is None or arguments[
+                                     'pkg'] == "all" or
+                                  arguments['pkg'] == "feature_tests")
+    return feature_test_tobe_included
+
+
+def launch_url_in_browser(driver_instance, url, retry_count):
+    """
+    Function launches urls in sepecided driver instance
+    :param driver_instance:browser instance
+    :param url:url to be launched
+    :param retry_count:
+    :return:
+    """
+    try:
+        driver_instance.get(url)
+    except WebDriverException as e:
+        # In case of WebDriverException sleep for 1 second and retry
+        # again. Retry 10 times and if still app will not start then
+        # raise exception.
+        time.sleep(1)
+        if retry_count < 60:
+            retry_count = retry_count + 1
+            launch_url_in_browser(driver_instance, url, retry_count)
+        else:
+            raise Exception('Unable to start python server even after '
+                            'retrying 60 times.')
+
+
+def get_remote_webdriver(browser, browser_ver, test_name):
+    hub_url = test_setup.config_data['selenoid_info']['selenoid_url']
+    test_name = browser + browser_ver + "_" + test_name + "-" + time.strftime(
+        "%m_%d_%y_%H_%M_%S", time.localtime())
+    driver_local = None
+
+    desired_capabilities = {
+        "version": browser_ver,
+        "enableVNC": True,
+        "enableVideo": True,
+        "enableLog": True,
+        "videoName": test_name + ".mp4",
+        "logName": test_name + ".log",
+        "name": test_name,
+        "timeZone": "Asia/Kolkata"
+    }
+
+    if browser == 'firefox':
+        profile = webdriver.FirefoxProfile()
+        profile.set_preference("dom.disable_beforeunload", True)
+        desired_capabilities["browserName"] = "firefox"
+        desired_capabilities["requireWindowFocus"] = True
+        desired_capabilities["enablePersistentHover"] = False
+        driver_local = webdriver.Remote(
+            command_executor=hub_url,
+            desired_capabilities=desired_capabilities, browser_profile=profile)
+        driver_local.implicitly_wait(1)
+
+    elif browser == 'chrome':
+        options = Options()
+        if test_setup.config_data:
+            if 'headless_chrome' in test_setup.config_data:
+                if test_setup.config_data['headless_chrome']:
+                    options.add_argument("--headless")
+        options.add_argument("--no-sandbox")
+        options.add_argument("--disable-setuid-sandbox")
+        options.add_argument("--window-size=1280,1024")
+        options.add_argument("--disable-infobars")
+        desired_capabilities["browserName"] = "chrome"
+        driver_local = webdriver.Remote(
+            command_executor=hub_url,
+            desired_capabilities=desired_capabilities, options=options)
+    else:
+        print("Specified browser does not exist.")
+
+    # maximize browser window
+    driver_local.maximize_window()
+    driver_local.implicitly_wait(2)
+    return driver_local
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index fcf73a886..d41c962a2 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -21,7 +21,8 @@ import traceback
 import json
 import random
 import coverage
-
+import threading
+import time
 import unittest
 
 if sys.version_info[0] >= 3:
@@ -136,7 +137,7 @@ scenarios.apply_scenario = test_utils.apply_scenario
 
 
 def get_suite(module_list, test_server, test_app_client, server_information,
-              test_db_name):
+              test_db_name, driver_passed):
     """
      This function add the tests to test suite and return modified test suite
       variable.
@@ -166,7 +167,7 @@ def get_suite(module_list, test_server, test_app_client, server_information,
         obj.setApp(app)
         obj.setTestClient(test_app_client)
         obj.setTestServer(test_server)
-        obj.setDriver(driver)
+        obj.setDriver(driver_passed)
         obj.setServerInformation(server_information)
         obj.setTestDatabaseName(test_db_name)
         scenario = scenarios.generate_scenarios(obj)
@@ -207,57 +208,59 @@ def get_test_modules(arguments):
         exclude_pkgs += arguments['exclude'].split(',')
 
     if 'feature_tests' not in exclude_pkgs and \
-            (arguments['pkg'] is None or arguments['pkg'] == "all" or
-             arguments['pkg'] == "feature_tests"):
-
-        from selenium import webdriver
-        from selenium.webdriver.chrome.options import Options
-        from selenium.webdriver.common.desired_capabilities import \
-            DesiredCapabilities
-
-        default_browser = 'chrome'
-
-        # Check default browser provided through command line. If provided
-        # then use that browser as default browser else check for the setting
-        # provided in test_config.json file.
-        if (
-            'default_browser' in arguments and
-            arguments['default_browser'] is not None
-        ):
-            default_browser = arguments['default_browser'].lower()
-        elif (
-            test_setup.config_data and
-            "default_browser" in test_setup.config_data
-        ):
-            default_browser = test_setup.config_data['default_browser'].lower()
-
-        if default_browser == 'firefox':
-            cap = DesiredCapabilities.FIREFOX
-            cap['requireWindowFocus'] = True
-            cap['enablePersistentHover'] = False
-            profile = webdriver.FirefoxProfile()
-            profile.set_preference("dom.disable_beforeunload", True)
-            driver = webdriver.Firefox(capabilities=cap,
-                                       firefox_profile=profile)
-            driver.implicitly_wait(1)
-        else:
-            options = Options()
-            if test_setup.config_data:
-                if 'headless_chrome' in test_setup.config_data:
-                    if test_setup.config_data['headless_chrome']:
-                        options.add_argument("--headless")
-            options.add_argument("--no-sandbox")
-            options.add_argument("--disable-setuid-sandbox")
-            options.add_argument("--window-size=1280,1024")
-            options.add_argument("--disable-infobars")
-            options.add_experimental_option('w3c', False)
-            driver = webdriver.Chrome(chrome_options=options)
-
-        # maximize browser window
-        driver.maximize_window()
-
-        app_starter = AppStarter(driver, config)
-        app_starter.start_app()
+        (arguments['pkg'] is None or arguments['pkg'] == "all" or
+         arguments['pkg'] == "feature_tests"):
+
+        if not test_utils.is_parallel_ui_tests(args):
+            from selenium import webdriver
+            from selenium.webdriver.chrome.options import Options
+            from selenium.webdriver.common.desired_capabilities import \
+                DesiredCapabilities
+
+            default_browser = 'chrome'
+
+            # Check default browser provided through command line. If provided
+            # then use that browser as default browser else check for the
+            # settingn provided in test_config.json file.
+            if (
+                'default_browser' in arguments and
+                arguments['default_browser'] is not None
+            ):
+                default_browser = arguments['default_browser'].lower()
+            elif (
+                test_setup.config_data and
+                "default_browser" in test_setup.config_data
+            ):
+                default_browser = test_setup.config_data[
+                    'default_browser'].lower()
+
+            if default_browser == 'firefox':
+                cap = DesiredCapabilities.FIREFOX
+                cap['requireWindowFocus'] = True
+                cap['enablePersistentHover'] = False
+                profile = webdriver.FirefoxProfile()
+                profile.set_preference("dom.disable_beforeunload", True)
+                driver = webdriver.Firefox(capabilities=cap,
+                                           firefox_profile=profile)
+                driver.implicitly_wait(1)
+            else:
+                options = Options()
+                if test_setup.config_data:
+                    if 'headless_chrome' in test_setup.config_data:
+                        if test_setup.config_data['headless_chrome']:
+                            options.add_argument("--headless")
+                options.add_argument("--no-sandbox")
+                options.add_argument("--disable-setuid-sandbox")
+                options.add_argument("--window-size=1280,1024")
+                options.add_argument("--disable-infobars")
+                options.add_experimental_option('w3c', False)
+                driver = webdriver.Chrome(chrome_options=options)
+
+            # maximize browser window
+            driver.maximize_window()
+
+            app_starter = AppStarter(driver, config)
+            app_starter.start_app()
 
     handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
     # Register cleanup function to cleanup on exit
@@ -319,6 +322,9 @@ def add_arguments():
         '--modules',
         help='Executes the feature test for specific modules in pkg'
     )
+    parser.add_argument('--parallel', nargs='?', const=True,
+                        type=bool, default=False,
+                        help='Enable parallel Feature Tests')
     arg = parser.parse_args()
 
     return arg
@@ -404,6 +410,115 @@ class StreamToLogger(object):
         pass
 
 
+driver_info = []
+
+
+def run_test(test_module_list_passed, server_passed, driver_passed):
+    try:
+        print("\n=============Running the test cases for '%s' ============="
+              % server_passed['name'], file=sys.stderr)
+
+        # Store driver object info & thread id
+        driver_info.append((threading.current_thread().ident, driver_passed))
+
+        # Create test server
+        server_information = \
+            test_utils.create_parent_server_node(server_passed)
+
+        # Create test database with random number to avoid conflict in
+        # parallel execution on different platforms. This database will be
+        # used across all feature tests.
+        test_db_name = "acceptance_test_db" + \
+                       str(random.randint(10000, 65535))
+        connection = test_utils.get_db_connection(
+            server_passed['db'],
+            server_passed['username'],
+            server_passed['db_password'],
+            server_passed['host'],
+            server_passed['port'],
+            server_passed['sslmode']
+        )
+
+        # Add the server version in server information
+        server_information['server_version'] = connection.server_version
+        server_information['type'] = server_passed['type']
+
+        # Drop the database if already exists.
+        test_utils.drop_database(connection, test_db_name)
+
+        # Create database
+        test_utils.create_database(server_passed, test_db_name)
+
+        # Configure preferences for the test cases
+        test_utils.configure_preferences(
+            default_binary_path=server_passed['default_binary_paths'])
+
+        # Get unit test suit
+        suite = get_suite(test_module_list_passed,
+                          server_passed,
+                          test_client,
+                          server_information, test_db_name, driver_passed)
+
+        # Run unit test suit created
+        tests = unittest.TextTestRunner(stream=sys.stderr,
+                                        descriptions=True,
+                                        verbosity=2).run(suite)
+
+        # processing results
+        ran_tests, failed_cases, skipped_cases, passed_cases = \
+            get_tests_result(tests)
+
+        # This is required when some tests are running parallel
+        # & some sequential in case of parallel ui tests
+        if thread.getName() == "sequential_tests":
+            try:
+                if test_result[server_passed['name']][0] is not None:
+                    ran_tests = test_result[server_passed['name']][0] + \
+                        ran_tests
+                    failed_cases.update(test_result[server_passed['name']][1])
+                    skipped_cases.update(test_result[server_passed['name']][2])
+                    passed_cases.update(test_result[server_passed['name']][3])
+                test_result[server_passed['name']] = [ran_tests, failed_cases,
+                                                      skipped_cases,
+                                                      passed_cases]
+            except KeyError:
+                pass
+
+        # Add final results server wise in test_result dict
+        test_result[server_passed['name']] = [ran_tests, failed_cases,
+                                              skipped_cases, passed_cases]
+
+        # Set empty list for 'passed' parameter for each testRun.
+        # So that it will not append same test case name
+        unittest.result.TestResult.passed = []
+
+        # Drop the testing database created initially
+        if connection:
+            test_utils.drop_database(connection, test_db_name)
+            connection.close()
+
+        # Delete test server
+        test_utils.delete_test_server(test_client)
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        print("Exception in {0}".format(threading.current_thread().ident))
+    finally:
+        # Delete web-driver instance
+        thread_name = "parallel_tests" + server_passed['name']
+        if threading.currentThread().getName() == thread_name:
+            for d in driver_info:
+                if d[0] == threading.currentThread().ident:
+                    d[1].quit()
+                    driver_info.remove(d)
+            # Need time by selenoid to clean previous thread before new thread
+            time.sleep(10)
+
+        # Print info about completed tests
+        print(
+            "\n=============Completed the test cases for '%s'============="
+            % server_passed['name'], file=sys.stderr)
+
+
 if __name__ == '__main__':
     # Failure detected?
     failure = False
@@ -423,7 +538,8 @@ if __name__ == '__main__':
     fh = logging.FileHandler(CURRENT_PATH + '/' +
                              'regression.log', 'w', 'utf-8')
     fh.setLevel(logging.DEBUG)
-    fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
+    fh.setFormatter(logging.Formatter('[%(thread)d] ' +
+                                      config.FILE_LOG_FORMAT))
 
     logger = logging.getLogger()
     logger.addHandler(fh)
@@ -451,69 +567,141 @@ if __name__ == '__main__':
         cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
         cov.start()
 
-    try:
-        for server in servers_info:
-            print("\n=============Running the test cases for '%s'============="
-                  % server['name'], file=sys.stderr)
-            # Create test server
-            server_information = test_utils.create_parent_server_node(server)
-
-            # Create test database with random number to avoid conflict in
-            # parallel execution on different platforms. This database will be
-            # used across all feature tests.
-            test_db_name = "acceptance_test_db" + \
-                           str(random.randint(10000, 65535))
-            connection = test_utils.get_db_connection(
-                server['db'],
-                server['username'],
-                server['db_password'],
-                server['host'],
-                server['port'],
-                server['sslmode']
-            )
-
-            # Add the server version in server information
-            server_information['server_version'] = connection.server_version
-            server_information['type'] = server['type']
-
-            # Drop the database if already exists.
-            test_utils.drop_database(connection, test_db_name)
-            # Create database
-            test_utils.create_database(server, test_db_name)
-            # Configure preferences for the test cases
-            test_utils.configure_preferences(
-                default_binary_path=server['default_binary_paths'])
-
-            suite = get_suite(test_module_list,
-                              server,
-                              test_client,
-                              server_information, test_db_name)
-            tests = unittest.TextTestRunner(stream=sys.stderr,
-                                            descriptions=True,
-                                            verbosity=2).run(suite)
-
-            ran_tests, failed_cases, skipped_cases, passed_cases = \
-                get_tests_result(tests)
-            test_result[server['name']] = [ran_tests, failed_cases,
-                                           skipped_cases, passed_cases]
-
-            # Set empty list for 'passed' parameter for each testRun.
-            # So that it will not append same test case name
-            unittest.result.TestResult.passed = []
-
-            if len(failed_cases) > 0:
-                failure = True
-
-            # Drop the testing database created initially
-            if connection:
-                test_utils.drop_database(connection, test_db_name)
-                connection.close()
-
-            # Delete test server
-            test_utils.delete_test_server(test_client)
-    except SystemExit:
-        if handle_cleanup:
-            handle_cleanup()
+    # Check if feature tests included & parallel tests switch passed
+    if test_utils.is_feature_test_included(args) and \
+            test_utils.is_parallel_ui_tests(args):
+
+        # Get selenium grid status & list of available browser out passed
+        selenium_grid_status, list_of_browsers \
+            = test_utils.get_selenium_grid_status_and_browser_list(
+                test_setup.config_data['selenoid_info']['selenoid_url'])
+        # list of files consisting tests that needs to be
+        # executed sequentially
+        sequential_tests_file = [
+            'pgadmin.feature_tests.pg_utilities_backup_restore_test',
+            'pgadmin.feature_tests.pg_utilities_maintenance_test',
+            'pgadmin.feature_tests.keyboard_shortcut_test',
+            'regression.re_sql.tests.test_resql']
+        # If selenium grid is up & browsers are available
+        if selenium_grid_status and len(list_of_browsers) > 0:
+
+            try:
+                # run across browsers
+                for browser_info in list_of_browsers:
+                    # browser info
+                    browser_name = browser_info["name"].lower()
+                    browser_version = browser_info["version"]
+
+                    #  list of tests can be executed in parallel
+                    parallel_tests = list(test_module_list)
+                    for module in test_module_list:
+                        if str(module[0]) in sequential_tests_file:
+                            parallel_tests.remove(module)
+
+                    #  list of tests can be executed in sequentially
+                    sequential_tests = list(
+                        filter(lambda i: i not in parallel_tests,
+                               test_module_list))
+
+                    print(
+                        "===================================================="
+                        "==================\n",
+                        file=sys.stderr
+                    )
+                    print(
+                        "Total Tests # {0}\nParallel Tests # {1}, Sequential "
+                        "Tests # {2}".format(
+                            len(test_module_list), len(parallel_tests),
+                            len(sequential_tests)),
+                        file=sys.stderr)
+                    print("Browser: [Name:{0}, Version: {1}]".format(
+                        browser_name.capitalize(), browser_version),
+                        file=sys.stderr)
+                    print(
+                        "===================================================="
+                        "==================\n",
+                        file=sys.stderr
+                    )
+
+                    # Running Parallel tests
+                    if len(parallel_tests) > 0:
+                        # Create app form source code
+                        app_starter_local = AppStarter(None, config)
+                        client_url = app_starter_local.start_app()
+
+                        try:
+                            for srv in servers_info:
+                                # Get driver instance at server level
+                                driver_obj = test_utils.get_remote_webdriver(
+                                    browser_name,
+                                    browser_version,
+                                    srv['name'])
+                                test_utils.launch_url_in_browser(driver_obj,
+                                                                 client_url, 0)
+
+                                threads = []
+                                t_name = "parallel_tests" + srv['name']
+                                thread = threading.Thread(target=run_test,
+                                                          name=t_name,
+                                                          args=(
+                                                              parallel_tests,
+                                                              srv, driver_obj))
+                                threads.append(thread)
+                                thread.start()
+                            for thread in threads:
+                                thread.join()
+                        except Exception:
+                            traceback.print_exc(file=sys.stderr)
+                            print("Exception in thread {0}".format(
+                                thread.ident()))
+
+                    # Sequential Tests
+                    if len(sequential_tests) > 0:
+                        # Get driver instance
+                        driver_obj2 = test_utils.get_remote_webdriver(
+                            browser_name,
+                            browser_version,
+                            "Sequential_Tests")
+                        test_utils.launch_url_in_browser(driver_obj2,
+                                                         client_url, 0)
+                        try:
+                            t_name = "sequential_tests"
+                            for srv in servers_info:
+                                thread = threading.Thread(target=run_test,
+                                                          name=t_name,
+                                                          args=(
+                                                              sequential_tests,
+                                                              srv,
+                                                              driver_obj2))
+                                thread.start()
+                                thread.join()
+                            driver_obj2.quit()
+                        except Exception:
+                            traceback.print_exc(file=sys.stderr)
+                            print("Exception in thread {0}".format(
+                                thread.ident()))
+
+                    # Clean up environmetn
+                    if app_starter_local:
+                        app_starter_local.stop_app()
+            except SystemExit:
+                if app_starter_local:
+                    app_starter_local.stop_app()
+                if handle_cleanup:
+                    handle_cleanup()
+    else:
+        try:
+            for server in servers_info:
+                thread = threading.Thread(target=run_test, args=(
+                    test_module_list, server, driver))
+                thread.start()
+                thread.join()
+        except SystemExit:
+            if handle_cleanup:
+                handle_cleanup()
+
+    # Pause before printing result in order not to mix output
+    time.sleep(3)
 
     print(
         "\n==============================================================="
@@ -542,6 +730,8 @@ if __name__ == '__main__':
                                  skipped_cases.items()).values())
         total_passed_cases = int(
             test_result[server_res][0]) - total_failed - total_skipped
+        if len(failed_cases) > 0:
+            failure = True
 
         print(
             "%s:\n\n\t%s test%s passed\n\t%s test%s failed%s%s"
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index 0a151e633..07bfacd7b 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,8 @@
 {
   "headless_chrome": false,
   "default_browser": "Chrome",
+  "selenoid_info":{"cross_Browsers": [{"name": "browser_name","version": "browserr_version"}],
+                      "selenoid_url": "Selenoid Url"},
   "pgAdmin4_login_credentials": {
     "new_password": "NEWPASSWORD",
     "login_password": "PASSWORD",


view thread (14+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected]
  Subject: Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
  In-Reply-To: <CAMa=N=NokBUU=gRybJzctGKu4N-H1GWu_peamTHqqTNm+v9CPw@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

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