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