public inbox for [email protected]  
help / color / mirror / Atom feed
From: Yogesh Mahajan <[email protected]>
To: Shubham Agarwal <[email protected]>
To: pgadmin-hackers <[email protected]>
Cc: navnath gadakh <[email protected]>
Cc: Akshay Joshi <[email protected]>
Subject: Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
Date: Fri, 1 May 2020 12:27:44 +0530
Message-ID: <CAMa=N=MD1ujhKigk9guTeVofhBrHkAsaNWH+z1=oRQJJQ8X4vQ@mail.gmail.com> (raw)
In-Reply-To: <CAKbCA9Rec2gWj5b90s3Vr2tKByQrAU_31-2Qn+ORdU0Nmu9hMQ@mail.gmail.com>
References: <CAMa=N=NokBUU=gRybJzctGKu4N-H1GWu_peamTHqqTNm+v9CPw@mail.gmail.com>
	<CANxoLDdksuEaXAg0TfLOz17Wk-pvYB6zKo5kZVxwhp4C2z0h3A@mail.gmail.com>
	<CAOAJCYoNnhy7U1wbpDGFU_f-+YNSY-PJvzNivnxmy4AVV0M6MQ@mail.gmail.com>
	<CAKbCA9Rec2gWj5b90s3Vr2tKByQrAU_31-2Qn+ORdU0Nmu9hMQ@mail.gmail.com>

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


view thread (14+ messages)  latest in thread

reply

Reply instructions:

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

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

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

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

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