public inbox for [email protected]  
help / color / mirror / Atom feed
[pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
14+ messages / 4 participants
[nested] [flat]

* [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-04-13 09:10  Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-04-13 09:10 UTC (permalink / raw)
  To: pgadmin-hackers

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",


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-04-13 09:19  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Akshay Joshi @ 2020-04-13 09:19 UTC (permalink / raw)
  To: navnath gadakh <[email protected]>; +Cc: pgadmin-hackers; Yogesh Mahajan <[email protected]>

Hi Navnath

Can you please review it?

On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
[email protected]> wrote:

> 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
>


-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-04-16 09:10  navnath gadakh <[email protected]>
  parent: Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: navnath gadakh @ 2020-04-16 09:10 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; +Cc: pgadmin-hackers; Yogesh Mahajan <[email protected]>

Hi,
I think I am not the right person to review this patch now as I already
reviewed this code offline in the last week. I know the approached Yogesh
has followed, also given some review comments on it.
Someone else please review it.

Thanks!

On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <[email protected]>
wrote:

> Hi Navnath
>
> Can you please review it?
>
> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> 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
>>
>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>


-- 
Regards,
Navnath Gadakh


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-04-21 07:48  Shubham Agarwal <[email protected]>
  parent: navnath gadakh <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Shubham Agarwal @ 2020-04-21 07:48 UTC (permalink / raw)
  To: navnath gadakh <[email protected]>; +Cc: Akshay Joshi <[email protected]>; pgadmin-hackers; Yogesh Mahajan <[email protected]>

Hi Yogesh,
Below are the review comments-

1. runtests.py
    a. The exception traceback logic at line number 653 in runtests.py is
not correct since it is particular to the thread
but there is much more code in that block which can throw some exception.
b. line number 447 -> The drop_database function will only try to drop the
database with the name which is newly created
at 431 line number, its probability is 1% instead of this you can write a
logic so that it will drop all the database which starts with name
‘acceptance_test_db'.
  c. line 584 - Why we are including resql test case execution in GUI
execution logic.
    d. Change the function name run_test as script name is also runtests.py

2. test_utils.py
    a. Remove the headless chrome code from get_remote_webdriver() in
test_utils.py since we are using solenoid and it is not required anymore.
    b. Create separate functions to instantiate the firefox driver and
chrome driver logic since the same code is used in multiple files.
c. launch_url_in_browser() -> you can simplify the definition of the
function like:
    retry = 60
        *while *retry > 0:
            try:
                driver.get(url)
            except WebDriverException:
                 retry -= 1
3. Execution logs are not printing as per the logic some time, I ran the
suite for two servers and attached are the execution logs.
4. Readme -
Please provide the Valid selenoid URL to be provided in the
test_config.json, with all the steps mentioned in the readme it is not
clear.
Revisit the readme and write the missing steps.
5. copy_selected_query_results_feature_tests.py-
Create the function to avoid duplicate code. The code for pasting the
values is repeating 8 times in the test code.
6. Provide the valid docstring in newly introduced functions and also valid
comments while calling it. for ex.- _update_preference() function is
introduced in pg_utilities_backup_restrore_test.py but from the function
name, it is not clear what preferences are going to update in it.
7. test_index_constraint_add test case is failing due to the latest change,
please merge and update this test case

On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
[email protected]> wrote:

> Hi,
> I think I am not the right person to review this patch now as I already
> reviewed this code offline in the last week. I know the approached Yogesh
> has followed, also given some review comments on it.
> Someone else please review it.
>
> Thanks!
>
> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
> [email protected]> wrote:
>
>> Hi Navnath
>>
>> Can you please review it?
>>
>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>> [email protected]> wrote:
>>
>>> 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
>>>
>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>>
>> *Sr. Software Architect*
>> *EnterpriseDB Software India Private Limited*
>> *Mobile: +91 976-788-8246*
>>
>
>
> --
> Regards,
> Navnath Gadakh
>


-- 
Thanks & Regards,
Shubham Agarwal
EnterpriseDB Corporation

The Postgres Database Company


Attachments:

  [application/octet-stream] regression.log (23.7K, 3-regression.log)
  download

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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-01 06:57  Yogesh Mahajan <[email protected]>
  parent: Shubham Agarwal <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-05-01 06:57 UTC (permalink / raw)
  To: Shubham Agarwal <[email protected]>; pgadmin-hackers; +Cc: navnath gadakh <[email protected]>; Akshay Joshi <[email protected]>

Hi,

Please find updated patch modified according to review comments -
Patch implements below things -
1.Enable the current framework to provide option to execute Feature tests
in parallel  on selenium grid set up.
   - Addition of new switch to start parallel features tests.
   - New parameters with respect to selenoid in test_config.json.in
   - Addition of new script to check solenoid updates.



Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
[email protected]> wrote:

> Hi Yogesh,
> Below are the review comments-
>
> 1. runtests.py
>     a. The exception traceback logic at line number 653 in runtests.py is
> not correct since it is particular to the thread
> but there is much more code in that block which can throw some exception.
> b. line number 447 -> The drop_database function will only try to drop
> the database with the name which is newly created
> at 431 line number, its probability is 1% instead of this you can write a
> logic so that it will drop all the database which starts with name
> ‘acceptance_test_db'.
>   c. line 584 - Why we are including resql test case execution in GUI
> execution logic.
>     d. Change the function name run_test as script name is also
> runtests.py
>
> 2. test_utils.py
>     a. Remove the headless chrome code from get_remote_webdriver() in
> test_utils.py since we are using solenoid and it is not required anymore.
>     b. Create separate functions to instantiate the firefox driver and
> chrome driver logic since the same code is used in multiple files.
> c. launch_url_in_browser() -> you can simplify the definition of the
> function like:
>     retry = 60
>         *while *retry > 0:
>             try:
>                 driver.get(url)
>             except WebDriverException:
>                  retry -= 1
> 3. Execution logs are not printing as per the logic some time, I ran the
> suite for two servers and attached are the execution logs.
> 4. Readme -
> Please provide the Valid selenoid URL to be provided in the
> test_config.json, with all the steps mentioned in the readme it is not
> clear.
> Revisit the readme and write the missing steps.
> 5. copy_selected_query_results_feature_tests.py-
> Create the function to avoid duplicate code. The code for pasting the
> values is repeating 8 times in the test code.
> 6. Provide the valid docstring in newly introduced functions and also
> valid comments while calling it. for ex.- _update_preference() function is
> introduced in pg_utilities_backup_restrore_test.py but from the function
> name, it is not clear what preferences are going to update in it.
> 7. test_index_constraint_add test case is failing due to the latest
> change, please merge and update this test case
>
> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
> [email protected]> wrote:
>
>> Hi,
>> I think I am not the right person to review this patch now as I already
>> reviewed this code offline in the last week. I know the approached Yogesh
>> has followed, also given some review comments on it.
>> Someone else please review it.
>>
>> Thanks!
>>
>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Navnath
>>>
>>> Can you please review it?
>>>
>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>> [email protected]> wrote:
>>>
>>>> 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
>>>>
>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect*
>>> *EnterpriseDB Software India Private Limited*
>>> *Mobile: +91 976-788-8246*
>>>
>>
>>
>> --
>> Regards,
>> Navnath Gadakh
>>
>
>
> --
> Thanks & Regards,
> Shubham Agarwal
> EnterpriseDB Corporation
>
> The Postgres Database Company
>


Attachments:

  [application/octet-stream] Selenium_Grid_Implementation_ver2.0.patch (155.5K, 3-Selenium_Grid_Implementation_ver2.0.patch)
  download | inline diff:
diff --git a/tools/requirements.txt b/tools/requirements.txt
index 87ecc2a..2804012 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -1,3 +1,4 @@
 requests>=2.21.0
 requests[security]>=2.21.0
 safety==1.8.5
+pyjq==2.4.0
diff --git a/tools/update_selenoid_browsers.py b/tools/update_selenoid_browsers.py
new file mode 100644
index 0000000..ac4d905
--- /dev/null
+++ b/tools/update_selenoid_browsers.py
@@ -0,0 +1,265 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+# #########################################################################
+# Updates browser images(selenoid-docker) depending on arguments passed while
+# running this script.
+
+import argparse
+import os
+import subprocess
+import sys
+import traceback
+import requests
+import pyjq
+import json
+
+
+def read_command_line():
+    """Read the command line arguments.
+    Returns:
+        ArgumentParser: The parsed arguments object
+
+    """
+    parser = argparse.ArgumentParser(
+        description='Get latest browser images(chrome & firefox) for selenoid.'
+                    'e.g. - --chrome /usr/bin/google-chrome --firefox '
+                    '/usr/bin/firefox')
+    parser.add_argument("--chrome", metavar="CHROME",
+                        help="the Chrome executable path")
+    parser.add_argument("--firefox", metavar="FIREFOX",
+                        help="the firefox executable path")
+    args_val = parser.parse_args()
+    return args_val
+
+
+def get_browser_version(browser_name, executable_path):
+    """
+    Function returns browser version for specified browser using executable
+    path passed in arguments.
+    :param browser_name:
+    :param executable_path: e.g. /usr/bin/firefox
+    :return: browser version
+    """
+    # On Linux/Mac we run the browser executable with the --version flag,
+    # then parse the output.
+    browser_version_val = None
+    try:
+        result = subprocess.Popen([executable_path, '--version'],
+                                  stdout=subprocess.PIPE)
+    except FileNotFoundError:
+        print('The specified browser executable could not be found.')
+        sys.exit(1)
+
+    version_str = result.stdout.read().decode("utf-8")
+
+    if browser_name.lower() == "chrome":
+        # Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
+        if "Chrom" not in version_str:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        # On some linux distro `chrome--version` gives output like
+        # 'Google Chrome 80.0.3987.132 unknown\n'
+        # so we need to check and remove the unknown string from the version
+        if version_str.endswith("unknown\n"):
+            version_str = version_str.strip("unknown\n").strip()
+
+        chrome_version = '.'.join(version_str.split()[-1].split('.')[:-2])
+
+        # Make sure browser version has only 1 decimal point
+        if chrome_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = chrome_version
+    elif browser_name.lower() == "firefox":
+        if "Firefox" not in version_str:
+            print('The specified Firefox executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+
+        # firefox --version gives output like
+        # 'Running without a11y support!
+        # Mozilla Firefox 68.7.0esr'
+        firefox_version = '.'.join(
+            version_str.split()[-1].split('.')[:-2]) + '.0'
+
+        # Make sure browser version has only 1 decimal point
+        if firefox_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = firefox_version
+    else:
+        print("{0} is not recognised ".format(browser_name))
+        sys.exit(1)
+    return browser_version_val
+
+
+def check_and_download_vnc_browser_image(browser_name, browser_version):
+    """
+    Function checks presence for vnc images for passed browser
+    at docker.io/selenoid/ registry
+    :param browser_name:
+    :param browser_version:
+    :return:true if browser image is available & downloaded else false
+    """
+    res = requests.get(
+        'https://registry.hub.docker.com/v2/repositories/selenoid/vnc_' +
+        browser_name + '/tags/')
+    res = res.json()
+    version_tag = pyjq.all('.["results"][]["name"]', res)
+    vnc_image_available = False
+    image_name = 'vnc_' + browser_name + ':' + browser_version
+
+    for idx, tag in enumerate(version_tag):
+        if browser_version == tag:
+            command = 'docker pull selenoid/vnc_' + browser_name + ':' \
+                      + browser_version
+            print(' VNC image is available & downloading now... {0}'.format(
+                command))
+            try:
+                subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+                vnc_image_available = True
+            except Exception:
+                traceback.print_exc(file=sys.stderr)
+                print(
+                    '{0}} Image found but could not download.'.format(command))
+                sys.exit(1)
+            break
+        elif idx == len(version_tag):
+            print("{0} Image is not available.".format(image_name))
+            vnc_image_available = False
+        else:
+            pass
+    return vnc_image_available
+
+
+def reload_selenoid_config():
+    """
+    Function runs command to refresh selenoid configuration
+    :return: true if command execution for selenoid reload is successful
+    else false
+    """
+    command = 'docker kill -s HUP selenoid'
+    reload_successful = False
+    try:
+        subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+        print(" Selenoid Configuration is reloaded.")
+        reload_successful = True
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        print('Error while reloading selenoid configuration.')
+        sys.exit(1)
+    return reload_successful
+
+
+def edit_browsers_json(browser_name, browser_version):
+    """
+    Function edits browsers.json which is used by selenoid to
+    load browser configuration.
+    Default path for this file is
+    "user_home_dir + '/.aerokube/selenoid/browsers.json'"
+    Currently this is hardcoded, might need to modify
+    if we want to pass customize browsers.json
+    :param browser_name:
+    :param browser_version:
+    :return:
+    """
+    file_edited = True
+    # Read existing browsers.json
+    json_file = open(file_path, 'r')
+    existing_data = json.load(json_file)
+    updated_data = None
+
+    # Update data for new browser images
+    if browser_name.lower() == 'chrome':
+        version_data = existing_data['chrome']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_chrome:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['chrome']['versions']).update(data_to_insert)
+            updated_data = existing_data
+            print(updated_data)
+
+    elif browser_name.lower() == 'firefox':
+        version_data = existing_data['firefox']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_firefox:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['firefox']['versions']).update(data_to_insert)
+            updated_data = existing_data
+    else:
+        print("Browser version not matched")
+        file_edited = False
+
+    # Write updated data in browsers.json
+    if updated_data is not None:
+        json_file = open(file_path, 'w')
+        json.dump(updated_data, json_file)
+        print(" 'browsers.json' is updated for {0} {1}".format(
+            browser_name, browser_version))
+
+        file_edited = True
+    return file_edited
+
+
+# Main Program starts here
+# Read command line arguments & get list of browser_name, executable path.
+args = vars(read_command_line())
+
+# Get path path for browsers.json
+user_home_dir = os.getenv("HOME")
+file_path = user_home_dir + '/.aerokube/selenoid/browsers.json'
+print("***** Updating '{0}' for new browser versions.*****".format(file_path))
+
+# Iterate over arguments passed
+for browser, executable_path in args.items():
+    if executable_path is not None:
+        # Get browser name
+        browser_name = browser
+        # Get browser version
+        browser_version = get_browser_version(browser, executable_path)
+        print(
+            " Browser version for {0} is {1} in current executable path ".
+            format(browser_name, browser_version))
+
+        # Download vnc browser image.
+        download_new_image = check_and_download_vnc_browser_image(
+            browser_name, browser_version)
+
+        # If browser vnc image is available, then edit browsers.json
+        if download_new_image:
+            if edit_browsers_json(browser_name, browser_version):
+                print(
+                    " File 'browsers.json' is updated for {0} - {1} \n".format(
+                        browser_name, browser_version))
+            else:
+                print(
+                    " File 'browsers.json' can NOT be updated for {0} - {1} \n"
+                    .format(browser_name, browser_version))
+        else:
+            print(" Browser image is not available for {0}, {1}".format(
+                browser_name, browser_version))
+
+# Reload selenoid configuration
+if reload_selenoid_config():
+    print(
+        "***** Updated '{0}' for new browser versions.*****".format(file_path))
diff --git a/web/config.py b/web/config.py
index b35d8a2..374cd6e 100644
--- a/web/config.py
+++ b/web/config.py
@@ -566,6 +566,9 @@ try:
 except ImportError:
     pass
 
+# Override DEFAULT_SERVE value from environment variable.
+if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
+    DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
 
 # SUPPORT_SSH_TUNNEL can be override in local config file and if that
 # setting is False in local config then we should not check the Python version.
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 f127063..88526ca 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
@@ -44,31 +44,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)
@@ -81,10 +80,9 @@ 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)
 
 
 class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
@@ -117,30 +115,28 @@ class ConstraintsUsingIndexAddTestCase(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 "
                             "constraint using index.")
-        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_constraint_%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_constraint_%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)
@@ -158,7 +154,6 @@ class ConstraintsUsingIndexAddTestCase(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 7eea5e9..f35a00b 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 c08c2ac..f838a2f 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 87289a6..6bdf703 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 bbed269..02203d2 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 0a2963a..8e5f665 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 c84276e..1f63ef5 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 d6180c4..7619ee6 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 7091ffa..1f3bd65 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
@@ -60,8 +59,18 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self._mouseup_outside_grid_still_makes_a_selection()
         self._copies_rows_with_header()
 
+    def paste_values_to_scratch_pad(self):
+        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")
+        scratch_pad_ele.clear()
+        return clipboard_text
+
     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 +79,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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 +95,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +111,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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,11 +127,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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()
@@ -128,15 +140,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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 +166,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             self.page.driver
         ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
-            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
 
     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 +192,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             Keys.CONTROL
         ).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +209,13 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
-            pyperclip.paste())
+            clipboard_text)
 
     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 +230,9 @@ 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())
+        clipboard_text = self.paste_values_to_scratch_pad()
+
+        self.assertIn('"cool info"', clipboard_text)
 
     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 6d01286..33f86ad 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/keyboard_shortcut_test.py b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
index ea381b4..f68e530 100644
--- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py
+++ b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
@@ -94,24 +94,33 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
             NavMenuLocators.preference_menu_item_css)
         pref_menu_item.click()
 
-        # Wait till the preference dialogue box is displayed by checking the
-        # visibility of Show System Object label
-        self.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()
-
         browser_node = self.page.find_by_xpath(
             NavMenuLocators.specified_preference_tree_node.format('Browser'))
         if self.page.find_by_xpath(
             NavMenuLocators.specified_pref_node_exp_status.
                 format('Browser')).get_attribute('aria-expanded') == 'false':
-
             ActionChains(self.driver).double_click(browser_node).perform()
 
+        display_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
+                'Browser', 'Display'))
+        attempt = 5
+        while attempt > 0:
+            display_node.click()
+            # After clicking the element gets loaded in to the dom but still
+            # not visible, hence sleeping for a sec.
+            time.sleep(1)
+            if self.page.wait_for_element_to_be_visible(
+                self.driver,
+                    NavMenuLocators.show_system_objects_pref_label_xpath, 3):
+                break
+            else:
+                attempt -= 1
+
+        maximize_button = self.page.find_by_css_selector(
+            NavMenuLocators.maximize_pref_dialogue_css)
+        maximize_button.click()
+
         keyboard_node = self.page.find_by_xpath(
             NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
                 'Browser', 'Keyboard shortcuts'))
diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
index dba4ead..baccc70 100644
--- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py
+++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
@@ -92,6 +92,18 @@ class PGDataypeFeatureTest(BaseFeatureTest):
 
         wait = WebDriverWait(self.page.driver, 10)
 
+        browser_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_preference_tree_node.format('Browser'))
+        if self.page.find_by_xpath(
+            NavMenuLocators.specified_pref_node_exp_status.
+                format('Browser')).get_attribute('aria-expanded') == 'false':
+            ActionChains(self.driver).double_click(browser_node).perform()
+
+        self.page.retry_click(
+            (By.XPATH, NavMenuLocators.specified_sub_node_of_pref_tree_node.
+             format('Browser', 'Display')),
+            (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
+
         # Wait till the preference dialogue box is displayed by checking the
         # visibility of Show System Object label
         wait.until(EC.presence_of_element_located(
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 1d6d7a4..4cf9282 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,74 @@ 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):
+        """
+        Function updates preferences for binary path.
+        """
+        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 2d7c4ef..ddb4e3c 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 3f53d86..21921cc 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,20 +178,21 @@ 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._add_row(config_data_local)
         self._verify_row_data(row_height=0,
-                              config_check_data=config_data['add'])
+                              config_check_data=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(row_height=0,
                               config_check_data=updated_row_data)
@@ -221,7 +224,6 @@ CREATE TABLE public.nonintpkey
         Returns: None
 
         """
-
         self.wait.until(EC.visibility_of_element_located(
             (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
         )
@@ -238,7 +240,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(
@@ -290,7 +292,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()
@@ -300,12 +302,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(row_height=25,
                               config_check_data=updated_row_data)
@@ -329,11 +331,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 9efad8a..69c3cba 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 a80310e..faff52a 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -120,9 +120,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):
@@ -137,17 +136,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 46adfcc..a764565 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,59 @@ 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 --args "-limit 3 -cpu 1.5 -mem 1.5g"
+    $./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
+    Note : In --args "-limit 3 -cpu 1.5 -mem 1.5g"
+                -limit 3 :limits maximum parallel sessions(dockers) in selenoid,
+                -cpu :limit memory and CPU usage,
+                -mem :limit memory per session.
+           Generally max parallel session is the number of cores * 1.5 – 2
+           You can list available flags by using ./cm selenoid args
+    Additional Information about tool
+            - https://aerokube.com/selenoid/latest/
+
+  - Update 'test_config.json' with selenoid related info like
+    pgAdmin_default_server -
+        It is the IP address for the machine where pgadmin source code is
+        present.
+        You can get it on linux running command  'ifconfig | grep inet'
+        e.g. - 192.168.143.121
+    max_parallel_sessions -
+        This is other way to control number of tests to be run in parallel.
+        This should be equal or less than limit specified while setting up
+        selenoid
+    cross_Browsers -
+        List of browser name & version enclosed in {} on which tests to be
+        executed.
+        Make sure list contains those browsers & versions only which are shown
+        in capabilities tab while in selenoid status web-page.
+        If version is mention as null, then latest version available in
+        selenoiod server will be used for execution
+        e.g. - [ {"name": "Chrome","version": "80.0"},
+                 {"name": "Firefox","version": "74.0"}]
+    selenoid url -
+        Url formed as below -
+        http://<IP address of Selenoid Installed machine>:4444/wd/hub/
+        e.g. - selenoid_url": "http://192.168.143.121:4444/wd/hub"
+
+
 - Change to the regression test directory:
      run 'cd web/regression'
 
@@ -190,9 +243,14 @@ Python Tests:
      Example 2)  Execute only reverse engineered SQL test framework for some modules
          run 'python runtests.py --pkg resql --modules sequences,functions'
 
+
      Example 3) Exclude reverse engineered SQL test framework for all modules
          run 'python runtests.py --exclude resql'
 
+- 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 fe4c441..7de10b2 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 b8169fa..4756231 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"
@@ -217,9 +219,9 @@ class QueryToolLocators:
     btn_commit = "#btn-commit"
 
     show_query_internally_btn = \
-        "//div[label[normalize-space(" \
-        "text())='Show queries generated internally by pgAdmin?']]" \
-        "//div[contains(@class,'toggle btn')]"
+        "//div[label[contains(normalize-space(text())," \
+        "'Show queries generated internally by')]]//" \
+        "div[contains(@class,'toggle btn')]"
 
     editable_column_icon_xpath = "//div[contains(@class," \
                                  " 'editable-column-header-icon')]" \
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index d7e8fe3..98c1826 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,23 @@ 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()
+
+    def wait_for_element_to_be_visible(self, driver, xpath, time_value=20):
+        """This will wait until an element is visible on page"""
+        element_located_status = False
+        try:
+            if WebDriverWait(driver, time_value).until(
+                EC.visibility_of_element_located((By.XPATH, xpath))):
+                element_located_status = True
+        except TimeoutException:
+            element_located_status = False
+        return element_located_status
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc24..e94a8b9 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -8,6 +8,8 @@
 ##########################################################################
 
 from __future__ import print_function
+
+import fileinput
 import traceback
 import os
 import sys
@@ -16,7 +18,17 @@ import psycopg2
 import sqlite3
 import shutil
 from functools import partial
+
+from selenium.webdriver.support.wait import WebDriverWait
 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
+from selenium.webdriver.support import expected_conditions as ec
 
 import config
 import regression
@@ -1216,3 +1228,247 @@ 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 = get_selenium_grid_status_json(selrnoid_url)
+
+        # 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
+
+        if selenoid_status:
+            available_browsers = selenoid_status["browsers"]
+            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"] is None:
+                        print("Specified version is None. Hence will use "
+                              "latest version available with selenoid server")
+                        browser_list.append(browser)
+                    elif 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, title='pgAdmin 4', timeout=40):
+    """
+    Function launches urls in specified driver instance
+    :param driver_instance:browser instance
+    :param url:url to be launched
+    :param title:web-page tile on successful launch default is 'pgAdmin 4'
+    :param timeout:in seconds for getting specified title default is 20sec
+    :return:
+    """
+    count = timeout / 10
+    while count > 0:
+        try:
+            driver_instance.get(url)
+            wait = WebDriverWait(driver_instance, 10)
+            wait.until(ec.title_is(title))
+            break
+        except WebDriverException as e:
+            count -= 1
+            if count == 0:
+                raise Exception(
+                    'Web-page title did not match to {0} \n'.format(title))
+
+
+def get_remote_webdriver(hub_url, browser, browser_ver, test_name):
+    """
+    This functions returns remote web-driver instance created in selenoid
+    machine.
+    :param hub_url
+    :param browser: browser name
+    :param browser_ver: version for browser
+    :param test_name: test name
+    :return: remote web-driver instance for specified browser
+    """
+    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)
+    elif browser == 'chrome':
+        options = Options()
+        options.add_argument("--window-size=1280,1024")
+        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
+
+
+def get_parallel_sequential_module_list(module_list):
+    """
+    Functions segregate parallel & sequential modules
+    :param module_list: Complete list of modules
+    :return: parallel & sequential module lists
+    """
+    # 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']
+
+    #  list of tests can be executed in parallel
+    parallel_tests = list(module_list)
+    for module in 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,
+               module_list))
+
+    # return parallel & sequential lists
+    return parallel_tests, sequential_tests
+
+
+def get_browser_details(browser_info_dict, url):
+    """
+    Function extracts browser name & version from browser info dict
+    in test_config.json
+    :param browser_info_dict:
+    :return: browser name & version
+    """
+    browser_name = browser_info_dict["name"].lower()
+    browser_version = browser_info_dict["version"]
+    if browser_version is None:
+        selenoid_status = get_selenium_grid_status_json(url)
+        versions = selenoid_status["browsers"][browser_name]
+        browser_version = max(versions)
+    return browser_name, browser_version
+
+
+def print_test_summary(complete_module_list, parallel_testlist,
+                       sequential_tests_list, browser_name, browser_version):
+    """
+    Prints test summary about total, parallel, sequential, browser name,
+    browser version information
+    :param complete_module_list:
+    :param parallel_testlist:
+    :param sequential_tests_list:
+    :param browser_name:
+    :param browser_version:
+    """
+    print(
+        "=================================================================",
+        file=sys.stderr
+    )
+    print(
+        "Total Tests # {0}\nParallel Tests # {1}, "
+        "Sequential Tests # {2}".format(
+            len(complete_module_list), len(parallel_testlist),
+            len(sequential_tests_list)),
+        file=sys.stderr)
+    print("Browser: [Name:{0}, Version: {1}]".format(
+        browser_name.capitalize(), browser_version),
+        file=sys.stderr)
+    print(
+        "=================================================================\n",
+        file=sys.stderr
+    )
+
+
+def get_selenium_grid_status_json(selenoid_url):
+    """
+    Functions returns json response received from selenoid server
+    :param selenoid_url:
+    :return:
+    """
+    try:
+        selenoid_status = urllib.urlopen(
+            "http://" + re.split('/', (re.split('//', selenoid_url, 1)[1]))[
+                0] + "/status", timeout=10)
+        selenoid_status = json.load(selenoid_status)
+        if isinstance(selenoid_status, dict):
+            return selenoid_status
+    except Exception as e:
+        print(str(e))
+        print("Unable to find Selenoid Status")
+        return None
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index fcf73a8..21c3baa 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,62 @@ 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 arguments['pkg'] == "feature_tests":
+            exclude_pkgs.extend(['resql'])
+
+        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
+            # 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()
 
     handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
     # Register cleanup function to cleanup on exit
@@ -319,6 +325,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 +413,212 @@ class StreamToLogger(object):
         pass
 
 
+def execute_test(test_module_list_passed, server_passed, driver_passed):
+    """
+    Function executes actually test
+    :param test_module_list_passed:
+    :param server_passed:
+    :param driver_passed:
+    :return:
+    """
+    try:
+        print("\n=============Running the test cases for '%s' ============="
+              % server_passed['name'], file=sys.stderr)
+        # 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 threading.current_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 as exc:
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        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:
+            driver_passed.quit()
+            time.sleep(20)
+
+        # Print info about completed tests
+        print(
+            "\n=============Completed the test cases for '%s'============="
+            % server_passed['name'], file=sys.stderr)
+
+
+def run_parallel_tests(url_client, servers_details, parallel_tests_lists,
+                       name_of_browser, version_of_browser, max_thread_count):
+    """
+    Function used to run tests in parallel
+    :param url_client:
+    :param servers_details:
+    :param parallel_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :param max_thread_count:
+    """
+    driver_object = None
+    try:
+        # Thread list
+        threads_list = []
+        # Create thread for each server
+        for ser in servers_details:
+            # Logic to add new threads
+            while True:
+                # If active thread count <= max_thread_count, add new thread
+                if threading.activeCount() <= max_thread_count:
+                    # Get remote web-driver instance at server level
+                    driver_object = \
+                        test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        ser['name'])
+                    # Launch client url in browser
+                    test_utils.launch_url_in_browser(driver_object, url_client)
+
+                    # Add name for thread
+                    thread_name = "parallel_tests" + ser['name']
+
+                    # Start thread
+                    t = threading.Thread(target=execute_test, name=thread_name,
+                                         args=(parallel_tests_lists, ser,
+                                               driver_object))
+                    threads_list.append(t)
+                    t.start()
+                    time.sleep(3)
+                    break
+                # else sleep for 10 seconds
+                else:
+                    time.sleep(10)
+
+        # Start threads in parallel
+        for t in threads_list:
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        # Clean driver object created
+        if driver_object is not None:
+            driver_object.quit()
+
+
+def run_sequential_tests(url_client, servers_details, sequential_tests_lists,
+                         name_of_browser, version_of_browser):
+    """
+    Function is used to execute tests that needs to be run in sequential
+    manner.
+    :param url_client:
+    :param servers_details:
+    :param sequential_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :return:
+    """
+    driver_object = None
+    try:
+        # Get remote web-driver instance
+        driver_object = test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        "Sequential_Tests")
+
+        # Launch client url in browser
+        test_utils.launch_url_in_browser(driver_object, url_client)
+
+        # Add name for thread
+        thread_name = "sequential_tests"
+
+        # Start thread
+        for ser in servers_details:
+            t = threading.Thread(target=execute_test,
+                                 name=thread_name,
+                                 args=(sequential_tests_lists, ser,
+                                       driver_object))
+            t.start()
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+    finally:
+        # Clean driver object created
+        driver_object.quit()
+
+
 if __name__ == '__main__':
     # Failure detected?
     failure = False
@@ -423,7 +638,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 +667,97 @@ 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 info dict hub url
+        selenoid_info = test_setup.config_data['selenoid_info']
+
+        # Set DEFAULT_SERVER value
+        default_server = selenoid_info['pgAdmin_default_server']
+        os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"] = str(default_server)
+        config.DEFAULT_SERVER = str(default_server)
+
+        # Get hub url
+        hub_url = selenoid_info['selenoid_url']
+        # 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(hub_url)
+
+        # Execute tests if selenium-grid is up
+        if selenium_grid_status and len(list_of_browsers) > 0:
+
+            app_starter_local = None
+            try:
+                # run across browsers
+                for browser_info in list_of_browsers:
+                    # browser info
+                    browser_name, browser_version = \
+                        test_utils.get_browser_details(browser_info, hub_url)
+
+                    # tests lists can be executed in parallel & sequentially
+                    parallel_tests, sequential_tests = \
+                        test_utils.get_parallel_sequential_module_list(
+                            test_module_list)
+
+                    # Print test summary
+                    test_utils.print_test_summary(test_module_list,
+                                                  parallel_tests,
+                                                  sequential_tests,
+                                                  browser_name,
+                                                  browser_version)
+
+                    # Create app form source code
+                    app_starter_local = AppStarter(None, config)
+                    client_url = app_starter_local.start_app()
+
+                    # Running Parallel tests
+                    if len(parallel_tests) > 0:
+                        parallel_sessions = int(selenoid_info[
+                            'max_parallel_sessions'])
+
+                        run_parallel_tests(client_url, servers_info,
+                                           parallel_tests, browser_name,
+                                           browser_version, parallel_sessions)
+
+                    # Wait till all threads started in parallel are finished
+                    while True:
+                        try:
+                            if threading.activeCount() <= 1:
+                                break
+                            else:
+                                time.sleep(10)
+                        except Exception as e:
+                            traceback.print_exc(file=sys.stderr)
+                            print(str(e))
+
+                    # Sequential Tests
+                    if len(sequential_tests) > 0:
+                        run_sequential_tests(client_url, servers_info,
+                                             sequential_tests, browser_name,
+                                             browser_version)
+
+                    # Clean up environment
+                    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()
+            # Pause before printing result in order not to mix output
+            time.sleep(3)
+    else:
+        try:
+            for server in servers_info:
+                thread = threading.Thread(target=execute_test, args=(
+                    test_module_list, server, driver))
+                thread.start()
+                thread.join()
+        except SystemExit:
+            if handle_cleanup:
+                handle_cleanup()
 
     print(
         "\n==============================================================="
@@ -543,6 +787,9 @@ if __name__ == '__main__':
         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"
             "\n\t%s test%s skipped%s%s\n" %
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index af061b9..bbd0e1e 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,10 @@
 {
   "headless_chrome": false,
   "default_browser": "Chrome",
+  "selenoid_info":{"pgAdmin_default_server":"ip address of machine where source code executed",
+                    "max_parallel_sessions": "3",
+                    "cross_Browsers": [{"name": "browser_name","version": "browser_version"}],
+                    "selenoid_url": "Selenoid Url"},
   "pgAdmin4_login_credentials": {
     "new_password": "NEWPASSWORD",
     "login_password": "PASSWORD",
diff --git a/web/yarn.lock b/web/yarn.lock
index 151e98c..6969848 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -29,12 +29,12 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.6.4", "@babel/generator@^7.9.0":
-  version "7.9.4"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce"
-  integrity sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==
+"@babel/generator@^7.6.4", "@babel/generator@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9"
+  integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==
   dependencies:
-    "@babel/types" "^7.9.0"
+    "@babel/types" "^7.9.5"
     jsesc "^2.5.1"
     lodash "^4.17.13"
     source-map "^0.5.0"
@@ -80,14 +80,14 @@
     "@babel/traverse" "^7.8.3"
     "@babel/types" "^7.8.3"
 
-"@babel/helper-function-name@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
-  integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==
+"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
+  integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==
   dependencies:
     "@babel/helper-get-function-arity" "^7.8.3"
     "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.9.5"
 
 "@babel/helper-get-function-arity@^7.8.3":
   version "7.8.3"
@@ -185,10 +185,10 @@
   dependencies:
     "@babel/types" "^7.8.3"
 
-"@babel/helper-validator-identifier@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed"
-  integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==
+"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
+  integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
 
 "@babel/helper-wrap-function@^7.8.3":
   version "7.8.3"
@@ -249,12 +249,13 @@
     "@babel/plugin-syntax-json-strings" "^7.8.0"
 
 "@babel/plugin-proposal-object-rest-spread@^7.6.2":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f"
-  integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116"
+  integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/plugin-transform-parameters" "^7.9.5"
 
 "@babel/plugin-proposal-optional-catch-binding@^7.2.0":
   version "7.8.3"
@@ -339,13 +340,13 @@
     lodash "^4.17.13"
 
 "@babel/plugin-transform-classes@^7.5.5":
-  version "7.9.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d"
-  integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c"
+  integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.8.3"
     "@babel/helper-define-map" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
+    "@babel/helper-function-name" "^7.9.5"
     "@babel/helper-optimise-call-expression" "^7.8.3"
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/helper-replace-supers" "^7.8.6"
@@ -360,9 +361,9 @@
     "@babel/helper-plugin-utils" "^7.8.3"
 
 "@babel/plugin-transform-destructuring@^7.6.0":
-  version "7.8.8"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b"
-  integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50"
+  integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.3"
 
@@ -477,10 +478,10 @@
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/helper-replace-supers" "^7.8.3"
 
-"@babel/plugin-transform-parameters@^7.4.4":
-  version "7.9.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a"
-  integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==
+"@babel/plugin-transform-parameters@^7.4.4", "@babel/plugin-transform-parameters@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795"
+  integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==
   dependencies:
     "@babel/helper-get-function-arity" "^7.8.3"
     "@babel/helper-plugin-utils" "^7.8.3"
@@ -624,26 +625,26 @@
     "@babel/types" "^7.8.6"
 
 "@babel/traverse@^7.6.3", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892"
-  integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
+  integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
   dependencies:
     "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.9.0"
-    "@babel/helper-function-name" "^7.8.3"
+    "@babel/generator" "^7.9.5"
+    "@babel/helper-function-name" "^7.9.5"
     "@babel/helper-split-export-declaration" "^7.8.3"
     "@babel/parser" "^7.9.0"
-    "@babel/types" "^7.9.0"
+    "@babel/types" "^7.9.5"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.13"
 
-"@babel/types@^7.6.3", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5"
-  integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==
+"@babel/types@^7.6.3", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
+  integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.9.0"
+    "@babel/helper-validator-identifier" "^7.9.5"
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
@@ -660,9 +661,9 @@
   integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
 
 "@types/node@*":
-  version "13.11.0"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
-  integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
+  version "13.11.1"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
+  integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
 
 "@types/q@^1.5.1":
   version "1.5.2"
@@ -837,6 +838,11 @@ JSONStream@^1.0.3:
     jsonparse "^1.2.0"
     through ">=2.2.7 <3"
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
 accepts@~1.3.4, accepts@~1.3.7:
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -973,7 +979,7 @@ anymatch@~3.1.1:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-aproba@^1.1.1:
+aproba@^1.0.3, aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
   integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
@@ -990,6 +996,14 @@ archive-type@^4.0.0:
   dependencies:
     file-type "^4.2.0"
 
+are-we-there-yet@~1.1.2:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+  integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
+  dependencies:
+    delegates "^1.0.0"
+    readable-stream "^2.0.6"
+
 argparse@^1.0.6, argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -1094,12 +1108,12 @@ atob@^2.1.2:
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
 autoprefixer@^9.6.4:
-  version "9.7.5"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.5.tgz#8df10b9ff9b5814a8d411a5cfbab9c793c392376"
-  integrity sha512-URo6Zvt7VYifomeAfJlMFnYDhow1rk2bufwkbamPEAtQFcL11moLk4PnR7n9vlu7M+BkXAZkHFA0mIcY7tjQFg==
+  version "9.7.6"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4"
+  integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==
   dependencies:
-    browserslist "^4.11.0"
-    caniuse-lite "^1.0.30001036"
+    browserslist "^4.11.1"
+    caniuse-lite "^1.0.30001039"
     chalk "^2.4.2"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
@@ -1882,61 +1896,7 @@ browserify-zlib@^0.2.0, browserify-zlib@~0.2.0:
   dependencies:
     pako "~1.0.5"
 
-browserify@^16.1.0:
-  version "16.5.1"
-  resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.1.tgz#3c13c97436802930d5c3ae28658ddc33bfd37dc2"
-  integrity sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A==
-  dependencies:
-    JSONStream "^1.0.3"
-    assert "^1.4.0"
-    browser-pack "^6.0.1"
-    browser-resolve "^1.11.0"
-    browserify-zlib "~0.2.0"
-    buffer "~5.2.1"
-    cached-path-relative "^1.0.0"
-    concat-stream "^1.6.0"
-    console-browserify "^1.1.0"
-    constants-browserify "~1.0.0"
-    crypto-browserify "^3.0.0"
-    defined "^1.0.0"
-    deps-sort "^2.0.0"
-    domain-browser "^1.2.0"
-    duplexer2 "~0.1.2"
-    events "^2.0.0"
-    glob "^7.1.0"
-    has "^1.0.0"
-    htmlescape "^1.1.0"
-    https-browserify "^1.0.0"
-    inherits "~2.0.1"
-    insert-module-globals "^7.0.0"
-    labeled-stream-splicer "^2.0.0"
-    mkdirp-classic "^0.5.2"
-    module-deps "^6.0.0"
-    os-browserify "~0.3.0"
-    parents "^1.0.1"
-    path-browserify "~0.0.0"
-    process "~0.11.0"
-    punycode "^1.3.2"
-    querystring-es3 "~0.2.0"
-    read-only-stream "^2.0.0"
-    readable-stream "^2.0.2"
-    resolve "^1.1.4"
-    shasum "^1.0.0"
-    shell-quote "^1.6.1"
-    stream-browserify "^2.0.0"
-    stream-http "^3.0.0"
-    string_decoder "^1.1.1"
-    subarg "^1.0.0"
-    syntax-error "^1.1.1"
-    through2 "^2.0.0"
-    timers-browserify "^1.0.1"
-    tty-browserify "0.0.1"
-    url "~0.11.0"
-    util "~0.10.1"
-    vm-browserify "^1.0.0"
-    xtend "^4.0.0"
-
-browserify@~16.2.3:
+browserify@^16.1.0, browserify@~16.2.3:
   version "16.2.3"
   resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b"
   integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==
@@ -1990,7 +1950,7 @@ browserify@~16.2.3:
     vm-browserify "^1.0.0"
     xtend "^4.0.0"
 
-browserslist@^4.0.0, browserslist@^4.11.0, browserslist@^4.6.0, browserslist@^4.8.3:
+browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.0, browserslist@^4.8.3:
   version "4.11.1"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b"
   integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==
@@ -2050,14 +2010,6 @@ buffer@^5.0.2, buffer@^5.2.1:
     base64-js "^1.0.2"
     ieee754 "^1.1.4"
 
-buffer@~5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
-  integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==
-  dependencies:
-    base64-js "^1.0.2"
-    ieee754 "^1.1.4"
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -2203,10 +2155,10 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001036, caniuse-lite@^1.0.30001038:
-  version "1.0.30001039"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe"
-  integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039:
+  version "1.0.30001040"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz#103fc8e6eb1d7397e95134cd0e996743353d58ea"
+  integrity sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==
 
 caw@^2.0.0, caw@^2.0.1:
   version "2.0.1"
@@ -2248,22 +2200,7 @@ check-types@^8.0.3:
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
   integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
 
-"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
-  integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
-  dependencies:
-    anymatch "~3.1.1"
-    braces "~3.0.2"
-    glob-parent "~5.1.0"
-    is-binary-path "~2.1.0"
-    is-glob "~4.0.1"
-    normalize-path "~3.0.0"
-    readdirp "~3.3.0"
-  optionalDependencies:
-    fsevents "~2.1.2"
-
-chokidar@^2.1.1, chokidar@^2.1.8:
+"chokidar@>=2.0.0 <4.0.0", chokidar@^2.1.1, chokidar@^2.1.8:
   version "2.1.8"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
   integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
@@ -2282,6 +2219,21 @@ chokidar@^2.1.1, chokidar@^2.1.8:
   optionalDependencies:
     fsevents "^1.2.7"
 
+chokidar@^3.0.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
+  integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.3.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 chownr@^1.1.1, chownr@^1.1.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -2363,6 +2315,11 @@ coa@^2.0.2:
     chalk "^2.4.1"
     q "^1.1.2"
 
+code-point-at@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+  integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+
 codemirror@^5.50.0:
   version "5.52.2"
   resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.52.2.tgz#c29e1f7179f85eb0dd17c0586fa810e4838ff584"
@@ -2383,16 +2340,11 @@ color-convert@^1.9.0, color-convert@^1.9.1:
   dependencies:
     color-name "1.1.3"
 
[email protected]:
[email protected], color-name@^1.0.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@^1.0.0:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
 color-string@^1.5.2:
   version "1.5.3"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
@@ -2504,6 +2456,11 @@ console-browserify@^1.1.0:
   resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
   integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
 
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+
 console-stream@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/console-stream/-/console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44"
@@ -2526,14 +2483,7 @@ content-type@~1.0.4:
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
-convert-source-map@^1.1.0, convert-source-map@^1.1.3:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
-  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-convert-source-map@~1.1.0:
+convert-source-map@^1.1.0, convert-source-map@^1.1.3, convert-source-map@~1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
   integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=
@@ -2602,9 +2552,9 @@ core-js@^2.4.0:
   integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
 
 core-js@^3.2.1, core-js@^3.6.4:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
-  integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
+  integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
 
 core-util-is@~1.0.0:
   version "1.0.2"
@@ -2896,7 +2846,7 @@ debug@=3.1.0, debug@~3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^3.0.0, debug@^3.2.6:
+debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -2980,6 +2930,11 @@ decompress@^4.0.0, decompress@^4.2.0:
     pify "^2.3.0"
     strip-dirs "^2.0.0"
 
+deep-extend@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
 deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -3019,6 +2974,11 @@ defined@^1.0.0:
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
   integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
 
+delegates@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+
 depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -3052,6 +3012,11 @@ detect-file@^1.0.0:
   resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
   integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
 
+detect-libc@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+
 detective@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
@@ -3220,9 +3185,9 @@ ejs@^3.0.0:
   integrity sha512-IncmUpn1yN84hy2shb0POJ80FWrfGNY0cxO9f4v+/sG7qcBvAtVWUA1IdzY/8EYUmOVhoKJVdJjNd3AZcnxOjA==
 
 electron-to-chromium@^1.3.390:
-  version "1.3.397"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.397.tgz#db640c2e67b08d590a504c20b56904537aa2bafa"
-  integrity sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A==
+  version "1.3.402"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.402.tgz#9ad93c0c8ea2e571431739e0d76bd6bc9788a846"
+  integrity sha512-gaCDfX7IUH0s3JmBiHCDPrvVcdnTTP1r4WLJc2dHkYYbLmXZ2XHiJCcGQ9Balf91aKTvuCKCyu2JjJYRykoI1w==
 
 elliptic@^6.0.0:
   version "6.5.2"
@@ -3941,20 +3906,13 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
[email protected]:
[email protected], follow-redirects@^1.0.0:
   version "1.5.10"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
   integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
   dependencies:
     debug "=3.1.0"
 
-follow-redirects@^1.0.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
-  integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==
-  dependencies:
-    debug "^3.0.0"
-
 font-awesome@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
@@ -4011,6 +3969,13 @@ fs-extra@^7.0.1:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-minipass@^1.2.5:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
+  integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
+  dependencies:
+    minipass "^2.6.0"
+
 fs-minipass@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@@ -4056,6 +4021,20 @@ functional-red-black-tree@^1.0.1:
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
   integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
+gauge@~2.7.3:
+  version "2.7.4"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+  integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+  dependencies:
+    aproba "^1.0.3"
+    console-control-strings "^1.0.0"
+    has-unicode "^2.0.0"
+    object-assign "^4.1.0"
+    signal-exit "^3.0.0"
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+    wide-align "^1.1.0"
+
 geometry-interfaces@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/geometry-interfaces/-/geometry-interfaces-1.1.4.tgz#9e82af6700ca639a675299f08e1f5fbc4a79d48d"
@@ -4321,6 +4300,11 @@ has-to-string-tag-x@^1.2.0:
   dependencies:
     has-symbol-support-x "^1.4.1"
 
+has-unicode@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -4436,7 +4420,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
   integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
 
[email protected]:
[email protected], http-errors@~1.7.2:
   version "1.7.2"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
   integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
@@ -4447,17 +4431,6 @@ [email protected]:
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
-http-errors@~1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
-  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.4"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
 http-proxy@^1.13.0:
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
@@ -4483,7 +4456,7 @@ iconfont-webpack-plugin@^4.2.1:
     svgicons2svgfont "9.1.1"
     ttf2woff "2.0.1"
 
[email protected], iconv-lite@^0.4.24:
[email protected], iconv-lite@^0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -4512,6 +4485,13 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
+ignore-walk@^3.0.1:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+  integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+  dependencies:
+    minimatch "^3.0.4"
+
 ignore@^3.3.5:
   version "3.3.10"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
@@ -4701,7 +4681,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, [email protected], inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4716,7 +4696,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
-ini@^1.3.4, ini@^1.3.5:
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -4948,6 +4928,13 @@ is-finite@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
   integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
 
+is-fullwidth-code-point@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+  integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+  dependencies:
+    number-is-nan "^1.0.0"
+
 is-fullwidth-code-point@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
@@ -5133,12 +5120,7 @@ isurl@^1.0.0-alpha5:
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
 
-jasmine-core@^3.3:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
-  integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==
-
-jasmine-core@~3.3.0:
+jasmine-core@^3.3, jasmine-core@~3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.3.0.tgz#dea1cdc634bc93c7e0d4ad27185df30fa971b10e"
   integrity sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==
@@ -5184,16 +5166,16 @@ js-string-escape@^1.0.0:
   resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
   integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
 
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-js-tokens@^3.0.2:
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
 js-yaml@^3.12.0, js-yaml@^3.13.1:
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@@ -5862,6 +5844,14 @@ minipass-pipeline@^1.2.2:
   dependencies:
     minipass "^3.0.0"
 
+minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
+  version "2.9.0"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
+  integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
+  dependencies:
+    safe-buffer "^5.1.2"
+    yallist "^3.0.0"
+
 minipass@^3.0.0, minipass@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
@@ -5869,6 +5859,13 @@ minipass@^3.0.0, minipass@^3.1.1:
   dependencies:
     yallist "^4.0.0"
 
+minizlib@^1.2.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
+  integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
+  dependencies:
+    minipass "^2.9.0"
+
 mississippi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
@@ -5893,11 +5890,6 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp-classic@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b"
-  integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==
-
 mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
@@ -6025,6 +6017,15 @@ neatequal@^1.0.0:
   dependencies:
     varstream "^0.3.2"
 
+needle@^2.2.1:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117"
+  integrity sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==
+  dependencies:
+    debug "^3.2.6"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 [email protected]:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -6069,11 +6070,35 @@ node-libs-browser@^2.2.1:
     util "^0.11.0"
     vm-browserify "^1.0.1"
 
+node-pre-gyp@*:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
+  integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
+  dependencies:
+    detect-libc "^1.0.2"
+    mkdirp "^0.5.1"
+    needle "^2.2.1"
+    nopt "^4.0.1"
+    npm-packlist "^1.1.6"
+    npmlog "^4.0.2"
+    rc "^1.2.7"
+    rimraf "^2.6.1"
+    semver "^5.3.0"
+    tar "^4.4.2"
+
 node-releases@^1.1.53:
   version "1.1.53"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4"
   integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==
 
+nopt@^4.0.1:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
+  integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==
+  dependencies:
+    abbrev "1"
+    osenv "^0.1.4"
+
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -6115,6 +6140,13 @@ normalize-url@^3.0.0:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
   integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
 
+npm-bundled@^1.0.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
+  integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
+  dependencies:
+    npm-normalize-package-bin "^1.0.1"
+
 npm-conf@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
@@ -6123,6 +6155,20 @@ npm-conf@^1.1.0:
     config-chain "^1.1.11"
     pify "^3.0.0"
 
+npm-normalize-package-bin@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
+  integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
+
+npm-packlist@^1.1.6:
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
+  integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
+  dependencies:
+    ignore-walk "^3.0.1"
+    npm-bundled "^1.0.1"
+    npm-normalize-package-bin "^1.0.1"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -6130,6 +6176,16 @@ npm-run-path@^2.0.0:
   dependencies:
     path-key "^2.0.0"
 
+npmlog@^4.0.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+  integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+  dependencies:
+    are-we-there-yet "~1.1.2"
+    console-control-strings "~1.1.0"
+    gauge "~2.7.3"
+    set-blocking "~2.0.0"
+
 nth-check@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@@ -6147,6 +6203,11 @@ num2fraction@^1.2.2:
   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
   integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
 
+number-is-nan@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+  integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+
 object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -6293,6 +6354,11 @@ os-filter-obj@^2.0.0:
   dependencies:
     arch "^2.1.0"
 
+os-homedir@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+  integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+
 os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
@@ -6307,11 +6373,19 @@ os-shim@^0.1.3:
   resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
   integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
 
-os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
+osenv@^0.1.4:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
+  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
+  dependencies:
+    os-homedir "^1.0.0"
+    os-tmpdir "^1.0.0"
+
 outpipe@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2"
@@ -7218,6 +7292,16 @@ raw-loader@^1.0.0:
     loader-utils "^1.1.0"
     schema-utils "^1.0.0"
 
+rc@^1.2.7:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+  integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+  dependencies:
+    deep-extend "^0.6.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
 read-only-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
@@ -7242,7 +7326,7 @@ read-pkg@^1.0.0:
     normalize-package-data "^2.3.2"
     path-type "^1.0.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -7265,15 +7349,6 @@ readable-stream@^1.0.33:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^3.0.6:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
-  integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
-  dependencies:
-    inherits "^2.0.3"
-    string_decoder "^1.1.1"
-    util-deprecate "^1.0.1"
-
 readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -7526,7 +7601,7 @@ [email protected]:
   dependencies:
     glob "^7.1.3"
 
-rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.3, rimraf@^2.7.1:
+rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -7580,12 +7655,12 @@ rxjs@^6.4.0:
   dependencies:
     tslib "^1.9.0"
 
[email protected], safe-buffer@~5.1.0, safe-buffer@~5.1.1:
[email protected], safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
   integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
@@ -7725,7 +7800,7 @@ [email protected]:
     parseurl "~1.3.3"
     send "0.17.1"
 
-set-blocking@^2.0.0:
+set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
@@ -8128,16 +8203,6 @@ stream-http@^2.0.0, stream-http@^2.7.2:
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
-stream-http@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.0.tgz#22fb33fe9b4056b4eccf58bd8f400c4b993ffe57"
-  integrity sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==
-  dependencies:
-    builtin-status-codes "^3.0.0"
-    inherits "^2.0.1"
-    readable-stream "^3.0.6"
-    xtend "^4.0.0"
-
 stream-shift@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
@@ -8167,6 +8232,15 @@ strict-uri-encode@^1.0.0:
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
   integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
 
+string-width@^1.0.1, "string-width@^1.0.2 || 2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+  integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    strip-ansi "^3.0.0"
+
 string-width@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -8195,9 +8269,9 @@ string.prototype.codepointat@^0.2.0:
   integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
 
 string.prototype.trimend@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373"
-  integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+  integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
   dependencies:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
@@ -8221,9 +8295,9 @@ string.prototype.trimright@^2.1.1:
     string.prototype.trimend "^1.0.0"
 
 string.prototype.trimstart@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2"
-  integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+  integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
   dependencies:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
@@ -8247,7 +8321,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-strip-ansi@^3.0.0:
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
   integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
@@ -8294,7 +8368,7 @@ strip-indent@^1.0.1:
   dependencies:
     get-stdin "^4.0.1"
 
-strip-json-comments@^2.0.1:
+strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -8457,6 +8531,19 @@ tar-stream@^1.5.2:
     to-buffer "^1.1.1"
     xtend "^4.0.0"
 
+tar@^4.4.2:
+  version "4.4.13"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
+  integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
+  dependencies:
+    chownr "^1.1.1"
+    fs-minipass "^1.2.5"
+    minipass "^2.8.6"
+    minizlib "^1.2.1"
+    mkdirp "^0.5.0"
+    safe-buffer "^5.1.2"
+    yallist "^3.0.3"
+
 temp-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
@@ -8521,9 +8608,9 @@ terser-webpack-plugin@^2.2.2:
     webpack-sources "^1.4.3"
 
 terser@^4.1.2, terser@^4.4.3, terser@^4.6.2:
-  version "4.6.10"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2"
-  integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA==
+  version "4.6.11"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
+  integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
   dependencies:
     commander "^2.20.0"
     source-map "~0.6.1"
@@ -8724,9 +8811,9 @@ umd@^3.0.0:
   integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==
 
 unbzip2-stream@^1.0.9:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.0.tgz#097ca7b18b5b71e6c8bc8e514a0f1884a12d6eb1"
-  integrity sha512-kVx7CDAsdBSWVf404Mw7oI9i09w5/mTT/Ruk+RWa64PLYKvsAucLLFHvQtnvjeADM4ZizxrvG5SHnF4Te4T2Cg==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.1.tgz#151b104af853df3efdaa135d8b1eca850a44b426"
+  integrity sha512-sgDYfSDPMsA4Hr2/w7vOlrJBlwzmyakk1+hW8ObLvxSp0LA36LcL2XItGvOT3OSblohSdevMuT8FQjLsqyy4sA==
   dependencies:
     buffer "^5.2.1"
     through "^2.3.8"
@@ -8893,7 +8980,7 @@ [email protected]:
     lru-cache "4.1.x"
     tmp "0.0.x"
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -9127,6 +9214,13 @@ which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1:
   dependencies:
     isexe "^2.0.0"
 
+wide-align@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+  integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+  dependencies:
+    string-width "^1.0.2 || 2"
+
 wkx@^0.4.6:
   version "0.4.8"
   resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.8.tgz#a092cf088d112683fdc7182fd31493b2c5820003"
@@ -9213,7 +9307,7 @@ yallist@^2.1.2:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
   integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
 
-yallist@^3.0.2:
+yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
   integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-04 09:21  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Akshay Joshi @ 2020-05-04 09:21 UTC (permalink / raw)
  To: Yogesh Mahajan <[email protected]>; +Cc: Shubham Agarwal <[email protected]>; pgadmin-hackers; navnath gadakh <[email protected]>

Hi Yogesh

The patch is not applied to the master branch. Can you please rebase and
send the patch again.

On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
[email protected]> wrote:

> Hi,
>
> Please find updated patch modified according to review comments -
> Patch implements below things -
> 1.Enable the current framework to provide option to execute Feature tests
> in parallel  on selenium grid set up.
>    - Addition of new switch to start parallel features tests.
>    - New parameters with respect to selenoid in test_config.json.in
>    - Addition of new script to check solenoid updates.
>
>
>
> Thanks,
> Yogesh Mahajan
> QA - Team
> EnterpriseDB Corporation
>
> Phone: +91-9741705709
>
>
> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
> [email protected]> wrote:
>
>> Hi Yogesh,
>> Below are the review comments-
>>
>> 1. runtests.py
>>     a. The exception traceback logic at line number 653 in runtests.py
>> is not correct since it is particular to the thread
>> but there is much more code in that block which can throw some exception.
>> b. line number 447 -> The drop_database function will only try to drop
>> the database with the name which is newly created
>> at 431 line number, its probability is 1% instead of this you can write a
>> logic so that it will drop all the database which starts with name
>> ‘acceptance_test_db'.
>>   c. line 584 - Why we are including resql test case execution in GUI
>> execution logic.
>>     d. Change the function name run_test as script name is also
>> runtests.py
>>
>> 2. test_utils.py
>>     a. Remove the headless chrome code from get_remote_webdriver() in
>> test_utils.py since we are using solenoid and it is not required anymore.
>>     b. Create separate functions to instantiate the firefox driver and
>> chrome driver logic since the same code is used in multiple files.
>> c. launch_url_in_browser() -> you can simplify the definition of the
>> function like:
>>     retry = 60
>>         *while *retry > 0:
>>             try:
>>                 driver.get(url)
>>             except WebDriverException:
>>                  retry -= 1
>> 3. Execution logs are not printing as per the logic some time, I ran the
>> suite for two servers and attached are the execution logs.
>> 4. Readme -
>> Please provide the Valid selenoid URL to be provided in the
>> test_config.json, with all the steps mentioned in the readme it is not
>> clear.
>> Revisit the readme and write the missing steps.
>> 5. copy_selected_query_results_feature_tests.py-
>> Create the function to avoid duplicate code. The code for pasting the
>> values is repeating 8 times in the test code.
>> 6. Provide the valid docstring in newly introduced functions and also
>> valid comments while calling it. for ex.- _update_preference() function is
>> introduced in pg_utilities_backup_restrore_test.py but from the function
>> name, it is not clear what preferences are going to update in it.
>> 7. test_index_constraint_add test case is failing due to the latest
>> change, please merge and update this test case
>>
>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>> [email protected]> wrote:
>>
>>> Hi,
>>> I think I am not the right person to review this patch now as I already
>>> reviewed this code offline in the last week. I know the approached Yogesh
>>> has followed, also given some review comments on it.
>>> Someone else please review it.
>>>
>>> Thanks!
>>>
>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Navnath
>>>>
>>>> Can you please review it?
>>>>
>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>> [email protected]> wrote:
>>>>
>>>>> 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
>>>>>
>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>>
>>>> *Sr. Software Architect*
>>>> *EnterpriseDB Software India Private Limited*
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>>
>>> --
>>> Regards,
>>> Navnath Gadakh
>>>
>>
>>
>> --
>> Thanks & Regards,
>> Shubham Agarwal
>> EnterpriseDB Corporation
>>
>> The Postgres Database Company
>>
>

-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-04 10:57  Yogesh Mahajan <[email protected]>
  parent: Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-05-04 10:57 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; pgadmin-hackers; +Cc: Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi Akshay,

Please find the updated patch.

Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <[email protected]>
wrote:

> Hi Yogesh
>
> The patch is not applied to the master branch. Can you please rebase and
> send the patch again.
>
> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find updated patch modified according to review comments -
>> Patch implements below things -
>> 1.Enable the current framework to provide option to execute Feature tests
>> in parallel  on selenium grid set up.
>>    - Addition of new switch to start parallel features tests.
>>    - New parameters with respect to selenoid in test_config.json.in
>>    - Addition of new script to check solenoid updates.
>>
>>
>>
>> Thanks,
>> Yogesh Mahajan
>> QA - Team
>> EnterpriseDB Corporation
>>
>> Phone: +91-9741705709
>>
>>
>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>> [email protected]> wrote:
>>
>>> Hi Yogesh,
>>> Below are the review comments-
>>>
>>> 1. runtests.py
>>>     a. The exception traceback logic at line number 653 in runtests.py
>>> is not correct since it is particular to the thread
>>> but there is much more code in that block which can throw some exception.
>>> b. line number 447 -> The drop_database function will only try to drop
>>> the database with the name which is newly created
>>> at 431 line number, its probability is 1% instead of this you can write
>>> a logic so that it will drop all the database which starts with name
>>> ‘acceptance_test_db'.
>>>   c. line 584 - Why we are including resql test case execution in GUI
>>> execution logic.
>>>     d. Change the function name run_test as script name is also
>>> runtests.py
>>>
>>> 2. test_utils.py
>>>     a. Remove the headless chrome code from get_remote_webdriver() in
>>> test_utils.py since we are using solenoid and it is not required
>>> anymore.
>>>     b. Create separate functions to instantiate the firefox driver and
>>> chrome driver logic since the same code is used in multiple files.
>>> c. launch_url_in_browser() -> you can simplify the definition of the
>>> function like:
>>>     retry = 60
>>>         *while *retry > 0:
>>>             try:
>>>                 driver.get(url)
>>>             except WebDriverException:
>>>                  retry -= 1
>>> 3. Execution logs are not printing as per the logic some time, I ran the
>>> suite for two servers and attached are the execution logs.
>>> 4. Readme -
>>> Please provide the Valid selenoid URL to be provided in the
>>> test_config.json, with all the steps mentioned in the readme it is not
>>> clear.
>>> Revisit the readme and write the missing steps.
>>> 5. copy_selected_query_results_feature_tests.py-
>>> Create the function to avoid duplicate code. The code for pasting the
>>> values is repeating 8 times in the test code.
>>> 6. Provide the valid docstring in newly introduced functions and also
>>> valid comments while calling it. for ex.- _update_preference() function is
>>> introduced in pg_utilities_backup_restrore_test.py but from the function
>>> name, it is not clear what preferences are going to update in it.
>>> 7. test_index_constraint_add test case is failing due to the latest
>>> change, please merge and update this test case
>>>
>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>> I think I am not the right person to review this patch now as I already
>>>> reviewed this code offline in the last week. I know the approached Yogesh
>>>> has followed, also given some review comments on it.
>>>> Someone else please review it.
>>>>
>>>> Thanks!
>>>>
>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Navnath
>>>>>
>>>>> Can you please review it?
>>>>>
>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> 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
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>>
>>>>> *Sr. Software Architect*
>>>>> *EnterpriseDB Software India Private Limited*
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>>
>>>> --
>>>> Regards,
>>>> Navnath Gadakh
>>>>
>>>
>>>
>>> --
>>> Thanks & Regards,
>>> Shubham Agarwal
>>> EnterpriseDB Corporation
>>>
>>> The Postgres Database Company
>>>
>>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>


Attachments:

  [application/octet-stream] Selenium_Grid_Implementation_ver3.0.patch (157.0K, 3-Selenium_Grid_Implementation_ver3.0.patch)
  download | inline diff:
diff --git a/tools/requirements.txt b/tools/requirements.txt
index 87ecc2a..2804012 100644
--- a/tools/requirements.txt
+++ b/tools/requirements.txt
@@ -1,3 +1,4 @@
 requests>=2.21.0
 requests[security]>=2.21.0
 safety==1.8.5
+pyjq==2.4.0
diff --git a/tools/update_selenoid_browsers.py b/tools/update_selenoid_browsers.py
new file mode 100644
index 0000000..ac4d905
--- /dev/null
+++ b/tools/update_selenoid_browsers.py
@@ -0,0 +1,265 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+# #########################################################################
+# Updates browser images(selenoid-docker) depending on arguments passed while
+# running this script.
+
+import argparse
+import os
+import subprocess
+import sys
+import traceback
+import requests
+import pyjq
+import json
+
+
+def read_command_line():
+    """Read the command line arguments.
+    Returns:
+        ArgumentParser: The parsed arguments object
+
+    """
+    parser = argparse.ArgumentParser(
+        description='Get latest browser images(chrome & firefox) for selenoid.'
+                    'e.g. - --chrome /usr/bin/google-chrome --firefox '
+                    '/usr/bin/firefox')
+    parser.add_argument("--chrome", metavar="CHROME",
+                        help="the Chrome executable path")
+    parser.add_argument("--firefox", metavar="FIREFOX",
+                        help="the firefox executable path")
+    args_val = parser.parse_args()
+    return args_val
+
+
+def get_browser_version(browser_name, executable_path):
+    """
+    Function returns browser version for specified browser using executable
+    path passed in arguments.
+    :param browser_name:
+    :param executable_path: e.g. /usr/bin/firefox
+    :return: browser version
+    """
+    # On Linux/Mac we run the browser executable with the --version flag,
+    # then parse the output.
+    browser_version_val = None
+    try:
+        result = subprocess.Popen([executable_path, '--version'],
+                                  stdout=subprocess.PIPE)
+    except FileNotFoundError:
+        print('The specified browser executable could not be found.')
+        sys.exit(1)
+
+    version_str = result.stdout.read().decode("utf-8")
+
+    if browser_name.lower() == "chrome":
+        # Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
+        if "Chrom" not in version_str:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        # On some linux distro `chrome--version` gives output like
+        # 'Google Chrome 80.0.3987.132 unknown\n'
+        # so we need to check and remove the unknown string from the version
+        if version_str.endswith("unknown\n"):
+            version_str = version_str.strip("unknown\n").strip()
+
+        chrome_version = '.'.join(version_str.split()[-1].split('.')[:-2])
+
+        # Make sure browser version has only 1 decimal point
+        if chrome_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = chrome_version
+    elif browser_name.lower() == "firefox":
+        if "Firefox" not in version_str:
+            print('The specified Firefox executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+
+        # firefox --version gives output like
+        # 'Running without a11y support!
+        # Mozilla Firefox 68.7.0esr'
+        firefox_version = '.'.join(
+            version_str.split()[-1].split('.')[:-2]) + '.0'
+
+        # Make sure browser version has only 1 decimal point
+        if firefox_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = firefox_version
+    else:
+        print("{0} is not recognised ".format(browser_name))
+        sys.exit(1)
+    return browser_version_val
+
+
+def check_and_download_vnc_browser_image(browser_name, browser_version):
+    """
+    Function checks presence for vnc images for passed browser
+    at docker.io/selenoid/ registry
+    :param browser_name:
+    :param browser_version:
+    :return:true if browser image is available & downloaded else false
+    """
+    res = requests.get(
+        'https://registry.hub.docker.com/v2/repositories/selenoid/vnc_' +
+        browser_name + '/tags/')
+    res = res.json()
+    version_tag = pyjq.all('.["results"][]["name"]', res)
+    vnc_image_available = False
+    image_name = 'vnc_' + browser_name + ':' + browser_version
+
+    for idx, tag in enumerate(version_tag):
+        if browser_version == tag:
+            command = 'docker pull selenoid/vnc_' + browser_name + ':' \
+                      + browser_version
+            print(' VNC image is available & downloading now... {0}'.format(
+                command))
+            try:
+                subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+                vnc_image_available = True
+            except Exception:
+                traceback.print_exc(file=sys.stderr)
+                print(
+                    '{0}} Image found but could not download.'.format(command))
+                sys.exit(1)
+            break
+        elif idx == len(version_tag):
+            print("{0} Image is not available.".format(image_name))
+            vnc_image_available = False
+        else:
+            pass
+    return vnc_image_available
+
+
+def reload_selenoid_config():
+    """
+    Function runs command to refresh selenoid configuration
+    :return: true if command execution for selenoid reload is successful
+    else false
+    """
+    command = 'docker kill -s HUP selenoid'
+    reload_successful = False
+    try:
+        subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+        print(" Selenoid Configuration is reloaded.")
+        reload_successful = True
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        print('Error while reloading selenoid configuration.')
+        sys.exit(1)
+    return reload_successful
+
+
+def edit_browsers_json(browser_name, browser_version):
+    """
+    Function edits browsers.json which is used by selenoid to
+    load browser configuration.
+    Default path for this file is
+    "user_home_dir + '/.aerokube/selenoid/browsers.json'"
+    Currently this is hardcoded, might need to modify
+    if we want to pass customize browsers.json
+    :param browser_name:
+    :param browser_version:
+    :return:
+    """
+    file_edited = True
+    # Read existing browsers.json
+    json_file = open(file_path, 'r')
+    existing_data = json.load(json_file)
+    updated_data = None
+
+    # Update data for new browser images
+    if browser_name.lower() == 'chrome':
+        version_data = existing_data['chrome']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_chrome:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['chrome']['versions']).update(data_to_insert)
+            updated_data = existing_data
+            print(updated_data)
+
+    elif browser_name.lower() == 'firefox':
+        version_data = existing_data['firefox']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_firefox:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['firefox']['versions']).update(data_to_insert)
+            updated_data = existing_data
+    else:
+        print("Browser version not matched")
+        file_edited = False
+
+    # Write updated data in browsers.json
+    if updated_data is not None:
+        json_file = open(file_path, 'w')
+        json.dump(updated_data, json_file)
+        print(" 'browsers.json' is updated for {0} {1}".format(
+            browser_name, browser_version))
+
+        file_edited = True
+    return file_edited
+
+
+# Main Program starts here
+# Read command line arguments & get list of browser_name, executable path.
+args = vars(read_command_line())
+
+# Get path path for browsers.json
+user_home_dir = os.getenv("HOME")
+file_path = user_home_dir + '/.aerokube/selenoid/browsers.json'
+print("***** Updating '{0}' for new browser versions.*****".format(file_path))
+
+# Iterate over arguments passed
+for browser, executable_path in args.items():
+    if executable_path is not None:
+        # Get browser name
+        browser_name = browser
+        # Get browser version
+        browser_version = get_browser_version(browser, executable_path)
+        print(
+            " Browser version for {0} is {1} in current executable path ".
+            format(browser_name, browser_version))
+
+        # Download vnc browser image.
+        download_new_image = check_and_download_vnc_browser_image(
+            browser_name, browser_version)
+
+        # If browser vnc image is available, then edit browsers.json
+        if download_new_image:
+            if edit_browsers_json(browser_name, browser_version):
+                print(
+                    " File 'browsers.json' is updated for {0} - {1} \n".format(
+                        browser_name, browser_version))
+            else:
+                print(
+                    " File 'browsers.json' can NOT be updated for {0} - {1} \n"
+                    .format(browser_name, browser_version))
+        else:
+            print(" Browser image is not available for {0}, {1}".format(
+                browser_name, browser_version))
+
+# Reload selenoid configuration
+if reload_selenoid_config():
+    print(
+        "***** Updated '{0}' for new browser versions.*****".format(file_path))
diff --git a/web/config.py b/web/config.py
index 7f74a7d..3978f4f 100644
--- a/web/config.py
+++ b/web/config.py
@@ -562,6 +562,18 @@ try:
 except ImportError:
     pass
 
+# Override DEFAULT_SERVE value from environment variable.
+if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
+    DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
+
+# SUPPORT_SSH_TUNNEL can be override in local config file and if that
+# setting is False in local config then we should not check the Python version.
+if (SUPPORT_SSH_TUNNEL is True and
+    ((sys.version_info[0] == 2 and sys.version_info[1] < 7) or
+     (sys.version_info[0] == 3 and sys.version_info[1] < 4))):
+    SUPPORT_SSH_TUNNEL = False
+    ALLOW_SAVE_TUNNEL_PASSWORD = False
+
 # Disable USER_INACTIVITY_TIMEOUT when SERVER_MODE=False
 if not SERVER_MODE:
     USER_INACTIVITY_TIMEOUT = 0
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 f127063..88526ca 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
@@ -44,31 +44,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)
@@ -81,10 +80,9 @@ 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)
 
 
 class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
@@ -117,30 +115,28 @@ class ConstraintsUsingIndexAddTestCase(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 "
                             "constraint using index.")
-        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_constraint_%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_constraint_%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)
@@ -158,7 +154,6 @@ class ConstraintsUsingIndexAddTestCase(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 7eea5e9..f35a00b 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 c08c2ac..f838a2f 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 87289a6..6bdf703 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 bbed269..02203d2 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 0a2963a..8e5f665 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 c84276e..1f63ef5 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/test_mviews_refresh.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
index b6c0bad..3b4055f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
@@ -48,8 +48,7 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
          ),
     ]
 
-    @classmethod
-    def setUpClass(self):
+    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"]
@@ -143,7 +142,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
                 # On success we get job_id from server
                 self.assertTrue('job_id' in response.json['data'])
 
-    @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 d6180c4..7619ee6 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 7091ffa..1f3bd65 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
@@ -60,8 +59,18 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self._mouseup_outside_grid_still_makes_a_selection()
         self._copies_rows_with_header()
 
+    def paste_values_to_scratch_pad(self):
+        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")
+        scratch_pad_ele.clear()
+        return clipboard_text
+
     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 +79,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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 +95,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +111,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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,11 +127,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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()
@@ -128,15 +140,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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 +166,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             self.page.driver
         ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
-            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
 
     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 +192,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             Keys.CONTROL
         ).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +209,13 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
-            pyperclip.paste())
+            clipboard_text)
 
     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 +230,9 @@ 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())
+        clipboard_text = self.paste_values_to_scratch_pad()
+
+        self.assertIn('"cool info"', clipboard_text)
 
     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 6d01286..33f86ad 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/keyboard_shortcut_test.py b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
index ea381b4..f68e530 100644
--- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py
+++ b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
@@ -94,24 +94,33 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
             NavMenuLocators.preference_menu_item_css)
         pref_menu_item.click()
 
-        # Wait till the preference dialogue box is displayed by checking the
-        # visibility of Show System Object label
-        self.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()
-
         browser_node = self.page.find_by_xpath(
             NavMenuLocators.specified_preference_tree_node.format('Browser'))
         if self.page.find_by_xpath(
             NavMenuLocators.specified_pref_node_exp_status.
                 format('Browser')).get_attribute('aria-expanded') == 'false':
-
             ActionChains(self.driver).double_click(browser_node).perform()
 
+        display_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
+                'Browser', 'Display'))
+        attempt = 5
+        while attempt > 0:
+            display_node.click()
+            # After clicking the element gets loaded in to the dom but still
+            # not visible, hence sleeping for a sec.
+            time.sleep(1)
+            if self.page.wait_for_element_to_be_visible(
+                self.driver,
+                    NavMenuLocators.show_system_objects_pref_label_xpath, 3):
+                break
+            else:
+                attempt -= 1
+
+        maximize_button = self.page.find_by_css_selector(
+            NavMenuLocators.maximize_pref_dialogue_css)
+        maximize_button.click()
+
         keyboard_node = self.page.find_by_xpath(
             NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
                 'Browser', 'Keyboard shortcuts'))
diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
index dba4ead..baccc70 100644
--- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py
+++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
@@ -92,6 +92,18 @@ class PGDataypeFeatureTest(BaseFeatureTest):
 
         wait = WebDriverWait(self.page.driver, 10)
 
+        browser_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_preference_tree_node.format('Browser'))
+        if self.page.find_by_xpath(
+            NavMenuLocators.specified_pref_node_exp_status.
+                format('Browser')).get_attribute('aria-expanded') == 'false':
+            ActionChains(self.driver).double_click(browser_node).perform()
+
+        self.page.retry_click(
+            (By.XPATH, NavMenuLocators.specified_sub_node_of_pref_tree_node.
+             format('Browser', 'Display')),
+            (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
+
         # Wait till the preference dialogue box is displayed by checking the
         # visibility of Show System Object label
         wait.until(EC.presence_of_element_located(
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 1d6d7a4..4cf9282 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,74 @@ 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):
+        """
+        Function updates preferences for binary path.
+        """
+        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 2d7c4ef..ddb4e3c 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 3f53d86..21921cc 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,20 +178,21 @@ 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._add_row(config_data_local)
         self._verify_row_data(row_height=0,
-                              config_check_data=config_data['add'])
+                              config_check_data=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(row_height=0,
                               config_check_data=updated_row_data)
@@ -221,7 +224,6 @@ CREATE TABLE public.nonintpkey
         Returns: None
 
         """
-
         self.wait.until(EC.visibility_of_element_located(
             (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
         )
@@ -238,7 +240,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(
@@ -290,7 +292,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()
@@ -300,12 +302,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(row_height=25,
                               config_check_data=updated_row_data)
@@ -329,11 +331,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 9efad8a..69c3cba 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 a80310e..faff52a 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -120,9 +120,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):
@@ -137,17 +136,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 46adfcc..a764565 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,59 @@ 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 --args "-limit 3 -cpu 1.5 -mem 1.5g"
+    $./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
+    Note : In --args "-limit 3 -cpu 1.5 -mem 1.5g"
+                -limit 3 :limits maximum parallel sessions(dockers) in selenoid,
+                -cpu :limit memory and CPU usage,
+                -mem :limit memory per session.
+           Generally max parallel session is the number of cores * 1.5 – 2
+           You can list available flags by using ./cm selenoid args
+    Additional Information about tool
+            - https://aerokube.com/selenoid/latest/
+
+  - Update 'test_config.json' with selenoid related info like
+    pgAdmin_default_server -
+        It is the IP address for the machine where pgadmin source code is
+        present.
+        You can get it on linux running command  'ifconfig | grep inet'
+        e.g. - 192.168.143.121
+    max_parallel_sessions -
+        This is other way to control number of tests to be run in parallel.
+        This should be equal or less than limit specified while setting up
+        selenoid
+    cross_Browsers -
+        List of browser name & version enclosed in {} on which tests to be
+        executed.
+        Make sure list contains those browsers & versions only which are shown
+        in capabilities tab while in selenoid status web-page.
+        If version is mention as null, then latest version available in
+        selenoiod server will be used for execution
+        e.g. - [ {"name": "Chrome","version": "80.0"},
+                 {"name": "Firefox","version": "74.0"}]
+    selenoid url -
+        Url formed as below -
+        http://<IP address of Selenoid Installed machine>:4444/wd/hub/
+        e.g. - selenoid_url": "http://192.168.143.121:4444/wd/hub"
+
+
 - Change to the regression test directory:
      run 'cd web/regression'
 
@@ -190,9 +243,14 @@ Python Tests:
      Example 2)  Execute only reverse engineered SQL test framework for some modules
          run 'python runtests.py --pkg resql --modules sequences,functions'
 
+
      Example 3) Exclude reverse engineered SQL test framework for all modules
          run 'python runtests.py --exclude resql'
 
+- 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 fe4c441..7de10b2 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 b8169fa..4756231 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"
@@ -217,9 +219,9 @@ class QueryToolLocators:
     btn_commit = "#btn-commit"
 
     show_query_internally_btn = \
-        "//div[label[normalize-space(" \
-        "text())='Show queries generated internally by pgAdmin?']]" \
-        "//div[contains(@class,'toggle btn')]"
+        "//div[label[contains(normalize-space(text())," \
+        "'Show queries generated internally by')]]//" \
+        "div[contains(@class,'toggle btn')]"
 
     editable_column_icon_xpath = "//div[contains(@class," \
                                  " 'editable-column-header-icon')]" \
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index d7e8fe3..d10b9eb 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,27 @@ class PgadminPage:
             except Exception:
                 attempt += 1
         return click_status
+
+    def paste_values(self, el=None):
+        """
+        Function paste values in scratch pad
+        :param el:
+        """
+        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()
+
+    def wait_for_element_to_be_visible(self, driver, xpath, time_value=20):
+        """This will wait until an element is visible on page"""
+        element_located_status = False
+        try:
+            if WebDriverWait(driver, time_value).until(
+                    EC.visibility_of_element_located((By.XPATH, xpath))):
+                element_located_status = True
+        except TimeoutException:
+            element_located_status = False
+        return element_located_status
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc24..e94a8b9 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -8,6 +8,8 @@
 ##########################################################################
 
 from __future__ import print_function
+
+import fileinput
 import traceback
 import os
 import sys
@@ -16,7 +18,17 @@ import psycopg2
 import sqlite3
 import shutil
 from functools import partial
+
+from selenium.webdriver.support.wait import WebDriverWait
 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
+from selenium.webdriver.support import expected_conditions as ec
 
 import config
 import regression
@@ -1216,3 +1228,247 @@ 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 = get_selenium_grid_status_json(selrnoid_url)
+
+        # 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
+
+        if selenoid_status:
+            available_browsers = selenoid_status["browsers"]
+            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"] is None:
+                        print("Specified version is None. Hence will use "
+                              "latest version available with selenoid server")
+                        browser_list.append(browser)
+                    elif 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, title='pgAdmin 4', timeout=40):
+    """
+    Function launches urls in specified driver instance
+    :param driver_instance:browser instance
+    :param url:url to be launched
+    :param title:web-page tile on successful launch default is 'pgAdmin 4'
+    :param timeout:in seconds for getting specified title default is 20sec
+    :return:
+    """
+    count = timeout / 10
+    while count > 0:
+        try:
+            driver_instance.get(url)
+            wait = WebDriverWait(driver_instance, 10)
+            wait.until(ec.title_is(title))
+            break
+        except WebDriverException as e:
+            count -= 1
+            if count == 0:
+                raise Exception(
+                    'Web-page title did not match to {0} \n'.format(title))
+
+
+def get_remote_webdriver(hub_url, browser, browser_ver, test_name):
+    """
+    This functions returns remote web-driver instance created in selenoid
+    machine.
+    :param hub_url
+    :param browser: browser name
+    :param browser_ver: version for browser
+    :param test_name: test name
+    :return: remote web-driver instance for specified browser
+    """
+    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)
+    elif browser == 'chrome':
+        options = Options()
+        options.add_argument("--window-size=1280,1024")
+        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
+
+
+def get_parallel_sequential_module_list(module_list):
+    """
+    Functions segregate parallel & sequential modules
+    :param module_list: Complete list of modules
+    :return: parallel & sequential module lists
+    """
+    # 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']
+
+    #  list of tests can be executed in parallel
+    parallel_tests = list(module_list)
+    for module in 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,
+               module_list))
+
+    # return parallel & sequential lists
+    return parallel_tests, sequential_tests
+
+
+def get_browser_details(browser_info_dict, url):
+    """
+    Function extracts browser name & version from browser info dict
+    in test_config.json
+    :param browser_info_dict:
+    :return: browser name & version
+    """
+    browser_name = browser_info_dict["name"].lower()
+    browser_version = browser_info_dict["version"]
+    if browser_version is None:
+        selenoid_status = get_selenium_grid_status_json(url)
+        versions = selenoid_status["browsers"][browser_name]
+        browser_version = max(versions)
+    return browser_name, browser_version
+
+
+def print_test_summary(complete_module_list, parallel_testlist,
+                       sequential_tests_list, browser_name, browser_version):
+    """
+    Prints test summary about total, parallel, sequential, browser name,
+    browser version information
+    :param complete_module_list:
+    :param parallel_testlist:
+    :param sequential_tests_list:
+    :param browser_name:
+    :param browser_version:
+    """
+    print(
+        "=================================================================",
+        file=sys.stderr
+    )
+    print(
+        "Total Tests # {0}\nParallel Tests # {1}, "
+        "Sequential Tests # {2}".format(
+            len(complete_module_list), len(parallel_testlist),
+            len(sequential_tests_list)),
+        file=sys.stderr)
+    print("Browser: [Name:{0}, Version: {1}]".format(
+        browser_name.capitalize(), browser_version),
+        file=sys.stderr)
+    print(
+        "=================================================================\n",
+        file=sys.stderr
+    )
+
+
+def get_selenium_grid_status_json(selenoid_url):
+    """
+    Functions returns json response received from selenoid server
+    :param selenoid_url:
+    :return:
+    """
+    try:
+        selenoid_status = urllib.urlopen(
+            "http://" + re.split('/', (re.split('//', selenoid_url, 1)[1]))[
+                0] + "/status", timeout=10)
+        selenoid_status = json.load(selenoid_status)
+        if isinstance(selenoid_status, dict):
+            return selenoid_status
+    except Exception as e:
+        print(str(e))
+        print("Unable to find Selenoid Status")
+        return None
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index 480aaad..2f4ddba 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 < (3, 4):
@@ -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,62 @@ 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 arguments['pkg'] == "feature_tests":
+            exclude_pkgs.extend(['resql'])
+
+        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
+            # 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()
 
     handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
     # Register cleanup function to cleanup on exit
@@ -319,6 +325,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 +413,212 @@ class StreamToLogger(object):
         pass
 
 
+def execute_test(test_module_list_passed, server_passed, driver_passed):
+    """
+    Function executes actually test
+    :param test_module_list_passed:
+    :param server_passed:
+    :param driver_passed:
+    :return:
+    """
+    try:
+        print("\n=============Running the test cases for '%s' ============="
+              % server_passed['name'], file=sys.stderr)
+        # 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 threading.current_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 as exc:
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        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:
+            driver_passed.quit()
+            time.sleep(20)
+
+        # Print info about completed tests
+        print(
+            "\n=============Completed the test cases for '%s'============="
+            % server_passed['name'], file=sys.stderr)
+
+
+def run_parallel_tests(url_client, servers_details, parallel_tests_lists,
+                       name_of_browser, version_of_browser, max_thread_count):
+    """
+    Function used to run tests in parallel
+    :param url_client:
+    :param servers_details:
+    :param parallel_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :param max_thread_count:
+    """
+    driver_object = None
+    try:
+        # Thread list
+        threads_list = []
+        # Create thread for each server
+        for ser in servers_details:
+            # Logic to add new threads
+            while True:
+                # If active thread count <= max_thread_count, add new thread
+                if threading.activeCount() <= max_thread_count:
+                    # Get remote web-driver instance at server level
+                    driver_object = \
+                        test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        ser['name'])
+                    # Launch client url in browser
+                    test_utils.launch_url_in_browser(driver_object, url_client)
+
+                    # Add name for thread
+                    thread_name = "parallel_tests" + ser['name']
+
+                    # Start thread
+                    t = threading.Thread(target=execute_test, name=thread_name,
+                                         args=(parallel_tests_lists, ser,
+                                               driver_object))
+                    threads_list.append(t)
+                    t.start()
+                    time.sleep(3)
+                    break
+                # else sleep for 10 seconds
+                else:
+                    time.sleep(10)
+
+        # Start threads in parallel
+        for t in threads_list:
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        # Clean driver object created
+        if driver_object is not None:
+            driver_object.quit()
+
+
+def run_sequential_tests(url_client, servers_details, sequential_tests_lists,
+                         name_of_browser, version_of_browser):
+    """
+    Function is used to execute tests that needs to be run in sequential
+    manner.
+    :param url_client:
+    :param servers_details:
+    :param sequential_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :return:
+    """
+    driver_object = None
+    try:
+        # Get remote web-driver instance
+        driver_object = test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        "Sequential_Tests")
+
+        # Launch client url in browser
+        test_utils.launch_url_in_browser(driver_object, url_client)
+
+        # Add name for thread
+        thread_name = "sequential_tests"
+
+        # Start thread
+        for ser in servers_details:
+            t = threading.Thread(target=execute_test,
+                                 name=thread_name,
+                                 args=(sequential_tests_lists, ser,
+                                       driver_object))
+            t.start()
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+    finally:
+        # Clean driver object created
+        driver_object.quit()
+
+
 if __name__ == '__main__':
     # Failure detected?
     failure = False
@@ -423,7 +638,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 +667,97 @@ 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 info dict hub url
+        selenoid_info = test_setup.config_data['selenoid_info']
+
+        # Set DEFAULT_SERVER value
+        default_server = selenoid_info['pgAdmin_default_server']
+        os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"] = str(default_server)
+        config.DEFAULT_SERVER = str(default_server)
+
+        # Get hub url
+        hub_url = selenoid_info['selenoid_url']
+        # 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(hub_url)
+
+        # Execute tests if selenium-grid is up
+        if selenium_grid_status and len(list_of_browsers) > 0:
+
+            app_starter_local = None
+            try:
+                # run across browsers
+                for browser_info in list_of_browsers:
+                    # browser info
+                    browser_name, browser_version = \
+                        test_utils.get_browser_details(browser_info, hub_url)
+
+                    # tests lists can be executed in parallel & sequentially
+                    parallel_tests, sequential_tests = \
+                        test_utils.get_parallel_sequential_module_list(
+                            test_module_list)
+
+                    # Print test summary
+                    test_utils.print_test_summary(test_module_list,
+                                                  parallel_tests,
+                                                  sequential_tests,
+                                                  browser_name,
+                                                  browser_version)
+
+                    # Create app form source code
+                    app_starter_local = AppStarter(None, config)
+                    client_url = app_starter_local.start_app()
+
+                    # Running Parallel tests
+                    if len(parallel_tests) > 0:
+                        parallel_sessions = int(selenoid_info[
+                            'max_parallel_sessions'])
+
+                        run_parallel_tests(client_url, servers_info,
+                                           parallel_tests, browser_name,
+                                           browser_version, parallel_sessions)
+
+                    # Wait till all threads started in parallel are finished
+                    while True:
+                        try:
+                            if threading.activeCount() <= 1:
+                                break
+                            else:
+                                time.sleep(10)
+                        except Exception as e:
+                            traceback.print_exc(file=sys.stderr)
+                            print(str(e))
+
+                    # Sequential Tests
+                    if len(sequential_tests) > 0:
+                        run_sequential_tests(client_url, servers_info,
+                                             sequential_tests, browser_name,
+                                             browser_version)
+
+                    # Clean up environment
+                    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()
+            # Pause before printing result in order not to mix output
+            time.sleep(3)
+    else:
+        try:
+            for server in servers_info:
+                thread = threading.Thread(target=execute_test, args=(
+                    test_module_list, server, driver))
+                thread.start()
+                thread.join()
+        except SystemExit:
+            if handle_cleanup:
+                handle_cleanup()
 
     print(
         "\n==============================================================="
@@ -543,6 +787,9 @@ if __name__ == '__main__':
         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"
             "\n\t%s test%s skipped%s%s\n" %
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index af061b9..bbd0e1e 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,10 @@
 {
   "headless_chrome": false,
   "default_browser": "Chrome",
+  "selenoid_info":{"pgAdmin_default_server":"ip address of machine where source code executed",
+                    "max_parallel_sessions": "3",
+                    "cross_Browsers": [{"name": "browser_name","version": "browser_version"}],
+                    "selenoid_url": "Selenoid Url"},
   "pgAdmin4_login_credentials": {
     "new_password": "NEWPASSWORD",
     "login_password": "PASSWORD",
diff --git a/web/yarn.lock b/web/yarn.lock
index 151e98c..6969848 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -29,12 +29,12 @@
     semver "^5.4.1"
     source-map "^0.5.0"
 
-"@babel/generator@^7.6.4", "@babel/generator@^7.9.0":
-  version "7.9.4"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.4.tgz#12441e90c3b3c4159cdecf312075bf1a8ce2dbce"
-  integrity sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==
+"@babel/generator@^7.6.4", "@babel/generator@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9"
+  integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==
   dependencies:
-    "@babel/types" "^7.9.0"
+    "@babel/types" "^7.9.5"
     jsesc "^2.5.1"
     lodash "^4.17.13"
     source-map "^0.5.0"
@@ -80,14 +80,14 @@
     "@babel/traverse" "^7.8.3"
     "@babel/types" "^7.8.3"
 
-"@babel/helper-function-name@^7.8.3":
-  version "7.8.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
-  integrity sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==
+"@babel/helper-function-name@^7.8.3", "@babel/helper-function-name@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
+  integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==
   dependencies:
     "@babel/helper-get-function-arity" "^7.8.3"
     "@babel/template" "^7.8.3"
-    "@babel/types" "^7.8.3"
+    "@babel/types" "^7.9.5"
 
 "@babel/helper-get-function-arity@^7.8.3":
   version "7.8.3"
@@ -185,10 +185,10 @@
   dependencies:
     "@babel/types" "^7.8.3"
 
-"@babel/helper-validator-identifier@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed"
-  integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==
+"@babel/helper-validator-identifier@^7.9.0", "@babel/helper-validator-identifier@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
+  integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
 
 "@babel/helper-wrap-function@^7.8.3":
   version "7.8.3"
@@ -249,12 +249,13 @@
     "@babel/plugin-syntax-json-strings" "^7.8.0"
 
 "@babel/plugin-proposal-object-rest-spread@^7.6.2":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.0.tgz#a28993699fc13df165995362693962ba6b061d6f"
-  integrity sha512-UgqBv6bjq4fDb8uku9f+wcm1J7YxJ5nT7WO/jBr0cl0PLKb7t1O6RNR1kZbjgx2LQtsDI9hwoQVmn0yhXeQyow==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz#3fd65911306d8746014ec0d0cf78f0e39a149116"
+  integrity sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
+    "@babel/plugin-transform-parameters" "^7.9.5"
 
 "@babel/plugin-proposal-optional-catch-binding@^7.2.0":
   version "7.8.3"
@@ -339,13 +340,13 @@
     lodash "^4.17.13"
 
 "@babel/plugin-transform-classes@^7.5.5":
-  version "7.9.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.2.tgz#8603fc3cc449e31fdbdbc257f67717536a11af8d"
-  integrity sha512-TC2p3bPzsfvSsqBZo0kJnuelnoK9O3welkUpqSqBQuBF6R5MN2rysopri8kNvtlGIb2jmUO7i15IooAZJjZuMQ==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.9.5.tgz#800597ddb8aefc2c293ed27459c1fcc935a26c2c"
+  integrity sha512-x2kZoIuLC//O5iA7PEvecB105o7TLzZo8ofBVhP79N+DO3jaX+KYfww9TQcfBEZD0nikNyYcGB1IKtRq36rdmg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.8.3"
     "@babel/helper-define-map" "^7.8.3"
-    "@babel/helper-function-name" "^7.8.3"
+    "@babel/helper-function-name" "^7.9.5"
     "@babel/helper-optimise-call-expression" "^7.8.3"
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/helper-replace-supers" "^7.8.6"
@@ -360,9 +361,9 @@
     "@babel/helper-plugin-utils" "^7.8.3"
 
 "@babel/plugin-transform-destructuring@^7.6.0":
-  version "7.8.8"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.8.tgz#fadb2bc8e90ccaf5658de6f8d4d22ff6272a2f4b"
-  integrity sha512-eRJu4Vs2rmttFCdhPUM3bV0Yo/xPSdPw6ML9KHs/bjB4bLA5HXlbvYXPOD5yASodGod+krjYx21xm1QmL8dCJQ==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.9.5.tgz#72c97cf5f38604aea3abf3b935b0e17b1db76a50"
+  integrity sha512-j3OEsGel8nHL/iusv/mRd5fYZ3DrOxWC82x0ogmdN/vHfAP4MYw+AFKYanzWlktNwikKvlzUV//afBW5FTp17Q==
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.3"
 
@@ -477,10 +478,10 @@
     "@babel/helper-plugin-utils" "^7.8.3"
     "@babel/helper-replace-supers" "^7.8.3"
 
-"@babel/plugin-transform-parameters@^7.4.4":
-  version "7.9.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.3.tgz#3028d0cc20ddc733166c6e9c8534559cee09f54a"
-  integrity sha512-fzrQFQhp7mIhOzmOtPiKffvCYQSK10NR8t6BBz2yPbeUHb9OLW8RZGtgDRBn8z2hGcwvKDL3vC7ojPTLNxmqEg==
+"@babel/plugin-transform-parameters@^7.4.4", "@babel/plugin-transform-parameters@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.9.5.tgz#173b265746f5e15b2afe527eeda65b73623a0795"
+  integrity sha512-0+1FhHnMfj6lIIhVvS4KGQJeuhe1GI//h5uptK4PvLt+BGBxsoUJbd3/IW002yk//6sZPlFgsG1hY6OHLcy6kA==
   dependencies:
     "@babel/helper-get-function-arity" "^7.8.3"
     "@babel/helper-plugin-utils" "^7.8.3"
@@ -624,26 +625,26 @@
     "@babel/types" "^7.8.6"
 
 "@babel/traverse@^7.6.3", "@babel/traverse@^7.8.3", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.0.tgz#d3882c2830e513f4fe4cec9fe76ea1cc78747892"
-  integrity sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
+  integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
   dependencies:
     "@babel/code-frame" "^7.8.3"
-    "@babel/generator" "^7.9.0"
-    "@babel/helper-function-name" "^7.8.3"
+    "@babel/generator" "^7.9.5"
+    "@babel/helper-function-name" "^7.9.5"
     "@babel/helper-split-export-declaration" "^7.8.3"
     "@babel/parser" "^7.9.0"
-    "@babel/types" "^7.9.0"
+    "@babel/types" "^7.9.5"
     debug "^4.1.0"
     globals "^11.1.0"
     lodash "^4.17.13"
 
-"@babel/types@^7.6.3", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
-  version "7.9.0"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.0.tgz#00b064c3df83ad32b2dbf5ff07312b15c7f1efb5"
-  integrity sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==
+"@babel/types@^7.6.3", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
+  version "7.9.5"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
+  integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.9.0"
+    "@babel/helper-validator-identifier" "^7.9.5"
     lodash "^4.17.13"
     to-fast-properties "^2.0.0"
 
@@ -660,9 +661,9 @@
   integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
 
 "@types/node@*":
-  version "13.11.0"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.0.tgz#390ea202539c61c8fa6ba4428b57e05bc36dc47b"
-  integrity sha512-uM4mnmsIIPK/yeO+42F2RQhGUIs39K2RFmugcJANppXe6J1nvH87PvzPZYpza7Xhhs8Yn9yIAVdLZ84z61+0xQ==
+  version "13.11.1"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"
+  integrity sha512-eWQGP3qtxwL8FGneRrC5DwrJLGN4/dH1clNTuLfN81HCrxVtxRjygDTUoZJ5ASlDEeo0ppYFQjQIlXhtXpOn6g==
 
 "@types/q@^1.5.1":
   version "1.5.2"
@@ -837,6 +838,11 @@ JSONStream@^1.0.3:
     jsonparse "^1.2.0"
     through ">=2.2.7 <3"
 
+abbrev@1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+  integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
 accepts@~1.3.4, accepts@~1.3.7:
   version "1.3.7"
   resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -973,7 +979,7 @@ anymatch@~3.1.1:
     normalize-path "^3.0.0"
     picomatch "^2.0.4"
 
-aproba@^1.1.1:
+aproba@^1.0.3, aproba@^1.1.1:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
   integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
@@ -990,6 +996,14 @@ archive-type@^4.0.0:
   dependencies:
     file-type "^4.2.0"
 
+are-we-there-yet@~1.1.2:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
+  integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
+  dependencies:
+    delegates "^1.0.0"
+    readable-stream "^2.0.6"
+
 argparse@^1.0.6, argparse@^1.0.7:
   version "1.0.10"
   resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -1094,12 +1108,12 @@ atob@^2.1.2:
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
 autoprefixer@^9.6.4:
-  version "9.7.5"
-  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.5.tgz#8df10b9ff9b5814a8d411a5cfbab9c793c392376"
-  integrity sha512-URo6Zvt7VYifomeAfJlMFnYDhow1rk2bufwkbamPEAtQFcL11moLk4PnR7n9vlu7M+BkXAZkHFA0mIcY7tjQFg==
+  version "9.7.6"
+  resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4"
+  integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ==
   dependencies:
-    browserslist "^4.11.0"
-    caniuse-lite "^1.0.30001036"
+    browserslist "^4.11.1"
+    caniuse-lite "^1.0.30001039"
     chalk "^2.4.2"
     normalize-range "^0.1.2"
     num2fraction "^1.2.2"
@@ -1882,61 +1896,7 @@ browserify-zlib@^0.2.0, browserify-zlib@~0.2.0:
   dependencies:
     pako "~1.0.5"
 
-browserify@^16.1.0:
-  version "16.5.1"
-  resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.1.tgz#3c13c97436802930d5c3ae28658ddc33bfd37dc2"
-  integrity sha512-EQX0h59Pp+0GtSRb5rL6OTfrttlzv+uyaUVlK6GX3w11SQ0jKPKyjC/54RhPR2ib2KmfcELM06e8FxcI5XNU2A==
-  dependencies:
-    JSONStream "^1.0.3"
-    assert "^1.4.0"
-    browser-pack "^6.0.1"
-    browser-resolve "^1.11.0"
-    browserify-zlib "~0.2.0"
-    buffer "~5.2.1"
-    cached-path-relative "^1.0.0"
-    concat-stream "^1.6.0"
-    console-browserify "^1.1.0"
-    constants-browserify "~1.0.0"
-    crypto-browserify "^3.0.0"
-    defined "^1.0.0"
-    deps-sort "^2.0.0"
-    domain-browser "^1.2.0"
-    duplexer2 "~0.1.2"
-    events "^2.0.0"
-    glob "^7.1.0"
-    has "^1.0.0"
-    htmlescape "^1.1.0"
-    https-browserify "^1.0.0"
-    inherits "~2.0.1"
-    insert-module-globals "^7.0.0"
-    labeled-stream-splicer "^2.0.0"
-    mkdirp-classic "^0.5.2"
-    module-deps "^6.0.0"
-    os-browserify "~0.3.0"
-    parents "^1.0.1"
-    path-browserify "~0.0.0"
-    process "~0.11.0"
-    punycode "^1.3.2"
-    querystring-es3 "~0.2.0"
-    read-only-stream "^2.0.0"
-    readable-stream "^2.0.2"
-    resolve "^1.1.4"
-    shasum "^1.0.0"
-    shell-quote "^1.6.1"
-    stream-browserify "^2.0.0"
-    stream-http "^3.0.0"
-    string_decoder "^1.1.1"
-    subarg "^1.0.0"
-    syntax-error "^1.1.1"
-    through2 "^2.0.0"
-    timers-browserify "^1.0.1"
-    tty-browserify "0.0.1"
-    url "~0.11.0"
-    util "~0.10.1"
-    vm-browserify "^1.0.0"
-    xtend "^4.0.0"
-
-browserify@~16.2.3:
+browserify@^16.1.0, browserify@~16.2.3:
   version "16.2.3"
   resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.3.tgz#7ee6e654ba4f92bce6ab3599c3485b1cc7a0ad0b"
   integrity sha512-zQt/Gd1+W+IY+h/xX2NYMW4orQWhqSwyV+xsblycTtpOuB27h1fZhhNQuipJ4t79ohw4P4mMem0jp/ZkISQtjQ==
@@ -1990,7 +1950,7 @@ browserify@~16.2.3:
     vm-browserify "^1.0.0"
     xtend "^4.0.0"
 
-browserslist@^4.0.0, browserslist@^4.11.0, browserslist@^4.6.0, browserslist@^4.8.3:
+browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.6.0, browserslist@^4.8.3:
   version "4.11.1"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.11.1.tgz#92f855ee88d6e050e7e7311d987992014f1a1f1b"
   integrity sha512-DCTr3kDrKEYNw6Jb9HFxVLQNaue8z+0ZfRBRjmCunKDEXEBajKDj2Y+Uelg+Pi29OnvaSGwjOsnRyNEkXzHg5g==
@@ -2050,14 +2010,6 @@ buffer@^5.0.2, buffer@^5.2.1:
     base64-js "^1.0.2"
     ieee754 "^1.1.4"
 
-buffer@~5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6"
-  integrity sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==
-  dependencies:
-    base64-js "^1.0.2"
-    ieee754 "^1.1.4"
-
 builtin-status-codes@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -2203,10 +2155,10 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001036, caniuse-lite@^1.0.30001038:
-  version "1.0.30001039"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001039.tgz#b3814a1c38ffeb23567f8323500c09526a577bbe"
-  integrity sha512-SezbWCTT34eyFoWHgx8UWso7YtvtM7oosmFoXbCkdC6qJzRfBTeTgE9REtKtiuKXuMwWTZEvdnFNGAyVMorv8Q==
+caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001038, caniuse-lite@^1.0.30001039:
+  version "1.0.30001040"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001040.tgz#103fc8e6eb1d7397e95134cd0e996743353d58ea"
+  integrity sha512-Ep0tEPeI5wCvmJNrXjE3etgfI+lkl1fTDU6Y3ZH1mhrjkPlVI9W4pcKbMo+BQLpEWKVYYp2EmYaRsqpPC3k7lQ==
 
 caw@^2.0.0, caw@^2.0.1:
   version "2.0.1"
@@ -2248,22 +2200,7 @@ check-types@^8.0.3:
   resolved "https://registry.yarnpkg.com/check-types/-/check-types-8.0.3.tgz#3356cca19c889544f2d7a95ed49ce508a0ecf552"
   integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ==
 
-"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
-  integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
-  dependencies:
-    anymatch "~3.1.1"
-    braces "~3.0.2"
-    glob-parent "~5.1.0"
-    is-binary-path "~2.1.0"
-    is-glob "~4.0.1"
-    normalize-path "~3.0.0"
-    readdirp "~3.3.0"
-  optionalDependencies:
-    fsevents "~2.1.2"
-
-chokidar@^2.1.1, chokidar@^2.1.8:
+"chokidar@>=2.0.0 <4.0.0", chokidar@^2.1.1, chokidar@^2.1.8:
   version "2.1.8"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
   integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
@@ -2282,6 +2219,21 @@ chokidar@^2.1.1, chokidar@^2.1.8:
   optionalDependencies:
     fsevents "^1.2.7"
 
+chokidar@^3.0.0:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.1.tgz#c84e5b3d18d9a4d77558fef466b1bf16bbeb3450"
+  integrity sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==
+  dependencies:
+    anymatch "~3.1.1"
+    braces "~3.0.2"
+    glob-parent "~5.1.0"
+    is-binary-path "~2.1.0"
+    is-glob "~4.0.1"
+    normalize-path "~3.0.0"
+    readdirp "~3.3.0"
+  optionalDependencies:
+    fsevents "~2.1.2"
+
 chownr@^1.1.1, chownr@^1.1.2:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
@@ -2363,6 +2315,11 @@ coa@^2.0.2:
     chalk "^2.4.1"
     q "^1.1.2"
 
+code-point-at@^1.0.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
+  integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+
 codemirror@^5.50.0:
   version "5.52.2"
   resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.52.2.tgz#c29e1f7179f85eb0dd17c0586fa810e4838ff584"
@@ -2383,16 +2340,11 @@ color-convert@^1.9.0, color-convert@^1.9.1:
   dependencies:
     color-name "1.1.3"
 
[email protected]:
[email protected], color-name@^1.0.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
   integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
 
-color-name@^1.0.0:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
-  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-
 color-string@^1.5.2:
   version "1.5.3"
   resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc"
@@ -2504,6 +2456,11 @@ console-browserify@^1.1.0:
   resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336"
   integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==
 
+console-control-strings@^1.0.0, console-control-strings@~1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
+  integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
+
 console-stream@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/console-stream/-/console-stream-0.1.1.tgz#a095fe07b20465955f2fafd28b5d72bccd949d44"
@@ -2526,14 +2483,7 @@ content-type@~1.0.4:
   resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
   integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
 
-convert-source-map@^1.1.0, convert-source-map@^1.1.3:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
-  integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
-  dependencies:
-    safe-buffer "~5.1.1"
-
-convert-source-map@~1.1.0:
+convert-source-map@^1.1.0, convert-source-map@^1.1.3, convert-source-map@~1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860"
   integrity sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=
@@ -2602,9 +2552,9 @@ core-js@^2.4.0:
   integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
 
 core-js@^3.2.1, core-js@^3.6.4:
-  version "3.6.4"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.4.tgz#440a83536b458114b9cb2ac1580ba377dc470647"
-  integrity sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==
+  version "3.6.5"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
+  integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
 
 core-util-is@~1.0.0:
   version "1.0.2"
@@ -2896,7 +2846,7 @@ debug@=3.1.0, debug@~3.1.0:
   dependencies:
     ms "2.0.0"
 
-debug@^3.0.0, debug@^3.2.6:
+debug@^3.2.6:
   version "3.2.6"
   resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
   integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -2980,6 +2930,11 @@ decompress@^4.0.0, decompress@^4.2.0:
     pify "^2.3.0"
     strip-dirs "^2.0.0"
 
+deep-extend@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+  integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
 deep-is@~0.1.3:
   version "0.1.3"
   resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -3019,6 +2974,11 @@ defined@^1.0.0:
   resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
   integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
 
+delegates@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
+  integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
+
 depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
@@ -3052,6 +3012,11 @@ detect-file@^1.0.0:
   resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
   integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=
 
+detect-libc@^1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
+  integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
+
 detective@^5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
@@ -3220,9 +3185,9 @@ ejs@^3.0.0:
   integrity sha512-IncmUpn1yN84hy2shb0POJ80FWrfGNY0cxO9f4v+/sG7qcBvAtVWUA1IdzY/8EYUmOVhoKJVdJjNd3AZcnxOjA==
 
 electron-to-chromium@^1.3.390:
-  version "1.3.397"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.397.tgz#db640c2e67b08d590a504c20b56904537aa2bafa"
-  integrity sha512-zcUd1p/7yzTSdWkCTrqGvbnEOASy96d0RJL/lc5BDJoO23Z3G/VHd0yIPbguDU9n8QNUTCigLO7oEdtOb7fp2A==
+  version "1.3.402"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.402.tgz#9ad93c0c8ea2e571431739e0d76bd6bc9788a846"
+  integrity sha512-gaCDfX7IUH0s3JmBiHCDPrvVcdnTTP1r4WLJc2dHkYYbLmXZ2XHiJCcGQ9Balf91aKTvuCKCyu2JjJYRykoI1w==
 
 elliptic@^6.0.0:
   version "6.5.2"
@@ -3941,20 +3906,13 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
[email protected]:
[email protected], follow-redirects@^1.0.0:
   version "1.5.10"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
   integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
   dependencies:
     debug "=3.1.0"
 
-follow-redirects@^1.0.0:
-  version "1.11.0"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb"
-  integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA==
-  dependencies:
-    debug "^3.0.0"
-
 font-awesome@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
@@ -4011,6 +3969,13 @@ fs-extra@^7.0.1:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-minipass@^1.2.5:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
+  integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==
+  dependencies:
+    minipass "^2.6.0"
+
 fs-minipass@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
@@ -4056,6 +4021,20 @@ functional-red-black-tree@^1.0.1:
   resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
   integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
 
+gauge@~2.7.3:
+  version "2.7.4"
+  resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
+  integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
+  dependencies:
+    aproba "^1.0.3"
+    console-control-strings "^1.0.0"
+    has-unicode "^2.0.0"
+    object-assign "^4.1.0"
+    signal-exit "^3.0.0"
+    string-width "^1.0.1"
+    strip-ansi "^3.0.1"
+    wide-align "^1.1.0"
+
 geometry-interfaces@^1.1.4:
   version "1.1.4"
   resolved "https://registry.yarnpkg.com/geometry-interfaces/-/geometry-interfaces-1.1.4.tgz#9e82af6700ca639a675299f08e1f5fbc4a79d48d"
@@ -4321,6 +4300,11 @@ has-to-string-tag-x@^1.2.0:
   dependencies:
     has-symbol-support-x "^1.4.1"
 
+has-unicode@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
+  integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
+
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -4436,7 +4420,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
   integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
 
[email protected]:
[email protected], http-errors@~1.7.2:
   version "1.7.2"
   resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
   integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
@@ -4447,17 +4431,6 @@ [email protected]:
     statuses ">= 1.5.0 < 2"
     toidentifier "1.0.0"
 
-http-errors@~1.7.2:
-  version "1.7.3"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
-  integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
-  dependencies:
-    depd "~1.1.2"
-    inherits "2.0.4"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
-
 http-proxy@^1.13.0:
   version "1.18.0"
   resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.0.tgz#dbe55f63e75a347db7f3d99974f2692a314a6a3a"
@@ -4483,7 +4456,7 @@ iconfont-webpack-plugin@^4.2.1:
     svgicons2svgfont "9.1.1"
     ttf2woff "2.0.1"
 
[email protected], iconv-lite@^0.4.24:
[email protected], iconv-lite@^0.4.24, iconv-lite@^0.4.4:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
   integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
@@ -4512,6 +4485,13 @@ iferr@^0.1.5:
   resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
   integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
 
+ignore-walk@^3.0.1:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+  integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+  dependencies:
+    minimatch "^3.0.4"
+
 ignore@^3.3.5:
   version "3.3.10"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
@@ -4701,7 +4681,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, [email protected], inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -4716,7 +4696,7 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
   integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
 
-ini@^1.3.4, ini@^1.3.5:
+ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   version "1.3.5"
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
   integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -4948,6 +4928,13 @@ is-finite@^1.0.0:
   resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3"
   integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==
 
+is-fullwidth-code-point@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
+  integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
+  dependencies:
+    number-is-nan "^1.0.0"
+
 is-fullwidth-code-point@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
@@ -5133,12 +5120,7 @@ isurl@^1.0.0-alpha5:
     has-to-string-tag-x "^1.2.0"
     is-object "^1.0.1"
 
-jasmine-core@^3.3:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
-  integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==
-
-jasmine-core@~3.3.0:
+jasmine-core@^3.3, jasmine-core@~3.3.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.3.0.tgz#dea1cdc634bc93c7e0d4ad27185df30fa971b10e"
   integrity sha512-3/xSmG/d35hf80BEN66Y6g9Ca5l/Isdeg/j6zvbTYlTzeKinzmaTM4p9am5kYqOmE05D7s1t8FGjzdSnbUbceA==
@@ -5184,16 +5166,16 @@ js-string-escape@^1.0.0:
   resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef"
   integrity sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=
 
-"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
-  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
-
-js-tokens@^3.0.2:
+"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^3.0.2:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
   integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
 
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
 js-yaml@^3.12.0, js-yaml@^3.13.1:
   version "3.13.1"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
@@ -5862,6 +5844,14 @@ minipass-pipeline@^1.2.2:
   dependencies:
     minipass "^3.0.0"
 
+minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0:
+  version "2.9.0"
+  resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6"
+  integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==
+  dependencies:
+    safe-buffer "^5.1.2"
+    yallist "^3.0.0"
+
 minipass@^3.0.0, minipass@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5"
@@ -5869,6 +5859,13 @@ minipass@^3.0.0, minipass@^3.1.1:
   dependencies:
     yallist "^4.0.0"
 
+minizlib@^1.2.1:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d"
+  integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==
+  dependencies:
+    minipass "^2.9.0"
+
 mississippi@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022"
@@ -5893,11 +5890,6 @@ mixin-deep@^1.2.0:
     for-in "^1.0.2"
     is-extendable "^1.0.1"
 
-mkdirp-classic@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b"
-  integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==
-
 mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
@@ -6025,6 +6017,15 @@ neatequal@^1.0.0:
   dependencies:
     varstream "^0.3.2"
 
+needle@^2.2.1:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117"
+  integrity sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw==
+  dependencies:
+    debug "^3.2.6"
+    iconv-lite "^0.4.4"
+    sax "^1.2.4"
+
 [email protected]:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -6069,11 +6070,35 @@ node-libs-browser@^2.2.1:
     util "^0.11.0"
     vm-browserify "^1.0.1"
 
+node-pre-gyp@*:
+  version "0.14.0"
+  resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
+  integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
+  dependencies:
+    detect-libc "^1.0.2"
+    mkdirp "^0.5.1"
+    needle "^2.2.1"
+    nopt "^4.0.1"
+    npm-packlist "^1.1.6"
+    npmlog "^4.0.2"
+    rc "^1.2.7"
+    rimraf "^2.6.1"
+    semver "^5.3.0"
+    tar "^4.4.2"
+
 node-releases@^1.1.53:
   version "1.1.53"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4"
   integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ==
 
+nopt@^4.0.1:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
+  integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==
+  dependencies:
+    abbrev "1"
+    osenv "^0.1.4"
+
 normalize-package-data@^2.3.2, normalize-package-data@^2.3.4:
   version "2.5.0"
   resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -6115,6 +6140,13 @@ normalize-url@^3.0.0:
   resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
   integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
 
+npm-bundled@^1.0.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
+  integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==
+  dependencies:
+    npm-normalize-package-bin "^1.0.1"
+
 npm-conf@^1.1.0:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9"
@@ -6123,6 +6155,20 @@ npm-conf@^1.1.0:
     config-chain "^1.1.11"
     pify "^3.0.0"
 
+npm-normalize-package-bin@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2"
+  integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==
+
+npm-packlist@^1.1.6:
+  version "1.4.8"
+  resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
+  integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
+  dependencies:
+    ignore-walk "^3.0.1"
+    npm-bundled "^1.0.1"
+    npm-normalize-package-bin "^1.0.1"
+
 npm-run-path@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
@@ -6130,6 +6176,16 @@ npm-run-path@^2.0.0:
   dependencies:
     path-key "^2.0.0"
 
+npmlog@^4.0.2:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
+  integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
+  dependencies:
+    are-we-there-yet "~1.1.2"
+    console-control-strings "~1.1.0"
+    gauge "~2.7.3"
+    set-blocking "~2.0.0"
+
 nth-check@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c"
@@ -6147,6 +6203,11 @@ num2fraction@^1.2.2:
   resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
   integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=
 
+number-is-nan@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
+  integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+
 object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -6293,6 +6354,11 @@ os-filter-obj@^2.0.0:
   dependencies:
     arch "^2.1.0"
 
+os-homedir@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
+  integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M=
+
 os-locale@^3.1.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
@@ -6307,11 +6373,19 @@ os-shim@^0.1.3:
   resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917"
   integrity sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=
 
-os-tmpdir@~1.0.2:
+os-tmpdir@^1.0.0, os-tmpdir@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
   integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
 
+osenv@^0.1.4:
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410"
+  integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==
+  dependencies:
+    os-homedir "^1.0.0"
+    os-tmpdir "^1.0.0"
+
 outpipe@^1.1.0:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/outpipe/-/outpipe-1.1.1.tgz#50cf8616365e87e031e29a5ec9339a3da4725fa2"
@@ -7218,6 +7292,16 @@ raw-loader@^1.0.0:
     loader-utils "^1.1.0"
     schema-utils "^1.0.0"
 
+rc@^1.2.7:
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+  integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+  dependencies:
+    deep-extend "^0.6.0"
+    ini "~1.3.0"
+    minimist "^1.2.0"
+    strip-json-comments "~2.0.1"
+
 read-only-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0"
@@ -7242,7 +7326,7 @@ read-pkg@^1.0.0:
     normalize-package-data "^2.3.2"
     path-type "^1.0.0"
 
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
   version "2.3.7"
   resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
   integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
@@ -7265,15 +7349,6 @@ readable-stream@^1.0.33:
     isarray "0.0.1"
     string_decoder "~0.10.x"
 
-readable-stream@^3.0.6:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
-  integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
-  dependencies:
-    inherits "^2.0.3"
-    string_decoder "^1.1.1"
-    util-deprecate "^1.0.1"
-
 readdirp@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
@@ -7526,7 +7601,7 @@ [email protected]:
   dependencies:
     glob "^7.1.3"
 
-rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.3, rimraf@^2.7.1:
+rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
   integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -7580,12 +7655,12 @@ rxjs@^6.4.0:
   dependencies:
     tslib "^1.9.0"
 
[email protected], safe-buffer@~5.1.0, safe-buffer@~5.1.1:
[email protected], safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0:
+safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
   version "5.2.0"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519"
   integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==
@@ -7725,7 +7800,7 @@ [email protected]:
     parseurl "~1.3.3"
     send "0.17.1"
 
-set-blocking@^2.0.0:
+set-blocking@^2.0.0, set-blocking@~2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
   integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
@@ -8128,16 +8203,6 @@ stream-http@^2.0.0, stream-http@^2.7.2:
     to-arraybuffer "^1.0.0"
     xtend "^4.0.0"
 
-stream-http@^3.0.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-3.1.0.tgz#22fb33fe9b4056b4eccf58bd8f400c4b993ffe57"
-  integrity sha512-cuB6RgO7BqC4FBYzmnvhob5Do3wIdIsXAgGycHJnW+981gHqoYcYz9lqjJrk8WXRddbwPuqPYRl+bag6mYv4lw==
-  dependencies:
-    builtin-status-codes "^3.0.0"
-    inherits "^2.0.1"
-    readable-stream "^3.0.6"
-    xtend "^4.0.0"
-
 stream-shift@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"
@@ -8167,6 +8232,15 @@ strict-uri-encode@^1.0.0:
   resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713"
   integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=
 
+string-width@^1.0.1, "string-width@^1.0.2 || 2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
+  integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
+  dependencies:
+    code-point-at "^1.0.0"
+    is-fullwidth-code-point "^1.0.0"
+    strip-ansi "^3.0.0"
+
 string-width@^2.1.0:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -8195,9 +8269,9 @@ string.prototype.codepointat@^0.2.0:
   integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
 
 string.prototype.trimend@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz#ee497fd29768646d84be2c9b819e292439614373"
-  integrity sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+  integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
   dependencies:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
@@ -8221,9 +8295,9 @@ string.prototype.trimright@^2.1.1:
     string.prototype.trimend "^1.0.0"
 
 string.prototype.trimstart@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz#afe596a7ce9de905496919406c9734845f01a2f2"
-  integrity sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+  integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
   dependencies:
     define-properties "^1.1.3"
     es-abstract "^1.17.5"
@@ -8247,7 +8321,7 @@ string_decoder@~1.1.1:
   dependencies:
     safe-buffer "~5.1.0"
 
-strip-ansi@^3.0.0:
+strip-ansi@^3.0.0, strip-ansi@^3.0.1:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
   integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
@@ -8294,7 +8368,7 @@ strip-indent@^1.0.1:
   dependencies:
     get-stdin "^4.0.1"
 
-strip-json-comments@^2.0.1:
+strip-json-comments@^2.0.1, strip-json-comments@~2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
   integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
@@ -8457,6 +8531,19 @@ tar-stream@^1.5.2:
     to-buffer "^1.1.1"
     xtend "^4.0.0"
 
+tar@^4.4.2:
+  version "4.4.13"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
+  integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
+  dependencies:
+    chownr "^1.1.1"
+    fs-minipass "^1.2.5"
+    minipass "^2.8.6"
+    minizlib "^1.2.1"
+    mkdirp "^0.5.0"
+    safe-buffer "^5.1.2"
+    yallist "^3.0.3"
+
 temp-dir@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
@@ -8521,9 +8608,9 @@ terser-webpack-plugin@^2.2.2:
     webpack-sources "^1.4.3"
 
 terser@^4.1.2, terser@^4.4.3, terser@^4.6.2:
-  version "4.6.10"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.10.tgz#90f5bd069ff456ddbc9503b18e52f9c493d3b7c2"
-  integrity sha512-qbF/3UOo11Hggsbsqm2hPa6+L4w7bkr+09FNseEe8xrcVD3APGLFqE+Oz1ZKAxjYnFsj80rLOfgAtJ0LNJjtTA==
+  version "4.6.11"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
+  integrity sha512-76Ynm7OXUG5xhOpblhytE7X58oeNSmC8xnNhjWVo8CksHit0U0kO4hfNbPrrYwowLWFgM2n9L176VNx2QaHmtA==
   dependencies:
     commander "^2.20.0"
     source-map "~0.6.1"
@@ -8724,9 +8811,9 @@ umd@^3.0.0:
   integrity sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==
 
 unbzip2-stream@^1.0.9:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.0.tgz#097ca7b18b5b71e6c8bc8e514a0f1884a12d6eb1"
-  integrity sha512-kVx7CDAsdBSWVf404Mw7oI9i09w5/mTT/Ruk+RWa64PLYKvsAucLLFHvQtnvjeADM4ZizxrvG5SHnF4Te4T2Cg==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.1.tgz#151b104af853df3efdaa135d8b1eca850a44b426"
+  integrity sha512-sgDYfSDPMsA4Hr2/w7vOlrJBlwzmyakk1+hW8ObLvxSp0LA36LcL2XItGvOT3OSblohSdevMuT8FQjLsqyy4sA==
   dependencies:
     buffer "^5.2.1"
     through "^2.3.8"
@@ -8893,7 +8980,7 @@ [email protected]:
     lru-cache "4.1.x"
     tmp "0.0.x"
 
-util-deprecate@^1.0.1, util-deprecate@~1.0.1:
+util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
   integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
@@ -9127,6 +9214,13 @@ which@^1.2.1, which@^1.2.14, which@^1.2.9, which@^1.3.1:
   dependencies:
     isexe "^2.0.0"
 
+wide-align@^1.1.0:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
+  integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
+  dependencies:
+    string-width "^1.0.2 || 2"
+
 wkx@^0.4.6:
   version "0.4.8"
   resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.8.tgz#a092cf088d112683fdc7182fd31493b2c5820003"
@@ -9213,7 +9307,7 @@ yallist@^2.1.2:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
   integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
 
-yallist@^3.0.2:
+yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
   integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-05 10:28  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Akshay Joshi @ 2020-05-05 10:28 UTC (permalink / raw)
  To: Yogesh Mahajan <[email protected]>; +Cc: pgadmin-hackers; Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi Yogesh

Following are the review comments:

   - *pyjq* package is not required as we used it only in one place. A
   result is a normal dictionary that can be easily looped through.
   - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
   config.py, we have already removed that.
   - Remove yarn.lock file.
   - Remove *pyperclip *from the regression/requirements.txt as we are not
   using it.
   - Please mentioned the value of *pgAdmin_default_server *should not be '
   *127.0.0.1*' in the README file even though everything runs on the same
   machine.
   - Please mentioned that if we set the value of the browser version is
   *null* then selenoid will take the latest available browser version.
   - Got the below error if selenoid_url is not provided:
      - list index out of range
      Unable to find Selenoid Status

*test_config.json.in <http://test_config.json.in>*:

   - "selenoid_info" should be renamed to "selenoid_config". Proper
   alignment is required.
   - "cross_Browsers" should be renamed to "cross_browsers" or
   "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
   browsers with version set to null so that it will run on the latest browser
   version.
   - "selenoid_url": "Selenoid Url" should be changed to "selenoid_url":
   "http://<IP address of Selenoid Installed machine>:4444/wd/hub".

If you change the names in test_config.json.in then please update the same
in README as well.


On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
[email protected]> wrote:

> Hi Akshay,
>
> Please find the updated patch.
>
> Thanks,
> Yogesh Mahajan
> QA - Team
> EnterpriseDB Corporation
>
> Phone: +91-9741705709
>
>
> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Yogesh
>>
>> The patch is not applied to the master branch. Can you please rebase and
>> send the patch again.
>>
>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> Please find updated patch modified according to review comments -
>>> Patch implements below things -
>>> 1.Enable the current framework to provide option to execute Feature
>>> tests in parallel  on selenium grid set up.
>>>    - Addition of new switch to start parallel features tests.
>>>    - New parameters with respect to selenoid in test_config.json.in
>>>    - Addition of new script to check solenoid updates.
>>>
>>>
>>>
>>> Thanks,
>>> Yogesh Mahajan
>>> QA - Team
>>> EnterpriseDB Corporation
>>>
>>> Phone: +91-9741705709
>>>
>>>
>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Yogesh,
>>>> Below are the review comments-
>>>>
>>>> 1. runtests.py
>>>>     a. The exception traceback logic at line number 653 in runtests.py
>>>> is not correct since it is particular to the thread
>>>> but there is much more code in that block which can throw some
>>>> exception.
>>>> b. line number 447 -> The drop_database function will only try to drop
>>>> the database with the name which is newly created
>>>> at 431 line number, its probability is 1% instead of this you can write
>>>> a logic so that it will drop all the database which starts with name
>>>> ‘acceptance_test_db'.
>>>>   c. line 584 - Why we are including resql test case execution in GUI
>>>> execution logic.
>>>>     d. Change the function name run_test as script name is also
>>>> runtests.py
>>>>
>>>> 2. test_utils.py
>>>>     a. Remove the headless chrome code from get_remote_webdriver() in
>>>> test_utils.py since we are using solenoid and it is not required
>>>> anymore.
>>>>     b. Create separate functions to instantiate the firefox driver and
>>>> chrome driver logic since the same code is used in multiple files.
>>>> c. launch_url_in_browser() -> you can simplify the definition of the
>>>> function like:
>>>>     retry = 60
>>>>         *while *retry > 0:
>>>>             try:
>>>>                 driver.get(url)
>>>>             except WebDriverException:
>>>>                  retry -= 1
>>>> 3. Execution logs are not printing as per the logic some time, I ran
>>>> the suite for two servers and attached are the execution logs.
>>>> 4. Readme -
>>>> Please provide the Valid selenoid URL to be provided in the
>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>> clear.
>>>> Revisit the readme and write the missing steps.
>>>> 5. copy_selected_query_results_feature_tests.py-
>>>> Create the function to avoid duplicate code. The code for pasting the
>>>> values is repeating 8 times in the test code.
>>>> 6. Provide the valid docstring in newly introduced functions and also
>>>> valid comments while calling it. for ex.- _update_preference() function is
>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>> function name, it is not clear what preferences are going to update in it.
>>>> 7. test_index_constraint_add test case is failing due to the latest
>>>> change, please merge and update this test case
>>>>
>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>> I think I am not the right person to review this patch now as I
>>>>> already reviewed this code offline in the last week. I know the approached
>>>>> Yogesh has followed, also given some review comments on it.
>>>>> Someone else please review it.
>>>>>
>>>>> Thanks!
>>>>>
>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Navnath
>>>>>>
>>>>>> Can you please review it?
>>>>>>
>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> 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
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>>
>>>>>> *Sr. Software Architect*
>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Regards,
>>>>> Navnath Gadakh
>>>>>
>>>>
>>>>
>>>> --
>>>> Thanks & Regards,
>>>> Shubham Agarwal
>>>> EnterpriseDB Corporation
>>>>
>>>> The Postgres Database Company
>>>>
>>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>>
>> *Sr. Software Architect*
>> *EnterpriseDB Software India Private Limited*
>> *Mobile: +91 976-788-8246*
>>
>

-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-08 08:05  Yogesh Mahajan <[email protected]>
  parent: Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-05-08 08:05 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; pgadmin-hackers; +Cc: Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi,

Please find the updates patch with above review comments.
Patch adds below functionality to existing framework -
1.Ability to run features in parallel using solenoid(selenium + docker).
     a.Selenoid setup steps are included in /regression/README
     b.'python runtests.py  --pkg feature_tests --parallel' will trigger
parallel feature tests.
2.Removes dependency for pyperclip python module.
3.New script in ../tools/update_selenoid_browsers.py updates browser
images at local selneoid server setup.


Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <[email protected]>
wrote:

> Hi Yogesh
>
> Following are the review comments:
>
>    - *pyjq* package is not required as we used it only in one place. A
>    result is a normal dictionary that can be easily looped through.
>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>    config.py, we have already removed that.
>    - Remove yarn.lock file.
>    - Remove *pyperclip *from the regression/requirements.txt as we are
>    not using it.
>    - Please mentioned the value of *pgAdmin_default_server *should not be
>    '*127.0.0.1*' in the README file even though everything runs on the
>    same machine.
>    - Please mentioned that if we set the value of the browser version is
>    *null* then selenoid will take the latest available browser version.
>    - Got the below error if selenoid_url is not provided:
>       - list index out of range
>       Unable to find Selenoid Status
>
> *test_config.json.in <http://test_config.json.in>*:
>
>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>    alignment is required.
>    - "cross_Browsers" should be renamed to "cross_browsers" or
>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>    browsers with version set to null so that it will run on the latest browser
>    version.
>    - "selenoid_url": "Selenoid Url" should be changed to "selenoid_url":
>    "http://<IP address of Selenoid Installed machine>:4444/wd/hub".
>
> If you change the names in test_config.json.in then please update the
> same in README as well.
>
>
> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> Hi Akshay,
>>
>> Please find the updated patch.
>>
>> Thanks,
>> Yogesh Mahajan
>> QA - Team
>> EnterpriseDB Corporation
>>
>> Phone: +91-9741705709
>>
>>
>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Yogesh
>>>
>>> The patch is not applied to the master branch. Can you please rebase and
>>> send the patch again.
>>>
>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please find updated patch modified according to review comments -
>>>> Patch implements below things -
>>>> 1.Enable the current framework to provide option to execute Feature
>>>> tests in parallel  on selenium grid set up.
>>>>    - Addition of new switch to start parallel features tests.
>>>>    - New parameters with respect to selenoid in test_config.json.in
>>>>    - Addition of new script to check solenoid updates.
>>>>
>>>>
>>>>
>>>> Thanks,
>>>> Yogesh Mahajan
>>>> QA - Team
>>>> EnterpriseDB Corporation
>>>>
>>>> Phone: +91-9741705709
>>>>
>>>>
>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Yogesh,
>>>>> Below are the review comments-
>>>>>
>>>>> 1. runtests.py
>>>>>     a. The exception traceback logic at line number 653 in
>>>>> runtests.py is not correct since it is particular to the thread
>>>>> but there is much more code in that block which can throw some
>>>>> exception.
>>>>> b. line number 447 -> The drop_database function will only try to
>>>>> drop the database with the name which is newly created
>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>> write a logic so that it will drop all the database which starts with name
>>>>> ‘acceptance_test_db'.
>>>>>   c. line 584 - Why we are including resql test case execution in GUI
>>>>> execution logic.
>>>>>     d. Change the function name run_test as script name is also
>>>>> runtests.py
>>>>>
>>>>> 2. test_utils.py
>>>>>     a. Remove the headless chrome code from get_remote_webdriver() in
>>>>> test_utils.py since we are using solenoid and it is not required
>>>>> anymore.
>>>>>     b. Create separate functions to instantiate the firefox driver
>>>>> and chrome driver logic since the same code is used in multiple files.
>>>>> c. launch_url_in_browser() -> you can simplify the definition of the
>>>>> function like:
>>>>>     retry = 60
>>>>>         *while *retry > 0:
>>>>>             try:
>>>>>                 driver.get(url)
>>>>>             except WebDriverException:
>>>>>                  retry -= 1
>>>>> 3. Execution logs are not printing as per the logic some time, I ran
>>>>> the suite for two servers and attached are the execution logs.
>>>>> 4. Readme -
>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>> clear.
>>>>> Revisit the readme and write the missing steps.
>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>> Create the function to avoid duplicate code. The code for pasting the
>>>>> values is repeating 8 times in the test code.
>>>>> 6. Provide the valid docstring in newly introduced functions and also
>>>>> valid comments while calling it. for ex.- _update_preference() function is
>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>> function name, it is not clear what preferences are going to update in it.
>>>>> 7. test_index_constraint_add test case is failing due to the latest
>>>>> change, please merge and update this test case
>>>>>
>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>> I think I am not the right person to review this patch now as I
>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>> Someone else please review it.
>>>>>>
>>>>>> Thanks!
>>>>>>
>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Navnath
>>>>>>>
>>>>>>> Can you please review it?
>>>>>>>
>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> 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
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> *Thanks & Regards*
>>>>>>> *Akshay Joshi*
>>>>>>>
>>>>>>> *Sr. Software Architect*
>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Regards,
>>>>>> Navnath Gadakh
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Thanks & Regards,
>>>>> Shubham Agarwal
>>>>> EnterpriseDB Corporation
>>>>>
>>>>> The Postgres Database Company
>>>>>
>>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect*
>>> *EnterpriseDB Software India Private Limited*
>>> *Mobile: +91 976-788-8246*
>>>
>>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-08 08:27  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Akshay Joshi @ 2020-05-08 08:27 UTC (permalink / raw)
  To: Yogesh Mahajan <[email protected]>; +Cc: pgadmin-hackers; Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi Yogesh

You forgot to attach patch :)

On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
[email protected]> wrote:

> Hi,
>
> Please find the updates patch with above review comments.
> Patch adds below functionality to existing framework -
> 1.Ability to run features in parallel using solenoid(selenium + docker).
>      a.Selenoid setup steps are included in /regression/README
>      b.'python runtests.py  --pkg feature_tests --parallel' will trigger
> parallel feature tests.
> 2.Removes dependency for pyperclip python module.
> 3.New script in ../tools/update_selenoid_browsers.py updates browser
> images at local selneoid server setup.
>
>
> Thanks,
> Yogesh Mahajan
> QA - Team
> EnterpriseDB Corporation
>
> Phone: +91-9741705709
>
>
> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Yogesh
>>
>> Following are the review comments:
>>
>>    - *pyjq* package is not required as we used it only in one place. A
>>    result is a normal dictionary that can be easily looped through.
>>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>    config.py, we have already removed that.
>>    - Remove yarn.lock file.
>>    - Remove *pyperclip *from the regression/requirements.txt as we are
>>    not using it.
>>    - Please mentioned the value of *pgAdmin_default_server *should not
>>    be '*127.0.0.1*' in the README file even though everything runs on
>>    the same machine.
>>    - Please mentioned that if we set the value of the browser version is
>>    *null* then selenoid will take the latest available browser version.
>>    - Got the below error if selenoid_url is not provided:
>>       - list index out of range
>>       Unable to find Selenoid Status
>>
>> *test_config.json.in <http://test_config.json.in>*:
>>
>>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>>    alignment is required.
>>    - "cross_Browsers" should be renamed to "cross_browsers" or
>>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>    browsers with version set to null so that it will run on the latest browser
>>    version.
>>    - "selenoid_url": "Selenoid Url" should be changed to "selenoid_url":
>>    "http://<IP address of Selenoid Installed machine>:4444/wd/hub".
>>
>> If you change the names in test_config.json.in then please update the
>> same in README as well.
>>
>>
>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>> [email protected]> wrote:
>>
>>> Hi Akshay,
>>>
>>> Please find the updated patch.
>>>
>>> Thanks,
>>> Yogesh Mahajan
>>> QA - Team
>>> EnterpriseDB Corporation
>>>
>>> Phone: +91-9741705709
>>>
>>>
>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Yogesh
>>>>
>>>> The patch is not applied to the master branch. Can you please rebase
>>>> and send the patch again.
>>>>
>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please find updated patch modified according to review comments -
>>>>> Patch implements below things -
>>>>> 1.Enable the current framework to provide option to execute Feature
>>>>> tests in parallel  on selenium grid set up.
>>>>>    - Addition of new switch to start parallel features tests.
>>>>>    - New parameters with respect to selenoid in test_config.json.in
>>>>>    - Addition of new script to check solenoid updates.
>>>>>
>>>>>
>>>>>
>>>>> Thanks,
>>>>> Yogesh Mahajan
>>>>> QA - Team
>>>>> EnterpriseDB Corporation
>>>>>
>>>>> Phone: +91-9741705709
>>>>>
>>>>>
>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Yogesh,
>>>>>> Below are the review comments-
>>>>>>
>>>>>> 1. runtests.py
>>>>>>     a. The exception traceback logic at line number 653 in
>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>> but there is much more code in that block which can throw some
>>>>>> exception.
>>>>>> b. line number 447 -> The drop_database function will only try to
>>>>>> drop the database with the name which is newly created
>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>> ‘acceptance_test_db'.
>>>>>>   c. line 584 - Why we are including resql test case execution in
>>>>>> GUI execution logic.
>>>>>>     d. Change the function name run_test as script name is also
>>>>>> runtests.py
>>>>>>
>>>>>> 2. test_utils.py
>>>>>>     a. Remove the headless chrome code from get_remote_webdriver()
>>>>>> in test_utils.py since we are using solenoid and it is not required
>>>>>> anymore.
>>>>>>     b. Create separate functions to instantiate the firefox driver
>>>>>> and chrome driver logic since the same code is used in multiple files.
>>>>>> c. launch_url_in_browser() -> you can simplify the definition of the
>>>>>> function like:
>>>>>>     retry = 60
>>>>>>         *while *retry > 0:
>>>>>>             try:
>>>>>>                 driver.get(url)
>>>>>>             except WebDriverException:
>>>>>>                  retry -= 1
>>>>>> 3. Execution logs are not printing as per the logic some time, I ran
>>>>>> the suite for two servers and attached are the execution logs.
>>>>>> 4. Readme -
>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>> clear.
>>>>>> Revisit the readme and write the missing steps.
>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>> Create the function to avoid duplicate code. The code for pasting the
>>>>>> values is repeating 8 times in the test code.
>>>>>> 6. Provide the valid docstring in newly introduced functions and also
>>>>>> valid comments while calling it. for ex.- _update_preference() function is
>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>> 7. test_index_constraint_add test case is failing due to the latest
>>>>>> change, please merge and update this test case
>>>>>>
>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>> Someone else please review it.
>>>>>>>
>>>>>>> Thanks!
>>>>>>>
>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Navnath
>>>>>>>>
>>>>>>>> Can you please review it?
>>>>>>>>
>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> 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
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> *Thanks & Regards*
>>>>>>>> *Akshay Joshi*
>>>>>>>>
>>>>>>>> *Sr. Software Architect*
>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Regards,
>>>>>>> Navnath Gadakh
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Thanks & Regards,
>>>>>> Shubham Agarwal
>>>>>> EnterpriseDB Corporation
>>>>>>
>>>>>> The Postgres Database Company
>>>>>>
>>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>>
>>>> *Sr. Software Architect*
>>>> *EnterpriseDB Software India Private Limited*
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>>
>> *Sr. Software Architect*
>> *EnterpriseDB Software India Private Limited*
>> *Mobile: +91 976-788-8246*
>>
>

-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-08 08:35  Yogesh Mahajan <[email protected]>
  parent: Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-05-08 08:35 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; pgadmin-hackers; +Cc: Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Attaching patch

Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


On Fri, May 8, 2020 at 1:57 PM Akshay Joshi <[email protected]>
wrote:

> Hi Yogesh
>
> You forgot to attach patch :)
>
> On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find the updates patch with above review comments.
>> Patch adds below functionality to existing framework -
>> 1.Ability to run features in parallel using solenoid(selenium + docker).
>>      a.Selenoid setup steps are included in /regression/README
>>      b.'python runtests.py  --pkg feature_tests --parallel' will trigger
>> parallel feature tests.
>> 2.Removes dependency for pyperclip python module.
>> 3.New script in ../tools/update_selenoid_browsers.py updates browser
>> images at local selneoid server setup.
>>
>>
>> Thanks,
>> Yogesh Mahajan
>> QA - Team
>> EnterpriseDB Corporation
>>
>> Phone: +91-9741705709
>>
>>
>> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Yogesh
>>>
>>> Following are the review comments:
>>>
>>>    - *pyjq* package is not required as we used it only in one place. A
>>>    result is a normal dictionary that can be easily looped through.
>>>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>>    config.py, we have already removed that.
>>>    - Remove yarn.lock file.
>>>    - Remove *pyperclip *from the regression/requirements.txt as we are
>>>    not using it.
>>>    - Please mentioned the value of *pgAdmin_default_server *should not
>>>    be '*127.0.0.1*' in the README file even though everything runs on
>>>    the same machine.
>>>    - Please mentioned that if we set the value of the browser version
>>>    is *null* then selenoid will take the latest available browser
>>>    version.
>>>    - Got the below error if selenoid_url is not provided:
>>>       - list index out of range
>>>       Unable to find Selenoid Status
>>>
>>> *test_config.json.in <http://test_config.json.in>*:
>>>
>>>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>>>    alignment is required.
>>>    - "cross_Browsers" should be renamed to "cross_browsers" or
>>>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>>    browsers with version set to null so that it will run on the latest browser
>>>    version.
>>>    - "selenoid_url": "Selenoid Url" should be changed
>>>    to "selenoid_url": "http://<IP address of Selenoid Installed
>>>    machine>:4444/wd/hub".
>>>
>>> If you change the names in test_config.json.in then please update the
>>> same in README as well.
>>>
>>>
>>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>>
>>>> Please find the updated patch.
>>>>
>>>> Thanks,
>>>> Yogesh Mahajan
>>>> QA - Team
>>>> EnterpriseDB Corporation
>>>>
>>>> Phone: +91-9741705709
>>>>
>>>>
>>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Yogesh
>>>>>
>>>>> The patch is not applied to the master branch. Can you please rebase
>>>>> and send the patch again.
>>>>>
>>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> Please find updated patch modified according to review comments -
>>>>>> Patch implements below things -
>>>>>> 1.Enable the current framework to provide option to execute Feature
>>>>>> tests in parallel  on selenium grid set up.
>>>>>>    - Addition of new switch to start parallel features tests.
>>>>>>    - New parameters with respect to selenoid in test_config.json.in
>>>>>>    - Addition of new script to check solenoid updates.
>>>>>>
>>>>>>
>>>>>>
>>>>>> Thanks,
>>>>>> Yogesh Mahajan
>>>>>> QA - Team
>>>>>> EnterpriseDB Corporation
>>>>>>
>>>>>> Phone: +91-9741705709
>>>>>>
>>>>>>
>>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Yogesh,
>>>>>>> Below are the review comments-
>>>>>>>
>>>>>>> 1. runtests.py
>>>>>>>     a. The exception traceback logic at line number 653 in
>>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>>> but there is much more code in that block which can throw some
>>>>>>> exception.
>>>>>>> b. line number 447 -> The drop_database function will only try to
>>>>>>> drop the database with the name which is newly created
>>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>>> ‘acceptance_test_db'.
>>>>>>>   c. line 584 - Why we are including resql test case execution in
>>>>>>> GUI execution logic.
>>>>>>>     d. Change the function name run_test as script name is also
>>>>>>> runtests.py
>>>>>>>
>>>>>>> 2. test_utils.py
>>>>>>>     a. Remove the headless chrome code from get_remote_webdriver()
>>>>>>> in test_utils.py since we are using solenoid and it is not required
>>>>>>> anymore.
>>>>>>>     b. Create separate functions to instantiate the firefox driver
>>>>>>> and chrome driver logic since the same code is used in multiple files.
>>>>>>> c. launch_url_in_browser() -> you can simplify the definition of
>>>>>>> the function like:
>>>>>>>     retry = 60
>>>>>>>         *while *retry > 0:
>>>>>>>             try:
>>>>>>>                 driver.get(url)
>>>>>>>             except WebDriverException:
>>>>>>>                  retry -= 1
>>>>>>> 3. Execution logs are not printing as per the logic some time, I ran
>>>>>>> the suite for two servers and attached are the execution logs.
>>>>>>> 4. Readme -
>>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>>> clear.
>>>>>>> Revisit the readme and write the missing steps.
>>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>>> Create the function to avoid duplicate code. The code for pasting
>>>>>>> the values is repeating 8 times in the test code.
>>>>>>> 6. Provide the valid docstring in newly introduced functions and
>>>>>>> also valid comments while calling it. for ex.- _update_preference()
>>>>>>> function is
>>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>>> 7. test_index_constraint_add test case is failing due to the latest
>>>>>>> change, please merge and update this test case
>>>>>>>
>>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi,
>>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>>> Someone else please review it.
>>>>>>>>
>>>>>>>> Thanks!
>>>>>>>>
>>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Navnath
>>>>>>>>>
>>>>>>>>> Can you please review it?
>>>>>>>>>
>>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> 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
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> *Thanks & Regards*
>>>>>>>>> *Akshay Joshi*
>>>>>>>>>
>>>>>>>>> *Sr. Software Architect*
>>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Regards,
>>>>>>>> Navnath Gadakh
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks & Regards,
>>>>>>> Shubham Agarwal
>>>>>>> EnterpriseDB Corporation
>>>>>>>
>>>>>>> The Postgres Database Company
>>>>>>>
>>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>>
>>>>> *Sr. Software Architect*
>>>>> *EnterpriseDB Software India Private Limited*
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect*
>>> *EnterpriseDB Software India Private Limited*
>>> *Mobile: +91 976-788-8246*
>>>
>>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>


Attachments:

  [application/octet-stream] Selenium_Grid_Implementation_ver4.0.patch (110.8K, 3-Selenium_Grid_Implementation_ver4.0.patch)
  download | inline diff:
diff --git a/tools/update_selenoid_browsers.py b/tools/update_selenoid_browsers.py
new file mode 100644
index 0000000..c067621
--- /dev/null
+++ b/tools/update_selenoid_browsers.py
@@ -0,0 +1,277 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+# #########################################################################
+# Updates browser images(selenoid-docker) depending on arguments passed while
+# running this script.
+# e.g. --chrome /usr/bin/google-chrome --firefox /usr/bin/firefox
+# Access details about switches using help
+# e.g. --help
+
+import argparse
+import os
+import subprocess
+import sys
+import traceback
+import requests
+import json
+
+
+def read_command_line():
+    """Read the command line arguments.
+    Returns:
+        ArgumentParser: The parsed arguments object
+
+    """
+    parser = argparse.ArgumentParser(
+        description='Get latest browser images(chrome & firefox) for selenoid.'
+                    'e.g. - --chrome /usr/bin/google-chrome --firefox '
+                    '/usr/bin/firefox')
+    parser.add_argument("--chrome", metavar="CHROME",
+                        help="the Chrome executable path")
+    parser.add_argument("--firefox", metavar="FIREFOX",
+                        help="the firefox executable path")
+    args_val = parser.parse_args()
+    return args_val
+
+
+def get_browser_version(browser_name, executable_path):
+    """
+    Function returns browser version for specified browser using executable
+    path passed in arguments.
+    :param browser_name:
+    :param executable_path: e.g. /usr/bin/firefox
+    :return: browser version
+    """
+    # On Linux/Mac we run the browser executable with the --version flag,
+    # then parse the output.
+    browser_version_val = None
+    try:
+        result = subprocess.Popen([executable_path, '--version'],
+                                  stdout=subprocess.PIPE)
+    except FileNotFoundError:
+        print('The specified browser executable could not be found.')
+        sys.exit(1)
+
+    version_str = result.stdout.read().decode("utf-8")
+
+    if browser_name.lower() == "chrome":
+        # Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
+        if "Chrom" not in version_str:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        # On some linux distro `chrome--version` gives output like
+        # 'Google Chrome 80.0.3987.132 unknown\n'
+        # so we need to check and remove the unknown string from the version
+        if version_str.endswith("unknown\n"):
+            version_str = version_str.strip("unknown\n").strip()
+
+        chrome_version = '.'.join(version_str.split()[-1].split('.')[:-2])
+
+        # Make sure browser version has only 1 decimal point
+        if chrome_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = chrome_version
+    elif browser_name.lower() == "firefox":
+        if "Firefox" not in version_str:
+            print('The specified Firefox executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+
+        # Some time firefox --version gives output like
+        # 'Running without a11y support!
+        # Mozilla Firefox 68.7.0esr'
+        # Other output - [root@localhost local]# /usr/bin/firefox --version
+        # Mozilla Firefox 75.0
+        if 'esr' in version_str:
+            firefox_version = '.'.join(
+                version_str.split()[-1].split('.')[:-2]) + '.0'
+        else:
+            firefox_version = '.'.join(
+                version_str.split()[-1].split('.')[:-1]) + '.0'
+
+        # Make sure browser version has only 1 decimal point
+        if firefox_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = firefox_version
+    else:
+        print("{0} is not recognised ".format(browser_name))
+        sys.exit(1)
+    return browser_version_val
+
+
+def check_and_download_vnc_browser_image(browser_name, browser_version):
+    """
+    Function checks presence for vnc images for passed browser
+    at docker.io/selenoid/ registry
+    :param browser_name:
+    :param browser_version:
+    :return:true if browser image is available & downloaded else false
+    """
+    res = requests.get(
+        'https://registry.hub.docker.com/v2/repositories/selenoid/vnc_' +
+        browser_name + '/tags/')
+    res = res.json()
+    version_tag = []
+    if len(res['results']) > 0:
+        for result in res['results']:
+            if 'name' in result:
+                version_tag.append(result['name'])
+    vnc_image_available = False
+    image_name = 'vnc_' + browser_name + ':' + browser_version
+
+    for idx, tag in enumerate(version_tag):
+        if browser_version == tag:
+            command = 'docker pull selenoid/vnc_' + browser_name + ':' \
+                      + browser_version
+            print(' VNC image is available & downloading now... {0}'.format(
+                command))
+            try:
+                subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+                vnc_image_available = True
+            except Exception:
+                traceback.print_exc(file=sys.stderr)
+                print(
+                    '{0}} Image found but could not download.'.format(command))
+                sys.exit(1)
+            break
+        elif idx == len(version_tag):
+            print("{0} Image is not available.".format(image_name))
+            vnc_image_available = False
+        else:
+            pass
+    return vnc_image_available
+
+
+def reload_selenoid_config():
+    """
+    Function runs command to refresh selenoid configuration
+    :return: true if command execution for selenoid reload is successful
+    else false
+    """
+    command = 'docker kill -s HUP selenoid'
+    reload_successful = False
+    try:
+        subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+        print(" Selenoid Configuration is reloaded.")
+        reload_successful = True
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        print('Error while reloading selenoid configuration.')
+        sys.exit(1)
+    return reload_successful
+
+
+def edit_browsers_json(browser_name, browser_version):
+    """
+    Function edits browsers.json which is used by selenoid to
+    load browser configuration.
+    Default path for this file is
+    "user_home_dir + '/.aerokube/selenoid/browsers.json'"
+    Currently this is hardcoded, might need to modify
+    if we want to pass customize browsers.json
+    :param browser_name:
+    :param browser_version:
+    :return:
+    """
+    file_edited = True
+    # Read existing browsers.json
+    json_file = open(file_path, 'r')
+    existing_data = json.load(json_file)
+    updated_data = None
+
+    # Update data for new browser images
+    if browser_name.lower() == 'chrome':
+        version_data = existing_data['chrome']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_chrome:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['chrome']['versions']).update(data_to_insert)
+            updated_data = existing_data
+            print(updated_data)
+
+    elif browser_name.lower() == 'firefox':
+        version_data = existing_data['firefox']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_firefox:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['firefox']['versions']).update(data_to_insert)
+            updated_data = existing_data
+    else:
+        print("Browser version not matched")
+        file_edited = False
+
+    # Write updated data in browsers.json
+    if updated_data is not None:
+        json_file = open(file_path, 'w')
+        json.dump(updated_data, json_file)
+        print(" 'browsers.json' is updated for {0} {1}".format(
+            browser_name, browser_version))
+
+        file_edited = True
+    return file_edited
+
+
+# Main Program starts here
+# Read command line arguments & get list of browser_name, executable path.
+args = vars(read_command_line())
+
+# Get path path for browsers.json
+user_home_dir = os.getenv("HOME")
+file_path = user_home_dir + '/.aerokube/selenoid/browsers.json'
+print("***** Updating '{0}' for new browser versions.*****".format(file_path))
+
+# Iterate over arguments passed
+for browser, executable_path in args.items():
+    if executable_path is not None:
+        # Get browser name
+        browser_name = browser
+        # Get browser version
+        browser_version = get_browser_version(browser, executable_path)
+        print(
+            " Browser version for {0} is {1} in current executable path ".
+            format(browser_name, browser_version))
+
+        # Download vnc browser image.
+        download_new_image = check_and_download_vnc_browser_image(
+            browser_name, browser_version)
+
+        # If browser vnc image is available, then edit browsers.json
+        if download_new_image:
+            if edit_browsers_json(browser_name, browser_version):
+                print(
+                    " File 'browsers.json' is updated for {0} - {1} \n".format(
+                        browser_name, browser_version))
+            else:
+                print(
+                    " File 'browsers.json' can NOT be updated for {0} - {1} \n"
+                    .format(browser_name, browser_version))
+        else:
+            print(" Browser image is not available for {0}, {1}".format(
+                browser_name, browser_version))
+
+# Reload selenoid configuration
+if reload_selenoid_config():
+    print(
+        "***** Updated '{0}' for new browser versions.*****".format(file_path))
diff --git a/web/config.py b/web/config.py
index 7f74a7d..d05aa04 100644
--- a/web/config.py
+++ b/web/config.py
@@ -562,6 +562,10 @@ try:
 except ImportError:
     pass
 
+# Override DEFAULT_SERVE value from environment variable.
+if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
+    DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
+
 # Disable USER_INACTIVITY_TIMEOUT when SERVER_MODE=False
 if not SERVER_MODE:
     USER_INACTIVITY_TIMEOUT = 0
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 f127063..88526ca 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
@@ -44,31 +44,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)
@@ -81,10 +80,9 @@ 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)
 
 
 class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
@@ -117,30 +115,28 @@ class ConstraintsUsingIndexAddTestCase(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 "
                             "constraint using index.")
-        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_constraint_%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_constraint_%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)
@@ -158,7 +154,6 @@ class ConstraintsUsingIndexAddTestCase(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 7eea5e9..f35a00b 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 c08c2ac..f838a2f 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 87289a6..6bdf703 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 bbed269..02203d2 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 0a2963a..8e5f665 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 c84276e..1f63ef5 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/test_mviews_refresh.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
index b6c0bad..3b4055f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
@@ -48,8 +48,7 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
          ),
     ]
 
-    @classmethod
-    def setUpClass(self):
+    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"]
@@ -143,7 +142,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
                 # On success we get job_id from server
                 self.assertTrue('job_id' in response.json['data'])
 
-    @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 d6180c4..7619ee6 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 7091ffa..1f3bd65 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
@@ -60,8 +59,18 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self._mouseup_outside_grid_still_makes_a_selection()
         self._copies_rows_with_header()
 
+    def paste_values_to_scratch_pad(self):
+        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")
+        scratch_pad_ele.clear()
+        return clipboard_text
+
     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 +79,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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 +95,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +111,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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,11 +127,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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()
@@ -128,15 +140,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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 +166,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             self.page.driver
         ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
-            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
 
     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 +192,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             Keys.CONTROL
         ).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +209,13 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
-            pyperclip.paste())
+            clipboard_text)
 
     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 +230,9 @@ 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())
+        clipboard_text = self.paste_values_to_scratch_pad()
+
+        self.assertIn('"cool info"', clipboard_text)
 
     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 6d01286..33f86ad 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/keyboard_shortcut_test.py b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
index ea381b4..f68e530 100644
--- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py
+++ b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
@@ -94,24 +94,33 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
             NavMenuLocators.preference_menu_item_css)
         pref_menu_item.click()
 
-        # Wait till the preference dialogue box is displayed by checking the
-        # visibility of Show System Object label
-        self.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()
-
         browser_node = self.page.find_by_xpath(
             NavMenuLocators.specified_preference_tree_node.format('Browser'))
         if self.page.find_by_xpath(
             NavMenuLocators.specified_pref_node_exp_status.
                 format('Browser')).get_attribute('aria-expanded') == 'false':
-
             ActionChains(self.driver).double_click(browser_node).perform()
 
+        display_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
+                'Browser', 'Display'))
+        attempt = 5
+        while attempt > 0:
+            display_node.click()
+            # After clicking the element gets loaded in to the dom but still
+            # not visible, hence sleeping for a sec.
+            time.sleep(1)
+            if self.page.wait_for_element_to_be_visible(
+                self.driver,
+                    NavMenuLocators.show_system_objects_pref_label_xpath, 3):
+                break
+            else:
+                attempt -= 1
+
+        maximize_button = self.page.find_by_css_selector(
+            NavMenuLocators.maximize_pref_dialogue_css)
+        maximize_button.click()
+
         keyboard_node = self.page.find_by_xpath(
             NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
                 'Browser', 'Keyboard shortcuts'))
diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
index dba4ead..baccc70 100644
--- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py
+++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
@@ -92,6 +92,18 @@ class PGDataypeFeatureTest(BaseFeatureTest):
 
         wait = WebDriverWait(self.page.driver, 10)
 
+        browser_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_preference_tree_node.format('Browser'))
+        if self.page.find_by_xpath(
+            NavMenuLocators.specified_pref_node_exp_status.
+                format('Browser')).get_attribute('aria-expanded') == 'false':
+            ActionChains(self.driver).double_click(browser_node).perform()
+
+        self.page.retry_click(
+            (By.XPATH, NavMenuLocators.specified_sub_node_of_pref_tree_node.
+             format('Browser', 'Display')),
+            (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
+
         # Wait till the preference dialogue box is displayed by checking the
         # visibility of Show System Object label
         wait.until(EC.presence_of_element_located(
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 1d6d7a4..4cf9282 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,74 @@ 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):
+        """
+        Function updates preferences for binary path.
+        """
+        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 2d7c4ef..ddb4e3c 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 3f53d86..21921cc 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,20 +178,21 @@ 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._add_row(config_data_local)
         self._verify_row_data(row_height=0,
-                              config_check_data=config_data['add'])
+                              config_check_data=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(row_height=0,
                               config_check_data=updated_row_data)
@@ -221,7 +224,6 @@ CREATE TABLE public.nonintpkey
         Returns: None
 
         """
-
         self.wait.until(EC.visibility_of_element_located(
             (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
         )
@@ -238,7 +240,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(
@@ -290,7 +292,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()
@@ -300,12 +302,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(row_height=25,
                               config_check_data=updated_row_data)
@@ -329,11 +331,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 9efad8a..69c3cba 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 a80310e..faff52a 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -120,9 +120,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):
@@ -137,17 +136,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 46adfcc..3a7472e 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,61 @@ 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 --args "-limit 3 -cpu 1.5 -mem 1.5g"
+    $./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
+    Note : In --args "-limit 3 -cpu 1.5 -mem 1.5g"
+                -limit 3 :limits maximum parallel sessions(dockers) in selenoid,
+                -cpu :limit memory and CPU usage,
+                -mem :limit memory per session.
+           Generally max parallel session is the number of cores * 1.5 – 2
+           You can list available flags by using ./cm selenoid args
+    Additional Information about tool
+            - https://aerokube.com/selenoid/latest/
+
+  - Update 'test_config.json' with selenoid config information
+    pgAdmin_default_server -
+        It is the IP address for the machine where pgadmin source code is
+        present.Value should NOT be '127.0.0.1' even though everything runs
+        on the same machine.
+        You can get it on linux running command  'ifconfig | grep inet'
+        e.g. - 192.168.143.121
+    max_parallel_sessions -
+        This is other way to control number of tests to be run in parallel.
+        This should be equal or less than limit specified while setting up
+        selenoid
+    selenoid_url -
+        Url should be formed as below -
+        http://<IP address of Selenoid Installed machine>:4444/wd/hub/
+        e.g. - selenoid_url": "http://192.168.143.121:4444/wd/hub"
+        If source code & selenoid servers are on same machine then
+        selenoid url value can be - "http://localhost:4444/wd/hub"
+    browsers_list -
+        List of browser name & version enclosed in {} on which tests to be
+        executed.
+        Make sure list contains those browsers & versions only which are shown
+        in capabilities tab while in selenoid status web-page.
+        If version is mention as null, then latest version available in
+        selenoid server will be used for execution.
+        e.g. - [ {"name": "Chrome","version": "80.0"},
+                 {"name": "Firefox","version": "74.0"}]
+
 - Change to the regression test directory:
      run 'cd web/regression'
 
@@ -190,9 +245,14 @@ Python Tests:
      Example 2)  Execute only reverse engineered SQL test framework for some modules
          run 'python runtests.py --pkg resql --modules sequences,functions'
 
+
      Example 3) Exclude reverse engineered SQL test framework for all modules
          run 'python runtests.py --exclude resql'
 
+- 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 fe4c441..7de10b2 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 b8169fa..4756231 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"
@@ -217,9 +219,9 @@ class QueryToolLocators:
     btn_commit = "#btn-commit"
 
     show_query_internally_btn = \
-        "//div[label[normalize-space(" \
-        "text())='Show queries generated internally by pgAdmin?']]" \
-        "//div[contains(@class,'toggle btn')]"
+        "//div[label[contains(normalize-space(text())," \
+        "'Show queries generated internally by')]]//" \
+        "div[contains(@class,'toggle btn')]"
 
     editable_column_icon_xpath = "//div[contains(@class," \
                                  " 'editable-column-header-icon')]" \
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index d7e8fe3..32ea85f 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,34 @@ class PgadminPage:
             except Exception:
                 attempt += 1
         return click_status
+
+    def paste_values(self, el=None):
+        """
+        Function paste values in scratch pad
+        :param el:
+        """
+        actions = ActionChains(self.driver)
+        if el:
+            # Must step
+            el.click()
+            if self.driver.capabilities["platformName"] == 'mac':
+                # FF step
+                el.send_keys(Keys.COMMAND + "v")
+                # Chrome Step
+                actions.key_down(Keys.SHIFT)
+                actions.send_keys(Keys.INSERT)
+                actions.key_up(Keys.SHIFT)
+                actions.perform()
+            else:
+                el.send_keys(Keys.CONTROL + "v")
+
+    def wait_for_element_to_be_visible(self, driver, xpath, time_value=20):
+        """This will wait until an element is visible on page"""
+        element_located_status = False
+        try:
+            if WebDriverWait(driver, time_value).until(
+                    EC.visibility_of_element_located((By.XPATH, xpath))):
+                element_located_status = True
+        except TimeoutException:
+            element_located_status = False
+        return element_located_status
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc24..b292472 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -8,6 +8,8 @@
 ##########################################################################
 
 from __future__ import print_function
+
+import fileinput
 import traceback
 import os
 import sys
@@ -16,7 +18,17 @@ import psycopg2
 import sqlite3
 import shutil
 from functools import partial
+
+from selenium.webdriver.support.wait import WebDriverWait
 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
+from selenium.webdriver.support import expected_conditions as ec
 
 import config
 import regression
@@ -1216,3 +1228,242 @@ 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(selenoid_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 = get_selenium_grid_status_json(selenoid_url)
+        if selenoid_status:
+            available_browsers = selenoid_status["browsers"]
+            list_of_browsers = test_setup.config_data['selenoid_config'][
+                'browsers_list']
+
+            for browser in list_of_browsers:
+                if browser["name"].lower() in available_browsers.keys():
+                    versions = available_browsers[(browser["name"].lower())]
+                    if browser["version"] is None:
+                        print("Specified version of browser is None. Hence "
+                              "latest version of {0} available with selenoid "
+                              "server will be used.\n".format(browser["name"]))
+                        browser_list.append(browser)
+                    elif 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:
+        (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, title='pgAdmin 4', timeout=40):
+    """
+    Function launches urls in specified driver instance
+    :param driver_instance:browser instance
+    :param url:url to be launched
+    :param title:web-page tile on successful launch default is 'pgAdmin 4'
+    :param timeout:in seconds for getting specified title default is 20sec
+    :return:
+    """
+    count = timeout / 5
+    while count > 0:
+        try:
+            driver_instance.get(url)
+            wait = WebDriverWait(driver_instance, 10)
+            wait.until(ec.title_is(title))
+            break
+        except WebDriverException as e:
+            time.sleep(6)
+            count -= 1
+            if count == 0:
+                exception_msg = 'Web-page title did not match to {0}. ' \
+                                'Please check url {1} accessible on ' \
+                                'internet.'.format(title, url)
+                raise Exception(exception_msg)
+
+
+def get_remote_webdriver(hub_url, browser, browser_ver, test_name):
+    """
+    This functions returns remote web-driver instance created in selenoid
+    machine.
+    :param hub_url
+    :param browser: browser name
+    :param browser_ver: version for browser
+    :param test_name: test name
+    :return: remote web-driver instance for specified browser
+    """
+    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)
+    elif browser == 'chrome':
+        options = Options()
+        options.add_argument("--window-size=1280,1024")
+        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
+
+
+def get_parallel_sequential_module_list(module_list):
+    """
+    Functions segregate parallel & sequential modules
+    :param module_list: Complete list of modules
+    :return: parallel & sequential module lists
+    """
+    # 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']
+
+    #  list of tests can be executed in parallel
+    parallel_tests = list(module_list)
+    for module in 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,
+               module_list))
+
+    # return parallel & sequential lists
+    return parallel_tests, sequential_tests
+
+
+def get_browser_details(browser_info_dict, url):
+    """
+    Function extracts browser name & version from browser info dict
+    in test_config.json
+    :param browser_info_dict:
+    :return: browser name & version
+    """
+    browser_name = browser_info_dict["name"].lower()
+    browser_version = browser_info_dict["version"]
+    if browser_version is None:
+        selenoid_status = get_selenium_grid_status_json(url)
+        versions = selenoid_status["browsers"][browser_name]
+        browser_version = max(versions)
+    return browser_name, browser_version
+
+
+def print_test_summary(complete_module_list, parallel_testlist,
+                       sequential_tests_list, browser_name, browser_version):
+    """
+    Prints test summary about total, parallel, sequential, browser name,
+    browser version information
+    :param complete_module_list:
+    :param parallel_testlist:
+    :param sequential_tests_list:
+    :param browser_name:
+    :param browser_version:
+    """
+    print(
+        "=================================================================",
+        file=sys.stderr
+    )
+    print(
+        "Total Tests # {0}\nParallel Tests # {1}, "
+        "Sequential Tests # {2}".format(
+            len(complete_module_list), len(parallel_testlist),
+            len(sequential_tests_list)),
+        file=sys.stderr)
+    print("Browser: [Name:{0}, Version: {1}]".format(
+        browser_name.capitalize(), browser_version),
+        file=sys.stderr)
+    print(
+        "=================================================================\n",
+        file=sys.stderr
+    )
+
+
+def get_selenium_grid_status_json(selenoid_url):
+    """
+    Functions returns json response received from selenoid server
+    :param selenoid_url:
+    :return:
+    """
+    try:
+        selenoid_status = urllib.urlopen(
+            "http://" + re.split('/', (re.split('//', selenoid_url, 1)[1]))[
+                0] + "/status", timeout=10)
+        selenoid_status = json.load(selenoid_status)
+        if isinstance(selenoid_status, dict):
+            return selenoid_status
+    except Exception as e:
+        print("Unable to find Selenoid Status.Kindly check url passed -'{0}'".
+              format(selenoid_url))
+        return None
diff --git a/web/regression/requirements.txt b/web/regression/requirements.txt
index 1eb922b..2367f2a 100644
--- a/web/regression/requirements.txt
+++ b/web/regression/requirements.txt
@@ -21,7 +21,6 @@ fixtures==3.0.0
 linecache2==1.0.0
 pbr==3.1.1
 pycodestyle>=2.5.0
-pyperclip~=1.6.0
 python-mimeparse==1.6.0
 testscenarios==0.5.0
 testtools==2.3.0
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index 480aaad..8759be3 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 < (3, 4):
@@ -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,62 @@ 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 arguments['pkg'] == "feature_tests":
+            exclude_pkgs.extend(['resql'])
+
+        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
+            # 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()
 
     handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
     # Register cleanup function to cleanup on exit
@@ -319,6 +325,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,117 +413,213 @@ class StreamToLogger(object):
         pass
 
 
-if __name__ == '__main__':
-    # Failure detected?
-    failure = False
-    test_result = dict()
-    cov = None
+def execute_test(test_module_list_passed, server_passed, driver_passed):
+    """
+    Function executes actually test
+    :param test_module_list_passed:
+    :param server_passed:
+    :param driver_passed:
+    :return:
+    """
+    try:
+        print("\n=============Running the test cases for '%s' ============="
+              % server_passed['name'], file=sys.stderr)
+        # 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']
+        )
 
-    # Set signal handler for cleanup
-    signal_list = dir(signal)
-    required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
-    # Get the OS wise supported signals
-    supported_signal_list = [sig for sig in required_signal_list if
-                             sig in signal_list]
-    for sig in supported_signal_list:
-        signal.signal(getattr(signal, sig), sig_handler)
+        # 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 threading.current_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()
 
-    # Set basic logging configuration for log file
-    fh = logging.FileHandler(CURRENT_PATH + '/' +
-                             'regression.log', 'w', 'utf-8')
-    fh.setLevel(logging.DEBUG)
-    fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
+        # Delete test server
+        test_utils.delete_test_server(test_client)
+    except Exception as exc:
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        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:
+            driver_passed.quit()
+            time.sleep(20)
+
+        # Print info about completed tests
+        print(
+            "\n=============Completed the test cases for '%s'============="
+            % server_passed['name'], file=sys.stderr)
 
-    logger = logging.getLogger()
-    logger.addHandler(fh)
 
-    # Create logger to write log in the logger file as well as on console
-    stderr_logger = logging.getLogger('STDERR')
-    sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
-    args = vars(add_arguments())
-    # Get test module list
+def run_parallel_tests(url_client, servers_details, parallel_tests_lists,
+                       name_of_browser, version_of_browser, max_thread_count):
+    """
+    Function used to run tests in parallel
+    :param url_client:
+    :param servers_details:
+    :param parallel_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :param max_thread_count:
+    """
+    driver_object = None
     try:
-        test_module_list = get_test_modules(args)
-    except Exception as e:
-        print(str(e))
-        sys.exit(1)
-    # Login the test client
-    test_utils.login_tester_account(test_client)
+        # Thread list
+        threads_list = []
+        # Create thread for each server
+        for ser in servers_details:
+            # Logic to add new threads
+            while True:
+                # If active thread count <= max_thread_count, add new thread
+                if threading.activeCount() <= max_thread_count:
+                    # Get remote web-driver instance at server level
+                    driver_object = \
+                        test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        ser['name'])
+                    # Launch client url in browser
+                    test_utils.launch_url_in_browser(driver_object, url_client)
+
+                    # Add name for thread
+                    thread_name = "parallel_tests" + ser['name']
+
+                    # Start thread
+                    t = threading.Thread(target=execute_test, name=thread_name,
+                                         args=(parallel_tests_lists, ser,
+                                               driver_object))
+                    threads_list.append(t)
+                    t.start()
+                    time.sleep(3)
+                    break
+                # else sleep for 10 seconds
+                else:
+                    time.sleep(10)
+
+        # Start threads in parallel
+        for t in threads_list:
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        # Clean driver object created
+        if driver_object is not None:
+            driver_object.quit()
 
-    servers_info = test_utils.get_config_data()
-    node_name = "all"
-    if args['pkg'] is not None:
-        node_name = args['pkg'].split('.')[-1]
-
-    # Start coverage
-    if test_utils.is_coverage_enabled(args):
-        cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
-        cov.start()
 
+def run_sequential_tests(url_client, servers_details, sequential_tests_lists,
+                         name_of_browser, version_of_browser):
+    """
+    Function is used to execute tests that needs to be run in sequential
+    manner.
+    :param url_client:
+    :param servers_details:
+    :param sequential_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :return:
+    """
+    driver_object = None
     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()
+        # Get remote web-driver instance
+        driver_object = test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        "Sequential_Tests")
+
+        # Launch client url in browser
+        test_utils.launch_url_in_browser(driver_object, url_client)
+
+        # Add name for thread
+        thread_name = "sequential_tests"
+
+        # Start thread
+        for ser in servers_details:
+            t = threading.Thread(target=execute_test,
+                                 name=thread_name,
+                                 args=(sequential_tests_lists, ser,
+                                       driver_object))
+            t.start()
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+    finally:
+        # Clean driver object created
+        driver_object.quit()
 
+
+def print_test_results():
     print(
         "\n==============================================================="
         "=======",
@@ -543,6 +648,10 @@ if __name__ == '__main__':
         total_passed_cases = int(
             test_result[server_res][0]) - total_failed - total_skipped
 
+        if len(failed_cases) > 0:
+            global failure
+            failure = True
+
         print(
             "%s:\n\n\t%s test%s passed\n\t%s test%s failed%s%s"
             "\n\t%s test%s skipped%s%s\n" %
@@ -578,12 +687,162 @@ if __name__ == '__main__':
         file=sys.stderr
     )
 
+
+if __name__ == '__main__':
+    # Failure detected?
+    failure = False
+    test_result = dict()
+    cov = None
+
+    # Set signal handler for cleanup
+    signal_list = dir(signal)
+    required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
+    # Get the OS wise supported signals
+    supported_signal_list = [sig for sig in required_signal_list if
+                             sig in signal_list]
+    for sig in supported_signal_list:
+        signal.signal(getattr(signal, sig), sig_handler)
+
+    # Set basic logging configuration for log file
+    fh = logging.FileHandler(CURRENT_PATH + '/' +
+                             'regression.log', 'w', 'utf-8')
+    fh.setLevel(logging.DEBUG)
+    fh.setFormatter(logging.Formatter('[%(thread)d] ' +
+                                      config.FILE_LOG_FORMAT))
+
+    logger = logging.getLogger()
+    logger.addHandler(fh)
+
+    # Create logger to write log in the logger file as well as on console
+    stderr_logger = logging.getLogger('STDERR')
+    sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
+    args = vars(add_arguments())
+    # Get test module list
+    try:
+        test_module_list = get_test_modules(args)
+    except Exception as e:
+        print(str(e))
+        sys.exit(1)
+    # Login the test client
+    test_utils.login_tester_account(test_client)
+
+    servers_info = test_utils.get_config_data()
+    node_name = "all"
+    if args['pkg'] is not None:
+        node_name = args['pkg'].split('.')[-1]
+
+    # Start coverage
+    if test_utils.is_coverage_enabled(args):
+        cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
+        cov.start()
+
+    # 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 config dict
+        selenoid_config = test_setup.config_data['selenoid_config']
+
+        # Set DEFAULT_SERVER value
+        default_server = selenoid_config['pgAdmin_default_server']
+        os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"] = str(default_server)
+        config.DEFAULT_SERVER = str(default_server)
+
+        # Get hub url
+        hub_url = selenoid_config['selenoid_url']
+
+        # 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(hub_url)
+
+        # Execute tests if selenium-grid is up
+        if selenium_grid_status and len(list_of_browsers) > 0:
+            app_starter_local = None
+            # run across browsers
+            for browser_info in list_of_browsers:
+                try:
+                    # browser info
+                    browser_name, browser_version = \
+                        test_utils.get_browser_details(browser_info, hub_url)
+
+                    # tests lists can be executed in parallel & sequentially
+                    parallel_tests, sequential_tests = \
+                        test_utils.get_parallel_sequential_module_list(
+                            test_module_list)
+
+                    # Print test summary
+                    test_utils.print_test_summary(test_module_list,
+                                                  parallel_tests,
+                                                  sequential_tests,
+                                                  browser_name,
+                                                  browser_version)
+
+                    # Create app form source code
+                    app_starter_local = AppStarter(None, config)
+                    client_url = app_starter_local.start_app()
+
+                    # Running Parallel tests
+                    if len(parallel_tests) > 0:
+                        parallel_sessions = int(selenoid_config[
+                                                'max_parallel_sessions'])
+
+                        run_parallel_tests(client_url, servers_info,
+                                           parallel_tests, browser_name,
+                                           browser_version, parallel_sessions)
+
+                    # Wait till all threads started in parallel are finished
+                    while True:
+                        try:
+                            if threading.activeCount() <= 1:
+                                break
+                            else:
+                                time.sleep(10)
+                        except Exception as e:
+                            traceback.print_exc(file=sys.stderr)
+                            print(str(e))
+
+                    # Sequential Tests
+                    if len(sequential_tests) > 0:
+                        run_sequential_tests(client_url, servers_info,
+                                             sequential_tests, browser_name,
+                                             browser_version)
+
+                    # Clean up environment
+                    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()
+                # Pause before printing result in order not to mix output
+                time.sleep(5)
+                # Print note for completion of execution in a browser.
+                print(
+                    "\n============= Test execution with {0} is "
+                    "completed.=============".format(browser_name),
+                    file=sys.stderr)
+                print_test_results()
+        del os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"]
+    else:
+        try:
+            for server in servers_info:
+                thread = threading.Thread(target=execute_test, args=(
+                    test_module_list, server, driver))
+                thread.start()
+                thread.join()
+        except SystemExit:
+            if handle_cleanup:
+                handle_cleanup()
+        print_test_results()
+
     # Stop code coverage
     if test_utils.is_coverage_enabled(args):
         cov.stop()
         cov.save()
 
-    # # Print coverage only if coverage args given in command line
+    # Print coverage only if coverage args given in command line
     if test_utils.is_coverage_enabled(args):
         test_utils.print_and_store_coverage_report(cov)
 
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index af061b9..8207711 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,14 @@
 {
   "headless_chrome": false,
   "default_browser": "Chrome",
+  "selenoid_config":{
+        "pgAdmin_default_server":"IP address of machine where source code is going to be executed",
+        "max_parallel_sessions": "3",
+        "selenoid_url": ""http://<IP address of Selenoid Installed machine>:4444/wd/hub",
+        "browsers_list": [
+            {"name": "Chrome","version":null},
+            {"name": "Firefox","version":null} ]
+  },
   "pgAdmin4_login_credentials": {
     "new_password": "NEWPASSWORD",
     "login_password": "PASSWORD",


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-08 09:05  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Akshay Joshi @ 2020-05-08 09:05 UTC (permalink / raw)
  To: Yogesh Mahajan <[email protected]>; +Cc: pgadmin-hackers; Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi Yogesh

Unable to apply the patch on the latest code. Please rebase and send it
again.

On Fri, May 8, 2020 at 2:05 PM Yogesh Mahajan <
[email protected]> wrote:

> Attaching patch
>
> Thanks,
> Yogesh Mahajan
> QA - Team
> EnterpriseDB Corporation
>
> Phone: +91-9741705709
>
>
> On Fri, May 8, 2020 at 1:57 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Yogesh
>>
>> You forgot to attach patch :)
>>
>> On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
>> [email protected]> wrote:
>>
>>> Hi,
>>>
>>> Please find the updates patch with above review comments.
>>> Patch adds below functionality to existing framework -
>>> 1.Ability to run features in parallel using solenoid(selenium + docker).
>>>      a.Selenoid setup steps are included in /regression/README
>>>      b.'python runtests.py  --pkg feature_tests --parallel' will trigger
>>> parallel feature tests.
>>> 2.Removes dependency for pyperclip python module.
>>> 3.New script in ../tools/update_selenoid_browsers.py updates browser
>>> images at local selneoid server setup.
>>>
>>>
>>> Thanks,
>>> Yogesh Mahajan
>>> QA - Team
>>> EnterpriseDB Corporation
>>>
>>> Phone: +91-9741705709
>>>
>>>
>>> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Yogesh
>>>>
>>>> Following are the review comments:
>>>>
>>>>    - *pyjq* package is not required as we used it only in one place. A
>>>>    result is a normal dictionary that can be easily looped through.
>>>>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>>>    config.py, we have already removed that.
>>>>    - Remove yarn.lock file.
>>>>    - Remove *pyperclip *from the regression/requirements.txt as we are
>>>>    not using it.
>>>>    - Please mentioned the value of *pgAdmin_default_server *should not
>>>>    be '*127.0.0.1*' in the README file even though everything runs on
>>>>    the same machine.
>>>>    - Please mentioned that if we set the value of the browser version
>>>>    is *null* then selenoid will take the latest available browser
>>>>    version.
>>>>    - Got the below error if selenoid_url is not provided:
>>>>       - list index out of range
>>>>       Unable to find Selenoid Status
>>>>
>>>> *test_config.json.in <http://test_config.json.in>*:
>>>>
>>>>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>>>>    alignment is required.
>>>>    - "cross_Browsers" should be renamed to "cross_browsers" or
>>>>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>>>    browsers with version set to null so that it will run on the latest browser
>>>>    version.
>>>>    - "selenoid_url": "Selenoid Url" should be changed
>>>>    to "selenoid_url": "http://<IP address of Selenoid Installed
>>>>    machine>:4444/wd/hub".
>>>>
>>>> If you change the names in test_config.json.in then please update the
>>>> same in README as well.
>>>>
>>>>
>>>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Akshay,
>>>>>
>>>>> Please find the updated patch.
>>>>>
>>>>> Thanks,
>>>>> Yogesh Mahajan
>>>>> QA - Team
>>>>> EnterpriseDB Corporation
>>>>>
>>>>> Phone: +91-9741705709
>>>>>
>>>>>
>>>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Yogesh
>>>>>>
>>>>>> The patch is not applied to the master branch. Can you please rebase
>>>>>> and send the patch again.
>>>>>>
>>>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi,
>>>>>>>
>>>>>>> Please find updated patch modified according to review comments -
>>>>>>> Patch implements below things -
>>>>>>> 1.Enable the current framework to provide option to execute Feature
>>>>>>> tests in parallel  on selenium grid set up.
>>>>>>>    - Addition of new switch to start parallel features tests.
>>>>>>>    - New parameters with respect to selenoid in test_config.json.in
>>>>>>>    - Addition of new script to check solenoid updates.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Yogesh Mahajan
>>>>>>> QA - Team
>>>>>>> EnterpriseDB Corporation
>>>>>>>
>>>>>>> Phone: +91-9741705709
>>>>>>>
>>>>>>>
>>>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Yogesh,
>>>>>>>> Below are the review comments-
>>>>>>>>
>>>>>>>> 1. runtests.py
>>>>>>>>     a. The exception traceback logic at line number 653 in
>>>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>>>> but there is much more code in that block which can throw some
>>>>>>>> exception.
>>>>>>>> b. line number 447 -> The drop_database function will only try to
>>>>>>>> drop the database with the name which is newly created
>>>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>>>> ‘acceptance_test_db'.
>>>>>>>>   c. line 584 - Why we are including resql test case execution in
>>>>>>>> GUI execution logic.
>>>>>>>>     d. Change the function name run_test as script name is also
>>>>>>>> runtests.py
>>>>>>>>
>>>>>>>> 2. test_utils.py
>>>>>>>>     a. Remove the headless chrome code from get_remote_webdriver()
>>>>>>>> in test_utils.py since we are using solenoid and it is not
>>>>>>>> required anymore.
>>>>>>>>     b. Create separate functions to instantiate the firefox driver
>>>>>>>> and chrome driver logic since the same code is used in multiple files.
>>>>>>>> c. launch_url_in_browser() -> you can simplify the definition of
>>>>>>>> the function like:
>>>>>>>>     retry = 60
>>>>>>>>         *while *retry > 0:
>>>>>>>>             try:
>>>>>>>>                 driver.get(url)
>>>>>>>>             except WebDriverException:
>>>>>>>>                  retry -= 1
>>>>>>>> 3. Execution logs are not printing as per the logic some time, I
>>>>>>>> ran the suite for two servers and attached are the execution logs.
>>>>>>>> 4. Readme -
>>>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>>>> clear.
>>>>>>>> Revisit the readme and write the missing steps.
>>>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>>>> Create the function to avoid duplicate code. The code for pasting
>>>>>>>> the values is repeating 8 times in the test code.
>>>>>>>> 6. Provide the valid docstring in newly introduced functions and
>>>>>>>> also valid comments while calling it. for ex.- _update_preference()
>>>>>>>> function is
>>>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>>>> 7. test_index_constraint_add test case is failing due to the
>>>>>>>> latest change, please merge and update this test case
>>>>>>>>
>>>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>>>> Someone else please review it.
>>>>>>>>>
>>>>>>>>> Thanks!
>>>>>>>>>
>>>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Navnath
>>>>>>>>>>
>>>>>>>>>> Can you please review it?
>>>>>>>>>>
>>>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> 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
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>
>>>>>>>>>> *Sr. Software Architect*
>>>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Regards,
>>>>>>>>> Navnath Gadakh
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Thanks & Regards,
>>>>>>>> Shubham Agarwal
>>>>>>>> EnterpriseDB Corporation
>>>>>>>>
>>>>>>>> The Postgres Database Company
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>>
>>>>>> *Sr. Software Architect*
>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>>
>>>> *Sr. Software Architect*
>>>> *EnterpriseDB Software India Private Limited*
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>>
>> *Sr. Software Architect*
>> *EnterpriseDB Software India Private Limited*
>> *Mobile: +91 976-788-8246*
>>
>

-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-08 09:42  Yogesh Mahajan <[email protected]>
  parent: Akshay Joshi <[email protected]>
  0 siblings, 1 reply; 14+ messages in thread

From: Yogesh Mahajan @ 2020-05-08 09:42 UTC (permalink / raw)
  To: Akshay Joshi <[email protected]>; pgadmin-hackers; +Cc: Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Hi Akshay,

Please find the attached rebase patch. Looks like there were some commits
after my today's git pull.

Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation

Phone: +91-9741705709


On Fri, May 8, 2020 at 2:35 PM Akshay Joshi <[email protected]>
wrote:

> Hi Yogesh
>
> Unable to apply the patch on the latest code. Please rebase and send it
> again.
>
> On Fri, May 8, 2020 at 2:05 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> Attaching patch
>>
>> Thanks,
>> Yogesh Mahajan
>> QA - Team
>> EnterpriseDB Corporation
>>
>> Phone: +91-9741705709
>>
>>
>> On Fri, May 8, 2020 at 1:57 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Yogesh
>>>
>>> You forgot to attach patch :)
>>>
>>> On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
>>> [email protected]> wrote:
>>>
>>>> Hi,
>>>>
>>>> Please find the updates patch with above review comments.
>>>> Patch adds below functionality to existing framework -
>>>> 1.Ability to run features in parallel using solenoid(selenium + docker).
>>>>      a.Selenoid setup steps are included in /regression/README
>>>>      b.'python runtests.py  --pkg feature_tests --parallel' will
>>>> trigger parallel feature tests.
>>>> 2.Removes dependency for pyperclip python module.
>>>> 3.New script in ../tools/update_selenoid_browsers.py updates browser
>>>> images at local selneoid server setup.
>>>>
>>>>
>>>> Thanks,
>>>> Yogesh Mahajan
>>>> QA - Team
>>>> EnterpriseDB Corporation
>>>>
>>>> Phone: +91-9741705709
>>>>
>>>>
>>>> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Yogesh
>>>>>
>>>>> Following are the review comments:
>>>>>
>>>>>    - *pyjq* package is not required as we used it only in one place.
>>>>>    A result is a normal dictionary that can be easily looped through.
>>>>>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>>>>    config.py, we have already removed that.
>>>>>    - Remove yarn.lock file.
>>>>>    - Remove *pyperclip *from the regression/requirements.txt as we
>>>>>    are not using it.
>>>>>    - Please mentioned the value of *pgAdmin_default_server *should
>>>>>    not be '*127.0.0.1*' in the README file even though everything
>>>>>    runs on the same machine.
>>>>>    - Please mentioned that if we set the value of the browser version
>>>>>    is *null* then selenoid will take the latest available browser
>>>>>    version.
>>>>>    - Got the below error if selenoid_url is not provided:
>>>>>       - list index out of range
>>>>>       Unable to find Selenoid Status
>>>>>
>>>>> *test_config.json.in <http://test_config.json.in>*:
>>>>>
>>>>>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>>>>>    alignment is required.
>>>>>    - "cross_Browsers" should be renamed to "cross_browsers" or
>>>>>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>>>>    browsers with version set to null so that it will run on the latest browser
>>>>>    version.
>>>>>    - "selenoid_url": "Selenoid Url" should be changed
>>>>>    to "selenoid_url": "http://<IP address of Selenoid Installed
>>>>>    machine>:4444/wd/hub".
>>>>>
>>>>> If you change the names in test_config.json.in then please update the
>>>>> same in README as well.
>>>>>
>>>>>
>>>>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Akshay,
>>>>>>
>>>>>> Please find the updated patch.
>>>>>>
>>>>>> Thanks,
>>>>>> Yogesh Mahajan
>>>>>> QA - Team
>>>>>> EnterpriseDB Corporation
>>>>>>
>>>>>> Phone: +91-9741705709
>>>>>>
>>>>>>
>>>>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Yogesh
>>>>>>>
>>>>>>> The patch is not applied to the master branch. Can you please rebase
>>>>>>> and send the patch again.
>>>>>>>
>>>>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi,
>>>>>>>>
>>>>>>>> Please find updated patch modified according to review comments -
>>>>>>>> Patch implements below things -
>>>>>>>> 1.Enable the current framework to provide option to execute Feature
>>>>>>>> tests in parallel  on selenium grid set up.
>>>>>>>>    - Addition of new switch to start parallel features tests.
>>>>>>>>    - New parameters with respect to selenoid in test_config.json.in
>>>>>>>>    - Addition of new script to check solenoid updates.
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> Thanks,
>>>>>>>> Yogesh Mahajan
>>>>>>>> QA - Team
>>>>>>>> EnterpriseDB Corporation
>>>>>>>>
>>>>>>>> Phone: +91-9741705709
>>>>>>>>
>>>>>>>>
>>>>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Yogesh,
>>>>>>>>> Below are the review comments-
>>>>>>>>>
>>>>>>>>> 1. runtests.py
>>>>>>>>>     a. The exception traceback logic at line number 653 in
>>>>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>>>>> but there is much more code in that block which can throw some
>>>>>>>>> exception.
>>>>>>>>> b. line number 447 -> The drop_database function will only try to
>>>>>>>>> drop the database with the name which is newly created
>>>>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>>>>> ‘acceptance_test_db'.
>>>>>>>>>   c. line 584 - Why we are including resql test case execution in
>>>>>>>>> GUI execution logic.
>>>>>>>>>     d. Change the function name run_test as script name is also
>>>>>>>>> runtests.py
>>>>>>>>>
>>>>>>>>> 2. test_utils.py
>>>>>>>>>     a. Remove the headless chrome code from
>>>>>>>>> get_remote_webdriver() in test_utils.py since we are using
>>>>>>>>> solenoid and it is not required anymore.
>>>>>>>>>     b. Create separate functions to instantiate the firefox
>>>>>>>>> driver and chrome driver logic since the same code is used in multiple
>>>>>>>>> files.
>>>>>>>>> c. launch_url_in_browser() -> you can simplify the definition of
>>>>>>>>> the function like:
>>>>>>>>>     retry = 60
>>>>>>>>>         *while *retry > 0:
>>>>>>>>>             try:
>>>>>>>>>                 driver.get(url)
>>>>>>>>>             except WebDriverException:
>>>>>>>>>                  retry -= 1
>>>>>>>>> 3. Execution logs are not printing as per the logic some time, I
>>>>>>>>> ran the suite for two servers and attached are the execution logs.
>>>>>>>>> 4. Readme -
>>>>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>>>>> clear.
>>>>>>>>> Revisit the readme and write the missing steps.
>>>>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>>>>> Create the function to avoid duplicate code. The code for pasting
>>>>>>>>> the values is repeating 8 times in the test code.
>>>>>>>>> 6. Provide the valid docstring in newly introduced functions and
>>>>>>>>> also valid comments while calling it. for ex.- _update_preference()
>>>>>>>>> function is
>>>>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>>>>> 7. test_index_constraint_add test case is failing due to the
>>>>>>>>> latest change, please merge and update this test case
>>>>>>>>>
>>>>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi,
>>>>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>>>>> Someone else please review it.
>>>>>>>>>>
>>>>>>>>>> Thanks!
>>>>>>>>>>
>>>>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Navnath
>>>>>>>>>>>
>>>>>>>>>>> Can you please review it?
>>>>>>>>>>>
>>>>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> 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
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>>
>>>>>>>>>>> *Sr. Software Architect*
>>>>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> Regards,
>>>>>>>>>> Navnath Gadakh
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Thanks & Regards,
>>>>>>>>> Shubham Agarwal
>>>>>>>>> EnterpriseDB Corporation
>>>>>>>>>
>>>>>>>>> The Postgres Database Company
>>>>>>>>>
>>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> *Thanks & Regards*
>>>>>>> *Akshay Joshi*
>>>>>>>
>>>>>>> *Sr. Software Architect*
>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>
>>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>>
>>>>> *Sr. Software Architect*
>>>>> *EnterpriseDB Software India Private Limited*
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect*
>>> *EnterpriseDB Software India Private Limited*
>>> *Mobile: +91 976-788-8246*
>>>
>>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>


Attachments:

  [application/octet-stream] Selenium_Grid_Implementation_ver5.0.patch (110.8K, 3-Selenium_Grid_Implementation_ver5.0.patch)
  download | inline diff:
diff --git a/tools/update_selenoid_browsers.py b/tools/update_selenoid_browsers.py
new file mode 100644
index 0000000..c067621
--- /dev/null
+++ b/tools/update_selenoid_browsers.py
@@ -0,0 +1,277 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+# #########################################################################
+# Updates browser images(selenoid-docker) depending on arguments passed while
+# running this script.
+# e.g. --chrome /usr/bin/google-chrome --firefox /usr/bin/firefox
+# Access details about switches using help
+# e.g. --help
+
+import argparse
+import os
+import subprocess
+import sys
+import traceback
+import requests
+import json
+
+
+def read_command_line():
+    """Read the command line arguments.
+    Returns:
+        ArgumentParser: The parsed arguments object
+
+    """
+    parser = argparse.ArgumentParser(
+        description='Get latest browser images(chrome & firefox) for selenoid.'
+                    'e.g. - --chrome /usr/bin/google-chrome --firefox '
+                    '/usr/bin/firefox')
+    parser.add_argument("--chrome", metavar="CHROME",
+                        help="the Chrome executable path")
+    parser.add_argument("--firefox", metavar="FIREFOX",
+                        help="the firefox executable path")
+    args_val = parser.parse_args()
+    return args_val
+
+
+def get_browser_version(browser_name, executable_path):
+    """
+    Function returns browser version for specified browser using executable
+    path passed in arguments.
+    :param browser_name:
+    :param executable_path: e.g. /usr/bin/firefox
+    :return: browser version
+    """
+    # On Linux/Mac we run the browser executable with the --version flag,
+    # then parse the output.
+    browser_version_val = None
+    try:
+        result = subprocess.Popen([executable_path, '--version'],
+                                  stdout=subprocess.PIPE)
+    except FileNotFoundError:
+        print('The specified browser executable could not be found.')
+        sys.exit(1)
+
+    version_str = result.stdout.read().decode("utf-8")
+
+    if browser_name.lower() == "chrome":
+        # Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
+        if "Chrom" not in version_str:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        # On some linux distro `chrome--version` gives output like
+        # 'Google Chrome 80.0.3987.132 unknown\n'
+        # so we need to check and remove the unknown string from the version
+        if version_str.endswith("unknown\n"):
+            version_str = version_str.strip("unknown\n").strip()
+
+        chrome_version = '.'.join(version_str.split()[-1].split('.')[:-2])
+
+        # Make sure browser version has only 1 decimal point
+        if chrome_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = chrome_version
+    elif browser_name.lower() == "firefox":
+        if "Firefox" not in version_str:
+            print('The specified Firefox executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+
+        # Some time firefox --version gives output like
+        # 'Running without a11y support!
+        # Mozilla Firefox 68.7.0esr'
+        # Other output - [root@localhost local]# /usr/bin/firefox --version
+        # Mozilla Firefox 75.0
+        if 'esr' in version_str:
+            firefox_version = '.'.join(
+                version_str.split()[-1].split('.')[:-2]) + '.0'
+        else:
+            firefox_version = '.'.join(
+                version_str.split()[-1].split('.')[:-1]) + '.0'
+
+        # Make sure browser version has only 1 decimal point
+        if firefox_version.count('.') != 1:
+            print('The specified Chrome executable output an unexpected '
+                  'version string: {}.'.format(version_str))
+            sys.exit(1)
+        browser_version_val = firefox_version
+    else:
+        print("{0} is not recognised ".format(browser_name))
+        sys.exit(1)
+    return browser_version_val
+
+
+def check_and_download_vnc_browser_image(browser_name, browser_version):
+    """
+    Function checks presence for vnc images for passed browser
+    at docker.io/selenoid/ registry
+    :param browser_name:
+    :param browser_version:
+    :return:true if browser image is available & downloaded else false
+    """
+    res = requests.get(
+        'https://registry.hub.docker.com/v2/repositories/selenoid/vnc_' +
+        browser_name + '/tags/')
+    res = res.json()
+    version_tag = []
+    if len(res['results']) > 0:
+        for result in res['results']:
+            if 'name' in result:
+                version_tag.append(result['name'])
+    vnc_image_available = False
+    image_name = 'vnc_' + browser_name + ':' + browser_version
+
+    for idx, tag in enumerate(version_tag):
+        if browser_version == tag:
+            command = 'docker pull selenoid/vnc_' + browser_name + ':' \
+                      + browser_version
+            print(' VNC image is available & downloading now... {0}'.format(
+                command))
+            try:
+                subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+                vnc_image_available = True
+            except Exception:
+                traceback.print_exc(file=sys.stderr)
+                print(
+                    '{0}} Image found but could not download.'.format(command))
+                sys.exit(1)
+            break
+        elif idx == len(version_tag):
+            print("{0} Image is not available.".format(image_name))
+            vnc_image_available = False
+        else:
+            pass
+    return vnc_image_available
+
+
+def reload_selenoid_config():
+    """
+    Function runs command to refresh selenoid configuration
+    :return: true if command execution for selenoid reload is successful
+    else false
+    """
+    command = 'docker kill -s HUP selenoid'
+    reload_successful = False
+    try:
+        subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+        print(" Selenoid Configuration is reloaded.")
+        reload_successful = True
+    except Exception:
+        traceback.print_exc(file=sys.stderr)
+        print('Error while reloading selenoid configuration.')
+        sys.exit(1)
+    return reload_successful
+
+
+def edit_browsers_json(browser_name, browser_version):
+    """
+    Function edits browsers.json which is used by selenoid to
+    load browser configuration.
+    Default path for this file is
+    "user_home_dir + '/.aerokube/selenoid/browsers.json'"
+    Currently this is hardcoded, might need to modify
+    if we want to pass customize browsers.json
+    :param browser_name:
+    :param browser_version:
+    :return:
+    """
+    file_edited = True
+    # Read existing browsers.json
+    json_file = open(file_path, 'r')
+    existing_data = json.load(json_file)
+    updated_data = None
+
+    # Update data for new browser images
+    if browser_name.lower() == 'chrome':
+        version_data = existing_data['chrome']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_chrome:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['chrome']['versions']).update(data_to_insert)
+            updated_data = existing_data
+            print(updated_data)
+
+    elif browser_name.lower() == 'firefox':
+        version_data = existing_data['firefox']['versions']
+        if browser_version in version_data.keys():
+            print(" {0}:{1} is already updated in browsers.json.".format(
+                browser_name, browser_version))
+            file_edited = True
+        else:
+            data_to_insert = dict(
+                {browser_version: {
+                    'image': 'selenoid/vnc_firefox:' + browser_version,
+                    'port': '4444', 'path': '/'}})
+            (existing_data['firefox']['versions']).update(data_to_insert)
+            updated_data = existing_data
+    else:
+        print("Browser version not matched")
+        file_edited = False
+
+    # Write updated data in browsers.json
+    if updated_data is not None:
+        json_file = open(file_path, 'w')
+        json.dump(updated_data, json_file)
+        print(" 'browsers.json' is updated for {0} {1}".format(
+            browser_name, browser_version))
+
+        file_edited = True
+    return file_edited
+
+
+# Main Program starts here
+# Read command line arguments & get list of browser_name, executable path.
+args = vars(read_command_line())
+
+# Get path path for browsers.json
+user_home_dir = os.getenv("HOME")
+file_path = user_home_dir + '/.aerokube/selenoid/browsers.json'
+print("***** Updating '{0}' for new browser versions.*****".format(file_path))
+
+# Iterate over arguments passed
+for browser, executable_path in args.items():
+    if executable_path is not None:
+        # Get browser name
+        browser_name = browser
+        # Get browser version
+        browser_version = get_browser_version(browser, executable_path)
+        print(
+            " Browser version for {0} is {1} in current executable path ".
+            format(browser_name, browser_version))
+
+        # Download vnc browser image.
+        download_new_image = check_and_download_vnc_browser_image(
+            browser_name, browser_version)
+
+        # If browser vnc image is available, then edit browsers.json
+        if download_new_image:
+            if edit_browsers_json(browser_name, browser_version):
+                print(
+                    " File 'browsers.json' is updated for {0} - {1} \n".format(
+                        browser_name, browser_version))
+            else:
+                print(
+                    " File 'browsers.json' can NOT be updated for {0} - {1} \n"
+                    .format(browser_name, browser_version))
+        else:
+            print(" Browser image is not available for {0}, {1}".format(
+                browser_name, browser_version))
+
+# Reload selenoid configuration
+if reload_selenoid_config():
+    print(
+        "***** Updated '{0}' for new browser versions.*****".format(file_path))
diff --git a/web/config.py b/web/config.py
index 7f74a7d..d05aa04 100644
--- a/web/config.py
+++ b/web/config.py
@@ -562,6 +562,10 @@ try:
 except ImportError:
     pass
 
+# Override DEFAULT_SERVE value from environment variable.
+if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
+    DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
+
 # Disable USER_INACTIVITY_TIMEOUT when SERVER_MODE=False
 if not SERVER_MODE:
     USER_INACTIVITY_TIMEOUT = 0
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 f127063..88526ca 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
@@ -44,31 +44,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)
@@ -81,10 +80,9 @@ 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)
 
 
 class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
@@ -117,30 +115,28 @@ class ConstraintsUsingIndexAddTestCase(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 "
                             "constraint using index.")
-        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_constraint_%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_constraint_%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)
@@ -158,7 +154,6 @@ class ConstraintsUsingIndexAddTestCase(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 7eea5e9..f35a00b 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 c08c2ac..f838a2f 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 87289a6..6bdf703 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 4bd59f5..fae094b 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
@@ -125,8 +125,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"]
@@ -142,12 +143,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."""
@@ -167,7 +170,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 0a2963a..8e5f665 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 177aa21..9f9a389 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
@@ -125,8 +125,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"]
@@ -150,17 +151,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,
@@ -180,7 +183,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/test_mviews_refresh.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
index b6c0bad..3b4055f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
@@ -48,8 +48,7 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
          ),
     ]
 
-    @classmethod
-    def setUpClass(self):
+    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"]
@@ -143,7 +142,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
                 # On success we get job_id from server
                 self.assertTrue('job_id' in response.json['data'])
 
-    @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 d6180c4..7619ee6 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 7091ffa..1f3bd65 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
@@ -60,8 +59,18 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         self._mouseup_outside_grid_still_makes_a_selection()
         self._copies_rows_with_header()
 
+    def paste_values_to_scratch_pad(self):
+        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")
+        scratch_pad_ele.clear()
+        return clipboard_text
+
     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 +79,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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 +95,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +111,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             QueryToolLocators.copy_button_css)
         copy_button.click()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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,11 +127,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual('"Some-Name"\t6\t"some info"',
-                         pyperclip.paste())
+                         clipboard_text)
 
     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()
@@ -128,15 +140,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             """\"Some-Name"
 "Some-Other-Name"
 "Yet-Another-Name\"""",
-            pyperclip.paste())
+            clipboard_text)
 
     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 +166,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             self.page.driver
         ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
-            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+            '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
 
     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 +192,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
             Keys.CONTROL
         ).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         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)
 
     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 +209,13 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
         ActionChains(self.page.driver).key_down(
             Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
 
+        clipboard_text = self.paste_values_to_scratch_pad()
+
         self.assertEqual(
             '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
-            pyperclip.paste())
+            clipboard_text)
 
     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 +230,9 @@ 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())
+        clipboard_text = self.paste_values_to_scratch_pad()
+
+        self.assertIn('"cool info"', clipboard_text)
 
     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 6d01286..33f86ad 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/keyboard_shortcut_test.py b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
index ea381b4..f68e530 100644
--- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py
+++ b/web/pgadmin/feature_tests/keyboard_shortcut_test.py
@@ -94,24 +94,33 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest):
             NavMenuLocators.preference_menu_item_css)
         pref_menu_item.click()
 
-        # Wait till the preference dialogue box is displayed by checking the
-        # visibility of Show System Object label
-        self.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()
-
         browser_node = self.page.find_by_xpath(
             NavMenuLocators.specified_preference_tree_node.format('Browser'))
         if self.page.find_by_xpath(
             NavMenuLocators.specified_pref_node_exp_status.
                 format('Browser')).get_attribute('aria-expanded') == 'false':
-
             ActionChains(self.driver).double_click(browser_node).perform()
 
+        display_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
+                'Browser', 'Display'))
+        attempt = 5
+        while attempt > 0:
+            display_node.click()
+            # After clicking the element gets loaded in to the dom but still
+            # not visible, hence sleeping for a sec.
+            time.sleep(1)
+            if self.page.wait_for_element_to_be_visible(
+                self.driver,
+                    NavMenuLocators.show_system_objects_pref_label_xpath, 3):
+                break
+            else:
+                attempt -= 1
+
+        maximize_button = self.page.find_by_css_selector(
+            NavMenuLocators.maximize_pref_dialogue_css)
+        maximize_button.click()
+
         keyboard_node = self.page.find_by_xpath(
             NavMenuLocators.specified_sub_node_of_pref_tree_node.format(
                 'Browser', 'Keyboard shortcuts'))
diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
index dba4ead..baccc70 100644
--- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py
+++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py
@@ -92,6 +92,18 @@ class PGDataypeFeatureTest(BaseFeatureTest):
 
         wait = WebDriverWait(self.page.driver, 10)
 
+        browser_node = self.page.find_by_xpath(
+            NavMenuLocators.specified_preference_tree_node.format('Browser'))
+        if self.page.find_by_xpath(
+            NavMenuLocators.specified_pref_node_exp_status.
+                format('Browser')).get_attribute('aria-expanded') == 'false':
+            ActionChains(self.driver).double_click(browser_node).perform()
+
+        self.page.retry_click(
+            (By.XPATH, NavMenuLocators.specified_sub_node_of_pref_tree_node.
+             format('Browser', 'Display')),
+            (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath))
+
         # Wait till the preference dialogue box is displayed by checking the
         # visibility of Show System Object label
         wait.until(EC.presence_of_element_located(
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 1d6d7a4..4cf9282 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,74 @@ 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):
+        """
+        Function updates preferences for binary path.
+        """
+        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 2d7c4ef..ddb4e3c 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 3f53d86..21921cc 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,20 +178,21 @@ 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._add_row(config_data_local)
         self._verify_row_data(row_height=0,
-                              config_check_data=config_data['add'])
+                              config_check_data=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(row_height=0,
                               config_check_data=updated_row_data)
@@ -221,7 +224,6 @@ CREATE TABLE public.nonintpkey
         Returns: None
 
         """
-
         self.wait.until(EC.visibility_of_element_located(
             (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING
         )
@@ -238,7 +240,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(
@@ -290,7 +292,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()
@@ -300,12 +302,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(row_height=25,
                               config_check_data=updated_row_data)
@@ -329,11 +331,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 9efad8a..69c3cba 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 a80310e..faff52a 100644
--- a/web/pgadmin/utils/route.py
+++ b/web/pgadmin/utils/route.py
@@ -120,9 +120,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):
@@ -137,17 +136,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 46adfcc..3a7472e 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,61 @@ 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 --args "-limit 3 -cpu 1.5 -mem 1.5g"
+    $./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
+    Note : In --args "-limit 3 -cpu 1.5 -mem 1.5g"
+                -limit 3 :limits maximum parallel sessions(dockers) in selenoid,
+                -cpu :limit memory and CPU usage,
+                -mem :limit memory per session.
+           Generally max parallel session is the number of cores * 1.5 – 2
+           You can list available flags by using ./cm selenoid args
+    Additional Information about tool
+            - https://aerokube.com/selenoid/latest/
+
+  - Update 'test_config.json' with selenoid config information
+    pgAdmin_default_server -
+        It is the IP address for the machine where pgadmin source code is
+        present.Value should NOT be '127.0.0.1' even though everything runs
+        on the same machine.
+        You can get it on linux running command  'ifconfig | grep inet'
+        e.g. - 192.168.143.121
+    max_parallel_sessions -
+        This is other way to control number of tests to be run in parallel.
+        This should be equal or less than limit specified while setting up
+        selenoid
+    selenoid_url -
+        Url should be formed as below -
+        http://<IP address of Selenoid Installed machine>:4444/wd/hub/
+        e.g. - selenoid_url": "http://192.168.143.121:4444/wd/hub"
+        If source code & selenoid servers are on same machine then
+        selenoid url value can be - "http://localhost:4444/wd/hub"
+    browsers_list -
+        List of browser name & version enclosed in {} on which tests to be
+        executed.
+        Make sure list contains those browsers & versions only which are shown
+        in capabilities tab while in selenoid status web-page.
+        If version is mention as null, then latest version available in
+        selenoid server will be used for execution.
+        e.g. - [ {"name": "Chrome","version": "80.0"},
+                 {"name": "Firefox","version": "74.0"}]
+
 - Change to the regression test directory:
      run 'cd web/regression'
 
@@ -190,9 +245,14 @@ Python Tests:
      Example 2)  Execute only reverse engineered SQL test framework for some modules
          run 'python runtests.py --pkg resql --modules sequences,functions'
 
+
      Example 3) Exclude reverse engineered SQL test framework for all modules
          run 'python runtests.py --exclude resql'
 
+- 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 fe4c441..7de10b2 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 b8169fa..4756231 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"
@@ -217,9 +219,9 @@ class QueryToolLocators:
     btn_commit = "#btn-commit"
 
     show_query_internally_btn = \
-        "//div[label[normalize-space(" \
-        "text())='Show queries generated internally by pgAdmin?']]" \
-        "//div[contains(@class,'toggle btn')]"
+        "//div[label[contains(normalize-space(text())," \
+        "'Show queries generated internally by')]]//" \
+        "div[contains(@class,'toggle btn')]"
 
     editable_column_icon_xpath = "//div[contains(@class," \
                                  " 'editable-column-header-icon')]" \
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index d7e8fe3..32ea85f 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,34 @@ class PgadminPage:
             except Exception:
                 attempt += 1
         return click_status
+
+    def paste_values(self, el=None):
+        """
+        Function paste values in scratch pad
+        :param el:
+        """
+        actions = ActionChains(self.driver)
+        if el:
+            # Must step
+            el.click()
+            if self.driver.capabilities["platformName"] == 'mac':
+                # FF step
+                el.send_keys(Keys.COMMAND + "v")
+                # Chrome Step
+                actions.key_down(Keys.SHIFT)
+                actions.send_keys(Keys.INSERT)
+                actions.key_up(Keys.SHIFT)
+                actions.perform()
+            else:
+                el.send_keys(Keys.CONTROL + "v")
+
+    def wait_for_element_to_be_visible(self, driver, xpath, time_value=20):
+        """This will wait until an element is visible on page"""
+        element_located_status = False
+        try:
+            if WebDriverWait(driver, time_value).until(
+                    EC.visibility_of_element_located((By.XPATH, xpath))):
+                element_located_status = True
+        except TimeoutException:
+            element_located_status = False
+        return element_located_status
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc24..b292472 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -8,6 +8,8 @@
 ##########################################################################
 
 from __future__ import print_function
+
+import fileinput
 import traceback
 import os
 import sys
@@ -16,7 +18,17 @@ import psycopg2
 import sqlite3
 import shutil
 from functools import partial
+
+from selenium.webdriver.support.wait import WebDriverWait
 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
+from selenium.webdriver.support import expected_conditions as ec
 
 import config
 import regression
@@ -1216,3 +1228,242 @@ 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(selenoid_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 = get_selenium_grid_status_json(selenoid_url)
+        if selenoid_status:
+            available_browsers = selenoid_status["browsers"]
+            list_of_browsers = test_setup.config_data['selenoid_config'][
+                'browsers_list']
+
+            for browser in list_of_browsers:
+                if browser["name"].lower() in available_browsers.keys():
+                    versions = available_browsers[(browser["name"].lower())]
+                    if browser["version"] is None:
+                        print("Specified version of browser is None. Hence "
+                              "latest version of {0} available with selenoid "
+                              "server will be used.\n".format(browser["name"]))
+                        browser_list.append(browser)
+                    elif 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:
+        (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, title='pgAdmin 4', timeout=40):
+    """
+    Function launches urls in specified driver instance
+    :param driver_instance:browser instance
+    :param url:url to be launched
+    :param title:web-page tile on successful launch default is 'pgAdmin 4'
+    :param timeout:in seconds for getting specified title default is 20sec
+    :return:
+    """
+    count = timeout / 5
+    while count > 0:
+        try:
+            driver_instance.get(url)
+            wait = WebDriverWait(driver_instance, 10)
+            wait.until(ec.title_is(title))
+            break
+        except WebDriverException as e:
+            time.sleep(6)
+            count -= 1
+            if count == 0:
+                exception_msg = 'Web-page title did not match to {0}. ' \
+                                'Please check url {1} accessible on ' \
+                                'internet.'.format(title, url)
+                raise Exception(exception_msg)
+
+
+def get_remote_webdriver(hub_url, browser, browser_ver, test_name):
+    """
+    This functions returns remote web-driver instance created in selenoid
+    machine.
+    :param hub_url
+    :param browser: browser name
+    :param browser_ver: version for browser
+    :param test_name: test name
+    :return: remote web-driver instance for specified browser
+    """
+    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)
+    elif browser == 'chrome':
+        options = Options()
+        options.add_argument("--window-size=1280,1024")
+        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
+
+
+def get_parallel_sequential_module_list(module_list):
+    """
+    Functions segregate parallel & sequential modules
+    :param module_list: Complete list of modules
+    :return: parallel & sequential module lists
+    """
+    # 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']
+
+    #  list of tests can be executed in parallel
+    parallel_tests = list(module_list)
+    for module in 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,
+               module_list))
+
+    # return parallel & sequential lists
+    return parallel_tests, sequential_tests
+
+
+def get_browser_details(browser_info_dict, url):
+    """
+    Function extracts browser name & version from browser info dict
+    in test_config.json
+    :param browser_info_dict:
+    :return: browser name & version
+    """
+    browser_name = browser_info_dict["name"].lower()
+    browser_version = browser_info_dict["version"]
+    if browser_version is None:
+        selenoid_status = get_selenium_grid_status_json(url)
+        versions = selenoid_status["browsers"][browser_name]
+        browser_version = max(versions)
+    return browser_name, browser_version
+
+
+def print_test_summary(complete_module_list, parallel_testlist,
+                       sequential_tests_list, browser_name, browser_version):
+    """
+    Prints test summary about total, parallel, sequential, browser name,
+    browser version information
+    :param complete_module_list:
+    :param parallel_testlist:
+    :param sequential_tests_list:
+    :param browser_name:
+    :param browser_version:
+    """
+    print(
+        "=================================================================",
+        file=sys.stderr
+    )
+    print(
+        "Total Tests # {0}\nParallel Tests # {1}, "
+        "Sequential Tests # {2}".format(
+            len(complete_module_list), len(parallel_testlist),
+            len(sequential_tests_list)),
+        file=sys.stderr)
+    print("Browser: [Name:{0}, Version: {1}]".format(
+        browser_name.capitalize(), browser_version),
+        file=sys.stderr)
+    print(
+        "=================================================================\n",
+        file=sys.stderr
+    )
+
+
+def get_selenium_grid_status_json(selenoid_url):
+    """
+    Functions returns json response received from selenoid server
+    :param selenoid_url:
+    :return:
+    """
+    try:
+        selenoid_status = urllib.urlopen(
+            "http://" + re.split('/', (re.split('//', selenoid_url, 1)[1]))[
+                0] + "/status", timeout=10)
+        selenoid_status = json.load(selenoid_status)
+        if isinstance(selenoid_status, dict):
+            return selenoid_status
+    except Exception as e:
+        print("Unable to find Selenoid Status.Kindly check url passed -'{0}'".
+              format(selenoid_url))
+        return None
diff --git a/web/regression/requirements.txt b/web/regression/requirements.txt
index 1eb922b..2367f2a 100644
--- a/web/regression/requirements.txt
+++ b/web/regression/requirements.txt
@@ -21,7 +21,6 @@ fixtures==3.0.0
 linecache2==1.0.0
 pbr==3.1.1
 pycodestyle>=2.5.0
-pyperclip~=1.6.0
 python-mimeparse==1.6.0
 testscenarios==0.5.0
 testtools==2.3.0
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index 480aaad..8759be3 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 < (3, 4):
@@ -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,62 @@ 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 arguments['pkg'] == "feature_tests":
+            exclude_pkgs.extend(['resql'])
+
+        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
+            # 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()
 
     handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
     # Register cleanup function to cleanup on exit
@@ -319,6 +325,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,117 +413,213 @@ class StreamToLogger(object):
         pass
 
 
-if __name__ == '__main__':
-    # Failure detected?
-    failure = False
-    test_result = dict()
-    cov = None
+def execute_test(test_module_list_passed, server_passed, driver_passed):
+    """
+    Function executes actually test
+    :param test_module_list_passed:
+    :param server_passed:
+    :param driver_passed:
+    :return:
+    """
+    try:
+        print("\n=============Running the test cases for '%s' ============="
+              % server_passed['name'], file=sys.stderr)
+        # 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']
+        )
 
-    # Set signal handler for cleanup
-    signal_list = dir(signal)
-    required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
-    # Get the OS wise supported signals
-    supported_signal_list = [sig for sig in required_signal_list if
-                             sig in signal_list]
-    for sig in supported_signal_list:
-        signal.signal(getattr(signal, sig), sig_handler)
+        # 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 threading.current_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()
 
-    # Set basic logging configuration for log file
-    fh = logging.FileHandler(CURRENT_PATH + '/' +
-                             'regression.log', 'w', 'utf-8')
-    fh.setLevel(logging.DEBUG)
-    fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
+        # Delete test server
+        test_utils.delete_test_server(test_client)
+    except Exception as exc:
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        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:
+            driver_passed.quit()
+            time.sleep(20)
+
+        # Print info about completed tests
+        print(
+            "\n=============Completed the test cases for '%s'============="
+            % server_passed['name'], file=sys.stderr)
 
-    logger = logging.getLogger()
-    logger.addHandler(fh)
 
-    # Create logger to write log in the logger file as well as on console
-    stderr_logger = logging.getLogger('STDERR')
-    sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
-    args = vars(add_arguments())
-    # Get test module list
+def run_parallel_tests(url_client, servers_details, parallel_tests_lists,
+                       name_of_browser, version_of_browser, max_thread_count):
+    """
+    Function used to run tests in parallel
+    :param url_client:
+    :param servers_details:
+    :param parallel_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :param max_thread_count:
+    """
+    driver_object = None
     try:
-        test_module_list = get_test_modules(args)
-    except Exception as e:
-        print(str(e))
-        sys.exit(1)
-    # Login the test client
-    test_utils.login_tester_account(test_client)
+        # Thread list
+        threads_list = []
+        # Create thread for each server
+        for ser in servers_details:
+            # Logic to add new threads
+            while True:
+                # If active thread count <= max_thread_count, add new thread
+                if threading.activeCount() <= max_thread_count:
+                    # Get remote web-driver instance at server level
+                    driver_object = \
+                        test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        ser['name'])
+                    # Launch client url in browser
+                    test_utils.launch_url_in_browser(driver_object, url_client)
+
+                    # Add name for thread
+                    thread_name = "parallel_tests" + ser['name']
+
+                    # Start thread
+                    t = threading.Thread(target=execute_test, name=thread_name,
+                                         args=(parallel_tests_lists, ser,
+                                               driver_object))
+                    threads_list.append(t)
+                    t.start()
+                    time.sleep(3)
+                    break
+                # else sleep for 10 seconds
+                else:
+                    time.sleep(10)
+
+        # Start threads in parallel
+        for t in threads_list:
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+        # Clean driver object created
+        if driver_object is not None:
+            driver_object.quit()
 
-    servers_info = test_utils.get_config_data()
-    node_name = "all"
-    if args['pkg'] is not None:
-        node_name = args['pkg'].split('.')[-1]
-
-    # Start coverage
-    if test_utils.is_coverage_enabled(args):
-        cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
-        cov.start()
 
+def run_sequential_tests(url_client, servers_details, sequential_tests_lists,
+                         name_of_browser, version_of_browser):
+    """
+    Function is used to execute tests that needs to be run in sequential
+    manner.
+    :param url_client:
+    :param servers_details:
+    :param sequential_tests_lists:
+    :param name_of_browser:
+    :param version_of_browser:
+    :return:
+    """
+    driver_object = None
     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()
+        # Get remote web-driver instance
+        driver_object = test_utils.get_remote_webdriver(hub_url,
+                                                        name_of_browser,
+                                                        version_of_browser,
+                                                        "Sequential_Tests")
+
+        # Launch client url in browser
+        test_utils.launch_url_in_browser(driver_object, url_client)
+
+        # Add name for thread
+        thread_name = "sequential_tests"
+
+        # Start thread
+        for ser in servers_details:
+            t = threading.Thread(target=execute_test,
+                                 name=thread_name,
+                                 args=(sequential_tests_lists, ser,
+                                       driver_object))
+            t.start()
+            t.join()
+    except Exception as exc:
+        # Print exception stack trace
+        traceback.print_exc(file=sys.stderr)
+        print(str(exc))
+    finally:
+        # Clean driver object created
+        driver_object.quit()
 
+
+def print_test_results():
     print(
         "\n==============================================================="
         "=======",
@@ -543,6 +648,10 @@ if __name__ == '__main__':
         total_passed_cases = int(
             test_result[server_res][0]) - total_failed - total_skipped
 
+        if len(failed_cases) > 0:
+            global failure
+            failure = True
+
         print(
             "%s:\n\n\t%s test%s passed\n\t%s test%s failed%s%s"
             "\n\t%s test%s skipped%s%s\n" %
@@ -578,12 +687,162 @@ if __name__ == '__main__':
         file=sys.stderr
     )
 
+
+if __name__ == '__main__':
+    # Failure detected?
+    failure = False
+    test_result = dict()
+    cov = None
+
+    # Set signal handler for cleanup
+    signal_list = dir(signal)
+    required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
+    # Get the OS wise supported signals
+    supported_signal_list = [sig for sig in required_signal_list if
+                             sig in signal_list]
+    for sig in supported_signal_list:
+        signal.signal(getattr(signal, sig), sig_handler)
+
+    # Set basic logging configuration for log file
+    fh = logging.FileHandler(CURRENT_PATH + '/' +
+                             'regression.log', 'w', 'utf-8')
+    fh.setLevel(logging.DEBUG)
+    fh.setFormatter(logging.Formatter('[%(thread)d] ' +
+                                      config.FILE_LOG_FORMAT))
+
+    logger = logging.getLogger()
+    logger.addHandler(fh)
+
+    # Create logger to write log in the logger file as well as on console
+    stderr_logger = logging.getLogger('STDERR')
+    sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
+    args = vars(add_arguments())
+    # Get test module list
+    try:
+        test_module_list = get_test_modules(args)
+    except Exception as e:
+        print(str(e))
+        sys.exit(1)
+    # Login the test client
+    test_utils.login_tester_account(test_client)
+
+    servers_info = test_utils.get_config_data()
+    node_name = "all"
+    if args['pkg'] is not None:
+        node_name = args['pkg'].split('.')[-1]
+
+    # Start coverage
+    if test_utils.is_coverage_enabled(args):
+        cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
+        cov.start()
+
+    # 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 config dict
+        selenoid_config = test_setup.config_data['selenoid_config']
+
+        # Set DEFAULT_SERVER value
+        default_server = selenoid_config['pgAdmin_default_server']
+        os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"] = str(default_server)
+        config.DEFAULT_SERVER = str(default_server)
+
+        # Get hub url
+        hub_url = selenoid_config['selenoid_url']
+
+        # 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(hub_url)
+
+        # Execute tests if selenium-grid is up
+        if selenium_grid_status and len(list_of_browsers) > 0:
+            app_starter_local = None
+            # run across browsers
+            for browser_info in list_of_browsers:
+                try:
+                    # browser info
+                    browser_name, browser_version = \
+                        test_utils.get_browser_details(browser_info, hub_url)
+
+                    # tests lists can be executed in parallel & sequentially
+                    parallel_tests, sequential_tests = \
+                        test_utils.get_parallel_sequential_module_list(
+                            test_module_list)
+
+                    # Print test summary
+                    test_utils.print_test_summary(test_module_list,
+                                                  parallel_tests,
+                                                  sequential_tests,
+                                                  browser_name,
+                                                  browser_version)
+
+                    # Create app form source code
+                    app_starter_local = AppStarter(None, config)
+                    client_url = app_starter_local.start_app()
+
+                    # Running Parallel tests
+                    if len(parallel_tests) > 0:
+                        parallel_sessions = int(selenoid_config[
+                                                'max_parallel_sessions'])
+
+                        run_parallel_tests(client_url, servers_info,
+                                           parallel_tests, browser_name,
+                                           browser_version, parallel_sessions)
+
+                    # Wait till all threads started in parallel are finished
+                    while True:
+                        try:
+                            if threading.activeCount() <= 1:
+                                break
+                            else:
+                                time.sleep(10)
+                        except Exception as e:
+                            traceback.print_exc(file=sys.stderr)
+                            print(str(e))
+
+                    # Sequential Tests
+                    if len(sequential_tests) > 0:
+                        run_sequential_tests(client_url, servers_info,
+                                             sequential_tests, browser_name,
+                                             browser_version)
+
+                    # Clean up environment
+                    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()
+                # Pause before printing result in order not to mix output
+                time.sleep(5)
+                # Print note for completion of execution in a browser.
+                print(
+                    "\n============= Test execution with {0} is "
+                    "completed.=============".format(browser_name),
+                    file=sys.stderr)
+                print_test_results()
+        del os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"]
+    else:
+        try:
+            for server in servers_info:
+                thread = threading.Thread(target=execute_test, args=(
+                    test_module_list, server, driver))
+                thread.start()
+                thread.join()
+        except SystemExit:
+            if handle_cleanup:
+                handle_cleanup()
+        print_test_results()
+
     # Stop code coverage
     if test_utils.is_coverage_enabled(args):
         cov.stop()
         cov.save()
 
-    # # Print coverage only if coverage args given in command line
+    # Print coverage only if coverage args given in command line
     if test_utils.is_coverage_enabled(args):
         test_utils.print_and_store_coverage_report(cov)
 
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index af061b9..8207711 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,14 @@
 {
   "headless_chrome": false,
   "default_browser": "Chrome",
+  "selenoid_config":{
+        "pgAdmin_default_server":"IP address of machine where source code is going to be executed",
+        "max_parallel_sessions": "3",
+        "selenoid_url": ""http://<IP address of Selenoid Installed machine>:4444/wd/hub",
+        "browsers_list": [
+            {"name": "Chrome","version":null},
+            {"name": "Firefox","version":null} ]
+  },
   "pgAdmin4_login_credentials": {
     "new_password": "NEWPASSWORD",
     "login_password": "PASSWORD",


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

* Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
@ 2020-05-11 06:42  Akshay Joshi <[email protected]>
  parent: Yogesh Mahajan <[email protected]>
  0 siblings, 0 replies; 14+ messages in thread

From: Akshay Joshi @ 2020-05-11 06:42 UTC (permalink / raw)
  To: Yogesh Mahajan <[email protected]>; +Cc: pgadmin-hackers; Shubham Agarwal <[email protected]>; navnath gadakh <[email protected]>

Thanks, patch applied.

On Fri, May 8, 2020 at 3:13 PM Yogesh Mahajan <
[email protected]> wrote:

> Hi Akshay,
>
> Please find the attached rebase patch. Looks like there were some commits
> after my today's git pull.
>
> Thanks,
> Yogesh Mahajan
> QA - Team
> EnterpriseDB Corporation
>
> Phone: +91-9741705709
>
>
> On Fri, May 8, 2020 at 2:35 PM Akshay Joshi <[email protected]>
> wrote:
>
>> Hi Yogesh
>>
>> Unable to apply the patch on the latest code. Please rebase and send it
>> again.
>>
>> On Fri, May 8, 2020 at 2:05 PM Yogesh Mahajan <
>> [email protected]> wrote:
>>
>>> Attaching patch
>>>
>>> Thanks,
>>> Yogesh Mahajan
>>> QA - Team
>>> EnterpriseDB Corporation
>>>
>>> Phone: +91-9741705709
>>>
>>>
>>> On Fri, May 8, 2020 at 1:57 PM Akshay Joshi <
>>> [email protected]> wrote:
>>>
>>>> Hi Yogesh
>>>>
>>>> You forgot to attach patch :)
>>>>
>>>> On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi,
>>>>>
>>>>> Please find the updates patch with above review comments.
>>>>> Patch adds below functionality to existing framework -
>>>>> 1.Ability to run features in parallel using solenoid(selenium +
>>>>> docker).
>>>>>      a.Selenoid setup steps are included in /regression/README
>>>>>      b.'python runtests.py  --pkg feature_tests --parallel' will
>>>>> trigger parallel feature tests.
>>>>> 2.Removes dependency for pyperclip python module.
>>>>> 3.New script in ../tools/update_selenoid_browsers.py updates browser
>>>>> images at local selneoid server setup.
>>>>>
>>>>>
>>>>> Thanks,
>>>>> Yogesh Mahajan
>>>>> QA - Team
>>>>> EnterpriseDB Corporation
>>>>>
>>>>> Phone: +91-9741705709
>>>>>
>>>>>
>>>>> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi Yogesh
>>>>>>
>>>>>> Following are the review comments:
>>>>>>
>>>>>>    - *pyjq* package is not required as we used it only in one place.
>>>>>>    A result is a normal dictionary that can be easily looped through.
>>>>>>    - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>>>>>    config.py, we have already removed that.
>>>>>>    - Remove yarn.lock file.
>>>>>>    - Remove *pyperclip *from the regression/requirements.txt as we
>>>>>>    are not using it.
>>>>>>    - Please mentioned the value of *pgAdmin_default_server *should
>>>>>>    not be '*127.0.0.1*' in the README file even though everything
>>>>>>    runs on the same machine.
>>>>>>    - Please mentioned that if we set the value of the browser
>>>>>>    version is *null* then selenoid will take the latest available
>>>>>>    browser version.
>>>>>>    - Got the below error if selenoid_url is not provided:
>>>>>>       - list index out of range
>>>>>>       Unable to find Selenoid Status
>>>>>>
>>>>>> *test_config.json.in <http://test_config.json.in>*:
>>>>>>
>>>>>>    - "selenoid_info" should be renamed to "selenoid_config". Proper
>>>>>>    alignment is required.
>>>>>>    - "cross_Browsers" should be renamed to "cross_browsers" or
>>>>>>    "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>>>>>    browsers with version set to null so that it will run on the latest browser
>>>>>>    version.
>>>>>>    - "selenoid_url": "Selenoid Url" should be changed
>>>>>>    to "selenoid_url": "http://<IP address of Selenoid Installed
>>>>>>    machine>:4444/wd/hub".
>>>>>>
>>>>>> If you change the names in test_config.json.in then please update
>>>>>> the same in README as well.
>>>>>>
>>>>>>
>>>>>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Akshay,
>>>>>>>
>>>>>>> Please find the updated patch.
>>>>>>>
>>>>>>> Thanks,
>>>>>>> Yogesh Mahajan
>>>>>>> QA - Team
>>>>>>> EnterpriseDB Corporation
>>>>>>>
>>>>>>> Phone: +91-9741705709
>>>>>>>
>>>>>>>
>>>>>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi Yogesh
>>>>>>>>
>>>>>>>> The patch is not applied to the master branch. Can you please
>>>>>>>> rebase and send the patch again.
>>>>>>>>
>>>>>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> Please find updated patch modified according to review comments -
>>>>>>>>> Patch implements below things -
>>>>>>>>> 1.Enable the current framework to provide option to execute
>>>>>>>>> Feature tests in parallel  on selenium grid set up.
>>>>>>>>>    - Addition of new switch to start parallel features tests.
>>>>>>>>>    - New parameters with respect to selenoid in
>>>>>>>>> test_config.json.in
>>>>>>>>>    - Addition of new script to check solenoid updates.
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Thanks,
>>>>>>>>> Yogesh Mahajan
>>>>>>>>> QA - Team
>>>>>>>>> EnterpriseDB Corporation
>>>>>>>>>
>>>>>>>>> Phone: +91-9741705709
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi Yogesh,
>>>>>>>>>> Below are the review comments-
>>>>>>>>>>
>>>>>>>>>> 1. runtests.py
>>>>>>>>>>     a. The exception traceback logic at line number 653 in
>>>>>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>>>>>> but there is much more code in that block which can throw some
>>>>>>>>>> exception.
>>>>>>>>>> b. line number 447 -> The drop_database function will only try
>>>>>>>>>> to drop the database with the name which is newly created
>>>>>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>>>>>> ‘acceptance_test_db'.
>>>>>>>>>>   c. line 584 - Why we are including resql test case execution
>>>>>>>>>> in GUI execution logic.
>>>>>>>>>>     d. Change the function name run_test as script name is also
>>>>>>>>>> runtests.py
>>>>>>>>>>
>>>>>>>>>> 2. test_utils.py
>>>>>>>>>>     a. Remove the headless chrome code from
>>>>>>>>>> get_remote_webdriver() in test_utils.py since we are using
>>>>>>>>>> solenoid and it is not required anymore.
>>>>>>>>>>     b. Create separate functions to instantiate the firefox
>>>>>>>>>> driver and chrome driver logic since the same code is used in multiple
>>>>>>>>>> files.
>>>>>>>>>> c. launch_url_in_browser() -> you can simplify the definition of
>>>>>>>>>> the function like:
>>>>>>>>>>     retry = 60
>>>>>>>>>>         *while *retry > 0:
>>>>>>>>>>             try:
>>>>>>>>>>                 driver.get(url)
>>>>>>>>>>             except WebDriverException:
>>>>>>>>>>                  retry -= 1
>>>>>>>>>> 3. Execution logs are not printing as per the logic some time, I
>>>>>>>>>> ran the suite for two servers and attached are the execution logs.
>>>>>>>>>> 4. Readme -
>>>>>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>>>>>> clear.
>>>>>>>>>> Revisit the readme and write the missing steps.
>>>>>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>>>>>> Create the function to avoid duplicate code. The code for pasting
>>>>>>>>>> the values is repeating 8 times in the test code.
>>>>>>>>>> 6. Provide the valid docstring in newly introduced functions and
>>>>>>>>>> also valid comments while calling it. for ex.- _update_preference()
>>>>>>>>>> function is
>>>>>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>>>>>> 7. test_index_constraint_add test case is failing due to the
>>>>>>>>>> latest change, please merge and update this test case
>>>>>>>>>>
>>>>>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi,
>>>>>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>>>>>> Someone else please review it.
>>>>>>>>>>>
>>>>>>>>>>> Thanks!
>>>>>>>>>>>
>>>>>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Hi Navnath
>>>>>>>>>>>>
>>>>>>>>>>>> Can you please review it?
>>>>>>>>>>>>
>>>>>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> 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
>>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> --
>>>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>>>
>>>>>>>>>>>> *Sr. Software Architect*
>>>>>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> Regards,
>>>>>>>>>>> Navnath Gadakh
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> Thanks & Regards,
>>>>>>>>>> Shubham Agarwal
>>>>>>>>>> EnterpriseDB Corporation
>>>>>>>>>>
>>>>>>>>>> The Postgres Database Company
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> *Thanks & Regards*
>>>>>>>> *Akshay Joshi*
>>>>>>>>
>>>>>>>> *Sr. Software Architect*
>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>>
>>>>>> *Sr. Software Architect*
>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>
>>>> --
>>>> *Thanks & Regards*
>>>> *Akshay Joshi*
>>>>
>>>> *Sr. Software Architect*
>>>> *EnterpriseDB Software India Private Limited*
>>>> *Mobile: +91 976-788-8246*
>>>>
>>>
>>
>> --
>> *Thanks & Regards*
>> *Akshay Joshi*
>>
>> *Sr. Software Architect*
>> *EnterpriseDB Software India Private Limited*
>> *Mobile: +91 976-788-8246*
>>
>

-- 
*Thanks & Regards*
*Akshay Joshi*

*Sr. Software Architect*
*EnterpriseDB Software India Private Limited*
*Mobile: +91 976-788-8246*


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


end of thread, other threads:[~2020-05-11 06:42 UTC | newest]

Thread overview: 14+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-04-13 09:10 [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework Yogesh Mahajan <[email protected]>
2020-04-13 09:19 ` Akshay Joshi <[email protected]>
2020-04-16 09:10   ` navnath gadakh <[email protected]>
2020-04-21 07:48     ` Shubham Agarwal <[email protected]>
2020-05-01 06:57       ` Yogesh Mahajan <[email protected]>
2020-05-04 09:21         ` Akshay Joshi <[email protected]>
2020-05-04 10:57           ` Yogesh Mahajan <[email protected]>
2020-05-05 10:28             ` Akshay Joshi <[email protected]>
2020-05-08 08:05               ` Yogesh Mahajan <[email protected]>
2020-05-08 08:27                 ` Akshay Joshi <[email protected]>
2020-05-08 08:35                   ` Yogesh Mahajan <[email protected]>
2020-05-08 09:05                     ` Akshay Joshi <[email protected]>
2020-05-08 09:42                       ` Yogesh Mahajan <[email protected]>
2020-05-11 06:42                         ` Akshay Joshi <[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