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/.sql' + self.XSS_FILE = '/tmp/.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, - '<img src=x onmouseover=alert("1")>.sql', - 'File manager' + '<img src=x ' + self.server['name'][:13] + + '=alert("1")>.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, '<script>alert(1)</script>', "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://:4444/status + - Should show json with browsers details + http://: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://: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" -color-name@1.1.3: +color-name@1.1.3, 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" -follow-redirects@1.5.10: +follow-redirects@1.5.10, 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 @@ http-cache-semantics@3.8.1: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== -http-errors@1.7.2: +http-errors@1.7.2, 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 @@ http-errors@1.7.2: 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" -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, 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, inherits@2.0.4, 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 @@ inherits@2.0.3: 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" + negotiator@0.6.2: 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 @@ rimraf@2.6.3: 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" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, 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 @@ serve-static@1.14.1: 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 @@ useragent@2.3.0: 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==