public inbox for [email protected]
help / color / mirror / Atom feedFrom: Yogesh Mahajan <[email protected]>
To: Akshay Joshi <[email protected]>
To: pgadmin-hackers <[email protected]>
Cc: Shubham Agarwal <[email protected]>
Cc: navnath gadakh <[email protected]>
Subject: Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
Date: Fri, 8 May 2020 14:05:18 +0530
Message-ID: <CAMa=N=N+JeysXRvC40_c2MnT3FDk14mknU-D-eDbtd_d_29fmw@mail.gmail.com> (raw)
In-Reply-To: <CANxoLDcMnU8zC+URh-vox-HLoowe+8vWxPR+CoR2n58QOS6OxA@mail.gmail.com>
References: <CAMa=N=NokBUU=gRybJzctGKu4N-H1GWu_peamTHqqTNm+v9CPw@mail.gmail.com>
<CANxoLDdksuEaXAg0TfLOz17Wk-pvYB6zKo5kZVxwhp4C2z0h3A@mail.gmail.com>
<CAOAJCYoNnhy7U1wbpDGFU_f-+YNSY-PJvzNivnxmy4AVV0M6MQ@mail.gmail.com>
<CAKbCA9Rec2gWj5b90s3Vr2tKByQrAU_31-2Qn+ORdU0Nmu9hMQ@mail.gmail.com>
<CAMa=N=MD1ujhKigk9guTeVofhBrHkAsaNWH+z1=oRQJJQ8X4vQ@mail.gmail.com>
<CANxoLDenKjctO90T1M=MDEhpPXw4jYSjSRC+6y+DsZR3oK5bNg@mail.gmail.com>
<CAMa=N=PDAwbROaC1js1iy4md+rzfawF0CCupyjCjPWSisOwtUQ@mail.gmail.com>
<CANxoLDc=4oSXgw926YVXX3BbmVXCOr0=ZNF93cn1rJ=noWWowg@mail.gmail.com>
<CAMa=N=OVDhWqeB=R4M7Qw_52uiVvSYX1QUgrNC-FehrOyaKPvA@mail.gmail.com>
<CANxoLDcMnU8zC+URh-vox-HLoowe+8vWxPR+CoR2n58QOS6OxA@mail.gmail.com>
Attaching patch
Thanks,
Yogesh Mahajan
QA - Team
EnterpriseDB Corporation
Phone: +91-9741705709
On Fri, May 8, 2020 at 1:57 PM Akshay Joshi <[email protected]>
wrote:
> Hi Yogesh
>
> You forgot to attach patch :)
>
> On Fri, May 8, 2020 at 1:35 PM Yogesh Mahajan <
> [email protected]> wrote:
>
>> Hi,
>>
>> Please find the updates patch with above review comments.
>> Patch adds below functionality to existing framework -
>> 1.Ability to run features in parallel using solenoid(selenium + docker).
>> a.Selenoid setup steps are included in /regression/README
>> b.'python runtests.py --pkg feature_tests --parallel' will trigger
>> parallel feature tests.
>> 2.Removes dependency for pyperclip python module.
>> 3.New script in ../tools/update_selenoid_browsers.py updates browser
>> images at local selneoid server setup.
>>
>>
>> Thanks,
>> Yogesh Mahajan
>> QA - Team
>> EnterpriseDB Corporation
>>
>> Phone: +91-9741705709
>>
>>
>> On Tue, May 5, 2020 at 3:58 PM Akshay Joshi <
>> [email protected]> wrote:
>>
>>> Hi Yogesh
>>>
>>> Following are the review comments:
>>>
>>> - *pyjq* package is not required as we used it only in one place. A
>>> result is a normal dictionary that can be easily looped through.
>>> - Remove "*if (SUPPORT_SSH_TUNNEL is True and ...*" logic from
>>> config.py, we have already removed that.
>>> - Remove yarn.lock file.
>>> - Remove *pyperclip *from the regression/requirements.txt as we are
>>> not using it.
>>> - Please mentioned the value of *pgAdmin_default_server *should not
>>> be '*127.0.0.1*' in the README file even though everything runs on
>>> the same machine.
>>> - Please mentioned that if we set the value of the browser version
>>> is *null* then selenoid will take the latest available browser
>>> version.
>>> - Got the below error if selenoid_url is not provided:
>>> - list index out of range
>>> Unable to find Selenoid Status
>>>
>>> *test_config.json.in <http://test_config.json.in>*:
>>>
>>> - "selenoid_info" should be renamed to "selenoid_config". Proper
>>> alignment is required.
>>> - "cross_Browsers" should be renamed to "cross_browsers" or
>>> "run_on_browsers" or "run_tests_on_browsers". Provide entries for supported
>>> browsers with version set to null so that it will run on the latest browser
>>> version.
>>> - "selenoid_url": "Selenoid Url" should be changed
>>> to "selenoid_url": "http://<IP address of Selenoid Installed
>>> machine>:4444/wd/hub".
>>>
>>> If you change the names in test_config.json.in then please update the
>>> same in README as well.
>>>
>>>
>>> On Mon, May 4, 2020 at 4:27 PM Yogesh Mahajan <
>>> [email protected]> wrote:
>>>
>>>> Hi Akshay,
>>>>
>>>> Please find the updated patch.
>>>>
>>>> Thanks,
>>>> Yogesh Mahajan
>>>> QA - Team
>>>> EnterpriseDB Corporation
>>>>
>>>> Phone: +91-9741705709
>>>>
>>>>
>>>> On Mon, May 4, 2020 at 2:51 PM Akshay Joshi <
>>>> [email protected]> wrote:
>>>>
>>>>> Hi Yogesh
>>>>>
>>>>> The patch is not applied to the master branch. Can you please rebase
>>>>> and send the patch again.
>>>>>
>>>>> On Fri, May 1, 2020 at 12:28 PM Yogesh Mahajan <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Hi,
>>>>>>
>>>>>> Please find updated patch modified according to review comments -
>>>>>> Patch implements below things -
>>>>>> 1.Enable the current framework to provide option to execute Feature
>>>>>> tests in parallel on selenium grid set up.
>>>>>> - Addition of new switch to start parallel features tests.
>>>>>> - New parameters with respect to selenoid in test_config.json.in
>>>>>> - Addition of new script to check solenoid updates.
>>>>>>
>>>>>>
>>>>>>
>>>>>> Thanks,
>>>>>> Yogesh Mahajan
>>>>>> QA - Team
>>>>>> EnterpriseDB Corporation
>>>>>>
>>>>>> Phone: +91-9741705709
>>>>>>
>>>>>>
>>>>>> On Tue, Apr 21, 2020 at 1:18 PM Shubham Agarwal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> Hi Yogesh,
>>>>>>> Below are the review comments-
>>>>>>>
>>>>>>> 1. runtests.py
>>>>>>> a. The exception traceback logic at line number 653 in
>>>>>>> runtests.py is not correct since it is particular to the thread
>>>>>>> but there is much more code in that block which can throw some
>>>>>>> exception.
>>>>>>> b. line number 447 -> The drop_database function will only try to
>>>>>>> drop the database with the name which is newly created
>>>>>>> at 431 line number, its probability is 1% instead of this you can
>>>>>>> write a logic so that it will drop all the database which starts with name
>>>>>>> ‘acceptance_test_db'.
>>>>>>> c. line 584 - Why we are including resql test case execution in
>>>>>>> GUI execution logic.
>>>>>>> d. Change the function name run_test as script name is also
>>>>>>> runtests.py
>>>>>>>
>>>>>>> 2. test_utils.py
>>>>>>> a. Remove the headless chrome code from get_remote_webdriver()
>>>>>>> in test_utils.py since we are using solenoid and it is not required
>>>>>>> anymore.
>>>>>>> b. Create separate functions to instantiate the firefox driver
>>>>>>> and chrome driver logic since the same code is used in multiple files.
>>>>>>> c. launch_url_in_browser() -> you can simplify the definition of
>>>>>>> the function like:
>>>>>>> retry = 60
>>>>>>> *while *retry > 0:
>>>>>>> try:
>>>>>>> driver.get(url)
>>>>>>> except WebDriverException:
>>>>>>> retry -= 1
>>>>>>> 3. Execution logs are not printing as per the logic some time, I ran
>>>>>>> the suite for two servers and attached are the execution logs.
>>>>>>> 4. Readme -
>>>>>>> Please provide the Valid selenoid URL to be provided in the
>>>>>>> test_config.json, with all the steps mentioned in the readme it is not
>>>>>>> clear.
>>>>>>> Revisit the readme and write the missing steps.
>>>>>>> 5. copy_selected_query_results_feature_tests.py-
>>>>>>> Create the function to avoid duplicate code. The code for pasting
>>>>>>> the values is repeating 8 times in the test code.
>>>>>>> 6. Provide the valid docstring in newly introduced functions and
>>>>>>> also valid comments while calling it. for ex.- _update_preference()
>>>>>>> function is
>>>>>>> introduced in pg_utilities_backup_restrore_test.py but from the
>>>>>>> function name, it is not clear what preferences are going to update in it.
>>>>>>> 7. test_index_constraint_add test case is failing due to the latest
>>>>>>> change, please merge and update this test case
>>>>>>>
>>>>>>> On Thu, Apr 16, 2020 at 2:41 PM navnath gadakh <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Hi,
>>>>>>>> I think I am not the right person to review this patch now as I
>>>>>>>> already reviewed this code offline in the last week. I know the approached
>>>>>>>> Yogesh has followed, also given some review comments on it.
>>>>>>>> Someone else please review it.
>>>>>>>>
>>>>>>>> Thanks!
>>>>>>>>
>>>>>>>> On Mon, Apr 13, 2020 at 2:49 PM Akshay Joshi <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi Navnath
>>>>>>>>>
>>>>>>>>> Can you please review it?
>>>>>>>>>
>>>>>>>>> On Mon, Apr 13, 2020 at 2:40 PM Yogesh Mahajan <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Hi,
>>>>>>>>>>
>>>>>>>>>> Please find the attached patch for running *features tests*
>>>>>>>>>> using solenoid(selenium grid + docker).
>>>>>>>>>> KIndly review.
>>>>>>>>>> To sun feature tests in parallel, required prerequisites can be
>>>>>>>>>> checked in '~/web/regression/README' file.
>>>>>>>>>> Also detailed instructions are added in the same file.
>>>>>>>>>> After applying the patch, any existing process for execution of
>>>>>>>>>> API/Features tests remains the same.
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Thanks,
>>>>>>>>>> Yogesh Mahajan
>>>>>>>>>> QA - Team
>>>>>>>>>> EnterpriseDB Corporation
>>>>>>>>>>
>>>>>>>>>> Phone: +91-9741705709
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> *Thanks & Regards*
>>>>>>>>> *Akshay Joshi*
>>>>>>>>>
>>>>>>>>> *Sr. Software Architect*
>>>>>>>>> *EnterpriseDB Software India Private Limited*
>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Regards,
>>>>>>>> Navnath Gadakh
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks & Regards,
>>>>>>> Shubham Agarwal
>>>>>>> EnterpriseDB Corporation
>>>>>>>
>>>>>>> The Postgres Database Company
>>>>>>>
>>>>>>
>>>>>
>>>>> --
>>>>> *Thanks & Regards*
>>>>> *Akshay Joshi*
>>>>>
>>>>> *Sr. Software Architect*
>>>>> *EnterpriseDB Software India Private Limited*
>>>>> *Mobile: +91 976-788-8246*
>>>>>
>>>>
>>>
>>> --
>>> *Thanks & Regards*
>>> *Akshay Joshi*
>>>
>>> *Sr. Software Architect*
>>> *EnterpriseDB Software India Private Limited*
>>> *Mobile: +91 976-788-8246*
>>>
>>
>
> --
> *Thanks & Regards*
> *Akshay Joshi*
>
> *Sr. Software Architect*
> *EnterpriseDB Software India Private Limited*
> *Mobile: +91 976-788-8246*
>
Attachments:
[application/octet-stream] Selenium_Grid_Implementation_ver4.0.patch (110.8K, 3-Selenium_Grid_Implementation_ver4.0.patch)
download | inline diff:
diff --git a/tools/update_selenoid_browsers.py b/tools/update_selenoid_browsers.py
new file mode 100644
index 0000000..c067621
--- /dev/null
+++ b/tools/update_selenoid_browsers.py
@@ -0,0 +1,277 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2020, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+# #########################################################################
+# Updates browser images(selenoid-docker) depending on arguments passed while
+# running this script.
+# e.g. --chrome /usr/bin/google-chrome --firefox /usr/bin/firefox
+# Access details about switches using help
+# e.g. --help
+
+import argparse
+import os
+import subprocess
+import sys
+import traceback
+import requests
+import json
+
+
+def read_command_line():
+ """Read the command line arguments.
+ Returns:
+ ArgumentParser: The parsed arguments object
+
+ """
+ parser = argparse.ArgumentParser(
+ description='Get latest browser images(chrome & firefox) for selenoid.'
+ 'e.g. - --chrome /usr/bin/google-chrome --firefox '
+ '/usr/bin/firefox')
+ parser.add_argument("--chrome", metavar="CHROME",
+ help="the Chrome executable path")
+ parser.add_argument("--firefox", metavar="FIREFOX",
+ help="the firefox executable path")
+ args_val = parser.parse_args()
+ return args_val
+
+
+def get_browser_version(browser_name, executable_path):
+ """
+ Function returns browser version for specified browser using executable
+ path passed in arguments.
+ :param browser_name:
+ :param executable_path: e.g. /usr/bin/firefox
+ :return: browser version
+ """
+ # On Linux/Mac we run the browser executable with the --version flag,
+ # then parse the output.
+ browser_version_val = None
+ try:
+ result = subprocess.Popen([executable_path, '--version'],
+ stdout=subprocess.PIPE)
+ except FileNotFoundError:
+ print('The specified browser executable could not be found.')
+ sys.exit(1)
+
+ version_str = result.stdout.read().decode("utf-8")
+
+ if browser_name.lower() == "chrome":
+ # Check for 'Chrom' not 'Chrome' in case the user is using Chromium.
+ if "Chrom" not in version_str:
+ print('The specified Chrome executable output an unexpected '
+ 'version string: {}.'.format(version_str))
+ sys.exit(1)
+ # On some linux distro `chrome--version` gives output like
+ # 'Google Chrome 80.0.3987.132 unknown\n'
+ # so we need to check and remove the unknown string from the version
+ if version_str.endswith("unknown\n"):
+ version_str = version_str.strip("unknown\n").strip()
+
+ chrome_version = '.'.join(version_str.split()[-1].split('.')[:-2])
+
+ # Make sure browser version has only 1 decimal point
+ if chrome_version.count('.') != 1:
+ print('The specified Chrome executable output an unexpected '
+ 'version string: {}.'.format(version_str))
+ sys.exit(1)
+ browser_version_val = chrome_version
+ elif browser_name.lower() == "firefox":
+ if "Firefox" not in version_str:
+ print('The specified Firefox executable output an unexpected '
+ 'version string: {}.'.format(version_str))
+ sys.exit(1)
+
+ # Some time firefox --version gives output like
+ # 'Running without a11y support!
+ # Mozilla Firefox 68.7.0esr'
+ # Other output - [root@localhost local]# /usr/bin/firefox --version
+ # Mozilla Firefox 75.0
+ if 'esr' in version_str:
+ firefox_version = '.'.join(
+ version_str.split()[-1].split('.')[:-2]) + '.0'
+ else:
+ firefox_version = '.'.join(
+ version_str.split()[-1].split('.')[:-1]) + '.0'
+
+ # Make sure browser version has only 1 decimal point
+ if firefox_version.count('.') != 1:
+ print('The specified Chrome executable output an unexpected '
+ 'version string: {}.'.format(version_str))
+ sys.exit(1)
+ browser_version_val = firefox_version
+ else:
+ print("{0} is not recognised ".format(browser_name))
+ sys.exit(1)
+ return browser_version_val
+
+
+def check_and_download_vnc_browser_image(browser_name, browser_version):
+ """
+ Function checks presence for vnc images for passed browser
+ at docker.io/selenoid/ registry
+ :param browser_name:
+ :param browser_version:
+ :return:true if browser image is available & downloaded else false
+ """
+ res = requests.get(
+ 'https://registry.hub.docker.com/v2/repositories/selenoid/vnc_' +
+ browser_name + '/tags/')
+ res = res.json()
+ version_tag = []
+ if len(res['results']) > 0:
+ for result in res['results']:
+ if 'name' in result:
+ version_tag.append(result['name'])
+ vnc_image_available = False
+ image_name = 'vnc_' + browser_name + ':' + browser_version
+
+ for idx, tag in enumerate(version_tag):
+ if browser_version == tag:
+ command = 'docker pull selenoid/vnc_' + browser_name + ':' \
+ + browser_version
+ print(' VNC image is available & downloading now... {0}'.format(
+ command))
+ try:
+ subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+ vnc_image_available = True
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ print(
+ '{0}} Image found but could not download.'.format(command))
+ sys.exit(1)
+ break
+ elif idx == len(version_tag):
+ print("{0} Image is not available.".format(image_name))
+ vnc_image_available = False
+ else:
+ pass
+ return vnc_image_available
+
+
+def reload_selenoid_config():
+ """
+ Function runs command to refresh selenoid configuration
+ :return: true if command execution for selenoid reload is successful
+ else false
+ """
+ command = 'docker kill -s HUP selenoid'
+ reload_successful = False
+ try:
+ subprocess.call([command], shell=True, stdout=subprocess.PIPE)
+ print(" Selenoid Configuration is reloaded.")
+ reload_successful = True
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ print('Error while reloading selenoid configuration.')
+ sys.exit(1)
+ return reload_successful
+
+
+def edit_browsers_json(browser_name, browser_version):
+ """
+ Function edits browsers.json which is used by selenoid to
+ load browser configuration.
+ Default path for this file is
+ "user_home_dir + '/.aerokube/selenoid/browsers.json'"
+ Currently this is hardcoded, might need to modify
+ if we want to pass customize browsers.json
+ :param browser_name:
+ :param browser_version:
+ :return:
+ """
+ file_edited = True
+ # Read existing browsers.json
+ json_file = open(file_path, 'r')
+ existing_data = json.load(json_file)
+ updated_data = None
+
+ # Update data for new browser images
+ if browser_name.lower() == 'chrome':
+ version_data = existing_data['chrome']['versions']
+ if browser_version in version_data.keys():
+ print(" {0}:{1} is already updated in browsers.json.".format(
+ browser_name, browser_version))
+ file_edited = True
+ else:
+ data_to_insert = dict(
+ {browser_version: {
+ 'image': 'selenoid/vnc_chrome:' + browser_version,
+ 'port': '4444', 'path': '/'}})
+ (existing_data['chrome']['versions']).update(data_to_insert)
+ updated_data = existing_data
+ print(updated_data)
+
+ elif browser_name.lower() == 'firefox':
+ version_data = existing_data['firefox']['versions']
+ if browser_version in version_data.keys():
+ print(" {0}:{1} is already updated in browsers.json.".format(
+ browser_name, browser_version))
+ file_edited = True
+ else:
+ data_to_insert = dict(
+ {browser_version: {
+ 'image': 'selenoid/vnc_firefox:' + browser_version,
+ 'port': '4444', 'path': '/'}})
+ (existing_data['firefox']['versions']).update(data_to_insert)
+ updated_data = existing_data
+ else:
+ print("Browser version not matched")
+ file_edited = False
+
+ # Write updated data in browsers.json
+ if updated_data is not None:
+ json_file = open(file_path, 'w')
+ json.dump(updated_data, json_file)
+ print(" 'browsers.json' is updated for {0} {1}".format(
+ browser_name, browser_version))
+
+ file_edited = True
+ return file_edited
+
+
+# Main Program starts here
+# Read command line arguments & get list of browser_name, executable path.
+args = vars(read_command_line())
+
+# Get path path for browsers.json
+user_home_dir = os.getenv("HOME")
+file_path = user_home_dir + '/.aerokube/selenoid/browsers.json'
+print("***** Updating '{0}' for new browser versions.*****".format(file_path))
+
+# Iterate over arguments passed
+for browser, executable_path in args.items():
+ if executable_path is not None:
+ # Get browser name
+ browser_name = browser
+ # Get browser version
+ browser_version = get_browser_version(browser, executable_path)
+ print(
+ " Browser version for {0} is {1} in current executable path ".
+ format(browser_name, browser_version))
+
+ # Download vnc browser image.
+ download_new_image = check_and_download_vnc_browser_image(
+ browser_name, browser_version)
+
+ # If browser vnc image is available, then edit browsers.json
+ if download_new_image:
+ if edit_browsers_json(browser_name, browser_version):
+ print(
+ " File 'browsers.json' is updated for {0} - {1} \n".format(
+ browser_name, browser_version))
+ else:
+ print(
+ " File 'browsers.json' can NOT be updated for {0} - {1} \n"
+ .format(browser_name, browser_version))
+ else:
+ print(" Browser image is not available for {0}, {1}".format(
+ browser_name, browser_version))
+
+# Reload selenoid configuration
+if reload_selenoid_config():
+ print(
+ "***** Updated '{0}' for new browser versions.*****".format(file_path))
diff --git a/web/config.py b/web/config.py
index 7f74a7d..d05aa04 100644
--- a/web/config.py
+++ b/web/config.py
@@ -562,6 +562,10 @@ try:
except ImportError:
pass
+# Override DEFAULT_SERVE value from environment variable.
+if 'PGADMIN_CONFIG_DEFAULT_SERVER' in os.environ:
+ DEFAULT_SERVER = os.environ['PGADMIN_CONFIG_DEFAULT_SERVER']
+
# Disable USER_INACTIVITY_TIMEOUT when SERVER_MODE=False
if not SERVER_MODE:
USER_INACTIVITY_TIMEOUT = 0
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
index f127063..88526ca 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py
@@ -44,31 +44,30 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
dict(url='/browser/unique_constraint/obj/', data=unique_key_data))
]
- @classmethod
- def setUpClass(cls):
- cls.db_name = parent_node_dict["database"][-1]["db_name"]
+ def setUp(self):
+ self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
- cls.server_id = schema_info["server_id"]
- cls.db_id = schema_info["db_id"]
- db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
- cls.server_id, cls.db_id)
+ self.server_id = schema_info["server_id"]
+ self.db_id = schema_info["db_id"]
+ db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+ self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to add a "
"index constraint(primary key or unique key).")
- cls.schema_id = schema_info["schema_id"]
- cls.schema_name = schema_info["schema_name"]
- schema_response = schema_utils.verify_schemas(cls.server,
- cls.db_name,
- cls.schema_name)
+ self.schema_id = schema_info["schema_id"]
+ self.schema_name = schema_info["schema_name"]
+ schema_response = schema_utils.verify_schemas(self.server,
+ self.db_name,
+ self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a index "
"constraint(primary key or unique key).")
- cls.table_name = "table_indexconstraint_%s" % \
- (str(uuid.uuid4())[1:8])
- cls.table_id = tables_utils.create_table(cls.server,
- cls.db_name,
- cls.schema_name,
- cls.table_name)
+ self.table_name = "table_indexconstraint_%s" % \
+ (str(uuid.uuid4())[1:8])
+ self.table_id = tables_utils.create_table(self.server,
+ self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will add index constraint(primary key or unique key)
@@ -81,10 +80,9 @@ class IndexConstraintAddTestCase(BaseTestGenerator):
content_type='html/json')
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
# Disconnect the database
- database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
@@ -117,30 +115,28 @@ class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
dict(url='/browser/unique_constraint/obj/', data=unique_key_data))
]
- @classmethod
- def setUpClass(cls):
- cls.db_name = parent_node_dict["database"][-1]["db_name"]
+ def setUp(self):
+ self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
- cls.server_id = schema_info["server_id"]
- cls.db_id = schema_info["db_id"]
- db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
- cls.server_id, cls.db_id)
+ self.server_id = schema_info["server_id"]
+ self.db_id = schema_info["db_id"]
+ db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+ self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to add a "
"constraint using index.")
- cls.schema_id = schema_info["schema_id"]
- cls.schema_name = schema_info["schema_name"]
- schema_response = schema_utils.verify_schemas(cls.server,
- cls.db_name,
- cls.schema_name)
+ self.schema_id = schema_info["schema_id"]
+ self.schema_name = schema_info["schema_name"]
+ schema_response = schema_utils.verify_schemas(self.server,
+ self.db_name,
+ self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a index "
"constraint(primary key or unique key).")
- cls.table_name = "table_constraint_%s" % (str(uuid.uuid4())[1:8])
- cls.table_id = tables_utils.create_table(cls.server,
- cls.db_name,
- cls.schema_name,
- cls.table_name)
+ self.table_name = "table_constraint_%s" % (str(uuid.uuid4())[1:8])
+ self.table_id = tables_utils.create_table(self.server, self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will add index constraint(primary key or unique key)
@@ -158,7 +154,6 @@ class ConstraintsUsingIndexAddTestCase(BaseTestGenerator):
content_type='html/json')
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
# Disconnect the database
- database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
index 7eea5e9..f35a00b 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py
@@ -38,31 +38,30 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator):
type="UNIQUE"))
]
- @classmethod
- def setUpClass(cls):
- cls.db_name = parent_node_dict["database"][-1]["db_name"]
+ def setUp(self):
+ self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
- cls.server_id = schema_info["server_id"]
- cls.db_id = schema_info["db_id"]
- db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
- cls.server_id, cls.db_id)
+ self.server_id = schema_info["server_id"]
+ self.db_id = schema_info["db_id"]
+ db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+ self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to add a "
"index constraint(primary key or unique key).")
- cls.schema_id = schema_info["schema_id"]
- cls.schema_name = schema_info["schema_name"]
- schema_response = schema_utils.verify_schemas(cls.server,
- cls.db_name,
- cls.schema_name)
+ self.schema_id = schema_info["schema_id"]
+ self.schema_name = schema_info["schema_name"]
+ schema_response = schema_utils.verify_schemas(self.server,
+ self.db_name,
+ self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a index "
"constraint(primary key or unique key).")
- cls.table_name = "table_indexconstraint_%s" % \
- (str(uuid.uuid4())[1:8])
- cls.table_id = tables_utils.create_table(cls.server,
- cls.db_name,
- cls.schema_name,
- cls.table_name)
+ self.table_name = "table_indexconstraint_%s" % \
+ (str(uuid.uuid4())[1:8])
+ self.table_id = tables_utils.create_table(self.server,
+ self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will delete index constraint(primary key or
@@ -81,7 +80,6 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator):
)
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
# Disconnect the database
- database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
index c08c2ac..f838a2f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py
@@ -38,31 +38,30 @@ class IndexConstraintGetTestCase(BaseTestGenerator):
type="UNIQUE"))
]
- @classmethod
- def setUpClass(cls):
- cls.db_name = parent_node_dict["database"][-1]["db_name"]
+ def setUp(self):
+ self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
- cls.server_id = schema_info["server_id"]
- cls.db_id = schema_info["db_id"]
- db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
- cls.server_id, cls.db_id)
+ self.server_id = schema_info["server_id"]
+ self.db_id = schema_info["db_id"]
+ db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+ self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to add a "
"index constraint(primary key or unique key).")
- cls.schema_id = schema_info["schema_id"]
- cls.schema_name = schema_info["schema_name"]
- schema_response = schema_utils.verify_schemas(cls.server,
- cls.db_name,
- cls.schema_name)
+ self.schema_id = schema_info["schema_id"]
+ self.schema_name = schema_info["schema_name"]
+ schema_response = schema_utils.verify_schemas(self.server,
+ self.db_name,
+ self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a index "
"constraint(primary key or unique key).")
- cls.table_name = "table_indexconstraint_%s" % \
- (str(uuid.uuid4())[1:8])
- cls.table_id = tables_utils.create_table(cls.server,
- cls.db_name,
- cls.schema_name,
- cls.table_name)
+ self.table_name = "table_indexconstraint_%s" % \
+ (str(uuid.uuid4())[1:8])
+ self.table_id = tables_utils.create_table(self.server,
+ self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will fetch the index constraint(primary key or
@@ -81,7 +80,6 @@ class IndexConstraintGetTestCase(BaseTestGenerator):
)
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
# Disconnect the database
- database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
index 87289a6..6bdf703 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py
@@ -40,31 +40,30 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator):
type="UNIQUE", data=data))
]
- @classmethod
- def setUpClass(cls):
- cls.db_name = parent_node_dict["database"][-1]["db_name"]
+ def setUp(self):
+ self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
- cls.server_id = schema_info["server_id"]
- cls.db_id = schema_info["db_id"]
- db_con = database_utils.connect_database(cls, utils.SERVER_GROUP,
- cls.server_id, cls.db_id)
+ self.server_id = schema_info["server_id"]
+ self.db_id = schema_info["db_id"]
+ db_con = database_utils.connect_database(self, utils.SERVER_GROUP,
+ self.server_id, self.db_id)
if not db_con['data']["connected"]:
raise Exception("Could not connect to database to add a "
"index constraint(primary key or unique key).")
- cls.schema_id = schema_info["schema_id"]
- cls.schema_name = schema_info["schema_name"]
- schema_response = schema_utils.verify_schemas(cls.server,
- cls.db_name,
- cls.schema_name)
+ self.schema_id = schema_info["schema_id"]
+ self.schema_name = schema_info["schema_name"]
+ schema_response = schema_utils.verify_schemas(self.server,
+ self.db_name,
+ self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a index "
"constraint(primary key or unique key).")
- cls.table_name = "table_indexconstraint_%s" % \
- (str(uuid.uuid4())[1:8])
- cls.table_id = tables_utils.create_table(cls.server,
- cls.db_name,
- cls.schema_name,
- cls.table_name)
+ self.table_name = "table_indexconstraint_%s" % \
+ (str(uuid.uuid4())[1:8])
+ self.table_id = tables_utils.create_table(self.server,
+ self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will update index constraint(primary key or
@@ -84,7 +83,6 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator):
follow_redirects=True)
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(cls):
+ def tearDown(self):
# Disconnect the database
- database_utils.disconnect_database(cls, cls.server_id, cls.db_id)
+ database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
index bbed269..02203d2 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py
@@ -46,8 +46,9 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
)
]
- @classmethod
- def setUpClass(self):
+ table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8])
+
+ def setUp(self):
self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
self.server_id = schema_info["server_id"]
@@ -63,12 +64,14 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
self.schema_name)
if not schema_response:
raise Exception("Could not find the schema to add a table.")
- self.table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8])
- self.table_id = tables_utils.create_table(
- self.server, self.db_name,
- self.schema_name,
- self.table_name)
+ self.table_id = tables_utils.get_table_id(self.server, self.db_name,
+ self.table_name)
+ if self.table_id is None:
+ self.table_id = tables_utils.create_table(
+ self.server, self.db_name,
+ self.schema_name,
+ self.table_name)
def runTest(self):
"""This function will fetch added table under schema node."""
@@ -130,7 +133,6 @@ class TableUpdateParameterTestCase(BaseTestGenerator):
follow_redirects=True)
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(self):
+ def tearDown(self):
# Disconnect the database
database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
index 0a2963a..8e5f665 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py
@@ -483,3 +483,26 @@ def get_hash_partitions_data(data):
}]
data['partition_keys'] = \
[{'key_type': 'column', 'pt_column': 'empno'}]
+
+
+def get_table_id(server, db_name, table_name):
+ try:
+ connection = utils.get_db_connection(db_name,
+ server['username'],
+ server['db_password'],
+ server['host'],
+ server['port'],
+ server['sslmode'])
+ pg_cursor = connection.cursor()
+ pg_cursor.execute("select oid from pg_class where relname='%s'" %
+ table_name)
+ table = pg_cursor.fetchone()
+ if table:
+ table_id = table[0]
+ else:
+ table_id = None
+ connection.close()
+ return table_id
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ raise
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
index c84276e..1f63ef5 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py
@@ -45,8 +45,9 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
)
]
- @classmethod
- def setUpClass(self):
+ m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8])
+
+ def setUp(self):
self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
self.server_id = schema_info["server_id"]
@@ -70,17 +71,19 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
if not schema_response:
raise Exception("Could not find the schema to update a mview.")
- self.m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8])
- m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE pg_default " \
- "AS SELECT 'test_pgadmin' WITH NO DATA;ALTER TABLE " \
- "%s.%s OWNER TO %s"
-
- self.m_view_id = views_utils.create_view(self.server,
- self.db_name,
- self.schema_name,
- m_view_sql,
+ self.m_view_id = views_utils.get_view_id(self.server, self.db_name,
self.m_view_name)
+ if self.m_view_id is None:
+ m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE " \
+ "pg_default AS SELECT 'test_pgadmin' WITH NO " \
+ "DATA;ALTER TABLE %s.%s OWNER TO %s"
+ self.m_view_id = views_utils.create_view(self.server,
+ self.db_name,
+ self.schema_name,
+ m_view_sql,
+ self.m_view_name)
+
def runTest(self):
"""This function will update the view/mview under schema node."""
mview_response = views_utils.verify_view(self.server, self.db_name,
@@ -141,7 +144,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
follow_redirects=True)
self.assertEquals(response.status_code, 200)
- @classmethod
- def tearDownClass(self):
+ def tearDown(self):
# Disconnect the database
database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
index b6c0bad..3b4055f 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_refresh.py
@@ -48,8 +48,7 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
),
]
- @classmethod
- def setUpClass(self):
+ def setUp(self):
self.db_name = parent_node_dict["database"][-1]["db_name"]
schema_info = parent_node_dict["schema"][-1]
self.server_id = schema_info["server_id"]
@@ -143,7 +142,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator):
# On success we get job_id from server
self.assertTrue('job_id' in response.json['data'])
- @classmethod
- def tearDownClass(self):
+ def tearDown(self):
# Disconnect the database
database_utils.disconnect_database(self, self.server_id, self.db_id)
diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
index d6180c4..7619ee6 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
+++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py
@@ -86,3 +86,28 @@ def verify_view(server, db_name, view_name):
except Exception:
traceback.print_exc(file=sys.stderr)
raise
+
+
+def get_view_id(server, db_name, view_name):
+ try:
+ connection = utils.get_db_connection(db_name,
+ server['username'],
+ server['db_password'],
+ server['host'],
+ server['port'],
+ server['sslmode'])
+ old_isolation_level = connection.isolation_level
+ connection.set_isolation_level(0)
+ pg_cursor = connection.cursor()
+ # Get 'oid' from newly created view
+ pg_cursor.execute("select oid from pg_class where relname='%s'" %
+ view_name)
+ view = pg_cursor.fetchone()
+ view_id = None
+ if view:
+ view_id = view[0]
+ connection.close()
+ return view_id
+ except Exception:
+ traceback.print_exc(file=sys.stderr)
+ raise
diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
index 7091ffa..1f3bd65 100644
--- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
+++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py
@@ -8,7 +8,6 @@
##########################################################################
from __future__ import print_function
-import pyperclip
import random
from selenium.webdriver import ActionChains
@@ -60,8 +59,18 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
self._mouseup_outside_grid_still_makes_a_selection()
self._copies_rows_with_header()
+ def paste_values_to_scratch_pad(self):
+ self.page.driver.switch_to.default_content()
+ self.page.driver.switch_to_frame(
+ self.page.driver.find_element_by_tag_name("iframe"))
+ scratch_pad_ele = self.page.find_by_css_selector(
+ QueryToolLocators.scratch_pad_css)
+ self.page.paste_values(scratch_pad_ele)
+ clipboard_text = scratch_pad_ele.get_attribute("value")
+ scratch_pad_ele.clear()
+ return clipboard_text
+
def _copies_rows(self):
- pyperclip.copy("old clipboard contents")
first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1))
first_row.click()
@@ -70,14 +79,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
QueryToolLocators.copy_button_css)
copy_button.click()
+ clipboard_text = self.paste_values_to_scratch_pad()
self.assertEqual('"Some-Name"\t6\t"some info"',
- pyperclip.paste())
+ clipboard_text)
def _copies_rows_with_header(self):
self.page.find_by_css_selector('#btn-copy-row-dropdown').click()
self.page.find_by_css_selector('a#btn-copy-with-header').click()
- pyperclip.copy("old clipboard contents")
select_all = self.page.find_by_xpath(
QueryToolLocators.select_all_column)
select_all.click()
@@ -86,13 +95,14 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
QueryToolLocators.copy_button_css)
copy_button.click()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual("""\"some_column"\t"value"\t"details"
\"Some-Name"\t6\t"some info"
\"Some-Other-Name"\t22\t"some other info"
-\"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste())
+\"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text)
def _copies_columns(self):
- pyperclip.copy("old clipboard contents")
column = self.page.find_by_css_selector(
QueryToolLocators.output_column_header_css.format('some_column'))
column.click()
@@ -101,14 +111,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
QueryToolLocators.copy_button_css)
copy_button.click()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual(
"""\"Some-Name"
"Some-Other-Name"
"Yet-Another-Name\"""",
- pyperclip.paste())
+ clipboard_text)
def _copies_row_using_keyboard_shortcut(self):
- pyperclip.copy("old clipboard contents")
first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1))
first_row.click()
@@ -116,11 +127,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
ActionChains(self.page.driver).key_down(
Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual('"Some-Name"\t6\t"some info"',
- pyperclip.paste())
+ clipboard_text)
def _copies_column_using_keyboard_shortcut(self):
- pyperclip.copy("old clipboard contents")
column = self.page.find_by_css_selector(
QueryToolLocators.output_column_header_css.format('some_column'))
column.click()
@@ -128,15 +140,15 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
ActionChains(self.page.driver).key_down(
Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual(
"""\"Some-Name"
"Some-Other-Name"
"Yet-Another-Name\"""",
- pyperclip.paste())
+ clipboard_text)
def _copies_rectangular_selection(self):
- pyperclip.copy("old clipboard contents")
-
top_left_cell = \
self.page.find_by_xpath(
QueryToolLocators.output_column_data_xpath.
@@ -154,12 +166,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
self.page.driver
).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual(
- '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste())
+ '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text)
def _shift_resizes_rectangular_selection(self):
- pyperclip.copy("old clipboard contents")
-
top_left_cell = self.page.find_by_xpath(
QueryToolLocators.output_column_data_xpath.
format('Some-Other-Name')
@@ -180,12 +192,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
Keys.CONTROL
).send_keys('c').key_up(Keys.CONTROL).perform()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual("""\"Some-Other-Name"\t22\t"some other info"
-"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste())
+"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text)
def _shift_resizes_column_selection(self):
- pyperclip.copy("old clipboard contents")
-
column = self.page.find_by_css_selector(
QueryToolLocators.output_column_header_css.format('value')
)
@@ -197,13 +209,13 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
ActionChains(self.page.driver).key_down(
Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
+ clipboard_text = self.paste_values_to_scratch_pad()
+
self.assertEqual(
'"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
- pyperclip.paste())
+ clipboard_text)
def _mouseup_outside_grid_still_makes_a_selection(self):
- pyperclip.copy("old clipboard contents")
-
bottom_right_cell = self.page.find_by_xpath(
QueryToolLocators.output_column_data_xpath.format('cool info')
)
@@ -218,7 +230,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
ActionChains(self.page.driver).key_down(
Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform()
- self.assertIn('"cool info"', pyperclip.paste())
+ clipboard_text = self.paste_values_to_scratch_pad()
+
+ self.assertIn('"cool info"', clipboard_text)
def after(self):
self.page.close_query_tool()
diff --git a/web/pgadmin/feature_tests/file_manager_test.py b/web/pgadmin/feature_tests/file_manager_test.py
index 6d01286..33f86ad 100644
--- a/web/pgadmin/feature_tests/file_manager_test.py
+++ b/web/pgadmin/feature_tests/file_manager_test.py
@@ -38,7 +38,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
self.page.add_server(self.server)
self.wait = WebDriverWait(self.page.driver, 10)
- self.XSS_FILE = '/tmp/<img src=x onmouseover=alert("1")>.sql'
+ self.XSS_FILE = '/tmp/<img src=x ' + self.server['name'][:13] \
+ + '=alert("1")>.sql'
# Remove any previous file
if os.path.isfile(self.XSS_FILE):
os.remove(self.XSS_FILE)
@@ -67,7 +68,7 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
self.page.open_query_tool()
def _create_new_file(self):
- self.page.find_by_css_selector(QueryToolLocators.btn_save_file)\
+ self.page.find_by_css_selector(QueryToolLocators.btn_save_file) \
.click()
# Set the XSS value in input
self.page.find_by_css_selector('.change_file_types')
@@ -112,8 +113,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest):
self.page.wait_for_query_tool_loading_indicator_to_disappear()
self._check_escaped_characters(
contents,
- '<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..3a7472e 100644
--- a/web/regression/README
+++ b/web/regression/README
@@ -141,6 +141,61 @@ Python Tests:
and registered automatically by its module name in
'pgadmin4/web/pgadmin/utils/test.py' file.
+- To run Feature Tests in parallel using selenoid(grid + docker), selenoid
+ need to be installed. Steps to install selenoid -
+
+ - Install & Start docker
+ $yum -y install docker docker-registry
+ $vi /etc/sysconfig/docker # in OPTIONS add ‘--selinux-enabled=false’
+ $systemctl enable docker.service
+ $systemctl start docker.service
+ $systemctl status docker.service
+
+ - Install & Start Selenoid
+ $curl -s https://aerokube.com/cm/bash | bash
+ $./cm selenoid start --vnc --args "-limit 3 -cpu 1.5 -mem 1.5g"
+ $./cm selenoid-ui start
+ Check selenoid status -
+ http://<IP address of Selenoid Installed machine>:4444/status
+ - Should show json with browsers details
+ http://<IP address of Selenoid Installed machine>:8080/#/
+ - Capabilities shows available browser
+ Note : In --args "-limit 3 -cpu 1.5 -mem 1.5g"
+ -limit 3 :limits maximum parallel sessions(dockers) in selenoid,
+ -cpu :limit memory and CPU usage,
+ -mem :limit memory per session.
+ Generally max parallel session is the number of cores * 1.5 – 2
+ You can list available flags by using ./cm selenoid args
+ Additional Information about tool
+ - https://aerokube.com/selenoid/latest/
+
+ - Update 'test_config.json' with selenoid config information
+ pgAdmin_default_server -
+ It is the IP address for the machine where pgadmin source code is
+ present.Value should NOT be '127.0.0.1' even though everything runs
+ on the same machine.
+ You can get it on linux running command 'ifconfig | grep inet'
+ e.g. - 192.168.143.121
+ max_parallel_sessions -
+ This is other way to control number of tests to be run in parallel.
+ This should be equal or less than limit specified while setting up
+ selenoid
+ selenoid_url -
+ Url should be formed as below -
+ http://<IP address of Selenoid Installed machine>:4444/wd/hub/
+ e.g. - selenoid_url": "http://192.168.143.121:4444/wd/hub"
+ If source code & selenoid servers are on same machine then
+ selenoid url value can be - "http://localhost:4444/wd/hub"
+ browsers_list -
+ List of browser name & version enclosed in {} on which tests to be
+ executed.
+ Make sure list contains those browsers & versions only which are shown
+ in capabilities tab while in selenoid status web-page.
+ If version is mention as null, then latest version available in
+ selenoid server will be used for execution.
+ e.g. - [ {"name": "Chrome","version": "80.0"},
+ {"name": "Firefox","version": "74.0"}]
+
- Change to the regression test directory:
run 'cd web/regression'
@@ -190,9 +245,14 @@ Python Tests:
Example 2) Execute only reverse engineered SQL test framework for some modules
run 'python runtests.py --pkg resql --modules sequences,functions'
+
Example 3) Exclude reverse engineered SQL test framework for all modules
run 'python runtests.py --exclude resql'
+- Execute ui selenium tests in parallel using selenoid(selenium grid + docker)
+ Example : --pkg feature_tests --parallel
+
+
Code Coverage:
---------------
diff --git a/web/regression/feature_utils/app_starter.py b/web/regression/feature_utils/app_starter.py
index fe4c441..7de10b2 100644
--- a/web/regression/feature_utils/app_starter.py
+++ b/web/regression/feature_utils/app_starter.py
@@ -61,11 +61,16 @@ class AppStarter:
raise Exception('Unable to start python server even after '
'retrying 60 times.')
- launch_browser(0)
+ if self.driver is not None:
+ launch_browser(0)
+ else:
+ return "http://" + self.app_config.DEFAULT_SERVER + ":" \
+ + random_server_port
def stop_app(self):
""" This function stop the started app by killing process """
- self.driver.quit()
+ if self.driver is not None:
+ self.driver.quit()
# os.killpg supported in Mac and Unix as this function not supported in
# Windows
try:
diff --git a/web/regression/feature_utils/locators.py b/web/regression/feature_utils/locators.py
index b8169fa..4756231 100644
--- a/web/regression/feature_utils/locators.py
+++ b/web/regression/feature_utils/locators.py
@@ -172,6 +172,8 @@ class QueryToolLocators:
new_row_xpath = "//div[contains(@class, 'new-row')]"
+ scratch_pad_css = ".sql-scratch > textarea"
+
copy_button_css = "#btn-copy-row"
paste_button_css = "#btn-paste-row"
@@ -217,9 +219,9 @@ class QueryToolLocators:
btn_commit = "#btn-commit"
show_query_internally_btn = \
- "//div[label[normalize-space(" \
- "text())='Show queries generated internally by pgAdmin?']]" \
- "//div[contains(@class,'toggle btn')]"
+ "//div[label[contains(normalize-space(text())," \
+ "'Show queries generated internally by')]]//" \
+ "div[contains(@class,'toggle btn')]"
editable_column_icon_xpath = "//div[contains(@class," \
" 'editable-column-header-icon')]" \
diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py
index d7e8fe3..32ea85f 100644
--- a/web/regression/feature_utils/pgadmin_page.py
+++ b/web/regression/feature_utils/pgadmin_page.py
@@ -88,11 +88,17 @@ class PgadminPage:
(By.CSS_SELECTOR, "button[type='save'].btn.btn-primary")))
self.find_by_css_selector("button[type='save'].btn.btn-primary").\
click()
-
- WebDriverWait(self.driver, 10).until(
- EC.visibility_of_element_located(
- (By.XPATH,
- "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
+ try:
+ WebDriverWait(self.driver, 10).until(
+ EC.visibility_of_element_located(
+ (By.XPATH,
+ "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
+ except TimeoutException:
+ self.toggle_open_servers_group()
+ WebDriverWait(self.driver, 10).until(
+ EC.visibility_of_element_located(
+ (By.XPATH,
+ "//*[@id='tree']//*[.='" + server_config['name'] + "']")))
def open_query_tool(self):
self.driver.find_element_by_link_text("Tools").click()
@@ -910,7 +916,11 @@ class PgadminPage:
return element
except (NoSuchElementException, WebDriverException):
return False
-
+ time.sleep(1)
+ self.driver.switch_to.default_content()
+ self.driver.switch_to_frame(
+ self.driver.find_element_by_tag_name("iframe"))
+ self.find_by_xpath("//a[text()='Query Editor']").click()
codemirror_ele = WebDriverWait(
self.driver, timeout=self.timeout, poll_frequency=0.01)\
.until(find_codemirror,
@@ -1161,3 +1171,34 @@ class PgadminPage:
except Exception:
attempt += 1
return click_status
+
+ def paste_values(self, el=None):
+ """
+ Function paste values in scratch pad
+ :param el:
+ """
+ actions = ActionChains(self.driver)
+ if el:
+ # Must step
+ el.click()
+ if self.driver.capabilities["platformName"] == 'mac':
+ # FF step
+ el.send_keys(Keys.COMMAND + "v")
+ # Chrome Step
+ actions.key_down(Keys.SHIFT)
+ actions.send_keys(Keys.INSERT)
+ actions.key_up(Keys.SHIFT)
+ actions.perform()
+ else:
+ el.send_keys(Keys.CONTROL + "v")
+
+ def wait_for_element_to_be_visible(self, driver, xpath, time_value=20):
+ """This will wait until an element is visible on page"""
+ element_located_status = False
+ try:
+ if WebDriverWait(driver, time_value).until(
+ EC.visibility_of_element_located((By.XPATH, xpath))):
+ element_located_status = True
+ except TimeoutException:
+ element_located_status = False
+ return element_located_status
diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py
index 971fc24..b292472 100644
--- a/web/regression/python_test_utils/test_utils.py
+++ b/web/regression/python_test_utils/test_utils.py
@@ -8,6 +8,8 @@
##########################################################################
from __future__ import print_function
+
+import fileinput
import traceback
import os
import sys
@@ -16,7 +18,17 @@ import psycopg2
import sqlite3
import shutil
from functools import partial
+
+from selenium.webdriver.support.wait import WebDriverWait
from testtools.testcase import clone_test_with_new_id
+import re
+import time
+from selenium.common.exceptions import WebDriverException
+import urllib.request as urllib
+import json
+from selenium import webdriver
+from selenium.webdriver.chrome.options import Options
+from selenium.webdriver.support import expected_conditions as ec
import config
import regression
@@ -1216,3 +1228,242 @@ def create_expected_output(parameters, actual_data):
actual_data.remove(value)
break
return expected_output
+
+
+def is_parallel_ui_tests(args):
+ """
+ This function checks for coverage args exists in command line args
+ :return: boolean
+ """
+ if "parallel" in args and args["parallel"]:
+ return True
+ return False
+
+
+def get_selenium_grid_status_and_browser_list(selenoid_url):
+ """
+ This function checks selenoid status for given url
+ :param selrnoid_url:
+ :return: status of selenoid & list of browsers available with selenoid if
+ status is up
+ """
+ selenoid_status = False
+ browser_list = []
+ try:
+ selenoid_status = get_selenium_grid_status_json(selenoid_url)
+ if selenoid_status:
+ available_browsers = selenoid_status["browsers"]
+ list_of_browsers = test_setup.config_data['selenoid_config'][
+ 'browsers_list']
+
+ for browser in list_of_browsers:
+ if browser["name"].lower() in available_browsers.keys():
+ versions = available_browsers[(browser["name"].lower())]
+ if browser["version"] is None:
+ print("Specified version of browser is None. Hence "
+ "latest version of {0} available with selenoid "
+ "server will be used.\n".format(browser["name"]))
+ browser_list.append(browser)
+ elif browser["version"] in versions.keys():
+ browser_list.append(browser)
+ else:
+ print(
+ "Available {0} versions {1}".format(
+ browser["name"], versions.keys()))
+ print("Specified Version = {0}".format(
+ browser["version"]))
+ else:
+ print("{0} is NOT available".format(browser["name"]))
+ except Exception as e:
+ (str(e))
+ print("Unable to find Selenoid Status")
+
+ return selenoid_status, browser_list
+
+
+def is_feature_test_included(arguments):
+ """
+ :param arguments: his is command line arguments for module name to
+ which test suite will run
+ :return: boolean value whether to execute feature tests or NOT &
+ browser name if feature_test_tobe_included = True
+ """
+ exclude_pkgs = []
+ if arguments['exclude'] is not None:
+ exclude_pkgs += arguments['exclude'].split(',')
+
+ feature_test_tobe_included = 'feature_tests' not in exclude_pkgs and \
+ (arguments['pkg'] is None or arguments[
+ 'pkg'] == "all" or
+ arguments['pkg'] == "feature_tests")
+ return feature_test_tobe_included
+
+
+def launch_url_in_browser(driver_instance, url, title='pgAdmin 4', timeout=40):
+ """
+ Function launches urls in specified driver instance
+ :param driver_instance:browser instance
+ :param url:url to be launched
+ :param title:web-page tile on successful launch default is 'pgAdmin 4'
+ :param timeout:in seconds for getting specified title default is 20sec
+ :return:
+ """
+ count = timeout / 5
+ while count > 0:
+ try:
+ driver_instance.get(url)
+ wait = WebDriverWait(driver_instance, 10)
+ wait.until(ec.title_is(title))
+ break
+ except WebDriverException as e:
+ time.sleep(6)
+ count -= 1
+ if count == 0:
+ exception_msg = 'Web-page title did not match to {0}. ' \
+ 'Please check url {1} accessible on ' \
+ 'internet.'.format(title, url)
+ raise Exception(exception_msg)
+
+
+def get_remote_webdriver(hub_url, browser, browser_ver, test_name):
+ """
+ This functions returns remote web-driver instance created in selenoid
+ machine.
+ :param hub_url
+ :param browser: browser name
+ :param browser_ver: version for browser
+ :param test_name: test name
+ :return: remote web-driver instance for specified browser
+ """
+ test_name = browser + browser_ver + "_" + test_name + "-" + time.strftime(
+ "%m_%d_%y_%H_%M_%S", time.localtime())
+ driver_local = None
+
+ desired_capabilities = {
+ "version": browser_ver,
+ "enableVNC": True,
+ "enableVideo": True,
+ "enableLog": True,
+ "videoName": test_name + ".mp4",
+ "logName": test_name + ".log",
+ "name": test_name,
+ "timeZone": "Asia/Kolkata"
+ }
+
+ if browser == 'firefox':
+ profile = webdriver.FirefoxProfile()
+ profile.set_preference("dom.disable_beforeunload", True)
+ desired_capabilities["browserName"] = "firefox"
+ desired_capabilities["requireWindowFocus"] = True
+ desired_capabilities["enablePersistentHover"] = False
+ driver_local = webdriver.Remote(
+ command_executor=hub_url,
+ desired_capabilities=desired_capabilities, browser_profile=profile)
+ elif browser == 'chrome':
+ options = Options()
+ options.add_argument("--window-size=1280,1024")
+ desired_capabilities["browserName"] = "chrome"
+ driver_local = webdriver.Remote(
+ command_executor=hub_url,
+ desired_capabilities=desired_capabilities, options=options)
+ else:
+ print("Specified browser does not exist.")
+
+ # maximize browser window
+ driver_local.maximize_window()
+
+ # driver_local.implicitly_wait(2)
+ return driver_local
+
+
+def get_parallel_sequential_module_list(module_list):
+ """
+ Functions segregate parallel & sequential modules
+ :param module_list: Complete list of modules
+ :return: parallel & sequential module lists
+ """
+ # list of files consisting tests that needs to be
+ # executed sequentially
+ sequential_tests_file = [
+ 'pgadmin.feature_tests.pg_utilities_backup_restore_test',
+ 'pgadmin.feature_tests.pg_utilities_maintenance_test',
+ 'pgadmin.feature_tests.keyboard_shortcut_test']
+
+ # list of tests can be executed in parallel
+ parallel_tests = list(module_list)
+ for module in module_list:
+ if str(module[0]) in sequential_tests_file:
+ parallel_tests.remove(module)
+
+ # list of tests can be executed in sequentially
+ sequential_tests = list(
+ filter(lambda i: i not in parallel_tests,
+ module_list))
+
+ # return parallel & sequential lists
+ return parallel_tests, sequential_tests
+
+
+def get_browser_details(browser_info_dict, url):
+ """
+ Function extracts browser name & version from browser info dict
+ in test_config.json
+ :param browser_info_dict:
+ :return: browser name & version
+ """
+ browser_name = browser_info_dict["name"].lower()
+ browser_version = browser_info_dict["version"]
+ if browser_version is None:
+ selenoid_status = get_selenium_grid_status_json(url)
+ versions = selenoid_status["browsers"][browser_name]
+ browser_version = max(versions)
+ return browser_name, browser_version
+
+
+def print_test_summary(complete_module_list, parallel_testlist,
+ sequential_tests_list, browser_name, browser_version):
+ """
+ Prints test summary about total, parallel, sequential, browser name,
+ browser version information
+ :param complete_module_list:
+ :param parallel_testlist:
+ :param sequential_tests_list:
+ :param browser_name:
+ :param browser_version:
+ """
+ print(
+ "=================================================================",
+ file=sys.stderr
+ )
+ print(
+ "Total Tests # {0}\nParallel Tests # {1}, "
+ "Sequential Tests # {2}".format(
+ len(complete_module_list), len(parallel_testlist),
+ len(sequential_tests_list)),
+ file=sys.stderr)
+ print("Browser: [Name:{0}, Version: {1}]".format(
+ browser_name.capitalize(), browser_version),
+ file=sys.stderr)
+ print(
+ "=================================================================\n",
+ file=sys.stderr
+ )
+
+
+def get_selenium_grid_status_json(selenoid_url):
+ """
+ Functions returns json response received from selenoid server
+ :param selenoid_url:
+ :return:
+ """
+ try:
+ selenoid_status = urllib.urlopen(
+ "http://" + re.split('/', (re.split('//', selenoid_url, 1)[1]))[
+ 0] + "/status", timeout=10)
+ selenoid_status = json.load(selenoid_status)
+ if isinstance(selenoid_status, dict):
+ return selenoid_status
+ except Exception as e:
+ print("Unable to find Selenoid Status.Kindly check url passed -'{0}'".
+ format(selenoid_url))
+ return None
diff --git a/web/regression/requirements.txt b/web/regression/requirements.txt
index 1eb922b..2367f2a 100644
--- a/web/regression/requirements.txt
+++ b/web/regression/requirements.txt
@@ -21,7 +21,6 @@ fixtures==3.0.0
linecache2==1.0.0
pbr==3.1.1
pycodestyle>=2.5.0
-pyperclip~=1.6.0
python-mimeparse==1.6.0
testscenarios==0.5.0
testtools==2.3.0
diff --git a/web/regression/runtests.py b/web/regression/runtests.py
index 480aaad..8759be3 100644
--- a/web/regression/runtests.py
+++ b/web/regression/runtests.py
@@ -21,7 +21,8 @@ import traceback
import json
import random
import coverage
-
+import threading
+import time
import unittest
if sys.version_info < (3, 4):
@@ -136,7 +137,7 @@ scenarios.apply_scenario = test_utils.apply_scenario
def get_suite(module_list, test_server, test_app_client, server_information,
- test_db_name):
+ test_db_name, driver_passed):
"""
This function add the tests to test suite and return modified test suite
variable.
@@ -166,7 +167,7 @@ def get_suite(module_list, test_server, test_app_client, server_information,
obj.setApp(app)
obj.setTestClient(test_app_client)
obj.setTestServer(test_server)
- obj.setDriver(driver)
+ obj.setDriver(driver_passed)
obj.setServerInformation(server_information)
obj.setTestDatabaseName(test_db_name)
scenario = scenarios.generate_scenarios(obj)
@@ -207,57 +208,62 @@ def get_test_modules(arguments):
exclude_pkgs += arguments['exclude'].split(',')
if 'feature_tests' not in exclude_pkgs and \
- (arguments['pkg'] is None or arguments['pkg'] == "all" or
- arguments['pkg'] == "feature_tests"):
-
- from selenium import webdriver
- from selenium.webdriver.chrome.options import Options
- from selenium.webdriver.common.desired_capabilities import \
- DesiredCapabilities
-
- default_browser = 'chrome'
-
- # Check default browser provided through command line. If provided
- # then use that browser as default browser else check for the setting
- # provided in test_config.json file.
- if (
- 'default_browser' in arguments and
- arguments['default_browser'] is not None
- ):
- default_browser = arguments['default_browser'].lower()
- elif (
- test_setup.config_data and
- "default_browser" in test_setup.config_data
- ):
- default_browser = test_setup.config_data['default_browser'].lower()
-
- if default_browser == 'firefox':
- cap = DesiredCapabilities.FIREFOX
- cap['requireWindowFocus'] = True
- cap['enablePersistentHover'] = False
- profile = webdriver.FirefoxProfile()
- profile.set_preference("dom.disable_beforeunload", True)
- driver = webdriver.Firefox(capabilities=cap,
- firefox_profile=profile)
- driver.implicitly_wait(1)
- else:
- options = Options()
- if test_setup.config_data:
- if 'headless_chrome' in test_setup.config_data:
- if test_setup.config_data['headless_chrome']:
- options.add_argument("--headless")
- options.add_argument("--no-sandbox")
- options.add_argument("--disable-setuid-sandbox")
- options.add_argument("--window-size=1280,1024")
- options.add_argument("--disable-infobars")
- options.add_experimental_option('w3c', False)
- driver = webdriver.Chrome(chrome_options=options)
-
- # maximize browser window
- driver.maximize_window()
-
- app_starter = AppStarter(driver, config)
- app_starter.start_app()
+ (arguments['pkg'] is None or arguments['pkg'] == "all" or
+ arguments['pkg'] == "feature_tests"):
+
+ if arguments['pkg'] == "feature_tests":
+ exclude_pkgs.extend(['resql'])
+
+ if not test_utils.is_parallel_ui_tests(args):
+ from selenium import webdriver
+ from selenium.webdriver.chrome.options import Options
+ from selenium.webdriver.common.desired_capabilities import \
+ DesiredCapabilities
+
+ default_browser = 'chrome'
+
+ # Check default browser provided through command line. If provided
+ # then use that browser as default browser else check for the
+ # setting provided in test_config.json file.
+ if (
+ 'default_browser' in arguments and
+ arguments['default_browser'] is not None
+ ):
+ default_browser = arguments['default_browser'].lower()
+ elif (
+ test_setup.config_data and
+ "default_browser" in test_setup.config_data
+ ):
+ default_browser = test_setup.config_data[
+ 'default_browser'].lower()
+
+ if default_browser == 'firefox':
+ cap = DesiredCapabilities.FIREFOX
+ cap['requireWindowFocus'] = True
+ cap['enablePersistentHover'] = False
+ profile = webdriver.FirefoxProfile()
+ profile.set_preference("dom.disable_beforeunload", True)
+ driver = webdriver.Firefox(capabilities=cap,
+ firefox_profile=profile)
+ driver.implicitly_wait(1)
+ else:
+ options = Options()
+ if test_setup.config_data:
+ if 'headless_chrome' in test_setup.config_data:
+ if test_setup.config_data['headless_chrome']:
+ options.add_argument("--headless")
+ options.add_argument("--no-sandbox")
+ options.add_argument("--disable-setuid-sandbox")
+ options.add_argument("--window-size=1280,1024")
+ options.add_argument("--disable-infobars")
+ options.add_experimental_option('w3c', False)
+ driver = webdriver.Chrome(chrome_options=options)
+
+ # maximize browser window
+ driver.maximize_window()
+
+ app_starter = AppStarter(driver, config)
+ app_starter.start_app()
handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter)
# Register cleanup function to cleanup on exit
@@ -319,6 +325,9 @@ def add_arguments():
'--modules',
help='Executes the feature test for specific modules in pkg'
)
+ parser.add_argument('--parallel', nargs='?', const=True,
+ type=bool, default=False,
+ help='Enable parallel Feature Tests')
arg = parser.parse_args()
return arg
@@ -404,117 +413,213 @@ class StreamToLogger(object):
pass
-if __name__ == '__main__':
- # Failure detected?
- failure = False
- test_result = dict()
- cov = None
+def execute_test(test_module_list_passed, server_passed, driver_passed):
+ """
+ Function executes actually test
+ :param test_module_list_passed:
+ :param server_passed:
+ :param driver_passed:
+ :return:
+ """
+ try:
+ print("\n=============Running the test cases for '%s' ============="
+ % server_passed['name'], file=sys.stderr)
+ # Create test server
+ server_information = \
+ test_utils.create_parent_server_node(server_passed)
+
+ # Create test database with random number to avoid conflict in
+ # parallel execution on different platforms. This database will be
+ # used across all feature tests.
+ test_db_name = "acceptance_test_db" + \
+ str(random.randint(10000, 65535))
+ connection = test_utils.get_db_connection(
+ server_passed['db'],
+ server_passed['username'],
+ server_passed['db_password'],
+ server_passed['host'],
+ server_passed['port'],
+ server_passed['sslmode']
+ )
- # Set signal handler for cleanup
- signal_list = dir(signal)
- required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
- # Get the OS wise supported signals
- supported_signal_list = [sig for sig in required_signal_list if
- sig in signal_list]
- for sig in supported_signal_list:
- signal.signal(getattr(signal, sig), sig_handler)
+ # Add the server version in server information
+ server_information['server_version'] = connection.server_version
+ server_information['type'] = server_passed['type']
+
+ # Drop the database if already exists.
+ test_utils.drop_database(connection, test_db_name)
+
+ # Create database
+ test_utils.create_database(server_passed, test_db_name)
+
+ # Configure preferences for the test cases
+ test_utils.configure_preferences(
+ default_binary_path=server_passed['default_binary_paths'])
+
+ # Get unit test suit
+ suite = get_suite(test_module_list_passed,
+ server_passed,
+ test_client,
+ server_information, test_db_name, driver_passed)
+
+ # Run unit test suit created
+ tests = unittest.TextTestRunner(stream=sys.stderr,
+ descriptions=True,
+ verbosity=2).run(suite)
+
+ # processing results
+ ran_tests, failed_cases, skipped_cases, passed_cases = \
+ get_tests_result(tests)
+
+ # This is required when some tests are running parallel
+ # & some sequential in case of parallel ui tests
+ if threading.current_thread().getName() == "sequential_tests":
+ try:
+ if test_result[server_passed['name']][0] is not None:
+ ran_tests = test_result[server_passed['name']][0] + \
+ ran_tests
+ failed_cases.update(test_result[server_passed['name']][1])
+ skipped_cases.update(test_result[server_passed['name']][2])
+ passed_cases.update(test_result[server_passed['name']][3])
+ test_result[server_passed['name']] = [ran_tests, failed_cases,
+ skipped_cases,
+ passed_cases]
+ except KeyError:
+ pass
+
+ # Add final results server wise in test_result dict
+ test_result[server_passed['name']] = [ran_tests, failed_cases,
+ skipped_cases, passed_cases]
+
+ # Set empty list for 'passed' parameter for each testRun.
+ # So that it will not append same test case name
+ # unittest.result.TestResult.passed = []
+
+ # Drop the testing database created initially
+ if connection:
+ test_utils.drop_database(connection, test_db_name)
+ connection.close()
- # Set basic logging configuration for log file
- fh = logging.FileHandler(CURRENT_PATH + '/' +
- 'regression.log', 'w', 'utf-8')
- fh.setLevel(logging.DEBUG)
- fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT))
+ # Delete test server
+ test_utils.delete_test_server(test_client)
+ except Exception as exc:
+ traceback.print_exc(file=sys.stderr)
+ print(str(exc))
+ print("Exception in {0}".format(threading.current_thread().ident))
+ finally:
+ # Delete web-driver instance
+ thread_name = "parallel_tests" + server_passed['name']
+ if threading.currentThread().getName() == thread_name:
+ driver_passed.quit()
+ time.sleep(20)
+
+ # Print info about completed tests
+ print(
+ "\n=============Completed the test cases for '%s'============="
+ % server_passed['name'], file=sys.stderr)
- logger = logging.getLogger()
- logger.addHandler(fh)
- # Create logger to write log in the logger file as well as on console
- stderr_logger = logging.getLogger('STDERR')
- sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
- args = vars(add_arguments())
- # Get test module list
+def run_parallel_tests(url_client, servers_details, parallel_tests_lists,
+ name_of_browser, version_of_browser, max_thread_count):
+ """
+ Function used to run tests in parallel
+ :param url_client:
+ :param servers_details:
+ :param parallel_tests_lists:
+ :param name_of_browser:
+ :param version_of_browser:
+ :param max_thread_count:
+ """
+ driver_object = None
try:
- test_module_list = get_test_modules(args)
- except Exception as e:
- print(str(e))
- sys.exit(1)
- # Login the test client
- test_utils.login_tester_account(test_client)
+ # Thread list
+ threads_list = []
+ # Create thread for each server
+ for ser in servers_details:
+ # Logic to add new threads
+ while True:
+ # If active thread count <= max_thread_count, add new thread
+ if threading.activeCount() <= max_thread_count:
+ # Get remote web-driver instance at server level
+ driver_object = \
+ test_utils.get_remote_webdriver(hub_url,
+ name_of_browser,
+ version_of_browser,
+ ser['name'])
+ # Launch client url in browser
+ test_utils.launch_url_in_browser(driver_object, url_client)
+
+ # Add name for thread
+ thread_name = "parallel_tests" + ser['name']
+
+ # Start thread
+ t = threading.Thread(target=execute_test, name=thread_name,
+ args=(parallel_tests_lists, ser,
+ driver_object))
+ threads_list.append(t)
+ t.start()
+ time.sleep(3)
+ break
+ # else sleep for 10 seconds
+ else:
+ time.sleep(10)
+
+ # Start threads in parallel
+ for t in threads_list:
+ t.join()
+ except Exception as exc:
+ # Print exception stack trace
+ traceback.print_exc(file=sys.stderr)
+ print(str(exc))
+ # Clean driver object created
+ if driver_object is not None:
+ driver_object.quit()
- servers_info = test_utils.get_config_data()
- node_name = "all"
- if args['pkg'] is not None:
- node_name = args['pkg'].split('.')[-1]
-
- # Start coverage
- if test_utils.is_coverage_enabled(args):
- cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
- cov.start()
+def run_sequential_tests(url_client, servers_details, sequential_tests_lists,
+ name_of_browser, version_of_browser):
+ """
+ Function is used to execute tests that needs to be run in sequential
+ manner.
+ :param url_client:
+ :param servers_details:
+ :param sequential_tests_lists:
+ :param name_of_browser:
+ :param version_of_browser:
+ :return:
+ """
+ driver_object = None
try:
- for server in servers_info:
- print("\n=============Running the test cases for '%s'============="
- % server['name'], file=sys.stderr)
- # Create test server
- server_information = test_utils.create_parent_server_node(server)
-
- # Create test database with random number to avoid conflict in
- # parallel execution on different platforms. This database will be
- # used across all feature tests.
- test_db_name = "acceptance_test_db" + \
- str(random.randint(10000, 65535))
- connection = test_utils.get_db_connection(
- server['db'],
- server['username'],
- server['db_password'],
- server['host'],
- server['port'],
- server['sslmode']
- )
-
- # Add the server version in server information
- server_information['server_version'] = connection.server_version
- server_information['type'] = server['type']
-
- # Drop the database if already exists.
- test_utils.drop_database(connection, test_db_name)
- # Create database
- test_utils.create_database(server, test_db_name)
- # Configure preferences for the test cases
- test_utils.configure_preferences(
- default_binary_path=server['default_binary_paths'])
-
- suite = get_suite(test_module_list,
- server,
- test_client,
- server_information, test_db_name)
- tests = unittest.TextTestRunner(stream=sys.stderr,
- descriptions=True,
- verbosity=2).run(suite)
-
- ran_tests, failed_cases, skipped_cases, passed_cases = \
- get_tests_result(tests)
- test_result[server['name']] = [ran_tests, failed_cases,
- skipped_cases, passed_cases]
-
- # Set empty list for 'passed' parameter for each testRun.
- # So that it will not append same test case name
- unittest.result.TestResult.passed = []
-
- if len(failed_cases) > 0:
- failure = True
-
- # Drop the testing database created initially
- if connection:
- test_utils.drop_database(connection, test_db_name)
- connection.close()
-
- # Delete test server
- test_utils.delete_test_server(test_client)
- except SystemExit:
- if handle_cleanup:
- handle_cleanup()
+ # Get remote web-driver instance
+ driver_object = test_utils.get_remote_webdriver(hub_url,
+ name_of_browser,
+ version_of_browser,
+ "Sequential_Tests")
+
+ # Launch client url in browser
+ test_utils.launch_url_in_browser(driver_object, url_client)
+
+ # Add name for thread
+ thread_name = "sequential_tests"
+
+ # Start thread
+ for ser in servers_details:
+ t = threading.Thread(target=execute_test,
+ name=thread_name,
+ args=(sequential_tests_lists, ser,
+ driver_object))
+ t.start()
+ t.join()
+ except Exception as exc:
+ # Print exception stack trace
+ traceback.print_exc(file=sys.stderr)
+ print(str(exc))
+ finally:
+ # Clean driver object created
+ driver_object.quit()
+
+def print_test_results():
print(
"\n==============================================================="
"=======",
@@ -543,6 +648,10 @@ if __name__ == '__main__':
total_passed_cases = int(
test_result[server_res][0]) - total_failed - total_skipped
+ if len(failed_cases) > 0:
+ global failure
+ failure = True
+
print(
"%s:\n\n\t%s test%s passed\n\t%s test%s failed%s%s"
"\n\t%s test%s skipped%s%s\n" %
@@ -578,12 +687,162 @@ if __name__ == '__main__':
file=sys.stderr
)
+
+if __name__ == '__main__':
+ # Failure detected?
+ failure = False
+ test_result = dict()
+ cov = None
+
+ # Set signal handler for cleanup
+ signal_list = dir(signal)
+ required_signal_list = ['SIGTERM', 'SIGABRT', 'SIGQUIT', 'SIGINT']
+ # Get the OS wise supported signals
+ supported_signal_list = [sig for sig in required_signal_list if
+ sig in signal_list]
+ for sig in supported_signal_list:
+ signal.signal(getattr(signal, sig), sig_handler)
+
+ # Set basic logging configuration for log file
+ fh = logging.FileHandler(CURRENT_PATH + '/' +
+ 'regression.log', 'w', 'utf-8')
+ fh.setLevel(logging.DEBUG)
+ fh.setFormatter(logging.Formatter('[%(thread)d] ' +
+ config.FILE_LOG_FORMAT))
+
+ logger = logging.getLogger()
+ logger.addHandler(fh)
+
+ # Create logger to write log in the logger file as well as on console
+ stderr_logger = logging.getLogger('STDERR')
+ sys.stderr = StreamToLogger(stderr_logger, logging.ERROR)
+ args = vars(add_arguments())
+ # Get test module list
+ try:
+ test_module_list = get_test_modules(args)
+ except Exception as e:
+ print(str(e))
+ sys.exit(1)
+ # Login the test client
+ test_utils.login_tester_account(test_client)
+
+ servers_info = test_utils.get_config_data()
+ node_name = "all"
+ if args['pkg'] is not None:
+ node_name = args['pkg'].split('.')[-1]
+
+ # Start coverage
+ if test_utils.is_coverage_enabled(args):
+ cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE)
+ cov.start()
+
+ # Check if feature tests included & parallel tests switch passed
+ if test_utils.is_feature_test_included(args) and \
+ test_utils.is_parallel_ui_tests(args):
+
+ # Get selenium config dict
+ selenoid_config = test_setup.config_data['selenoid_config']
+
+ # Set DEFAULT_SERVER value
+ default_server = selenoid_config['pgAdmin_default_server']
+ os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"] = str(default_server)
+ config.DEFAULT_SERVER = str(default_server)
+
+ # Get hub url
+ hub_url = selenoid_config['selenoid_url']
+
+ # Get selenium grid status & list of available browser out passed
+ selenium_grid_status, list_of_browsers \
+ = test_utils.get_selenium_grid_status_and_browser_list(hub_url)
+
+ # Execute tests if selenium-grid is up
+ if selenium_grid_status and len(list_of_browsers) > 0:
+ app_starter_local = None
+ # run across browsers
+ for browser_info in list_of_browsers:
+ try:
+ # browser info
+ browser_name, browser_version = \
+ test_utils.get_browser_details(browser_info, hub_url)
+
+ # tests lists can be executed in parallel & sequentially
+ parallel_tests, sequential_tests = \
+ test_utils.get_parallel_sequential_module_list(
+ test_module_list)
+
+ # Print test summary
+ test_utils.print_test_summary(test_module_list,
+ parallel_tests,
+ sequential_tests,
+ browser_name,
+ browser_version)
+
+ # Create app form source code
+ app_starter_local = AppStarter(None, config)
+ client_url = app_starter_local.start_app()
+
+ # Running Parallel tests
+ if len(parallel_tests) > 0:
+ parallel_sessions = int(selenoid_config[
+ 'max_parallel_sessions'])
+
+ run_parallel_tests(client_url, servers_info,
+ parallel_tests, browser_name,
+ browser_version, parallel_sessions)
+
+ # Wait till all threads started in parallel are finished
+ while True:
+ try:
+ if threading.activeCount() <= 1:
+ break
+ else:
+ time.sleep(10)
+ except Exception as e:
+ traceback.print_exc(file=sys.stderr)
+ print(str(e))
+
+ # Sequential Tests
+ if len(sequential_tests) > 0:
+ run_sequential_tests(client_url, servers_info,
+ sequential_tests, browser_name,
+ browser_version)
+
+ # Clean up environment
+ if app_starter_local:
+ app_starter_local.stop_app()
+
+ except SystemExit:
+ if app_starter_local:
+ app_starter_local.stop_app()
+ if handle_cleanup:
+ handle_cleanup()
+ # Pause before printing result in order not to mix output
+ time.sleep(5)
+ # Print note for completion of execution in a browser.
+ print(
+ "\n============= Test execution with {0} is "
+ "completed.=============".format(browser_name),
+ file=sys.stderr)
+ print_test_results()
+ del os.environ["PGADMIN_CONFIG_DEFAULT_SERVER"]
+ else:
+ try:
+ for server in servers_info:
+ thread = threading.Thread(target=execute_test, args=(
+ test_module_list, server, driver))
+ thread.start()
+ thread.join()
+ except SystemExit:
+ if handle_cleanup:
+ handle_cleanup()
+ print_test_results()
+
# Stop code coverage
if test_utils.is_coverage_enabled(args):
cov.stop()
cov.save()
- # # Print coverage only if coverage args given in command line
+ # Print coverage only if coverage args given in command line
if test_utils.is_coverage_enabled(args):
test_utils.print_and_store_coverage_report(cov)
diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in
index af061b9..8207711 100644
--- a/web/regression/test_config.json.in
+++ b/web/regression/test_config.json.in
@@ -1,6 +1,14 @@
{
"headless_chrome": false,
"default_browser": "Chrome",
+ "selenoid_config":{
+ "pgAdmin_default_server":"IP address of machine where source code is going to be executed",
+ "max_parallel_sessions": "3",
+ "selenoid_url": ""http://<IP address of Selenoid Installed machine>:4444/wd/hub",
+ "browsers_list": [
+ {"name": "Chrome","version":null},
+ {"name": "Firefox","version":null} ]
+ },
"pgAdmin4_login_credentials": {
"new_password": "NEWPASSWORD",
"login_password": "PASSWORD",
view thread (14+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected]
Subject: Re: [pgAdmin 4 - Housekeeping #5255] Implement Selenium Grid using multi-threading & solenoid using current test framework
In-Reply-To: <CAMa=N=N+JeysXRvC40_c2MnT3FDk14mknU-D-eDbtd_d_29fmw@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox