From c6eb30a1e61af4a53607901425bc86f8e941b628 Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Thu, 5 Mar 2020 17:10:06 +0200 Subject: [PATCH 01/23] toolkit version bump to 2.0.0 --- app/util/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/util/conf.py b/app/util/conf.py index dd20dba11..b871fb7fa 100644 --- a/app/util/conf.py +++ b/app/util/conf.py @@ -2,7 +2,7 @@ from util.project_paths import JIRA_YML, CONFLUENCE_YML, BITBUCKET_YML -TOOLKIT_VERSION = '1.3.0' +TOOLKIT_VERSION = '2.0.0' def read_yml_file(file): From 882d427fd3666927f96e501ed7da4271e2bd66eb Mon Sep 17 00:00:00 2001 From: mmizin Date: Fri, 6 Mar 2020 14:16:15 +0200 Subject: [PATCH 02/23] DCA-332: Add a check if git was installed --- app/util/environment_checker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/util/environment_checker.py b/app/util/environment_checker.py index 96339855a..1d88d4da6 100644 --- a/app/util/environment_checker.py +++ b/app/util/environment_checker.py @@ -1,4 +1,5 @@ from sys import version_info +import subprocess from util.conf import TOOLKIT_VERSION @@ -13,3 +14,7 @@ raise Exception( "Python version {} is not supported. " "Please use Python version {} or higher.".format(python_version, MIN_SUPPORTED_PYTHON_VERSION)) + +git_exist = subprocess.getstatusoutput("git --version")[0] +if git_exist == 1 or git_exist == 127: + raise Exception("Git is not installed") From a730de5cc94d426b5f011b3efaf18b988b37c35f Mon Sep 17 00:00:00 2001 From: mmizin Date: Mon, 9 Mar 2020 13:13:37 +0200 Subject: [PATCH 03/23] DCA-332: rewrite check git installation for lower python version --- app/util/environment_checker.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/util/environment_checker.py b/app/util/environment_checker.py index 1d88d4da6..9a81d5b0e 100644 --- a/app/util/environment_checker.py +++ b/app/util/environment_checker.py @@ -1,3 +1,4 @@ +import errno from sys import version_info import subprocess @@ -15,6 +16,8 @@ "Python version {} is not supported. " "Please use Python version {} or higher.".format(python_version, MIN_SUPPORTED_PYTHON_VERSION)) -git_exist = subprocess.getstatusoutput("git --version")[0] -if git_exist == 1 or git_exist == 127: - raise Exception("Git is not installed") +try: + subprocess.call("git") +except OSError as e: + if e.errno == errno.ENOENT: + raise Exception("Git is not installed") \ No newline at end of file From 4b6610ff2d984e380fd9ff8eca6dd2aaf57d1903 Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Tue, 10 Mar 2020 15:43:49 +0200 Subject: [PATCH 04/23] use new Elasticsearch disk-space per node (GB) parametr --- ...-apps-performance-toolkit-user-guide-bitbucket.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 0eb6b3783..2a2527043 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -104,7 +104,8 @@ The **Master (admin) password** will be used later when restoring the SQL databa | Parameter | Recommended Value | | --------- | ----------------- | -| Elasticsearch instance type | m4.xlarge.elasticsearch| +| Elasticsearch instance type | m4.xlarge.elasticsearch | +| Elasticsearch disk-space per node (GB) | 1000 | **Networking (for new ASI)** @@ -295,14 +296,7 @@ After [Importing the main dataset](#importingdataset), you'll now have to pre-lo {{% note %}} Do not close or interrupt the session. It will take about two hours to upload attachments to Elastic File Storage (EFS). {{% /note %}} - - -### Increase Elasticsearch Service EBS volume size -Elasticsearch EBS volume size has to be increased in order to generate index needed for search functionality. - -1. In the AWS console, go to **Services > Elasticsearch Service > Select your domain**. -1. Click **Edit domain** button, go to the **Storage configuration** section, set **750** GiB to the **EBS storage size per node** field. -1. Click **Submit** button. + ### Start Bitbucket Server 1. Using SSH, connect to the Bitbucket node via the Bastion instance: From fd482230f06b3cc9bf60b6992faaee123a6f0529 Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Tue, 10 Mar 2020 16:04:24 +0200 Subject: [PATCH 05/23] update docs --- docs/dc-apps-performance-toolkit-user-guide-bitbucket.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 2a2527043..5178cde79 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -112,7 +112,7 @@ The **Master (admin) password** will be used later when restoring the SQL databa | Parameter | Recommended Value | | --------- | ----------------- | | Trusted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | -| Availability Zones | _Select two availability zones in your region. Both zones must support EFS (see [Supported AWS regions](https://confluence.atlassian.com/enterprise/getting-started-with-jira-data-center-on-aws-969535550.html#GettingstartedwithJiraDataCenteronAWS-SupportedAWSregions) for details)._ | +| Availability Zones | _Select two availability zones in your region_ | | Permitted IP range | 0.0.0.0/0 _(for public access) or your own trusted IP range_ | | Make instance internet facing | true | | Key Name | _The EC2 Key Pair to allow SSH access. See [Amazon EC2 Key Pairs](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html) for more info._ | @@ -294,7 +294,7 @@ After [Importing the main dataset](#importingdataset), you'll now have to pre-lo ``` {{% note %}} -Do not close or interrupt the session. It will take about two hours to upload attachments to Elastic File Storage (EFS). +Do not close or interrupt the session. It will take about two hours to upload attachments. {{% /note %}} From 46ed9fe25a63d06d352b24e75755553fe2d7dce7 Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Tue, 10 Mar 2020 17:45:56 +0200 Subject: [PATCH 06/23] refactoring --- app/bitbucket.yml | 1 + app/util/environment_checker.py | 17 +++++------------ app/util/git_client_check.py | 7 +++++++ 3 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 app/util/git_client_check.py diff --git a/app/bitbucket.yml b/app/bitbucket.yml index 6a5ceb0a4..c9003cfdd 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -19,6 +19,7 @@ services: - module: shellexec prepare: - python util/environment_checker.py + - python util/git_client_check.py - python util/data_preparation/bitbucket/prepare-data.py shutdown: - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl diff --git a/app/util/environment_checker.py b/app/util/environment_checker.py index 9a81d5b0e..8e19ac680 100644 --- a/app/util/environment_checker.py +++ b/app/util/environment_checker.py @@ -1,14 +1,7 @@ -import errno from sys import version_info -import subprocess - -from util.conf import TOOLKIT_VERSION MIN_SUPPORTED_PYTHON_VERSION = (3, 6, 0) - -print("Data Center App Performance Toolkit version: {}".format(TOOLKIT_VERSION)) - python_version = version_info[0:3] print("Python version: {}".format(python_version)) if python_version < MIN_SUPPORTED_PYTHON_VERSION: @@ -16,8 +9,8 @@ "Python version {} is not supported. " "Please use Python version {} or higher.".format(python_version, MIN_SUPPORTED_PYTHON_VERSION)) -try: - subprocess.call("git") -except OSError as e: - if e.errno == errno.ENOENT: - raise Exception("Git is not installed") \ No newline at end of file + +# Print toolkit version after Python check +from util.conf import TOOLKIT_VERSION + +print("Data Center App Performance Toolkit version: {}".format(TOOLKIT_VERSION)) diff --git a/app/util/git_client_check.py b/app/util/git_client_check.py new file mode 100644 index 000000000..34bb0db6f --- /dev/null +++ b/app/util/git_client_check.py @@ -0,0 +1,7 @@ +from subprocess import check_output + + +try: + print("Git version: {}".format(check_output(["git", "--version"]))) +except Exception as e: + raise Exception("Please check if git installed and available from command line.") From d0b9b728f0f3e9480610a0ac01f7f0550dc27c9b Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Tue, 10 Mar 2020 17:50:32 +0200 Subject: [PATCH 07/23] lint fixes --- app/selenium_ui/bitbucket/modules.py | 1 - app/util/git_client_check.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/selenium_ui/bitbucket/modules.py b/app/selenium_ui/bitbucket/modules.py index c12387142..030385935 100644 --- a/app/selenium_ui/bitbucket/modules.py +++ b/app/selenium_ui/bitbucket/modules.py @@ -223,7 +223,6 @@ def measure(webdriver, interaction): _wait_until(webdriver, ec.visibility_of_element_located(( By.ID, 'targetBranch')), interaction) webdriver.execute_script("document.querySelector('#targetBranch').click()") - #safe_click(webdriver, By.ID, 'targetBranch', interaction) _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'targetBranchDialog')), interaction) branch_name_to_dropdown = webdriver.find_element_by_id('targetBranchDialog-search-input') branch_name_to_dropdown.send_keys(f'{random_repo_with_pr[3]}') diff --git a/app/util/git_client_check.py b/app/util/git_client_check.py index 34bb0db6f..5d4cb46d2 100644 --- a/app/util/git_client_check.py +++ b/app/util/git_client_check.py @@ -3,5 +3,5 @@ try: print("Git version: {}".format(check_output(["git", "--version"]))) -except Exception as e: +except Exception: raise Exception("Please check if git installed and available from command line.") From c3b27393d0c083922251f953124a1b8dc2bb23dd Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Wed, 18 Mar 2020 17:14:25 +0200 Subject: [PATCH 08/23] Dca 361 bbs 7 (#161) * bbs7 support * fixes * refactoring --- README.md | 11 +- .../jira/examples/drawio/extension_ui.py | 10 +- app/selenium_ui/bitbucket/modules.py | 198 ++++++++++++++---- app/util/analytics.py | 2 +- app/util/bitbucket/populate_db.sh | 11 +- app/util/bitbucket/upload_attachments.sh | 4 +- 6 files changed, 174 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 2f95c33c1..499defc3b 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ At the moment, Jira DC, Confluence DC and Bitbucket DC support is in beta. ## Supported versions * Supported Jira versions: - * The latest Platform Release: 8.0.3 - * The following Jira [Enterprise Releases](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.6 and 8.5.0 + * Jira [Enterprise Releases](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.6 and 8.5.0 + * Jira Platform Release: 8.0.3 * Supported Confluence versions: - * The latest Confluence [Enterprise Release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.8 - * The latest Confluence Platform Release: 7.0.4 + * Confluence [Enterprise Release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.13.8 + * Confluence Platform Release: 7.0.4 * Supported Bitbucket Server versions: - * The latest Bitbucket Server [Enterprise Release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.0 + * Bitbucket Server [Enterprise Release](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 6.10.0 + * Bitbucket Server Platform Release: 7.0.0 ## Support In case of technical questions, issues or problems with DC Apps Performance Toolkit, contact us for support in the [community Slack](http://bit.ly/dcapt_slack) **#data-center-app-performance-toolkit** channel. diff --git a/app/extension/jira/examples/drawio/extension_ui.py b/app/extension/jira/examples/drawio/extension_ui.py index d891324c7..348f54652 100644 --- a/app/extension/jira/examples/drawio/extension_ui.py +++ b/app/extension/jira/examples/drawio/extension_ui.py @@ -1,9 +1,10 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.wait import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from jira.selenium_ui.conftest import print_timing, application_url +from selenium.webdriver.support import expected_conditions as ec +from app.selenium_ui.conftest import print_timing +from util.conf import JIRA_SETTINGS -APPLICATION_URL = application_url() +APPLICATION_URL = JIRA_SETTINGS.server_url timeout = 20 @@ -11,9 +12,10 @@ def custom_action(webdriver, datasets): # Click more webdriver.find_element_by_id('opsbar-operations_more').click() + @print_timing def measure(webdriver, interaction): # Click to add a diagram (opens the drawio editor) webdriver.find_element_by_id('drawio-add-menu-item').click() - WebDriverWait(webdriver, timeout).until(EC.frame_to_be_available_and_switch_to_it((By.ID, 'drawioEditor'))) + WebDriverWait(webdriver, timeout).until(ec.frame_to_be_available_and_switch_to_it((By.ID, 'drawioEditor'))) measure(webdriver, 'selenium_open_drawio_editor') diff --git a/app/selenium_ui/bitbucket/modules.py b/app/selenium_ui/bitbucket/modules.py index c0e3da734..c1ed6b04b 100644 --- a/app/selenium_ui/bitbucket/modules.py +++ b/app/selenium_ui/bitbucket/modules.py @@ -15,6 +15,46 @@ PROJECTS_URL = f"{BITBUCKET_SETTINGS.server_url}/projects" timeout = 20 +# TODO Move to a page objects DCA-53 +SELECTORS = { + 'activity_selector': { + 'v6': (By.CSS_SELECTOR, ".pull-request-activity-content"), + 'v7': (By.CSS_SELECTOR, ".pull-request-activities") + }, + 'comment_text_area_selector': { + 'v6': (By.CSS_SELECTOR, "textarea.text"), + 'v7': (By.CLASS_NAME, "comment-editor-wrapper") + }, + 'text_area_selector': { + 'v6': (By.CSS_SELECTOR, 'textarea.text'), + 'v7': (By.CLASS_NAME, 'CodeMirror-code') + }, + 'comment_button_selector': { + 'v6': (By.CSS_SELECTOR, "div.buttons>button:nth-child(1)"), + 'v7': (By.CSS_SELECTOR, "div.editor-controls>button:nth-child(1)") + }, + 'code_area_selector': { + 'v6': (By.CLASS_NAME, "CodeMirror-code"), + 'v7': (By.CLASS_NAME, "diff-segment") + }, + 'inline_comment_button_selector': { + 'v6': (By.CSS_SELECTOR, "button.add-comment-trigger>span.aui-iconfont-add-comment"), + 'v7': (By.CSS_SELECTOR, ".diff-line-comment-trigger") + }, + 'diagram_selector': { + 'v6': (By.CSS_SELECTOR, 'div.diagram-image'), + 'v7': (By.CLASS_NAME, 'branches-diagram') + }, + 'merge_pr_button_selector': { + 'v6': (By.CSS_SELECTOR, 'button.confirm-button'), + 'v7': (By.CSS_SELECTOR, "button[type='submit']") + }, + 'del_branch_checkbox_selector': { + 'v6': (By.CSS_SELECTOR, 'span.pull-request-cleanup-checkbox-wrapper'), + 'v7': (By.NAME, 'deleteSourceRef') + } +} + def _dismiss_popup(webdriver, *args): for elem in args: @@ -24,6 +64,18 @@ def _dismiss_popup(webdriver, *args): pass +def get_selector(selector_name, version): + selector_dict = SELECTORS.get(selector_name) + if selector_dict is None: + raise Exception(f'Selector {selector_name} not defined.') + if type(selector_dict) != dict: + raise Exception(f'Selector {selector_name} is not dictionary') + selector = selector_dict.get(version) + if selector is None: + raise Exception(f'Selector {selector_name} for version {version} is not found') + return selector + + def login(webdriver, datasets): user = random.choice(datasets["users"]) datasets['current_user_id'] = int(user[0]) - 1 @@ -41,6 +93,9 @@ def measure(webdriver, interaction): def measure(webdriver, interaction): webdriver.get(f'{LOGIN_URL}') + # Get application major version e.g. v7 or v6 + webdriver.app_version = webdriver.find_element_by_id('product-version').text.split('.')[0] + measure(webdriver, "selenium_login:open_login_page") webdriver.find_element_by_id('j_username').send_keys(user[1]) webdriver.find_element_by_id('j_password').send_keys(user[2]) @@ -49,7 +104,8 @@ def measure(webdriver, interaction): def measure(webdriver, interaction): _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "submit")), interaction).click() - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "marketing-page-footer")), interaction) + _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "marketing-page-footer")), + interaction) measure(webdriver, "selenium_login:login_get_started") measure(webdriver, "selenium_login") @@ -73,6 +129,7 @@ def measure(webdriver, interaction): def view_project_repos(webdriver, datasets): project_url = f"{PROJECTS_URL}/{datasets['current_project_key']}" + @print_timing def measure(webdriver, interaction): webdriver.get(f"{project_url}") @@ -84,6 +141,7 @@ def measure(webdriver, interaction): def view_repo(webdriver, datasets): repo_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/browse" + @print_timing def measure(webdriver, interaction): webdriver.get(f"{repo_url}") @@ -93,7 +151,9 @@ def measure(webdriver, interaction): def view_list_pull_requests(webdriver, datasets): - repo_pr_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests" + repo_pr_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests") + @print_timing def measure(webdriver, interaction): webdriver.get(f"{repo_pr_url}") @@ -102,79 +162,109 @@ def measure(webdriver, interaction): def view_pull_request_overview_tab(webdriver, datasets): - pull_requests_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview" - webdriver.get(pull_requests_url) + pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview") @print_timing def measure(webdriver, interaction): webdriver.get(pull_requests_url) _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, "ul.tabs-menu")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') measure(webdriver, 'selenium_view_pull_request_overview') def view_pull_request_diff_tab(webdriver, datasets): - pull_requests_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff" + pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff") + @print_timing def measure(webdriver, interaction): webdriver.get(pull_requests_url) - diff_tab = ".diff-tree-toolbar" - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, diff_tab)), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + _wait_until(webdriver, ec.visibility_of_any_elements_located((By.LINK_TEXT, 'Diff')), interaction) + _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') measure(webdriver, 'selenium_view_pull_request_diff') def view_pull_request_commits_tab(webdriver, datasets): - pull_requests_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/commits" + pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/commits") + @print_timing def measure(webdriver, interaction): webdriver.get(pull_requests_url) _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, "tr>th.message")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') measure(webdriver, 'selenium_view_pull_request_commits') def comment_pull_request_diff(webdriver, datasets): - pull_requests_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff" + pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff") webdriver.get(pull_requests_url) @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, ".diff-tree-toolbar")), - interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CLASS_NAME, "CodeMirror-code")), + _wait_until(webdriver, ec.visibility_of_element_located((By.LINK_TEXT, "Diff")), interaction) + _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') + + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('code_area_selector', webdriver.app_version)), interaction) - webdriver.execute_script(f"elems=document.querySelectorAll('button.add-comment-trigger>span.aui-iconfont-add-comment'); " + + css_selector = get_selector('inline_comment_button_selector', webdriver.app_version)[1] + webdriver.execute_script(f"elems=document.querySelectorAll('{css_selector}'); " "item=elems[Math.floor(Math.random() * elems.length)];" "item.scrollIntoView();" - "item.click();" - "document.querySelector('textarea.text').value = 'Comment from Selenium script';") - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, "textarea.text")), - interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, "div.buttons>button:nth-child(1)")), + "item.click();") + + if 'v6' in webdriver.app_version: + css_selector = get_selector('comment_text_area_selector', webdriver.app_version)[1] + webdriver.execute_script( + f"document.querySelector('{css_selector}').value = 'Comment from Selenium script';") + elif 'v7' in webdriver.app_version: + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('text_area_selector', webdriver.app_version)), + interaction).send_keys('Comment from Selenium script') + + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('comment_button_selector', webdriver.app_version)), interaction).click() + measure(webdriver, 'selenium_comment_pull_request_file') def comment_pull_request_overview(webdriver, datasets): - pull_requests_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview" + pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview") webdriver.get(pull_requests_url) @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, ".pull-request-activity-content")), + + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), interaction) - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - comment_text_area = webdriver.find_element_by_css_selector('textarea.text') - comment_text_area.click() - comment_text_area.send_keys(f"{generate_random_string(50)}") - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, "div.buttons>button:nth-child(1)")), + _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close', '.css-1it7f5o') + + _wait_until(webdriver, + ec.element_to_be_clickable(get_selector('comment_text_area_selector', webdriver.app_version)), interaction).click() + + _wait_until(webdriver, + ec.element_to_be_clickable(get_selector('text_area_selector', webdriver.app_version)), + interaction).send_keys(f"{generate_random_string(50)}") + + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('comment_button_selector', webdriver.app_version)), + interaction).click() + measure(webdriver, 'selenium_comment_pull_request_overview') def view_branches(webdriver, datasets): - repo_branches_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/branches" + repo_branches_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" + f"repos/{datasets['current_repo_slug']}/branches") + @print_timing def measure(webdriver, interaction): webdriver.get(repo_branches_url) @@ -207,7 +297,8 @@ def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - safe_click(webdriver, By.CSS_SELECTOR, '.aui-sidebar-group.sidebar-navigation>ul>li:nth-child(4)', interaction) + safe_click(webdriver, By.CSS_SELECTOR, '.aui-sidebar-group.sidebar-navigation>ul>li:nth-child(4)', + interaction) _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'pull-requests-content')), interaction) safe_click(webdriver, By.ID, 'empty-list-create-pr-button', interaction) _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'branch-compare')), interaction) @@ -219,7 +310,9 @@ def measure(webdriver, interaction): _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, '#sourceBranchDialog>div.results>div.spinner-wrapper')), interaction) branch_name_from_dropdown.send_keys(Keys.ENTER) - _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), interaction) + _wait_until(webdriver, + ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), + interaction) # Choose project source safe_click(webdriver, By.ID, 'targetRepo', interaction) safe_click(webdriver, By.CSS_SELECTOR, "div#targetRepoDialog>div>ul.results-list>li:nth-child(1)", @@ -234,16 +327,19 @@ def measure(webdriver, interaction): _wait_until(webdriver, ec.invisibility_of_element_located( (By.CSS_SELECTOR, '#targetBranchDialog>div.results>div.spinner-wrapper')), interaction) branch_name_to_dropdown.send_keys(Keys.ENTER) - _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), interaction) + _wait_until(webdriver, + ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), interaction) _wait_until(webdriver, ec.element_to_be_clickable((By.ID, 'show-create-pr-button')), interaction) safe_click(webdriver, By.ID, 'show-create-pr-button', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, 'textarea#pull-request-description')), + _wait_until(webdriver, + ec.visibility_of_element_located((By.CSS_SELECTOR, 'textarea#pull-request-description')), interaction) webdriver.find_element_by_id('title').clear() webdriver.find_element_by_id('title').send_keys('Selenium test pull request') safe_click(webdriver, By.ID, 'submit-form', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, 'div.activity-item-content')), + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), interaction) _wait_until(webdriver, ec.element_to_be_clickable((By.CSS_SELECTOR, 'button.merge-button')), interaction) @@ -253,19 +349,31 @@ def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, 'div.activity-item-content')), + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), interaction) _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - _wait_until(webdriver, ec.element_to_be_clickable((By.CSS_SELECTOR, 'button.merge-button')), - interaction) + if 'v6' in webdriver.app_version: + _wait_until(webdriver, + ec.presence_of_element_located((By.CSS_SELECTOR, "aui-spinner[size='small']")), + interaction, + time_out=1) + _wait_until(webdriver, + ec.invisibility_of_element_located((By.CSS_SELECTOR, "aui-spinner[size='small']")), + interaction) + _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, 'merge-button')), + interaction).click() _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - while not get_element_or_none(webdriver, By.CSS_SELECTOR, 'div.diagram-image'): - webdriver.execute_script("document.querySelector('button.merge-button').click()") - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, 'div.diagram-image')), + _wait_until(webdriver, + ec.visibility_of_element_located(get_selector('diagram_selector', webdriver.app_version)), + interaction) + _wait_until(webdriver, + ec.element_to_be_clickable(get_selector('merge_pr_button_selector', webdriver.app_version)), + interaction).click() + _wait_until(webdriver, + ec.invisibility_of_element_located( + get_selector('del_branch_checkbox_selector', webdriver.app_version)), interaction) - safe_click(webdriver, By.CSS_SELECTOR, 'button.confirm-button', interaction) - _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, - 'span.pull-request-cleanup-checkbox-wrapper')), interaction) measure(webdriver, 'selenium_create_pull_request:merge_pull_request') @print_timing @@ -286,6 +394,7 @@ def measure(webdriver, interaction): def view_commits(webdriver, datasets): repo_commits_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/commits" + @print_timing def measure(webdriver, interaction): webdriver.get(repo_commits_url) @@ -344,7 +453,6 @@ def get_random_repo_with_pr(datasets): return random.choice(repos_with_prs) - def get_element_or_none(webdriver, by, element): try: element = webdriver.find_element(by, element) @@ -354,4 +462,4 @@ def get_element_or_none(webdriver, by, element): class NoPullRequestFoundException(Exception): - pass \ No newline at end of file + pass diff --git a/app/util/analytics.py b/app/util/analytics.py index ee6e6d5d7..14d393562 100644 --- a/app/util/analytics.py +++ b/app/util/analytics.py @@ -179,7 +179,7 @@ def get_confluence_version(self): def get_bitbucket_version(self): client = BitbucketRestClient(host=self.config_yml.server_url, user=self.config_yml.admin_login, - password=self.config_yml.admin_password) + password=self.config_yml.admin_password) return client.get_bitbucket_version() def get_application_version(self): diff --git a/app/util/bitbucket/populate_db.sh b/app/util/bitbucket/populate_db.sh index d06c62914..0bd607153 100644 --- a/app/util/bitbucket/populate_db.sh +++ b/app/util/bitbucket/populate_db.sh @@ -21,7 +21,7 @@ BITBUCKET_DB_USER="postgres" BITBUCKET_DB_PASS="Password1!" # BITBUCKET version variables -SUPPORTED_BITBUCKET_VERSIONS=(6.10.0) +SUPPORTED_BITBUCKET_VERSIONS=(6.10.0 7.0.0) BITBUCKET_VERSION=$(sudo su bitbucket -c "cat ${BITBUCKET_VERSION_FILE}") echo "Bitbucket version: ${BITBUCKET_VERSION}" @@ -39,7 +39,7 @@ if [[ ! "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "${BITBUCKET_VERSION}" ]]; then echo "Bitbucket Version: ${BITBUCKET_VERSION} is not officially supported by Data Center App Performance Toolkit." echo "Supported Bitbucket Versions: ${SUPPORTED_BITBUCKET_VERSIONS[@]}" echo "If you want to force apply an existing datasets to your Bitbucket, use --force flag with version of dataset you want to apply:" - echo "e.g. ./populate_db.sh --force 6.8.0" + echo "e.g. ./populate_db.sh --force 6.10.0" echo "!!! Warning !!! This may break your Bitbucket instance. Also, note that downgrade is not supported by Bitbucket." # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then @@ -137,25 +137,26 @@ if [[ $? -ne 0 ]]; then exit 1 fi echo "Drop DB" -PGPASSWORD=${BITBUCKET_DB_PASS} dropdb -U ${BITBUCKET_DB_USER} -h ${DB_HOST} ${BITBUCKET_DB_NAME} +sudo su -c "PGPASSWORD=${BITBUCKET_DB_PASS} dropdb -U ${BITBUCKET_DB_USER} -h ${DB_HOST} ${BITBUCKET_DB_NAME}" if [[ $? -ne 0 ]]; then echo "Drop DB failed. Please make sure you stop Bitbucket." exit 1 fi sleep 5 echo "Create DB" -PGPASSWORD=${BITBUCKET_DB_PASS} createdb -U ${BITBUCKET_DB_USER} -h ${DB_HOST} -T template0 ${BITBUCKET_DB_NAME} +sudo su -c "PGPASSWORD=${BITBUCKET_DB_PASS} createdb -U ${BITBUCKET_DB_USER} -h ${DB_HOST} -T template0 ${BITBUCKET_DB_NAME}" if [[ $? -ne 0 ]]; then echo "Create DB failed." exit 1 fi sleep 5 echo "PG Restore" -sudo su bitbucket -c "time PGPASSWORD=${BITBUCKET_DB_PASS} pg_restore -v -j 8 -U ${BITBUCKET_DB_USER} -h ${DB_HOST} -d ${BITBUCKET_DB_NAME} ${DUMP_DIR}/${DB_DUMP_NAME}" +sudo su -c "time PGPASSWORD=${BITBUCKET_DB_PASS} pg_restore -v -j 8 -U ${BITBUCKET_DB_USER} -h ${DB_HOST} -d ${BITBUCKET_DB_NAME} ${DUMP_DIR}/${DB_DUMP_NAME}" if [[ $? -ne 0 ]]; then echo "SQL Restore failed!" exit 1 fi +sudo su -c "rm -rf ${DUMP_DIR}/${DB_DUMP_NAME}" echo "Finished" echo # move to a new line diff --git a/app/util/bitbucket/upload_attachments.sh b/app/util/bitbucket/upload_attachments.sh index 9bc76b7b4..c1241caf4 100644 --- a/app/util/bitbucket/upload_attachments.sh +++ b/app/util/bitbucket/upload_attachments.sh @@ -6,7 +6,7 @@ pgrep nfsd > /dev/null && echo "NFS found" || (echo "NFS process was not found. ################### Variables section ################### # Bitbucket version variables BITBUCKET_VERSION_FILE="/media/atl/bitbucket/shared/bitbucket.version" -SUPPORTED_BITBUCKET_VERSIONS=(6.10.0) +SUPPORTED_BITBUCKET_VERSIONS=(6.10.0 7.0.0) BITBUCKET_VERSION=$(sudo su bitbucket -c "cat ${BITBUCKET_VERSION_FILE}") echo "Bitbucket Version: ${BITBUCKET_VERSION}" @@ -23,7 +23,7 @@ if [[ ! "${SUPPORTED_BITBUCKET_VERSIONS[@]}" =~ "${BITBUCKET_VERSION}" ]]; then echo "Bitbucket Version: ${BITBUCKET_VERSION} is not officially supported by Data Center App Peformance Toolkit." echo "Supported Bitbucket Versions: ${SUPPORTED_BITBUCKET_VERSIONS[@]}" echo "If you want to force apply an existing datasets to your BITBUCKET, use --force flag with version of dataset you want to apply:" - echo "e.g. ./upload_attachments --force 6.13.8" + echo "e.g. ./upload_attachments --force 6.10.0" echo "!!! Warning !!! This may broke your Bitbucket instance." # Check if --force flag is passed into command if [[ "$1" == "--force" ]]; then From 3a2040fa9208308ca74ffeb6503473e8d3a20f40 Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Fri, 20 Mar 2020 12:07:37 +0200 Subject: [PATCH 09/23] post env jmeter check (#162) --- app/bitbucket.yml | 1 + app/confluence.yml | 1 + app/jira.yml | 1 + app/util/jmeter_post_check.py | 25 +++++++++++++++++++++++++ app/util/jtl_convertor/jtl_validator.py | 2 +- 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 app/util/jmeter_post_check.py diff --git a/app/bitbucket.yml b/app/bitbucket.yml index 2968f9c20..fa9d71134 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -22,6 +22,7 @@ services: - python util/git_client_check.py - python util/data_preparation/bitbucket/prepare-data.py shutdown: + - python util/jmeter_post_check.py - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py bitbucket diff --git a/app/confluence.yml b/app/confluence.yml index 73853a203..18aebb131 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -21,6 +21,7 @@ services: - python util/environment_checker.py - python util/data_preparation/confluence/prepare-data.py shutdown: + - python util/jmeter_post_check.py - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py confluence diff --git a/app/jira.yml b/app/jira.yml index 88bf5a88e..6cda4e255 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -21,6 +21,7 @@ services: - python util/environment_checker.py - python util/data_preparation/jira/prepare-data.py shutdown: + - python util/jmeter_post_check.py - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py jira diff --git a/app/util/jmeter_post_check.py b/app/util/jmeter_post_check.py new file mode 100644 index 000000000..6f1faa517 --- /dev/null +++ b/app/util/jmeter_post_check.py @@ -0,0 +1,25 @@ +import os +from pathlib import Path +from shutil import rmtree + + +ENV_TAURUS_ARTIFACT_DIR = 'TAURUS_ARTIFACTS_DIR' +JMETER_JTL_FILE_NAME = 'kpi.jtl' + +artifacts_dir = os.getenv(ENV_TAURUS_ARTIFACT_DIR) +if artifacts_dir is None: + raise SystemExit(f'Error: env variable {ENV_TAURUS_ARTIFACT_DIR} is not set') + +jmeter_home_path = Path().home() / '.bzt' / 'jmeter-taurus' + +jmeter_jtl_file = f"{artifacts_dir}/{JMETER_JTL_FILE_NAME}" + +if not os.path.exists(jmeter_jtl_file): + if jmeter_home_path.exists(): + print(f'jmeter_post_check: removing {jmeter_home_path}') + rmtree(str(jmeter_home_path)) + raise SystemExit(f'jmeter_post_check: ERROR - {jmeter_jtl_file} was not found. ' + f'JMeter folder {jmeter_home_path} was removed for recovery ' + f'and will be automatically downloaded on the next bzt run.') + +print('jmeter_post_check: PASS') diff --git a/app/util/jtl_convertor/jtl_validator.py b/app/util/jtl_convertor/jtl_validator.py index b50e64e6c..143f9a145 100644 --- a/app/util/jtl_convertor/jtl_validator.py +++ b/app/util/jtl_convertor/jtl_validator.py @@ -90,6 +90,6 @@ def validate(file_path: Path) -> None: __validate_header(reader.fieldnames) __validate_rows(reader) except (ValidationException, FileNotFoundError) as e: - raise SystemExit(f"Validation failed. File path: [{file_path}]. Validation details: {str(e)}") + raise SystemExit(f"ERROR: Validation failed. File path: [{file_path}]. Validation details: {str(e)}") print(f'File: {file_path} validated in {time.time() - start_time} seconds') From 69604c4b98e4d4bba28cd12adf93c7d3963d3942 Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Tue, 24 Mar 2020 17:37:38 +0200 Subject: [PATCH 10/23] fix typo (#163) --- docs/dc-apps-performance-toolkit-user-guide-jira.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dc-apps-performance-toolkit-user-guide-jira.md b/docs/dc-apps-performance-toolkit-user-guide-jira.md index e35455250..5b71c5f0e 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jira.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jira.md @@ -47,7 +47,7 @@ Monthly charges will be based on your actual usage of AWS services, and may vary | Stack | Estimated hourly cost ($) | | ----- | ------------------------- | | One Node Jira DC | 1 - 1.3 | -| Two Nodes Jira DC 1.7 - 2.1 | +| Two Nodes Jira DC | 1.7 - 2.1 | | Four Nodes Jira DC | 3.1 - 3.8 | #### Quick Start parameters From 159cfed57c3f9dd3a875ff74eb377131aee36872 Mon Sep 17 00:00:00 2001 From: Sergey Moroz Date: Wed, 25 Mar 2020 11:42:52 +0200 Subject: [PATCH 11/23] None refactor analytics datetime iso8601 format (#164) * refactored datetime to iso 8601 format --- app/util/analytics.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/util/analytics.py b/app/util/analytics.py index 14d393562..acbd5294e 100644 --- a/app/util/analytics.py +++ b/app/util/analytics.py @@ -2,7 +2,7 @@ import os import re import requests -from datetime import datetime +from datetime import datetime, timezone import platform import uuid import getpass @@ -163,7 +163,7 @@ def __convert_to_sec(duration): def set_date_timestamp(self): utc_now = datetime.utcnow() self.time_stamp = int(round(utc_now.timestamp() * 1000)) - self.date = utc_now.strftime("%m/%d/%Y-%H:%M:%S") + self.date = datetime.utcnow().replace(tzinfo=timezone.utc).isoformat('T', 'seconds') def get_jira_version(self): client = JiraRestClient(host=self.config_yml.server_url, user=self.config_yml.admin_login, From 8c42d38cb3bbd51ee94596d59b97f7e55e65ca28 Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Tue, 31 Mar 2020 16:06:44 +0300 Subject: [PATCH 12/23] fix typo in docs (#166) --- docs/dc-apps-performance-toolkit-user-guide-bitbucket.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 5178cde79..8f515035f 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -214,7 +214,7 @@ To populate the database with SQL: ```bash ssh-add path_to_your_private_key_pem export BASTION_IP=bastion_instance_public_ip - export NFS_SERVER_IP=node_private_ip + export NFS_SERVER_IP=nfs_server_private_ip export SSH_OPTS='-o ServerAliveInterval=60 -o ServerAliveCountMax=30' ssh ${SSH_OPTS} -o "proxycommand ssh -W %h:%p ${SSH_OPTS} ec2-user@${BASTION_IP}" ec2-user@${NFS_SERVER_IP} ``` From 70715e9c8918018064c2f45f52fee7aff20af6c9 Mon Sep 17 00:00:00 2001 From: Sergey Moroz Date: Wed, 1 Apr 2020 16:18:59 +0300 Subject: [PATCH 13/23] None pageobject pattern bitbucket (#165) * Refactored selenium tests to page object pattern Co-authored-by: Oleksandr Metelytsia --- app/extension/bitbucket/extension_ui.py | 16 +- app/selenium_ui/base_page.py | 111 ++++ app/selenium_ui/bitbucket/modules.py | 476 +++++------------- app/selenium_ui/bitbucket/pages/pages.py | 307 +++++++++++ app/selenium_ui/bitbucket/pages/selectors.py | 227 +++++++++ app/selenium_ui/bitbucket_ui.py | 30 +- app/selenium_ui/conftest.py | 25 +- .../bitbucket/prepare-data.py | 4 +- 8 files changed, 805 insertions(+), 391 deletions(-) create mode 100644 app/selenium_ui/base_page.py create mode 100644 app/selenium_ui/bitbucket/pages/pages.py create mode 100644 app/selenium_ui/bitbucket/pages/selectors.py diff --git a/app/extension/bitbucket/extension_ui.py b/app/extension/bitbucket/extension_ui.py index 4caf41474..4bff60291 100644 --- a/app/extension/bitbucket/extension_ui.py +++ b/app/extension/bitbucket/extension_ui.py @@ -1,29 +1,25 @@ from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as ec - -from selenium_ui.confluence.modules import _wait_until from selenium_ui.conftest import print_timing from util.conf import BITBUCKET_SETTINGS -APPLICATION_URL = BITBUCKET_SETTINGS.server_url -timeout = 20 +from selenium_ui.base_page import BasePage def custom_action(webdriver, datasets): + page = BasePage(webdriver) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/reporter') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-element")), interaction) + page.go_to_url(f"{BITBUCKET_SETTINGS.server_url}/plugin/report") + page.wait_until_visible((By.ID, 'report_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_report') @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/administration') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-element")), interaction) + page.go_to_url(f"{BITBUCKET_SETTINGS.server_url}/plugin/dashboard") + page.wait_until_visible((By.ID, 'dashboard_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_dashboard') - measure(webdriver, 'selenium_app_custom_action') diff --git a/app/selenium_ui/base_page.py b/app/selenium_ui/base_page.py new file mode 100644 index 000000000..29fccdd81 --- /dev/null +++ b/app/selenium_ui/base_page.py @@ -0,0 +1,111 @@ +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as ec +from selenium_ui.conftest import AnyEc +import random +import string + +TIMEOUT = 20 + + +class BasePage: + page_url = '' + page_loaded_selector = {} + + def __init__(self, driver): + self.driver = driver + + def go_to(self): + self.driver.get(self.page_url) + + def wait_for_page_loaded(self, interaction): + if type(self.page_loaded_selector) == list: + for selector in self.page_loaded_selector: + self.wait_until_visible(selector, interaction) + else: + self.wait_until_visible(self.page_loaded_selector, interaction) + + def go_to_url(self, url): + self.driver.get(url) + + def get_element(self, selector): + selector_name = self.get_selector(selector) + by, locator = selector_name[0], selector_name[1] + return self.driver.find_element(by, locator) + + def get_elements(self, selector): + selector_name = self.get_selector(selector) + by, locator = selector_name[0], selector_name[1] + return self.driver.find_elements(by, locator) + + def wait_until_invisible(self, selector_name, interaction=None): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.invisibility_of_element_located(selector), + interaction=interaction) + + def wait_until_visible(self, selector_name, interaction=None): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.visibility_of_element_located(selector), + interaction=interaction) + + def wait_until_present(self, selector_name, interaction=None, time_out=TIMEOUT): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.presence_of_element_located(selector), + interaction=interaction, time_out=time_out) + + def wait_until_clickable(self, selector_name, interaction=None): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.element_to_be_clickable(selector), + interaction=interaction) + + def wait_until_any_element_visible(self, selector_name, interaction=None): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.visibility_of_any_elements_located(selector), + interaction=interaction) + + def __wait_until(self, expected_condition, interaction, time_out=TIMEOUT): + message = f"Interaction: {interaction}. " + ec_type = type(expected_condition) + if ec_type == AnyEc: + conditions_text = "" + for ecs in expected_condition.ecs: + conditions_text = conditions_text + " " + f"Condition: {str(ecs)} Locator: {ecs.locator}\n" + + message += f"Timed out after {time_out} sec waiting for one of the conditions: \n{conditions_text}" + + elif ec_type == ec.invisibility_of_element_located: + message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" + f"Locator: {expected_condition.target}") + + elif ec_type == ec.frame_to_be_available_and_switch_to_it: + message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" + f"Locator: {expected_condition.frame_locator}") + + else: + message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" + f"Locator: {expected_condition.locator}") + + return WebDriverWait(self.driver, time_out).until(expected_condition, message=message) + + def dismiss_popup(self, *args): + for elem in args: + try: + self.driver.execute_script(f"document.querySelector(\'{elem}\').click()") + except: + pass + + def get_selector(self, selector_name): + selector = selector_name.get(self.app_version) if type(selector_name) == dict else selector_name + if selector is None: + raise Exception(f'Selector {selector_name} for version {self.app_version} is not found') + return selector + + def execute_js(self, js): + return self.driver.execute_script(js) + + @property + def app_version(self): + return self.driver.app_version if 'app_version' in dir(self.driver) else None + + @staticmethod + def generate_random_string(length): + return "".join([random.choice(string.digits + string.ascii_letters + ' ') for _ in range(length)]) diff --git a/app/selenium_ui/bitbucket/modules.py b/app/selenium_ui/bitbucket/modules.py index c1ed6b04b..a1321ec86 100644 --- a/app/selenium_ui/bitbucket/modules.py +++ b/app/selenium_ui/bitbucket/modules.py @@ -1,465 +1,243 @@ import random +from selenium_ui.conftest import print_timing -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.wait import WebDriverWait -from selenium.common.exceptions import NoSuchElementException - -from selenium_ui.conftest import print_timing, AnyEc, generate_random_string -from util.conf import BITBUCKET_SETTINGS - -APPLICATION_URL = BITBUCKET_SETTINGS.server_url -LOGIN_URL = f"{BITBUCKET_SETTINGS.server_url}/getting-started" -DASHBOARD_URL = f"{BITBUCKET_SETTINGS.server_url}/dashboard" -PROJECTS_URL = f"{BITBUCKET_SETTINGS.server_url}/projects" -timeout = 20 - -# TODO Move to a page objects DCA-53 -SELECTORS = { - 'activity_selector': { - 'v6': (By.CSS_SELECTOR, ".pull-request-activity-content"), - 'v7': (By.CSS_SELECTOR, ".pull-request-activities") - }, - 'comment_text_area_selector': { - 'v6': (By.CSS_SELECTOR, "textarea.text"), - 'v7': (By.CLASS_NAME, "comment-editor-wrapper") - }, - 'text_area_selector': { - 'v6': (By.CSS_SELECTOR, 'textarea.text'), - 'v7': (By.CLASS_NAME, 'CodeMirror-code') - }, - 'comment_button_selector': { - 'v6': (By.CSS_SELECTOR, "div.buttons>button:nth-child(1)"), - 'v7': (By.CSS_SELECTOR, "div.editor-controls>button:nth-child(1)") - }, - 'code_area_selector': { - 'v6': (By.CLASS_NAME, "CodeMirror-code"), - 'v7': (By.CLASS_NAME, "diff-segment") - }, - 'inline_comment_button_selector': { - 'v6': (By.CSS_SELECTOR, "button.add-comment-trigger>span.aui-iconfont-add-comment"), - 'v7': (By.CSS_SELECTOR, ".diff-line-comment-trigger") - }, - 'diagram_selector': { - 'v6': (By.CSS_SELECTOR, 'div.diagram-image'), - 'v7': (By.CLASS_NAME, 'branches-diagram') - }, - 'merge_pr_button_selector': { - 'v6': (By.CSS_SELECTOR, 'button.confirm-button'), - 'v7': (By.CSS_SELECTOR, "button[type='submit']") - }, - 'del_branch_checkbox_selector': { - 'v6': (By.CSS_SELECTOR, 'span.pull-request-cleanup-checkbox-wrapper'), - 'v7': (By.NAME, 'deleteSourceRef') - } -} - - -def _dismiss_popup(webdriver, *args): - for elem in args: - try: - webdriver.execute_script(f"document.querySelector(\'{elem}\').click()") - except: - pass - - -def get_selector(selector_name, version): - selector_dict = SELECTORS.get(selector_name) - if selector_dict is None: - raise Exception(f'Selector {selector_name} not defined.') - if type(selector_dict) != dict: - raise Exception(f'Selector {selector_name} is not dictionary') - selector = selector_dict.get(version) - if selector is None: - raise Exception(f'Selector {selector_name} for version {version} is not found') - return selector +from selenium_ui.bitbucket.pages.pages import LoginPage, GetStarted, Dashboard, Projects, Project, Repository, \ + RepoNavigationPanel, PopupManager, RepoPullRequests, PullRequest, RepositoryBranches, RepositoryCommits, LogoutPage -def login(webdriver, datasets): +def setup_run_data(datasets): user = random.choice(datasets["users"]) - datasets['current_user_id'] = int(user[0]) - 1 - datasets['current_user_name'] = user[1] project_with_repo_prs = random.choice(datasets["pull_requests"]) - datasets['current_project_key'] = project_with_repo_prs[1] - datasets['current_repo_slug'] = project_with_repo_prs[0] - # If PRs number > 2, choose random between first 2 PRs - datasets['pull_request_id'] = random.choice([project_with_repo_prs[2], project_with_repo_prs[5] - if len(project_with_repo_prs) > 5 else project_with_repo_prs[2]]) + datasets['username'] = user[1] + datasets['password'] = user[2] + datasets['project_key'] = project_with_repo_prs[1] + datasets['repo_slug'] = project_with_repo_prs[0] + datasets['pull_request_branch_from'] = project_with_repo_prs[3] + datasets['pull_request_branch_to'] = project_with_repo_prs[4] + datasets['pull_request_id'] = project_with_repo_prs[2] + +def login(webdriver, datasets): + setup_run_data(datasets) + login_page = LoginPage(webdriver) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{LOGIN_URL}') - - # Get application major version e.g. v7 or v6 - webdriver.app_version = webdriver.find_element_by_id('product-version').text.split('.')[0] - + login_page.go_to() + webdriver.app_version = login_page.get_app_version() measure(webdriver, "selenium_login:open_login_page") - webdriver.find_element_by_id('j_username').send_keys(user[1]) - webdriver.find_element_by_id('j_password').send_keys(user[2]) + + login_page.set_credentials(datasets['username'], datasets['password']) @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "submit")), - interaction).click() - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "marketing-page-footer")), - interaction) + login_page.submit_login(interaction) + get_started_page = GetStarted(webdriver) + get_started_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_login:login_get_started") - measure(webdriver, "selenium_login") def view_dashboard(webdriver, datasets): @print_timing def measure(webdriver, interaction): - webdriver.get(DASHBOARD_URL) - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "dashboard-your-work")), interaction) + dashboard_page = Dashboard(webdriver) + dashboard_page.go_to() + dashboard_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_dashboard") def view_projects(webdriver, datasets): @print_timing def measure(webdriver, interaction): - webdriver.get(PROJECTS_URL) - _wait_until(webdriver, ec.presence_of_element_located((By.ID, "projects-container")), interaction) + projects_page = Projects(webdriver) + projects_page.go_to() + projects_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_projects") def view_project_repos(webdriver, datasets): - project_url = f"{PROJECTS_URL}/{datasets['current_project_key']}" - @print_timing def measure(webdriver, interaction): - webdriver.get(f"{project_url}") - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "repositories-container")), interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, "span.repository-name")), interaction) - + project_page = Project(webdriver, project_key=datasets['project_key']) + project_page.go_to() + project_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_project_repositories") def view_repo(webdriver, datasets): - repo_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/browse" + repository_page = Repository(webdriver, + project_key=datasets['project_key'], + repo_slug=datasets['repo_slug']) @print_timing def measure(webdriver, interaction): - webdriver.get(f"{repo_url}") - _wait_until(webdriver, ec.presence_of_element_located((By.ID, "repo-clone-dialog")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + repository_page.go_to() + nav_panel = RepoNavigationPanel(webdriver) + nav_panel.wait_for_page_loaded(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, "selenium_view_repository") def view_list_pull_requests(webdriver, datasets): - repo_pr_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests") + repo_pull_requests_page = RepoPullRequests(webdriver, + project_key=datasets['project_key'], + repo_slug=datasets['repo_slug']) @print_timing def measure(webdriver, interaction): - webdriver.get(f"{repo_pr_url}") - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "pull-requests-content")), interaction) + repo_pull_requests_page.go_to() + repo_pull_requests_page.wait_for_page_loaded(interaction) measure(webdriver, 'selenium_view_list_pull_requests') def view_pull_request_overview_tab(webdriver, datasets): - pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview") + pull_request_page = PullRequest(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug'], + pull_request_key=datasets['pull_request_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(pull_requests_url) - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, "ul.tabs-menu")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') + pull_request_page.go_to_overview() + pull_request_page.wait_for_overview_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, 'selenium_view_pull_request_overview') def view_pull_request_diff_tab(webdriver, datasets): - pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff") + pull_request_page = PullRequest(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug'], + pull_request_key=datasets['pull_request_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(pull_requests_url) - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.LINK_TEXT, 'Diff')), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') + pull_request_page.go_to_diff() + pull_request_page.wait_for_diff_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, 'selenium_view_pull_request_diff') def view_pull_request_commits_tab(webdriver, datasets): - pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/commits") + pull_request_page = PullRequest(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug'], + pull_request_key=datasets['pull_request_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(pull_requests_url) - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, "tr>th.message")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') + pull_request_page.go_to_commits() + pull_request_page.wait_for_commits_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, 'selenium_view_pull_request_commits') def comment_pull_request_diff(webdriver, datasets): - pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/diff") - webdriver.get(pull_requests_url) - + pull_request_page = PullRequest(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug'], + pull_request_key=datasets['pull_request_id']) + pull_request_page.go_to_diff() @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.visibility_of_element_located((By.LINK_TEXT, "Diff")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close', '.css-1it7f5o') - - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('code_area_selector', webdriver.app_version)), - interaction) - - css_selector = get_selector('inline_comment_button_selector', webdriver.app_version)[1] - webdriver.execute_script(f"elems=document.querySelectorAll('{css_selector}'); " - "item=elems[Math.floor(Math.random() * elems.length)];" - "item.scrollIntoView();" - "item.click();") - - if 'v6' in webdriver.app_version: - css_selector = get_selector('comment_text_area_selector', webdriver.app_version)[1] - webdriver.execute_script( - f"document.querySelector('{css_selector}').value = 'Comment from Selenium script';") - elif 'v7' in webdriver.app_version: - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('text_area_selector', webdriver.app_version)), - interaction).send_keys('Comment from Selenium script') - - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('comment_button_selector', webdriver.app_version)), - interaction).click() - + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.wait_for_diff_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.wait_for_code_diff(interaction) + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.click_inline_comment_button_js() + pull_request_page.add_code_comment(interaction) measure(webdriver, 'selenium_comment_pull_request_file') def comment_pull_request_overview(webdriver, datasets): - pull_requests_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/pull-requests/{datasets['pull_request_id']}/overview") - webdriver.get(pull_requests_url) - + pull_request_page = PullRequest(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug'], + pull_request_key=datasets['pull_request_id']) + pull_request_page.go_to() @print_timing def measure(webdriver, interaction): - - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), - interaction) - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close', '.css-1it7f5o') - - _wait_until(webdriver, - ec.element_to_be_clickable(get_selector('comment_text_area_selector', webdriver.app_version)), - interaction).click() - - _wait_until(webdriver, - ec.element_to_be_clickable(get_selector('text_area_selector', webdriver.app_version)), - interaction).send_keys(f"{generate_random_string(50)}") - - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('comment_button_selector', webdriver.app_version)), - interaction).click() - + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.wait_for_overview_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.add_overview_comment(interaction) + pull_request_page.click_save_comment_button(interaction) measure(webdriver, 'selenium_comment_pull_request_overview') def view_branches(webdriver, datasets): - repo_branches_url = (f"{PROJECTS_URL}/{datasets['current_project_key']}/" - f"repos/{datasets['current_repo_slug']}/branches") + branches_page = RepositoryBranches(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug']) @print_timing def measure(webdriver, interaction): - webdriver.get(repo_branches_url) - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.ID, "branch-name-column")), interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + branches_page.go_to() + branches_page.wait_for_page_loaded(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, 'selenium_view_branches') def create_pull_request(webdriver, datasets): - random_repo_with_pr = get_random_repo_with_pr(datasets) - fork_name = f"fork-{random_repo_with_pr[0]}_{generate_random_string(7)}" - webdriver.get(f"{PROJECTS_URL}/{random_repo_with_pr[1]}/repos/{random_repo_with_pr[0]}") - _dismiss_popup(webdriver, '.feature-discovery-close') + repository_page = Repository(webdriver, + project_key=datasets['project_key'], + repo_slug=datasets['repo_slug']) + repo_pull_requests_page = RepoPullRequests(webdriver, repo_slug=repository_page.repo_slug, + project_key=repository_page.project_key) + repository_branches_page = RepositoryBranches(webdriver, repo_slug=repository_page.repo_slug, + project_key=repository_page.project_key) + navigation_panel = RepoNavigationPanel(webdriver) + PopupManager(webdriver).dismiss_default_popup() @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.presence_of_element_located((By.ID, "repo-clone-dialog")), interaction) - safe_click(webdriver, By.CSS_SELECTOR, 'span.icon-fork', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "enable-ref-syncing")), - interaction) - safe_click(webdriver, By.ID, 'enable-ref-syncing', interaction) - fork_name_field = webdriver.find_element_by_id('name') - fork_name_field.clear() - fork_name_field.send_keys(f'{fork_name}') - safe_click(webdriver, By.ID, "fork-repo-submit", interaction) - _wait_until(webdriver, ec.presence_of_element_located((By.ID, "repo-clone-dialog")), interaction) - measure(webdriver, 'selenium_create_pull_request:create_repos_fork') + branch_from = datasets['pull_request_branch_from'] + branch_to = datasets['pull_request_branch_to'] + repository_branches_page.open_base_branch(interaction=interaction, + base_branch_name=branch_from) + fork_branch_from = repository_branches_page.create_branch_fork_rnd_name(interaction=interaction, + base_branch_name=branch_from) + navigation_panel.wait_for_navigation_panel(interaction) + repository_branches_page.open_base_branch(interaction=interaction, + base_branch_name=branch_to) + fork_branch_to = repository_branches_page.create_branch_fork_rnd_name(interaction=interaction, + base_branch_name=branch_to) + datasets['pull_request_fork_branch_to'] = fork_branch_to + navigation_panel.wait_for_navigation_panel(interaction) + + repo_pull_requests_page.create_new_pull_request(interaction=interaction, from_branch=fork_branch_from, + to_branch=fork_branch_to) + PopupManager(webdriver).dismiss_default_popup() - @print_timing - def measure(webdriver, interaction): - safe_click(webdriver, By.CSS_SELECTOR, '.aui-sidebar-group.sidebar-navigation>ul>li:nth-child(4)', - interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'pull-requests-content')), interaction) - safe_click(webdriver, By.ID, 'empty-list-create-pr-button', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'branch-compare')), interaction) - # Choose branch source - safe_click(webdriver, By.ID, 'sourceBranch', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), interaction) - branch_name_from_dropdown = webdriver.find_element_by_id('sourceBranchDialog-search-input') - branch_name_from_dropdown.send_keys(f'{random_repo_with_pr[3]}') - _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, - '#sourceBranchDialog>div.results>div.spinner-wrapper')), interaction) - branch_name_from_dropdown.send_keys(Keys.ENTER) - _wait_until(webdriver, - ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), - interaction) - # Choose project source - safe_click(webdriver, By.ID, 'targetRepo', interaction) - safe_click(webdriver, By.CSS_SELECTOR, "div#targetRepoDialog>div>ul.results-list>li:nth-child(1)", - interaction) - # Choose branch destination - _wait_until(webdriver, ec.visibility_of_element_located(( - By.ID, 'targetBranch')), interaction) - webdriver.execute_script("document.querySelector('#targetBranch').click()") - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'targetBranchDialog')), interaction) - branch_name_to_dropdown = webdriver.find_element_by_id('targetBranchDialog-search-input') - branch_name_to_dropdown.send_keys(f'{random_repo_with_pr[4]}') - _wait_until(webdriver, ec.invisibility_of_element_located( - (By.CSS_SELECTOR, '#targetBranchDialog>div.results>div.spinner-wrapper')), interaction) - branch_name_to_dropdown.send_keys(Keys.ENTER) - _wait_until(webdriver, - ec.invisibility_of_element_located((By.CSS_SELECTOR, 'ul.results-list')), interaction) - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, 'show-create-pr-button')), - interaction) - safe_click(webdriver, By.ID, 'show-create-pr-button', interaction) - _wait_until(webdriver, - ec.visibility_of_element_located((By.CSS_SELECTOR, 'textarea#pull-request-description')), - interaction) - webdriver.find_element_by_id('title').clear() - webdriver.find_element_by_id('title').send_keys('Selenium test pull request') - safe_click(webdriver, By.ID, 'submit-form', interaction) - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), - interaction) - _wait_until(webdriver, ec.element_to_be_clickable((By.CSS_SELECTOR, 'button.merge-button')), - interaction) - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') measure(webdriver, 'selenium_create_pull_request:create_pull_request') @print_timing def measure(webdriver, interaction): - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('activity_selector', webdriver.app_version)), - interaction) - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - if 'v6' in webdriver.app_version: - _wait_until(webdriver, - ec.presence_of_element_located((By.CSS_SELECTOR, "aui-spinner[size='small']")), - interaction, - time_out=1) - _wait_until(webdriver, - ec.invisibility_of_element_located((By.CSS_SELECTOR, "aui-spinner[size='small']")), - interaction) - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, 'merge-button')), - interaction).click() - _dismiss_popup(webdriver, 'button.aui-button-link.feature-discovery-close') - _wait_until(webdriver, - ec.visibility_of_element_located(get_selector('diagram_selector', webdriver.app_version)), - interaction) - _wait_until(webdriver, - ec.element_to_be_clickable(get_selector('merge_pr_button_selector', webdriver.app_version)), - interaction).click() - _wait_until(webdriver, - ec.invisibility_of_element_located( - get_selector('del_branch_checkbox_selector', webdriver.app_version)), - interaction) + PopupManager(webdriver).dismiss_default_popup() + pull_request_page = PullRequest(webdriver) + pull_request_page.wait_for_overview_tab(interaction) + PopupManager(webdriver).dismiss_default_popup() + pull_request_page.merge_pull_request(interaction) measure(webdriver, 'selenium_create_pull_request:merge_pull_request') - - @print_timing - def measure(webdriver, interaction): - webdriver.get(f"{APPLICATION_URL}/users/{datasets['current_user_name']}/repos/{fork_name}/settings") - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, - 'div.aui-page-panel-nav')), interaction) - safe_click(webdriver, By.ID, 'repository-settings-delete-button', interaction) - webdriver.find_element_by_id('confirmRepoName').send_keys(f'{fork_name}') - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, - 'delete-repository-dialog-submit')), interaction) - safe_click(webdriver, By.ID, 'delete-repository-dialog-submit', interaction) - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, - 'div.user-detail.username')), interaction) - measure(webdriver, 'selenium_create_pull_request:delete_fork_repo') + repository_branches_page.go_to() + repository_branches_page.delete_branch(interaction=interaction, + branch_name=datasets['pull_request_fork_branch_to']) measure(webdriver, 'selenium_create_pull_request') def view_commits(webdriver, datasets): - repo_commits_url = f"{PROJECTS_URL}/{datasets['current_project_key']}/repos/{datasets['current_repo_slug']}/commits" - + repo_commits_page = RepositoryCommits(webdriver, project_key=datasets['project_key'], + repo_slug=datasets['repo_slug']) @print_timing def measure(webdriver, interaction): - webdriver.get(repo_commits_url) - _wait_until(webdriver, ec.visibility_of_any_elements_located((By.CSS_SELECTOR, "svg.commit-graph")), - interaction) - _dismiss_popup(webdriver, '.feature-discovery-close') + repo_commits_page.go_to() + repo_commits_page.wait_for_page_loaded(interaction) + PopupManager(webdriver).dismiss_default_popup() measure(webdriver, 'selenium_view_commits') def logout(webdriver, datasets): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/j_atl_security_logout') + logout_page_page = LogoutPage(webdriver) + logout_page_page.go_to() measure(webdriver, "selenium_log_out") - - -def _wait_until(webdriver, expected_condition, interaction, time_out=timeout): - message = f"Interaction: {interaction}. " - ec_type = type(expected_condition) - if ec_type == AnyEc: - conditions_text = "" - for ecs in expected_condition.ecs: - conditions_text = conditions_text + " " + f"Condition: {str(ecs)} Locator: {ecs.locator}\n" - - message += f"Timed out after {time_out} sec waiting for one of the conditions: \n{conditions_text}" - - elif ec_type == ec.invisibility_of_element_located: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.target}") - - elif ec_type == ec.frame_to_be_available_and_switch_to_it: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.frame_locator}") - - else: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.locator}") - - return WebDriverWait(webdriver, time_out).until(expected_condition, message=message) - - -def safe_click(webdriver, by, element, interaction): - return _wait_until(webdriver, ec.visibility_of_element_located((by, element)), - interaction).click() - - -def get_random_repo_with_pr(datasets): - repos_with_prs = [] - prs = datasets['pull_requests'] - for pr in prs: - # pr = [repo_slug, project_key, from_branch1, to_branch1, from_branch2, to_branch2...] - if len(pr) >= 4: - repos_with_prs.append(pr) - if len(repos_with_prs) == 0: - raise NoPullRequestFoundException('app/datasets/bitbucket/pull_requests.csv does not have any pull request') - return random.choice(repos_with_prs) - - -def get_element_or_none(webdriver, by, element): - try: - element = webdriver.find_element(by, element) - return element - except NoSuchElementException: - return None - - -class NoPullRequestFoundException(Exception): - pass diff --git a/app/selenium_ui/bitbucket/pages/pages.py b/app/selenium_ui/bitbucket/pages/pages.py new file mode 100644 index 000000000..71d288007 --- /dev/null +++ b/app/selenium_ui/bitbucket/pages/pages.py @@ -0,0 +1,307 @@ +from selenium.webdriver.common.keys import Keys + +from selenium_ui.base_page import BasePage +from selenium_ui.bitbucket.pages.selectors import LoginPageLocators, GetStartedLocators, \ + DashboardLocators, ProjectsLocators, ProjectLocators, RepoLocators, RepoNavigationPanelLocators, PopupLocators, \ + PullRequestLocator, BranchesLocator, RepositorySettingsLocator, UserSettingsLocator, RepoCommitsLocator, \ + LogoutPageLocators, UrlManager + + +class LoginPage(BasePage): + page_url = UrlManager().login_url() + + def fill_username(self, username): + self.get_element(LoginPageLocators.username_textfield).send_keys(username) + + def fill_password(self, password): + self.get_element(LoginPageLocators.password_textfield).send_keys(password) + + def submit_login(self, interaction=None): + self.wait_until_visible(LoginPageLocators.submit_button, interaction).click() + + def set_credentials(self, username, password): + self.fill_username(username) + self.fill_password(password) + + def get_app_version(self): + el = self.get_element(LoginPageLocators.application_version) + return ''.join([i for i in el.text.split('.')[0] if i.isdigit()]) + + +class LogoutPage(BasePage): + + page_url = LogoutPageLocators.logout_url + + +class GetStarted(BasePage): + page_url = GetStartedLocators.get_started_url + page_loaded_selector = GetStartedLocators.bitbucket_is_ready_widget + + +class Dashboard(BasePage): + page_url = DashboardLocators.dashboard_url + page_loaded_selector = DashboardLocators.dashboard_presence + + +class Projects(BasePage): + page_url = ProjectsLocators.project_url + page_loaded_selector = ProjectsLocators.projects_list + + +class Project(BasePage): + page_loaded_selector = [ProjectLocators.repositories_container, ProjectLocators.repository_name] + + def __init__(self, driver, project_key): + BasePage.__init__(self, driver) + self.project_key = project_key + url_manager = UrlManager(project_key=project_key) + self.page_url = url_manager.project_url() + + +class RepoNavigationPanel(BasePage): + page_loaded_selector = RepoNavigationPanelLocators.navigation_panel + + def __clone_repo_button(self): + return self.get_element(RepoNavigationPanelLocators.clone_repo_button) + + def wait_for_navigation_panel(self, interaction): + return self.wait_until_present(RepoNavigationPanelLocators.navigation_panel, interaction) + + def clone_repo_click(self): + self.__clone_repo_button().click() + + def fork_repo(self, interaction): + return self.wait_until_visible(RepoNavigationPanelLocators.fork_repo_button, interaction) + + def create_pull_request(self, interaction): + self.wait_until_visible(RepoNavigationPanelLocators.create_pull_request_button, interaction).click() + self.wait_until_visible(RepoLocators.pull_requests_list, interaction) + + +class PopupManager(BasePage): + + def dismiss_default_popup(self): + return self.dismiss_popup(PopupLocators.default_popup, PopupLocators.popup_1, PopupLocators.popup_2) + + +class Repository(BasePage): + + def __init__(self, driver, project_key, repo_slug): + BasePage.__init__(self, driver) + url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug) + self.page_url = url_manager.repo_url() + self.repo_slug = repo_slug + self.project_key = project_key + + def set_enable_fork_sync(self, interaction, value): + checkbox = self.wait_until_visible(RepoLocators.repo_fork_sync, interaction) + current_state = checkbox.is_selected() + if (value and not current_state) or (not value and current_state): + checkbox.click() + + def submit_fork_repo(self): + self.wait_until_visible(RepoLocators.fork_repo_submit_button).click() + + def set_fork_repo_name(self): + fork_name_field = self.get_element(RepoLocators.fork_name_field) + fork_name_field.clear() + fork_name = f"{self.repo_slug}-{self.generate_random_string(5)}" + fork_name_field.send_keys(fork_name) + return fork_name + + +class RepoPullRequests(BasePage): + page_loaded_selector = RepoLocators.pull_requests_list + + def __init__(self, driver, project_key, repo_slug): + BasePage.__init__(self, driver) + self.url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug) + self.page_url = self.url_manager.repo_pull_requests() + + def create_new_pull_request(self, from_branch, to_branch, interaction): + self.go_to_url(url=self.url_manager.create_pull_request_url(from_branch=from_branch, + to_branch=to_branch)) + self.submit_pull_request(interaction) + + def set_pull_request_source_branch(self, interaction, source_branch): + self.wait_until_visible(RepoLocators.pr_source_branch_field, interaction).click() + self.wait_until_visible(RepoLocators.pr_branches_dropdown, interaction) + source_branch_name_field = self.get_element(RepoLocators.pr_source_branch_name) + source_branch_name_field.send_keys(source_branch) + self.wait_until_invisible(RepoLocators.pr_source_branch_spinner, interaction) + source_branch_name_field.send_keys(Keys.ENTER) + self.wait_until_invisible(RepoLocators.pr_branches_dropdown, interaction) + + def set_pull_request_destination_repo(self, interaction): + self.wait_until_visible(RepoLocators.pr_destination_repo_field, interaction).click() + self.wait_until_visible(RepoLocators.pr_destination_first_repo_dropdown, interaction).click() + + def set_pull_request_destination_branch(self, interaction, destination_branch): + self.wait_until_visible(RepoLocators.pr_destination_branch_field, interaction) + self.execute_js("document.querySelector('#targetBranch').click()") + self.wait_until_visible(RepoLocators.pr_destination_branch_dropdown, interaction) + destination_branch_name_field = self.get_element(RepoLocators.pr_destination_branch_name) + destination_branch_name_field.send_keys(destination_branch) + self.wait_until_invisible(RepoLocators.pr_destination_branch_spinner, interaction) + destination_branch_name_field.send_keys(Keys.ENTER) + self.wait_until_invisible(RepoLocators.pr_branches_dropdown, interaction) + self.wait_until_clickable(RepoLocators.pr_continue_button, interaction) + self.wait_until_visible(RepoLocators.pr_continue_button, interaction).click() + + def submit_pull_request(self, interaction): + self.wait_until_visible(RepoLocators.pr_description_field, interaction) + title = self.get_element(RepoLocators.pr_title_field) + title.clear() + title.send_keys('Selenium test pull request') + self.wait_until_visible(RepoLocators.pr_submit_button, interaction).click() + self.wait_until_visible(PullRequestLocator.pull_request_activity_content, interaction) + self.wait_until_clickable(PullRequestLocator.pull_request_page_merge_button, interaction) + + +class PullRequest(BasePage): + + def __init__(self, driver, project_key=None, repo_slug=None, pull_request_key=None): + BasePage.__init__(self, driver) + url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug, + pull_request_key=pull_request_key) + self.page_url = url_manager.pull_request_overview() + self.diff_url = url_manager.pull_request_diff() + self.commits_url = url_manager.pull_request_commits() + + def wait_for_overview_tab(self, interaction): + return self.wait_until_visible(PullRequestLocator.pull_request_activity_content, interaction) + + def go_to_overview(self): + return self.go_to() + + def go_to_diff(self): + self.go_to_url(url=self.diff_url) + + def go_to_commits(self): + self.go_to_url(self.commits_url) + + def wait_for_diff_tab(self, interaction): + return self.wait_until_any_element_visible(PullRequestLocator.commit_files, interaction) + + def wait_for_code_diff(self, interaction): + return self.wait_until_visible(PullRequestLocator.diff_code_lines, interaction) + + def wait_for_commits_tab(self, interaction): + self.wait_until_any_element_visible(PullRequestLocator.commit_message_label, interaction) + + def click_inline_comment_button_js(self): + selector = self.get_selector(PullRequestLocator.inline_comment_button) + self.execute_js(f"elems=document.querySelectorAll('{selector[1]}'); " + "item=elems[Math.floor(Math.random() * elems.length)];" + "item.scrollIntoView();" + "item.click();") + + def wait_for_comment_text_area(self, interaction): + return self.wait_until_visible(PullRequestLocator.comment_text_area, interaction) + + def add_code_comment_v6(self, interaction): + self.wait_for_comment_text_area(interaction) + selector = self.get_selector(PullRequestLocator.comment_text_area) + self.execute_js(f"document.querySelector('{selector[1]}').value = 'Comment from Selenium script';") + self.click_save_comment_button(interaction) + + def add_code_comment_v7(self, interaction): + self.wait_until_visible(PullRequestLocator.text_area, interaction).send_keys('Comment from Selenium script') + self.click_save_comment_button(interaction) + + def add_code_comment(self, interaction): + if self.app_version == '6': + self.add_code_comment_v6(interaction) + elif self.app_version == '7': + self.add_code_comment_v7(interaction) + + def click_save_comment_button(self, interaction): + return self.wait_until_visible(PullRequestLocator.comment_button, interaction).click() + + def add_overview_comment(self, interaction): + self.wait_for_comment_text_area(interaction).click() + self.wait_until_clickable(PullRequestLocator.text_area, interaction).send_keys(self.generate_random_string(50)) + + def wait_merge_button_clickable(self, interaction): + self.wait_until_clickable(PullRequestLocator.pull_request_page_merge_button, interaction) + + def merge_pull_request(self, interaction): + if self.driver.app_version == '6': + if self.get_elements(PullRequestLocator.merge_spinner): + self.wait_until_invisible(PullRequestLocator.merge_spinner, interaction) + self.wait_until_present(PullRequestLocator.pull_request_page_merge_button).click() + PopupManager(self.driver).dismiss_default_popup() + self.wait_until_visible(PullRequestLocator.diagram_selector) + self.get_element(PullRequestLocator.delete_branch_per_merge_checkbox).click() + self.wait_until_clickable(PullRequestLocator.pull_request_modal_merge_button, interaction).click() + self.wait_until_invisible(PullRequestLocator.del_branch_checkbox_selector, interaction) + + +class RepositoryBranches(BasePage): + page_loaded_selector = BranchesLocator.branches_name + + def __init__(self, driver, project_key, repo_slug): + BasePage.__init__(self, driver) + self.url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug) + self.page_url = self.url_manager.repo_branches() + + def open_base_branch(self, base_branch_name, interaction): + self.go_to_url(f"{self.url_manager.base_branch_url()}{base_branch_name}") + self.wait_until_visible(BranchesLocator.branches_name, interaction) + + def create_branch_fork_rnd_name(self, base_branch_name, interaction): + self.wait_until_visible(BranchesLocator.branches_action, interaction).click() + self.get_element(BranchesLocator.branches_action_create_branch).click() + self.wait_until_visible(BranchesLocator.new_branch_name_textfield, interaction) + branch_name = f"{base_branch_name}-{self.generate_random_string(5)}".replace(' ', '-') + self.get_element(BranchesLocator.new_branch_name_textfield).send_keys(branch_name) + self.wait_until_clickable(BranchesLocator.new_branch_submit_button, interaction).click() + return branch_name + + def delete_branch(self, interaction, branch_name): + self.wait_until_visible(BranchesLocator.search_branch_textfield, interaction).send_keys(branch_name) + self.wait_until_visible(BranchesLocator.branches_name, interaction) + self.wait_until_visible(BranchesLocator.search_branch_action, interaction).click() + self.execute_js("document.querySelector('li>a.delete-branch').click()") + self.wait_until_clickable(BranchesLocator.delete_branch_diaglog_submit, interaction).click() + + +class RepositorySettings(BasePage): + + def wait_repository_settings(self, interaction): + self.wait_until_visible(RepositorySettingsLocator.repository_settings_menu, interaction) + + def delete_repository(self, interaction, repo_slug): + self.wait_repository_settings(interaction) + self.wait_until_visible(RepositorySettingsLocator.delete_repository_button, interaction).click() + self.wait_until_visible(RepositorySettingsLocator.delete_repository_modal_text_field, + interaction).send_keys(repo_slug) + self.wait_until_clickable(RepositorySettingsLocator.delete_repository_modal_submit_button, interaction) + self.wait_until_visible(RepositorySettingsLocator.delete_repository_modal_submit_button, interaction).click() + + +class ForkRepositorySettings(RepositorySettings): + def __init__(self, driver, user, repo_slug): + BasePage.__init__(self, driver) + url_manager = UrlManager(user=user, repo_slug=repo_slug) + self.page_url = url_manager.fork_repo_url() + + +class UserSettings(BasePage): + + def __init__(self, driver, user): + BasePage.__init__(self, driver) + url_manager = UrlManager(user=user) + self.page_url = url_manager.user_settings_url() + + def user_role_visible(self, interaction): + return self.wait_until_visible(UserSettingsLocator.user_role_label, interaction) + + +class RepositoryCommits(BasePage): + page_loaded_selector = RepoCommitsLocator.repo_commits_graph + + def __init__(self, driver, project_key, repo_slug): + BasePage.__init__(self, driver) + url_manager = UrlManager(project_key=project_key, repo_slug=repo_slug) + self.page_url = url_manager.commits_url() diff --git a/app/selenium_ui/bitbucket/pages/selectors.py b/app/selenium_ui/bitbucket/pages/selectors.py new file mode 100644 index 000000000..8738c594d --- /dev/null +++ b/app/selenium_ui/bitbucket/pages/selectors.py @@ -0,0 +1,227 @@ +from selenium.webdriver.common.by import By +from util.conf import BITBUCKET_SETTINGS + + +class UrlManager: + + def __init__(self, user=None, project_key=None, repo_slug=None, pull_request_key=None): + self.host = BITBUCKET_SETTINGS.server_url + self.user = user + self.project_key = project_key + self.repo_slug = repo_slug + self.project_params = f'/projects/{self.project_key}' + self.repo_params_browse = f'/projects/{self.project_key}/repos/{self.repo_slug}/browse' + self.repo_params = f'/projects/{self.project_key}/repos/{self.repo_slug}' + self.repo_pull_requests_params = f'{self.repo_params}/pull-requests' + self.pull_request_params_overview = f'{self.repo_params}/pull-requests/{pull_request_key}/overview' + self.pull_request_params_diff = f'{self.repo_params}/pull-requests/{pull_request_key}/diff' + self.pull_request_params_commits = f'{self.repo_params}/pull-requests/{pull_request_key}/commits' + self.branches_params = f'{self.repo_params}/branches' + self.fork_repo_params = f'/users/{self.user}/repos/{self.repo_slug}/settings' + self.user_settings_params = f'/users/{self.user}' + self.repo_commits_params = f'{self.repo_params}/commits' + self.login_params = '/login?next=/getting-started' + self.logout_params = '/j_atl_security_logout' + self.get_started_params = '/getting-started' + self.dashboard_params = '/dashboard' + self.projects_params = '/projects' + self.branches_base_branch = f'/projects/{self.project_key}/repos/{self.repo_slug}/branches?base=' + + def create_pull_request_url(self, from_branch, to_branch): + return f"{self.host}/projects/{self.project_key}/repos/{self.repo_slug}/pull-requests?create&targetBranch=" \ + f"refs%2Fheads%2F{to_branch}&sourceBranch=refs%2Fheads%2F{from_branch}" + + def base_branch_url(self): + return f"{self.host}{self.branches_base_branch}" + + def project_url(self): + return f"{self.host}{self.project_params}" + + def repo_url(self): + return f"{self.host}{self.repo_params_browse}" + + def repo_pull_requests(self): + return f"{self.host}{self.repo_pull_requests_params}" + + def repo_branches(self): + return f"{self.host}{self.branches_params}" + + def pull_request_overview(self): + return f"{self.host}{self.pull_request_params_overview}" + + def pull_request_diff(self): + return f"{self.host}{self.pull_request_params_diff}" + + def pull_request_commits(self): + return f"{self.host}{self.pull_request_params_commits}" + + def fork_repo_url(self): + return f"{self.host}{self.fork_repo_params}" + + def user_settings_url(self): + return f"{self.host}{self.user_settings_params}" + + def commits_url(self): + return f"{self.host}{self.repo_commits_params}" + + def login_url(self): + return f"{self.host}{self.login_params}" + + def logout_url(self): + return f"{self.host}{self.logout_params}" + + def get_started_url(self): + return f"{self.host}{self.get_started_params}" + + def dashboard_url(self): + return f"{self.host}{self.dashboard_params}" + + def projects_url(self): + return f"{self.host}{self.projects_params}" + + +class PopupLocators: + default_popup = '.feature-discovery-close' + popup_1 = '.css-1it7f5o' + popup_2 = 'button.aui-button-link.feature-discovery-close' + + +class LoginPageLocators: + login_params = UrlManager().login_params + login_url = UrlManager().login_url() + + submit_button = {'6': (By.ID, "submit"), '7': (By.ID, "submit")} + username_textfield = {'6': (By.ID, "j_username"), '7': (By.ID, "j_username")} + password_textfield = {'6': (By.ID, "j_password"), '7': (By.ID, "j_password")} + application_version = (By.ID, 'product-version') + + +class LogoutPageLocators: + logout_params = UrlManager().logout_params + logout_url = UrlManager().logout_url() + + +class GetStartedLocators: + get_started_params = UrlManager().get_started_params + get_started_url = UrlManager().get_started_url() + + bitbucket_is_ready_widget = {'6': (By.CLASS_NAME, "marketing-page-footer"), + '7': (By.CLASS_NAME, "marketing-page-footer")} + + +class DashboardLocators: + dashboard_params = UrlManager().dashboard_params + dashboard_url = UrlManager().dashboard_url() + + dashboard_presence = {'6': (By.CLASS_NAME, 'dashboard-your-work'), '7': (By.CLASS_NAME, 'dashboard-your-work')} + + +class ProjectsLocators: + projects_params = UrlManager().projects_params + project_url = UrlManager().projects_url() + + projects_list = {'6': (By.ID, "projects-container"), '7': (By.ID, "projects-container")} + + +class ProjectLocators: + + repositories_container = {'6': (By.ID, "repositories-container"), '7': (By.ID, "repositories-container")} + repository_name = {'6': (By.CSS_SELECTOR, "span.repository-name"), '7': (By.CSS_SELECTOR, "span.repository-name")} + + +class RepoNavigationPanelLocators: + + navigation_panel = {'6': (By.CSS_SELECTOR, '.aui-navgroup-vertical>.aui-navgroup-inner'), + '7': (By.CSS_SELECTOR, '.aui-navgroup-vertical>.aui-navgroup-inner')} + clone_repo_button = {'6': (By.CSS_SELECTOR, '.clone-repo>#clone-repo-button'), + '7': (By.CSS_SELECTOR, '.clone-repo>#clone-repo-button')} + + fork_repo_button = (By.CSS_SELECTOR, 'span.icon-fork') + + create_pull_request_button = (By.CSS_SELECTOR, '.aui-sidebar-group.sidebar-navigation>ul>li:nth-child(4)') + + +class RepoLocators: + + pull_requests_list = {'6': (By.ID, 'pull-requests-content'), '7': (By.ID, 'pull-requests-content')} + repo_fork_sync = (By.ID, "enable-ref-syncing") + fork_name_field = (By.ID, 'name') + fork_repo_submit_button = (By.ID, "fork-repo-submit") + create_pull_request_button = (By.ID, 'empty-list-create-pr-button') + new_pull_request_branch_compare_window = (By.ID, 'branch-compare') + + pr_source_branch_field = (By.ID, 'sourceBranch') + pr_branches_dropdown = (By.CSS_SELECTOR, 'ul.results-list') + pr_source_branch_name = (By.ID, 'sourceBranchDialog-search-input') + pr_source_branch_spinner = (By.CSS_SELECTOR, '#sourceBranchDialog>div.results>div.spinner-wrapper') + + pr_destination_repo_field = (By.ID, 'targetRepo') + pr_destination_first_repo_dropdown = (By.CSS_SELECTOR, 'div#targetRepoDialog>div>ul.results-list>li:nth-child(1)') + + pr_destination_branch_field = (By.ID, 'targetBranch') + pr_destination_branch_dropdown = (By.ID, 'targetBranchDialog') + pr_destination_branch_name = (By.ID, 'targetBranchDialog-search-input') + pr_destination_branch_spinner = (By.CSS_SELECTOR, '#targetBranchDialog>div.results>div.spinner-wrapper') + + pr_continue_button = (By.ID, 'show-create-pr-button') + pr_description_field = (By.CSS_SELECTOR, 'textarea#pull-request-description') + pr_title_field = (By.ID, 'title') + pr_submit_button = (By.ID, 'submit-form') + + +class PullRequestLocator: + + tab_panel = {'6': (By.CSS_SELECTOR, 'ul.tabs-menu'), '7': (By.CSS_SELECTOR, 'ul.tabs-menu')} + + commit_files = {'6': (By.CSS_SELECTOR, '.commit-files>.file-tree-container'), + '7': (By.CSS_SELECTOR, '.changes-sidebar>.changes-scope-content')} + diff_code_lines = {'6': (By.CLASS_NAME, 'CodeMirror-code'), + '7': (By.CLASS_NAME, "diff-segment")} + + commit_message_label = (By.CSS_SELECTOR, 'tr>th.message') + inline_comment_button = {'6': (By.CSS_SELECTOR, "button.add-comment-trigger>span.aui-iconfont-add-comment"), + '7': (By.CSS_SELECTOR, ".diff-line-comment-trigger")} + comment_text_area = {'6': (By.CSS_SELECTOR, "textarea.text"), '7': (By.CLASS_NAME, "comment-editor-wrapper")} + text_area = {'6': (By.CSS_SELECTOR, 'textarea.text'), '7': (By.CLASS_NAME, 'CodeMirror-code')} + comment_button = {'6': (By.CSS_SELECTOR, "div.buttons>button:nth-child(1)"), + '7': (By.CSS_SELECTOR, "div.editor-controls>button:nth-child(1)")} + pull_request_activity_content = {'6': (By.CSS_SELECTOR, ".pull-request-activity-content"), + '7': (By.CSS_SELECTOR, ".pull-request-activities")} + + pull_request_page_merge_button = (By.CLASS_NAME, 'merge-button') + + merge_spinner = (By.CSS_SELECTOR, "aui-spinner[size='small']") + diagram_selector = {'6': (By.CSS_SELECTOR, 'div.diagram-image'), '7': (By.CLASS_NAME, 'branches-diagram')} + pull_request_modal_merge_button = {'6': (By.CSS_SELECTOR, 'button.confirm-button'), + '7': (By.CSS_SELECTOR, "button[type='submit']")} + del_branch_checkbox_selector = {'6': (By.CSS_SELECTOR, 'span.pull-request-cleanup-checkbox-wrapper'), + '7': (By.NAME, 'deleteSourceRef')} + delete_branch_per_merge_checkbox = (By.CSS_SELECTOR, "input[type='checkbox']") + + +class BranchesLocator: + + branches_name = (By.ID, "branch-name-column") + branches_action = (By.ID, "branch-actions") + branches_action_create_branch = (By.CSS_SELECTOR, "a.create-branch") + new_branch_name_textfield = (By.CSS_SELECTOR, "input.text.branch-name") + new_branch_submit_button = (By.ID, "create-branch-submit") + search_branch_textfield = (By.ID, 'paged-table-input-for-branch-list') + search_branch_action = (By.CSS_SELECTOR, '.branch-actions-column>button') + search_action_delete_branch = (By.CSS_SELECTOR, 'li>a.delete-branch') + delete_branch_diaglog_submit = (By.ID, 'delete-branch-dialog-submit') + + +class RepositorySettingsLocator: + repository_settings_menu = (By.CSS_SELECTOR, 'div.aui-page-panel-nav') + delete_repository_button = (By.ID, 'repository-settings-delete-button') + delete_repository_modal_text_field = (By.ID, 'confirmRepoName') + delete_repository_modal_submit_button = (By.ID, 'delete-repository-dialog-submit') + + +class UserSettingsLocator: + user_role_label = (By.CSS_SELECTOR, 'div.user-detail.username') + + +class RepoCommitsLocator: + repo_commits_graph = (By.CSS_SELECTOR, 'svg.commit-graph') diff --git a/app/selenium_ui/bitbucket_ui.py b/app/selenium_ui/bitbucket_ui.py index d879127b3..6363a726f 100644 --- a/app/selenium_ui/bitbucket_ui.py +++ b/app/selenium_ui/bitbucket_ui.py @@ -11,52 +11,56 @@ def test_1_selenium_view_dashboard(webdriver, bitbucket_datasets, bitbucket_scre modules.view_dashboard(webdriver, bitbucket_datasets) -def test_2_selenium_view_projects(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_2_selenium_create_pull_request(webdriver, bitbucket_datasets, bitbucket_screen_shots): + modules.create_pull_request(webdriver, bitbucket_datasets) + + +def test_3_selenium_view_projects(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_projects(webdriver, bitbucket_datasets) -def test_3_selenium_view_project_repositories(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_4_selenium_view_project_repositories(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_project_repos(webdriver, bitbucket_datasets) -def test_4_selenium_browse_repo(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_5_selenium_view_repo(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_repo(webdriver, bitbucket_datasets) -def test_5_selenium_view_list_pull_requests(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_6_selenium_view_list_pull_requests(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_list_pull_requests(webdriver, bitbucket_datasets) -def test_6_selenium_view_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_7_selenium_view_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_pull_request_overview_tab(webdriver, bitbucket_datasets) -def test_7_selenium_view_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_8_selenium_view_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_pull_request_diff_tab(webdriver, bitbucket_datasets) -def test_8_selenium_view_pull_request_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_9_selenium_view_pull_request_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_pull_request_commits_tab(webdriver, bitbucket_datasets) -def test_9_selenium_comment_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_10_selenium_comment_pull_request_diff(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.comment_pull_request_diff(webdriver, bitbucket_datasets) -def test_10_selenium_comment_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_11_selenium_comment_pull_request_overview(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.comment_pull_request_overview(webdriver, bitbucket_datasets) -def test_11_selenium_view_branches(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_12_selenium_view_branches(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_branches(webdriver, bitbucket_datasets) -def test_12_selenium_view_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): +def test_13_selenium_view_commits(webdriver, bitbucket_datasets, bitbucket_screen_shots): modules.view_commits(webdriver, bitbucket_datasets) -def test_13_selenium_create_pull_request(webdriver, bitbucket_datasets, bitbucket_screen_shots): - modules.create_pull_request(webdriver, bitbucket_datasets) +# def test_01_selenium_create_pull_request(webdriver, bitbucket_datasets, bitbucket_screen_shots): +# modules.create_pull_request(webdriver, bitbucket_datasets) """ diff --git a/app/selenium_ui/conftest.py b/app/selenium_ui/conftest.py index 26917a730..8de5ff5b6 100644 --- a/app/selenium_ui/conftest.py +++ b/app/selenium_ui/conftest.py @@ -15,7 +15,8 @@ from util.conf import CONFLUENCE_SETTINGS, JIRA_SETTINGS, BITBUCKET_SETTINGS from util.project_paths import JIRA_DATASET_ISSUES, JIRA_DATASET_JQLS, JIRA_DATASET_KANBAN_BOARDS, \ - JIRA_DATASET_PROJECT_KEYS, JIRA_DATASET_SCRUM_BOARDS, JIRA_DATASET_USERS + JIRA_DATASET_PROJECT_KEYS, JIRA_DATASET_SCRUM_BOARDS, JIRA_DATASET_USERS, BITBUCKET_USERS, BITBUCKET_PROJECTS, \ + BITBUCKET_REPOS, BITBUCKET_PRS, CONFLUENCE_BLOGS, CONFLUENCE_PAGES, CONFLUENCE_USERS SCREEN_WIDTH = 1920 SCREEN_HEIGHT = 1080 @@ -182,28 +183,20 @@ def jira_datasets(): @pytest.fixture(scope="module") def confluence_datasets(): - # TODO extract paths to project_paths datasets = dict() - input_data_path = Path(__file__).parents[1] / "datasets" / "confluence" - - datasets["pages"] = __read_input_file(input_data_path / "pages.csv") - datasets["blogs"] = __read_input_file(input_data_path / "blogs.csv") - datasets["users"] = __read_input_file(input_data_path / "users.csv") - + datasets["pages"] = __read_input_file(CONFLUENCE_PAGES) + datasets["blogs"] = __read_input_file(CONFLUENCE_BLOGS) + datasets["users"] = __read_input_file(CONFLUENCE_USERS) return datasets @pytest.fixture(scope="module") def bitbucket_datasets(): - # TODO extract paths to project_paths datasets = dict() - input_data_path = Path(__file__).parents[1] / "datasets" / "bitbucket" - - datasets["projects"] = __read_input_file(input_data_path / "projects.csv") - datasets["users"] = __read_input_file(input_data_path / "users.csv") - datasets["repos"] = __read_input_file(input_data_path / "repos.csv") - datasets["pull_requests"] = __read_input_file(input_data_path / "pull_requests.csv") - + datasets["projects"] = __read_input_file(BITBUCKET_PROJECTS) + datasets["users"] = __read_input_file(BITBUCKET_USERS) + datasets["repos"] = __read_input_file(BITBUCKET_REPOS) + datasets["pull_requests"] = __read_input_file(BITBUCKET_PRS) return datasets diff --git a/app/util/data_preparation/bitbucket/prepare-data.py b/app/util/data_preparation/bitbucket/prepare-data.py index b590543fd..e18ab89ec 100644 --- a/app/util/data_preparation/bitbucket/prepare-data.py +++ b/app/util/data_preparation/bitbucket/prepare-data.py @@ -72,12 +72,10 @@ def __get_prs(bitbucket_api): start_time = time.time() repos = bitbucket_api.get_non_fork_repos(REPOS_TO_FETCH) for repo in repos: - repo_prs = [repo['slug'], repo['project']['key']] if len(repos_prs) <= concurrency: prs = bitbucket_api.get_pull_request(project_key=repo['project']['key'], repo_key=repo['slug']) for pr in prs['values']: - repo_prs.extend([pr['id'], pr['fromRef']['displayId'], pr['toRef']['displayId']]) - repos_prs.append(repo_prs) + repos_prs.append([repo['slug'], repo['project']['key'], pr['id'], pr['fromRef']['displayId'], pr['toRef']['displayId']]) if len(repos_prs) < concurrency: raise SystemExit(f'Repositories from list {[repo["project"]["key"] - repo["slug"] for repo in repos]} ' f'do not contain {concurrency} pull requests') From 27d41309a65a9f71767d2526d271841a0f8ab05b Mon Sep 17 00:00:00 2001 From: mmizin <30836532+mmizin@users.noreply.github.com> Date: Wed, 1 Apr 2020 17:25:50 +0300 Subject: [PATCH 14/23] DCA-314: Evaluate and cleanup results dir files (#167) * DCA-314:Delete unnecessary files from result directory * DCA-314: Add cleanup_result_dir in post-process action * DCA-314: Add logging for removed files; Rename variables; Add check for env variable * DCA-314: Corrected file name in configuration yml files * DCA-314: Update variable name --- app/bitbucket.yml | 1 + app/confluence.yml | 1 + app/jira.yml | 1 + app/util/cleanup_results_dir.py | 24 ++++++++++++++++++++++++ 4 files changed, 27 insertions(+) create mode 100644 app/util/cleanup_results_dir.py diff --git a/app/bitbucket.yml b/app/bitbucket.yml index fa9d71134..457de91f4 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -26,6 +26,7 @@ services: - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py bitbucket + - python util/cleanup_results_dir.py execution: - scenario: jmeter concurrency: ${concurrency} diff --git a/app/confluence.yml b/app/confluence.yml index 18aebb131..ae89c1468 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -25,6 +25,7 @@ services: - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py confluence + - python util/cleanup_results_dir.py execution: - scenario: jmeter concurrency: ${concurrency} diff --git a/app/jira.yml b/app/jira.yml index 6cda4e255..6358149bd 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -25,6 +25,7 @@ services: - python util/jtl_convertor/jtls-to-csv.py kpi.jtl selenium.jtl post-process: - python util/analytics.py jira + - python util/cleanup_results_dir.py execution: - scenario: jmeter concurrency: ${concurrency} diff --git a/app/util/cleanup_results_dir.py b/app/util/cleanup_results_dir.py new file mode 100644 index 000000000..1511e28ca --- /dev/null +++ b/app/util/cleanup_results_dir.py @@ -0,0 +1,24 @@ +from pathlib import Path +import os + +ENV_TAURUS_ARTIFACT_DIR = 'TAURUS_ARTIFACTS_DIR' +FILES_TO_REMOVE = ['jmeter.out', + 'jmeter-bzt.properties', + 'merged.json', + 'merged.yml', + 'PyTestExecutor.ldjson', + 'system.properties'] + +if ENV_TAURUS_ARTIFACT_DIR in os.environ: + artifacts_dir = os.environ.get(ENV_TAURUS_ARTIFACT_DIR) +else: + raise SystemExit(f'Error: env variable {ENV_TAURUS_ARTIFACT_DIR} is not set') + +for file in FILES_TO_REMOVE: + file_path = Path(f'{artifacts_dir}/{file}') + try: + os.remove(file_path) + print(f'The {file} was removed successfully') + except OSError as e: + print(f'Deleting of the {file} failed!\n' + f'Error: {file_path}: {e.strerror}') From fa7c68c1100cf28c5e03c144c5b79f5ae5dfc57f Mon Sep 17 00:00:00 2001 From: Sergey Moroz Date: Thu, 2 Apr 2020 12:55:31 +0300 Subject: [PATCH 15/23] hotfix for delete branch action (#170) --- app/selenium_ui/bitbucket/modules.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/selenium_ui/bitbucket/modules.py b/app/selenium_ui/bitbucket/modules.py index a1321ec86..1fda10f23 100644 --- a/app/selenium_ui/bitbucket/modules.py +++ b/app/selenium_ui/bitbucket/modules.py @@ -219,6 +219,7 @@ def measure(webdriver, interaction): pull_request_page.merge_pull_request(interaction) measure(webdriver, 'selenium_create_pull_request:merge_pull_request') repository_branches_page.go_to() + repository_branches_page.wait_for_page_loaded(interaction) repository_branches_page.delete_branch(interaction=interaction, branch_name=datasets['pull_request_fork_branch_to']) measure(webdriver, 'selenium_create_pull_request') From 98effcecb6369796cdddbc31d7a566b4b4a28047 Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Thu, 2 Apr 2020 18:14:26 +0300 Subject: [PATCH 16/23] Dca-318 results validation (#169) Results summary --- app/util/analytics.py | 162 +++++++++++++++++- docs/bitbucket/README.md | 2 + docs/confluence/README.md | 2 + ...erformance-toolkit-user-guide-bitbucket.md | 26 +++ ...rformance-toolkit-user-guide-confluence.md | 26 +++ ...pps-performance-toolkit-user-guide-jira.md | 25 +++ docs/jira/README.md | 2 + 7 files changed, 237 insertions(+), 8 deletions(-) diff --git a/app/util/analytics.py b/app/util/analytics.py index acbd5294e..cfac8b092 100644 --- a/app/util/analytics.py +++ b/app/util/analytics.py @@ -18,6 +18,11 @@ CONFLUENCE = 'confluence' BITBUCKET = 'bitbucket' +MIN_DEFAULTS = {JIRA: {'test_duration': 2700, 'concurrency': 200}, + CONFLUENCE: {'test_duration': 2700, 'concurrency': 200}, + BITBUCKET: {'test_duration': 3000, 'concurrency': 20, 'git_operations_per_hour': 14400} + } + # List in value in case of specific output appears for some OS for command platform.system() OS = {'macOS': ['Darwin'], 'Windows': ['Windows'], 'Linux': ['Linux']} DT_REGEX = r'(\d{4}-\d{1,2}-\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2})' @@ -25,9 +30,19 @@ JMETER_TEST_REGX = r'jmeter_\S*' SELENIUM_TEST_REGX = r'selenium_\S*' BASE_URL = 'https://s7hdm2mnj1.execute-api.us-east-2.amazonaws.com/default/analytics_collector' +SUCCESS_TEST_RATE = 95.00 +RESULTS_CSV = 'results.csv' +BZT_LOG = 'bzt.log' +LABEL_HEADER = 'Label' +LABEL_HEADER_INDEX = 0 +SAMPLES_HEADER = '# Samples' +SAMPLES_HEADER_INDEX = 1 +GIT_OPERATIONS = ['jmeter_clone_repo_via_http', 'jmeter_clone_repo_via_ssh', + 'jmeter_git_push_via_http', 'jmeter_git_fetch_via_http', + 'jmeter_git_push_via_ssh', 'jmeter_git_fetch_via_ssh'] - -APP_TYPE_MSG = 'Please run util/analytics.py with application type as argument. E.g. python util/analytics.py jira' +APP_TYPE_MSG = ('ERROR: Please run util/analytics.py with application type as argument. ' + 'E.g. python util/analytics.py jira') def __validate_app_type(): @@ -54,11 +69,12 @@ def __init__(self, application_type): self.duration = 0 self.concurrency = 0 self.actual_duration = 0 - self.selenium_test_rates = 0 - self.jmeter_test_rates = 0 + self.selenium_test_rates = dict() + self.jmeter_test_rates = dict() self.time_stamp = "" self.date = "" self.application_version = "" + self.summary = [] @property def config_yml(self): @@ -74,11 +90,11 @@ def _log_dir(self): if 'TAURUS_ARTIFACTS_DIR' in os.environ: return os.environ.get('TAURUS_ARTIFACTS_DIR') else: - raise SystemExit('Taurus result directory could not be found') + raise SystemExit('ERROR: Taurus result directory could not be found') @property def bzt_log_file(self): - with open(f'{self._log_dir}/bzt.log') as log_file: + with open(f'{self._log_dir}/{BZT_LOG}') as log_file: log_file = log_file.readlines() return log_file @@ -94,7 +110,7 @@ def is_analytics_enabled(self): def __validate_bzt_log_not_empty(self): if len(self.bzt_log_file) == 0: - raise SystemExit(f'bzt.log file in {self._log_dir} is empty') + raise SystemExit(f'ERROR: {BZT_LOG} file in {self._log_dir} is empty') def get_duration_by_start_finish_strings(self): first_string = self.bzt_log_file[0] @@ -208,6 +224,135 @@ def generate_analytics(self): self.set_date_timestamp() self.application_version = self.get_application_version() + @property + def actual_git_operations_count(self): + count = 0 + + if self.application_type != BITBUCKET: + raise Exception(f'ERROR: {self.application_type} is not {BITBUCKET}') + results_csv_file_path = f'{self._log_dir}/results.csv' + if not os.path.exists(results_csv_file_path): + raise SystemExit(f'ERROR: {results_csv_file_path} was not found.') + with open(results_csv_file_path) as res_file: + header = res_file.readline() + results = res_file.readlines() + + headers_list = header.split(',') + if headers_list[LABEL_HEADER_INDEX] != LABEL_HEADER: + raise SystemExit(f'ERROR: {results_csv_file_path} has unexpected header. ' + f'Actual: {headers_list[LABEL_HEADER_INDEX]}, Expected: {LABEL_HEADER}') + if headers_list[SAMPLES_HEADER_INDEX] != SAMPLES_HEADER: + raise SystemExit(f'ERROR: {results_csv_file_path} has unexpected header. ' + f'Actual: {headers_list[SAMPLES_HEADER_INDEX]}, Expected: {SAMPLES_HEADER}') + + for line in results: + if any(s in line for s in GIT_OPERATIONS): + count = count + int(line.split(',')[SAMPLES_HEADER_INDEX]) + + return count + + @staticmethod + def is_all_tests_successful(tests): + for success_rate in tests.values(): + if success_rate < SUCCESS_TEST_RATE: + return False + return True + + def __is_success(self): + message = 'OK' + if not self.jmeter_test_rates: + return False, f"JMeter test results was not found." + if not self.selenium_test_rates: + return False, f"Selenium test results was not found." + + success = (self.is_all_tests_successful(self.jmeter_test_rates) and + self.is_all_tests_successful(self.selenium_test_rates)) + + if not success: + message = f"One or more actions have success rate < {SUCCESS_TEST_RATE} %." + return success, message + + def __is_finished(self): + message = 'OK' + finished = self.actual_duration >= self.duration + if not finished: + message = (f"Actual test duration {self.actual_duration} sec " + f"< than expected test_duration {self.duration} sec.") + return finished, message + + def __is_compliant(self): + message = 'OK' + compliant = (self.actual_duration >= MIN_DEFAULTS[self.application_type]['test_duration'] and + self.concurrency >= MIN_DEFAULTS[self.application_type]['concurrency']) + if not compliant: + err_msg = [] + if self.actual_duration < MIN_DEFAULTS[self.application_type]['test_duration']: + err_msg.append(f"Test run duration {self.actual_duration} sec < than minimum test " + f"duration {MIN_DEFAULTS[self.application_type]['test_duration']} sec.") + if self.concurrency < MIN_DEFAULTS[self.application_type]['concurrency']: + err_msg.append(f"Test run concurrency {self.concurrency} < than minimum test " + f"concurrency {MIN_DEFAULTS[self.application_type]['concurrency']}.") + message = ' '.join(err_msg) + return compliant, message + + def __is_git_operations_compliant(self): + # calculate expected git operations for a particular test duration + message = 'OK' + expected_get_operations_count = int(MIN_DEFAULTS[BITBUCKET]['git_operations_per_hour'] / 3600 * self.duration) + git_operations_compliant = self.actual_git_operations_count >= expected_get_operations_count + if not git_operations_compliant: + message = (f"Total git operations {self.actual_git_operations_count} < than " + f"{expected_get_operations_count} - minimum for expected duration {self.duration} sec.") + return git_operations_compliant, message + + def generate_report_summary(self): + summary_report = [] + summary_report_file = f'{self._log_dir}/results_summary.log' + + finished = self.__is_finished() + compliant = self.__is_compliant() + success = self.__is_success() + + overall_status = 'OK' if finished[0] and success[0] and compliant[0] else 'FAIL' + + if self.application_type == BITBUCKET: + git_compliant = self.__is_git_operations_compliant() + overall_status = 'OK' if overall_status and git_compliant[0] else 'FAIL' + + summary_report.append(self.format_string(f'Summary run status|{overall_status}\n')) + summary_report.append(self.format_string(f'OS|{self.os}')) + summary_report.append(self.format_string(f'DC Apps Performance Toolkit version|{self.tool_version}')) + summary_report.append(self.format_string(f'Application|{self.application_type} {self.application_version}')) + summary_report.append(self.format_string(f'Concurrency|{self.concurrency}')) + summary_report.append(self.format_string(f'Expected test run duration from yml file|{self.duration} sec')) + summary_report.append(self.format_string(f'Actual test run duration|{self.actual_duration} sec')) + + if self.application_type == BITBUCKET: + summary_report.append(self.format_string(f'Total Git operations count|{self.actual_git_operations_count}')) + summary_report.append(self.format_string(f'Total Git operations compliant|{git_compliant}')) + + summary_report.append(self.format_string(f'Finished|{finished}')) + summary_report.append(self.format_string(f'Compliant|{compliant}')) + summary_report.append(self.format_string(f'Success|{success}\n')) + + summary_report.append(self.format_string(f'Action|Success Rate|Status')) + + for key, value in {**self.jmeter_test_rates, **self.selenium_test_rates}.items(): + status = 'OK' if value >= SUCCESS_TEST_RATE else 'Fail' + summary_report.append(self.format_string(f'{key}|{value}|{status}')) + + self.__write_to_file(summary_report, summary_report_file) + + @staticmethod + def __write_to_file(content, file): + with open(file, 'w') as f: + f.writelines(content) + + @staticmethod + def format_string(string_to_format, offset=50): + # format string with delimiter "|" + return ''.join([f'{item}{" "*(offset-len(str(item)))}' for item in string_to_format.split("|")]) + "\n" + class AnalyticsSender: @@ -239,8 +384,9 @@ def send_request(self): def main(): app_type = get_application_type() collector = AnalyticsCollector(app_type) + collector.generate_analytics() + collector.generate_report_summary() if collector.is_analytics_enabled(): - collector.generate_analytics() sender = AnalyticsSender(collector) sender.send_request() diff --git a/docs/bitbucket/README.md b/docs/bitbucket/README.md index 7e934984d..7a6032a15 100644 --- a/docs/bitbucket/README.md +++ b/docs/bitbucket/README.md @@ -38,6 +38,8 @@ Results are located in the `resutls/bitbucket/YY-MM-DD-hh-mm-ss` directory: * `pytest.out` - detailed log of Selenium execution, including stacktraces of Selenium fails * `selenium.jtl` - Selenium raw data * `results.csv` - consolidated results of execution +* `resutls_summary.log` - detailed summary of the run. Make sure that overall run status is `OK` before moving to the +next steps. # Useful information diff --git a/docs/confluence/README.md b/docs/confluence/README.md index c51eddc9c..b35ce1dca 100644 --- a/docs/confluence/README.md +++ b/docs/confluence/README.md @@ -37,6 +37,8 @@ Results are located in the `resutls/confluence/YY-MM-DD-hh-mm-ss` directory: * `pytest.out` - detailed log of Selenium execution, including stacktraces of Selenium fails * `selenium.jtl` - Selenium raw data * `results.csv` - consolidated results of execution +* `resutls_summary.log` - detailed summary of the run. Make sure that overall run status is `OK` before moving to the +next steps. # Useful information diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 8f515035f..3b72fb931 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -372,6 +372,7 @@ To receive performance baseline results without an app installed: bzt bitbucket.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/bitbucket/YY-MM-DD-hh-mm-ss` folder: + - `results_summary.log`: detailed run summary - `results.csv`: aggregated .csv file with all actions and timings - `bzt.log`: logs of the Taurus tool execution - `jmeter.*`: logs of the JMeter tool execution @@ -381,6 +382,11 @@ To receive performance baseline results without an app installed: When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "without app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Run 2 (~1 hour) To receive performance results with an app installed: @@ -396,6 +402,11 @@ To receive performance results with an app installed: When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "with app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a performance regression report To generate a performance regression report: @@ -472,6 +483,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 1"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 4 (~1 hour) To receive scalability benchmark results for two-node Bitbucket DC with app-specific actions: @@ -490,6 +506,11 @@ To receive scalability benchmark results for two-node Bitbucket DC with app-spec When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 2"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 5 (~1 hour) To receive scalability benchmark results for four-node Bitbucket DC with app-specific actions: @@ -507,6 +528,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 4"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a report for scalability scenario To generate a scalability report: diff --git a/docs/dc-apps-performance-toolkit-user-guide-confluence.md b/docs/dc-apps-performance-toolkit-user-guide-confluence.md index 25c402bac..b1af4a383 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-confluence.md +++ b/docs/dc-apps-performance-toolkit-user-guide-confluence.md @@ -339,6 +339,7 @@ To receive performance baseline results without an app installed: bzt confluence.yml ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/confluence/YY-MM-DD-hh-mm-ss` folder: + - `results_summary.log`: detailed run summary - `results.csv`: aggregated .csv file with all actions and timings - `bzt.log`: logs of the Taurus tool execution - `jmeter.*`: logs of the JMeter tool execution @@ -348,6 +349,11 @@ To receive performance baseline results without an app installed: When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "without app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Run 2 (~50 min) To receive performance results with an app installed: @@ -363,6 +369,11 @@ To receive performance results with an app installed: When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "with app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a performance regression report To generate a performance regression report: @@ -504,6 +515,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 1"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 4 (~50 min) To receive scalability benchmark results for two-node Confluence DC with app-specific actions: @@ -544,6 +560,11 @@ To receive scalability benchmark results for two-node Confluence DC with app-spe When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 2"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 5 (~50 min) To receive scalability benchmark results for four-node Confluence DC with app-specific actions: @@ -561,6 +582,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 4"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a report for scalability scenario To generate a scalability report: diff --git a/docs/dc-apps-performance-toolkit-user-guide-jira.md b/docs/dc-apps-performance-toolkit-user-guide-jira.md index 5b71c5f0e..8400b7ee3 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jira.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jira.md @@ -358,6 +358,7 @@ To receive performance baseline results without an app installed: ``` 1. View the following main results of the run in the `dc-app-performance-toolkit/app/results/jira/YY-MM-DD-hh-mm-ss` folder: + - `results_summary.log`: detailed run summary - `results.csv`: aggregated .csv file with all actions and timings - `bzt.log`: logs of the Taurus tool execution - `jmeter.*`: logs of the JMeter tool execution @@ -367,6 +368,10 @@ To receive performance baseline results without an app installed: When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "without app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + #### Run 2 (~50 min + Lucene Index timing test) If you are submitting a Jira app, you are required to conduct a Lucene Index timing test. This involves conducting a foreground re-index on a single-node Data Center deployment (without and with your app installed) and a dataset that has 1M issues. @@ -402,6 +407,11 @@ After attaching both screenshots to your DC HELP ticket, move on to performance When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "with app"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a performance regression report To generate a performance regression report: @@ -543,6 +553,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 1"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 4 (~50 min) To receive scalability benchmark results for two-node Jira DC with app-specific actions: @@ -584,6 +599,11 @@ To receive scalability benchmark results for two-node Jira DC with app-specific When the execution is successfully completed, the `INFO: Artifacts dir:` line with the full path to results directory will be displayed in console output. Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 2"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + ##### Run 5 (~50 min) To receive scalability benchmark results for four-node Jira DC with app-specific actions: @@ -601,6 +621,11 @@ When the execution is successfully completed, the `INFO: Artifacts dir:` line wi Save this full path to the run results folder. Later you will have to insert it under `runName: "Node 4"` for report generation. {{% /note %}} +{{% note %}} +Review `results_summary.log` file under artifacts dir location. Make sure that overall status is `OK` before moving to the next steps. +{{% /note %}} + + #### Generating a report for scalability scenario To generate a scalability report: diff --git a/docs/jira/README.md b/docs/jira/README.md index c5d29a207..647f48e17 100644 --- a/docs/jira/README.md +++ b/docs/jira/README.md @@ -37,6 +37,8 @@ Results are located in the `resutls/jira/YY-MM-DD-hh-mm-ss` directory: * `pytest.out` - detailed log of Selenium execution, including stacktraces of Selenium fails * `selenium.jtl` - Selenium raw data * `results.csv` - consolidated results of execution +* `resutls_summary.log` - detailed summary of the run. Make sure that overall run status is `OK` before moving to the +next steps. # Useful information From 9ca4bed0d326001ab5374c2bd989e450ceb3466f Mon Sep 17 00:00:00 2001 From: Sergey Moroz Date: Thu, 2 Apr 2020 19:37:28 +0300 Subject: [PATCH 17/23] NONE pageobject pattern jira (#168) * Added Jira pageobjects Co-authored-by: Oleksandr Metelytsia --- app/extension/jira/extension_ui.py | 16 +- app/selenium_ui/base_page.py | 24 ++ app/selenium_ui/jira/modules.py | 319 +++++++----------------- app/selenium_ui/jira/pages/pages.py | 196 +++++++++++++++ app/selenium_ui/jira/pages/selectors.py | 149 +++++++++++ app/selenium_ui/jira_ui.py | 8 +- 6 files changed, 465 insertions(+), 247 deletions(-) create mode 100644 app/selenium_ui/jira/pages/pages.py create mode 100644 app/selenium_ui/jira/pages/selectors.py diff --git a/app/extension/jira/extension_ui.py b/app/extension/jira/extension_ui.py index 21d81e612..92a987380 100644 --- a/app/extension/jira/extension_ui.py +++ b/app/extension/jira/extension_ui.py @@ -1,29 +1,25 @@ from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as ec - from selenium_ui.conftest import print_timing -from selenium_ui.jira.modules import _wait_until from util.conf import JIRA_SETTINGS -APPLICATION_URL = JIRA_SETTINGS.server_url -timeout = 20 +from selenium_ui.base_page import BasePage def custom_action(webdriver, datasets): + page = BasePage(webdriver) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/reporter') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-element")), interaction) + page.go_to_url(f"{JIRA_SETTINGS.server_url}/plugin/report") + page.wait_until_visible((By.ID, 'report_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_report') @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/administration') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-dashboard")), interaction) + page.go_to_url(f"{JIRA_SETTINGS.server_url}/plugin/dashboard") + page.wait_until_visible((By.ID, 'dashboard_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_dashboard') - measure(webdriver, 'selenium_app_custom_action') diff --git a/app/selenium_ui/base_page.py b/app/selenium_ui/base_page.py index 29fccdd81..dbd9e6d1f 100644 --- a/app/selenium_ui/base_page.py +++ b/app/selenium_ui/base_page.py @@ -1,5 +1,7 @@ from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as ec +from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.support.ui import Select from selenium_ui.conftest import AnyEc import random import string @@ -47,6 +49,11 @@ def wait_until_visible(self, selector_name, interaction=None): return self.__wait_until(expected_condition=ec.visibility_of_element_located(selector), interaction=interaction) + def wait_until_available_to_switch(self, selector_name, interaction=None): + selector = self.get_selector(selector_name) + return self.__wait_until(expected_condition=ec.frame_to_be_available_and_switch_to_it(selector), + interaction=interaction) + def wait_until_present(self, selector_name, interaction=None, time_out=TIMEOUT): selector = self.get_selector(selector_name) return self.__wait_until(expected_condition=ec.presence_of_element_located(selector), @@ -62,6 +69,14 @@ def wait_until_any_element_visible(self, selector_name, interaction=None): return self.__wait_until(expected_condition=ec.visibility_of_any_elements_located(selector), interaction=interaction) + def wait_until_any_ec_presented(self, selector_names, interaction): + origin_selectors = [] + for selector in selector_names: + origin_selectors.append(self.get_selector(selector)) + any_ec = AnyEc() + any_ec.ecs = tuple(ec.presence_of_element_located(origin_selector) for origin_selector in origin_selectors) + return self.__wait_until(expected_condition=any_ec, interaction=interaction) + def __wait_until(self, expected_condition, interaction, time_out=TIMEOUT): message = f"Interaction: {interaction}. " ec_type = type(expected_condition) @@ -93,6 +108,9 @@ def dismiss_popup(self, *args): except: pass + def return_to_parent_frame(self): + return self.driver.switch_to.parent_frame() + def get_selector(self, selector_name): selector = selector_name.get(self.app_version) if type(selector_name) == dict else selector_name if selector is None: @@ -109,3 +127,9 @@ def app_version(self): @staticmethod def generate_random_string(length): return "".join([random.choice(string.digits + string.ascii_letters + ' ') for _ in range(length)]) + + def select(self, element): + return Select(element) + + def action_chains(self): + return ActionChains(self.driver) \ No newline at end of file diff --git a/app/selenium_ui/jira/modules.py b/app/selenium_ui/jira/modules.py index 42c1a5784..e10946b84 100644 --- a/app/selenium_ui/jira/modules.py +++ b/app/selenium_ui/jira/modules.py @@ -1,355 +1,208 @@ import random -import time import urllib.parse -from selenium.webdriver.common.action_chains import ActionChains -from selenium.webdriver.common.by import By -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.ui import Select -from selenium.webdriver.support.wait import WebDriverWait +from selenium_ui.conftest import print_timing +from selenium_ui.jira.pages.pages import Login, PopupManager, Issue, Project, Search, ProjectsList, \ + BoardsList, Board, Dashboard, Logout -from selenium_ui.conftest import print_timing, AnyEc, generate_random_string -from util.conf import JIRA_SETTINGS -timeout = 20 - -ISSUE_TYPE_DROPDOWN = 'issuetype-field' -APPLICATION_URL = JIRA_SETTINGS.server_url - - -def _dismiss_popup(webdriver, *args): - for elem in args: - try: - webdriver.execute_script(f"document.querySelector(\'{elem}\').click()") - except: - pass +def setup_run_data(datasets): + page_size = 25 + projects_count = len(datasets['project_keys']) + user = random.choice(datasets["users"]) + issue = random.choice(datasets["issues"]) + scrum_boards = random.choice(datasets["scrum_boards"]) + kanban_boards = random.choice(datasets["kanban_boards"]) + project_key = random.choice(datasets["issues"])[2] + datasets['username'] = user[0] + datasets['password'] = user[1] + datasets['issue_key'] = issue[0] + datasets['issue_id'] = issue[1] + datasets['project_key'] = project_key + datasets['scrum_board_id'] = scrum_boards[0] + datasets['kanban_board_id'] = kanban_boards[0] + datasets['jql'] = urllib.parse.quote(random.choice(datasets["jqls"][0])) + datasets['pages'] = projects_count // page_size if projects_count % page_size == 0 \ + else projects_count // page_size + 1 def login(webdriver, datasets): + setup_run_data(datasets) @print_timing def measure(webdriver, interaction): + login_page = Login(webdriver) @print_timing # TODO do we need this unused argument? Suggest rewriting without using the same function names and inner funcs def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/login.jsp') - + login_page.go_to() measure(webdriver, "selenium_login:open_login_page") - def _setup_page_is_presented(): - elems = webdriver.find_elements_by_id('next') - return True if elems else False - - def _user_setup(): - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "next")), interaction) - next_el = webdriver.find_element_by_id('next') - next_el.send_keys(Keys.ESCAPE) - next_el.click() - _wait_until(webdriver, - ec.visibility_of_element_located((By.CSS_SELECTOR, "input[value='Next']")), interaction - ).click() - _wait_until(webdriver, - ec.visibility_of_element_located((By.CSS_SELECTOR, "a[data-step-key='browseprojects']")), - interaction - ).click() - webdriver.get(f'{APPLICATION_URL}/secure/Dashboard.jspa') - - user = random.choice(datasets["users"]) - webdriver.find_element_by_id('login-form-username').send_keys(user[0]) - webdriver.find_element_by_id('login-form-password').send_keys(user[1]) - @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id('login-form-submit').click() - if _setup_page_is_presented(): - _user_setup() - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "page-type-dashboard")), interaction) - + login_page.set_credentials(username=datasets['username'], password=datasets['password']) + if login_page.is_first_login(): + login_page.first_login_setup(interaction) + login_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_login:login_and_view_dashboard") - measure(webdriver, "selenium_login") - - _dismiss_popup(webdriver, - '.aui-message .icon-close', - 'form.tip-footer>.helptip-close', - '.aui-inline-dialog-contents .cancel' - ) + PopupManager(webdriver).dismiss_default_popup() def view_issue(webdriver, datasets): - issue = random.choice(datasets["issues"])[0] - + issue_page = Issue(webdriver, issue_key=datasets['issue_key']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/browse/{issue}') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "summary-val")), interaction) - + issue_page.go_to() + issue_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_issue") def create_issue(webdriver, datasets): + issue_modal = Issue(webdriver) @print_timing def measure(webdriver, interaction): - # open quick create - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, "create_link")), interaction) - @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id('create_link').click() - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "create-issue-dialog")), interaction) - + issue_modal.open_create_issue_modal(interaction) measure(webdriver, "selenium_create_issue:open_quick_create") - # create issue @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, "summary")), interaction - ).send_keys(f"Issue created date {time.time()}") - - text_description = f'Description: {generate_random_string(100)}' - element = webdriver.find_element_by_id('description') - _write_to_text_area(webdriver, element, text_description, interaction) - - # Assign to me - assign_els = webdriver.find_elements_by_id('assign-to-me-trigger') - if assign_els: - assign_els[0].click() - - # Set resolution if there is such field - resolution_els = webdriver.find_elements_by_id('resolution') - if resolution_els: - dropdown_length = len(Select(resolution_els[0]).options) - random_resolution_id = random.randint(1, dropdown_length - 1) - Select(resolution_els[0]).select_by_index(random_resolution_id) - - def __filer_epic(element): - return "epic" not in element.get_attribute("class").lower() - - webdriver.find_element_by_id(ISSUE_TYPE_DROPDOWN).click() - issue_elements_in_dropdown = webdriver.find_elements_by_class_name("aui-list-item") - if issue_elements_in_dropdown: - filtered_issue_elements = list(filter(__filer_epic, issue_elements_in_dropdown)) - rnd_issue_type_el = random.choice(filtered_issue_elements) - action = ActionChains(webdriver) - action.move_to_element(rnd_issue_type_el).click(rnd_issue_type_el).perform() - - # Wait until issue-form saves issue-type - _wait_until(webdriver, ec.invisibility_of_element_located((By.CSS_SELECTOR, ".buttons>.throbber")), - interaction) + issue_modal.fill_summary_create(interaction) # Fill summary field + issue_modal.fill_description_create(interaction) # Fill description field + issue_modal.assign_to_me() # Click assign to me + issue_modal.set_resolution() # Set resolution if there is such field + issue_modal.set_issue_type(interaction) # Set issue type, use non epic type @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, "create-issue-submit")), interaction).click() - _wait_until(webdriver, ec.invisibility_of_element_located((By.ID, "create-issue-dialog")), interaction) - + issue_modal.submit_issue(interaction) measure(webdriver, "selenium_create_issue:submit_issue_form") - measure(webdriver, "selenium_create_issue:fill_and_submit_issue_form") - measure(webdriver, "selenium_create_issue") - _dismiss_popup(webdriver, '.aui-inline-dialog-contents .cancel') + PopupManager(webdriver).dismiss_default_popup() def view_project_summary(webdriver, datasets): - project_key = random.choice(datasets["issues"])[2] + project_page = Project(webdriver, project_key=datasets['project_key']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/browse/{project_key}/summary') - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "project-meta-column")), interaction) - + project_page.go_to() + project_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_project_summary") def search_jql(webdriver, datasets): - jql = urllib.parse.quote(random.choice(datasets["jqls"][0])) + search_page = Search(webdriver, jql=datasets['jql']) @print_timing def measure(webdriver, interaction): - webdriver.get(APPLICATION_URL + f"/issues/?jql={jql}") - _wait_until(webdriver, AnyEc( - ec.presence_of_element_located((By.ID, "issuetable")), - ec.presence_of_element_located((By.ID, "issue-content")), - ec.presence_of_element_located((By.CLASS_NAME, "no-results-hint")) - ), interaction) - + search_page.go_to() + search_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_search_jql") def edit_issue(webdriver, datasets): - issue_id = random.choice(datasets["issues"])[1] + issue_page = Issue(webdriver, issue_id=datasets['issue_id']) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - # open editor - webdriver.get(f'{APPLICATION_URL}/secure/EditIssue!default.jspa?id={issue_id}') - _wait_until(webdriver, ec.presence_of_element_located((By.ID, "issue-edit")), interaction) - + issue_page.go_to_edit_issue(interaction) # open editor measure(webdriver, "selenium_edit_issue:open_edit_issue_form") - # edit summary - text_summary = f"Edit summary form selenium - {generate_random_string(10)}" - webdriver.find_element_by_id('summary').send_keys(text_summary) - - # edit description - text_description = f"Edit description form selenium - {generate_random_string(30)}" - element = webdriver.find_element_by_id('description') - _write_to_text_area(webdriver, element, text_description, interaction) + issue_page.fill_summary_edit() # edit summary + issue_page.fill_description_edit(interaction) # edit description @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id('issue-edit-submit').click() - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "summary-val")), interaction) - + issue_page.edit_issue_submit() # submit edit issue + issue_page.wait_for_issue_title(interaction) measure(webdriver, "selenium_edit_issue:save_edit_issue_form") - measure(webdriver, "selenium_edit_issue") def save_comment(webdriver, datasets): - # open comment editor - issue_id = random.choice(datasets["issues"])[1] - + issue_page = Issue(webdriver, issue_id=datasets['issue_id']) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/AddComment!default.jspa?id={issue_id}') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "comment-add-submit")), interaction) - + issue_page.go_to_edit_comment(interaction) # Open edit comment page measure(webdriver, "selenium_save_comment:open_comment_form") - # save editor - element = webdriver.find_element_by_id('comment') - text_comment = "Comment from selenium" - _write_to_text_area(webdriver, element, text_comment, interaction) + issue_page.fill_comment_edit(interaction) # Fill comment text field @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id('comment-add-submit').click() - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "summary-val")), interaction) - + issue_page.edit_comment_submit(interaction) # Submit comment measure(webdriver, "selenium_save_comment:submit_form") - measure(webdriver, "selenium_save_comment") -def browse_project(webdriver, datasets): +def browse_projects_list(webdriver, datasets): @print_timing def measure(webdriver, interaction): - page_size = 25 - projects_count = len(datasets['project_keys']) - pages = projects_count // page_size if projects_count % page_size == 0 else projects_count // page_size + 1 - webdriver.get( - APPLICATION_URL + - f'/secure/BrowseProjects.jspa?selectedCategory=all&selectedProjectType=all&page={random.randint(1, pages)}') - _wait_until(webdriver, AnyEc(ec.presence_of_element_located((By.CSS_SELECTOR, "tbody.projects-list")), - ec.presence_of_element_located((By.CLASS_NAME, "none-panel")) - ), interaction) - measure(webdriver, "selenium_browse_project") - - -def browse_board(webdriver, datasets): - @print_timing - def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/ManageRapidViews.jspa') - _wait_until(webdriver, ec.presence_of_element_located((By.CSS_SELECTOR, "#ghx-content-main table.aui")), - interaction) + projects_list_page = ProjectsList(webdriver, projects_list_pages=datasets['pages']) + projects_list_page.go_to() + projects_list_page.wait_for_page_loaded(interaction) + measure(webdriver, "selenium_browse_projects_list") - measure(webdriver, "selenium_browse_board") - _dismiss_popup(webdriver, 'aui-inline-dialog-contents .cancel') +def browse_boards_list(webdriver, datasets): + @print_timing + def measure(webdriver, interaction): + boards_list_page = BoardsList(webdriver) + boards_list_page.go_to() + boards_list_page.wait_for_page_loaded(interaction) + measure(webdriver, "selenium_browse_boards_list") + PopupManager(webdriver).dismiss_default_popup() def view_backlog_for_scrum_board(webdriver, datasets): - board_id = random.choice(datasets["scrum_boards"])[0] - + scrum_board_page = Board(webdriver, board_id=datasets['scrum_board_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/RapidBoard.jspa?rapidView={board_id}&view=planning') - _wait_until(webdriver, ec.presence_of_element_located( - (By.CSS_SELECTOR, "#ghx-backlog[data-rendered]:not(.browser-metrics-stale)")), interaction) - + scrum_board_page.go_to_backlog() + scrum_board_page.wait_for_scrum_board_backlog(interaction) measure(webdriver, "selenium_view_scrum_board_backlog") def view_scrum_board(webdriver, datasets): - board_id = random.choice(datasets["scrum_boards"])[0] - + scrum_board_page = Board(webdriver, board_id=datasets['scrum_board_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/RapidBoard.jspa?rapidView={board_id}') - _wait_until(webdriver, ec.presence_of_element_located((By.CSS_SELECTOR, ".ghx-column")), interaction) - + scrum_board_page.go_to() + scrum_board_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_scrum_board") def view_kanban_board(webdriver, datasets): - board_id = random.choice(datasets["kanban_boards"]) - + kanban_board_page = Board(webdriver, board_id=datasets['kanban_board_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/RapidBoard.jspa?rapidView={board_id}') - _wait_until(webdriver, ec.presence_of_element_located((By.CSS_SELECTOR, ".ghx-column")), interaction) - + kanban_board_page.go_to() + kanban_board_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_kanban_board") def view_dashboard(webdriver, datasets): + dashboard_page = Dashboard(webdriver) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/secure/Dashboard.jspa') - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "page-type-dashboard")), interaction) - + dashboard_page.go_to() + dashboard_page.wait_dashboard_presented(interaction) measure(webdriver, "selenium_view_dashboard") def log_out(webdriver, datasets): + logout_page = Logout(webdriver) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/logoutconfirm.jsp') - webdriver.find_element_by_id('confirm-logout-submit').click() - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, "login-link")), interaction) - + logout_page.go_to() + logout_page.click_logout() + logout_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_log_out") - -def _write_to_text_area(webdriver, element, input_text, interaction): - # Rich Text Editor - if "richeditor-cover" in element.get_attribute("class"): - attribute_id = element.get_attribute("id") - iframe_xpath = f"//div[textarea[@id='{attribute_id}']]//iframe" - _wait_until(webdriver, ec.frame_to_be_available_and_switch_to_it((By.XPATH, iframe_xpath)), interaction) - # Send keys seems flaky when using Rich Text Editor (tinymce). Make the "input_text" small. - webdriver.find_element_by_id("tinymce").send_keys(input_text) - webdriver.switch_to.parent_frame() - # Plain text - else: - element.send_keys(input_text) - - -def _wait_until(webdriver, expected_condition, interaction, time_out=timeout): - message = f"Interaction: {interaction}. " - ec_type = type(expected_condition) - if ec_type == AnyEc: - conditions_text = "" - for ecs in expected_condition.ecs: - conditions_text = conditions_text + " " + f"Condition: {str(ecs)} Locator: {ecs.locator}\n" - - message += f"Timed out after {time_out} sec waiting for one of the conditions: \n{conditions_text}" - - elif ec_type == ec.invisibility_of_element_located: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.target}") - - elif ec_type == ec.frame_to_be_available_and_switch_to_it: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.frame_locator}") - - else: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.locator}") - - return WebDriverWait(webdriver, time_out).until(expected_condition, message=message) diff --git a/app/selenium_ui/jira/pages/pages.py b/app/selenium_ui/jira/pages/pages.py new file mode 100644 index 000000000..b2e6119ca --- /dev/null +++ b/app/selenium_ui/jira/pages/pages.py @@ -0,0 +1,196 @@ +from selenium.webdriver.common.keys import Keys +import time +import random + +from selenium_ui.base_page import BasePage +from selenium_ui.jira.pages.selectors import UrlManager, LoginPageLocators, DashboardLocators, PopupLocators, \ + IssueLocators, ProjectLocators, SearchLocators, BoardsListLocators, BoardLocators, LogoutLocators + + +class PopupManager(BasePage): + + def dismiss_default_popup(self): + return self.dismiss_popup(PopupLocators.default_popup, PopupLocators.popup_1, PopupLocators.popup_2) + + +class Login(BasePage): + page_url = LoginPageLocators.login_url + page_loaded_selector = LoginPageLocators.system_dashboard + + def is_first_login(self): + return True if self.get_elements(LoginPageLocators.continue_button) else False + + def first_login_setup(self, interaction): + self.wait_until_visible(LoginPageLocators.continue_button, interaction).send_keys(Keys.ESCAPE) + self.get_element(LoginPageLocators.continue_button).click() + self.wait_until_visible(LoginPageLocators.avatar_page_next_button, interaction).click() + self.wait_until_visible(LoginPageLocators.explore_current_projects, interaction).click() + self.go_to_url(DashboardLocators.dashboard_url) + self.wait_until_visible(DashboardLocators.dashboard_window, interaction) + + def set_credentials(self, username, password): + self.get_element(LoginPageLocators.login_field).send_keys(username) + self.get_element(LoginPageLocators.password_field).send_keys(password) + self.get_element(LoginPageLocators.login_submit_button).click() + + +class Logout(BasePage): + page_url = LogoutLocators.logout_url + + def click_logout(self): + self.get_element(LogoutLocators.logout_submit_button).click() + + def wait_for_page_loaded(self, interaction): + self.wait_until_present(LogoutLocators.login_button_link, interaction) + + +class Dashboard(BasePage): + page_url = DashboardLocators.dashboard_url + + def wait_dashboard_presented(self, interaction): + self.wait_until_present(DashboardLocators.dashboard_window, interaction) + + +class Issue(BasePage): + page_loaded_selector = IssueLocators.issue_title + + def __init__(self, driver, issue_key=None, issue_id=None): + BasePage.__init__(self, driver) + url_manager_modal = UrlManager(issue_key=issue_key) + url_manager_edit_page = UrlManager(issue_id=issue_id) + self.page_url = url_manager_modal.issue_url() + self.page_url_edit_issue = url_manager_edit_page.edit_issue_url() + self.page_url_edit_comment = url_manager_edit_page.edit_comments_url() + + def wait_for_issue_title(self, interaction): + self.wait_until_visible(IssueLocators.issue_title, interaction) + + def go_to_edit_issue(self, interaction): + self.go_to_url(self.page_url_edit_issue) + self.wait_until_visible(IssueLocators.edit_issue_page, interaction) + + def go_to_edit_comment(self, interaction): + self.go_to_url(self.page_url_edit_comment) + self.wait_until_visible(IssueLocators.edit_comment_add_comment_button, interaction) + + def fill_summary_edit(self): + text_summary = f"Edit summary form selenium - {self.generate_random_string(10)}" + self.get_element(IssueLocators.issue_summary_field).send_keys(text_summary) + + def __fill_rich_editor_textfield(self, text, interaction, selector): + self.wait_until_available_to_switch(selector, interaction) + self.get_element(IssueLocators.tinymce_description_field).send_keys(text) + self.return_to_parent_frame() + + def edit_issue_submit(self): + self.get_element(IssueLocators.edit_issue_submit).click() + + def fill_description_edit(self, interaction): + text_description = f"Edit description form selenium - {self.generate_random_string(30)}" + self.__fill_rich_editor_textfield(text_description, interaction, selector=IssueLocators.issue_description_field) + + def open_create_issue_modal(self, interaction): + self.wait_until_clickable(IssueLocators.create_issue_button, interaction).click() + self.wait_until_visible(IssueLocators.issue_modal, interaction) + + def fill_description_create(self, interaction): + text_description = f'Description: {self.generate_random_string(100)}' + self.__fill_rich_editor_textfield(text_description, interaction, selector=IssueLocators.issue_description_field) + + def fill_summary_create(self, interaction): + summary = f"Issue created date {time.time()}" + self.wait_until_clickable(IssueLocators.issue_summary_field, interaction).send_keys(summary) + + def assign_to_me(self): + assign_to_me_links = self.get_elements(IssueLocators.issue_assign_to_me_link) + for link in assign_to_me_links: + link.click() + + def set_resolution(self): + resolution_field = self.get_elements(IssueLocators.issue_resolution_field) + if resolution_field: + dropdown_length = len(self.select(resolution_field[0]).options) + random_resolution_id = random.randint(1, dropdown_length - 1) + self.select(resolution_field[0]).select_by_index(random_resolution_id) + + def set_issue_type(self, interaction): + def __filer_epic(element): + return "epic" not in element.get_attribute("class").lower() + + self.get_element(IssueLocators.issue_type_field).click() + issue_dropdown_elements = self.get_elements(IssueLocators.issue_type_dropdown_elements) + if issue_dropdown_elements: + filtered_issue_elements = list(filter(__filer_epic, issue_dropdown_elements)) + rnd_issue_type_el = random.choice(filtered_issue_elements) + self.action_chains().move_to_element(rnd_issue_type_el).click(rnd_issue_type_el).perform() + self.wait_until_invisible(IssueLocators.issue_ready_to_save_spinner, interaction) + + def submit_issue(self, interaction): + self.wait_until_clickable(IssueLocators.issue_submit_button, interaction).click() + self.wait_until_invisible(IssueLocators.issue_modal) + + def fill_comment_edit(self, interaction): + text = 'Comment from selenium' + self.__fill_rich_editor_textfield(text, interaction, selector=IssueLocators.edit_comment_text_field) + + def edit_comment_submit(self, interaction): + self.get_element(IssueLocators.edit_comment_add_comment_button).click() + self.wait_until_visible(IssueLocators.issue_title) + + +class Project(BasePage): + page_loaded_selector = ProjectLocators.project_summary_property_column + + def __init__(self, driver, project_key): + BasePage.__init__(self, driver) + url_manager = UrlManager(project_key=project_key) + self.page_url = url_manager.project_summary_url() + + +class ProjectsList(BasePage): + + def __init__(self, driver, projects_list_pages): + BasePage.__init__(self, driver) + self.projects_list_page = random.randint(1, projects_list_pages) + url_manager = UrlManager(projects_list_page=self.projects_list_page) + self.page_url = url_manager.projects_list_page_url() + + def wait_for_page_loaded(self, interaction): + self.wait_until_any_ec_presented(selector_names=[ProjectLocators.projects_list, + ProjectLocators.projects_not_found], + interaction=interaction) + + +class BoardsList(BasePage): + page_url = BoardsListLocators.boards_list_url + page_loaded_selector = BoardsListLocators.boards_list + + +class Search(BasePage): + + def __init__(self, driver, jql): + BasePage.__init__(self, driver) + url_manager = UrlManager(jql=jql) + self.page_url = url_manager.jql_search_url() + + def wait_for_page_loaded(self, interaction): + self.wait_until_any_ec_presented(selector_names=[SearchLocators.search_issue_table, + SearchLocators.search_issue_content, + SearchLocators.search_no_issue_found], + interaction=interaction) + + +class Board(BasePage): + page_loaded_selector = BoardLocators.board_columns + + def __init__(self, driver, board_id): + BasePage.__init__(self, driver) + url_manager = UrlManager(board_id=board_id) + self.page_url = url_manager.scrum_board_url() + self.backlog_url = url_manager.scrum_board_backlog_url() + + def go_to_backlog(self): + self.go_to_url(self.backlog_url) + + def wait_for_scrum_board_backlog(self, interaction): + self.wait_until_present(BoardLocators.scrum_board_backlog_content, interaction) diff --git a/app/selenium_ui/jira/pages/selectors.py b/app/selenium_ui/jira/pages/selectors.py new file mode 100644 index 000000000..1247da305 --- /dev/null +++ b/app/selenium_ui/jira/pages/selectors.py @@ -0,0 +1,149 @@ +from selenium.webdriver.common.by import By +from util.conf import JIRA_SETTINGS + + +class PopupLocators: + default_popup = '.aui-message .icon-close' + popup_1 = 'form.tip-footer>.helptip-close' + popup_2 = '.aui-inline-dialog-contents .cancel' + + +class UrlManager: + + def __init__(self, issue_key=None, issue_id=None, project_key=None, jql=None, projects_list_page=None, + board_id=None): + self.host = JIRA_SETTINGS.server_url + self.login_params = '/login.jsp' + self.logout_params = '/logoutconfirm.jsp' + self.dashboard_params = '/secure/Dashboard.jspa' + self.issue_params = f"/browse/{issue_key}" + self.project_summary_params = f"/projects/{project_key}/summary" + self.jql_params = f"/issues/?jql={jql}" + self.edit_issue_params = f"/secure/EditIssue!default.jspa?id={issue_id}" + self.edit_comments_params = f"/secure/AddComment!default.jspa?id={issue_id}" + self.projects_list_params = f'/secure/BrowseProjects.jspa?selectedCategory=all&selectedProjectType=all&page=' \ + f'{projects_list_page}' + self.boards_list_params = '/secure/ManageRapidViews.jspa' + self.scrum_board_backlog_params = f"/secure/RapidBoard.jspa?rapidView={board_id}&view=planning" + self.scrum_board_params = f"/secure/RapidBoard.jspa?rapidView={board_id}" + + def login_url(self): + return f"{self.host}{self.login_params}" + + def dashboard_url(self): + return f"{self.host}{self.dashboard_params}" + + def issue_url(self): + return f"{self.host}{self.issue_params}" + + def project_summary_url(self): + return f"{self.host}{self.project_summary_params}" + + def jql_search_url(self): + return f"{self.host}{self.jql_params}" + + def edit_issue_url(self): + return f"{self.host}{self.edit_issue_params}" + + def edit_comments_url(self): + return f"{self.host}{self.edit_comments_params}" + + def projects_list_page_url(self): + return f"{self.host}{self.projects_list_params}" + + def boards_list_page_url(self): + return f"{self.host}{self.boards_list_params}" + + def scrum_board_backlog_url(self): + return f"{self.host}{self.scrum_board_backlog_params}" + + def scrum_board_url(self): + return f"{self.host}{self.scrum_board_params}" + + def logout_url(self): + return f"{self.host}{self.logout_params}" + + +class LoginPageLocators: + + login_url = UrlManager().login_url() + login_params = UrlManager().login_params + + # First time login setup page + continue_button = (By.ID, 'next') + avatar_page_next_button = (By.CSS_SELECTOR, "input[value='Next']") + explore_current_projects = (By.CSS_SELECTOR, "a[data-step-key='browseprojects']") + login_field = (By.ID, 'login-form-username') + password_field = (By.ID, 'login-form-password') + login_submit_button = (By.ID, 'login-form-submit') + system_dashboard = (By.ID, "dashboard") + + +class LogoutLocators: + + logout_url = UrlManager().logout_url() + logout_submit_button = (By.ID, "confirm-logout-submit") + login_button_link = (By.CLASS_NAME, "login-link") + + +class DashboardLocators: + + dashboard_url = UrlManager().dashboard_url() + dashboard_params = UrlManager().dashboard_params + dashboard_window = (By.CLASS_NAME, "page-type-dashboard") + + +class IssueLocators: + + issue_title = (By.ID, "summary-val") + + create_issue_button = (By.ID, "create_link") + # Issue create modal form + issue_modal = (By.ID, "create-issue-dialog") + issue_summary_field = (By.ID, "summary") + issue_description_field = (By.XPATH, f"//div[textarea[@id='description']]//iframe") + tinymce_description_field = (By.ID, "tinymce") + issue_assign_to_me_link = (By.ID, 'assign-to-me-trigger') + issue_resolution_field = (By.ID, 'resolution') + issue_type_field = (By.ID, 'issuetype-field') + issue_type_dropdown_elements = (By.CLASS_NAME, "aui-list-item") + issue_ready_to_save_spinner = (By.CSS_SELECTOR, ".buttons>.throbber") + issue_submit_button = (By.ID, "create-issue-submit") + + # Edit Issue page + edit_issue_page = (By.ID, "issue-edit") + edit_issue_description = (By.ID, 'description') + edit_issue_submit = (By.ID, 'issue-edit-submit') + + # Edit Comments page + edit_comment_add_comment_button = (By.ID, "comment-add-submit") + edit_comment_text_field = (By.XPATH, f"//div[textarea[@id='comment']]//iframe") + + +class ProjectLocators: + + project_summary_property_column = (By.CLASS_NAME, 'project-meta-column') + + # projects list locators + projects_list = (By.CSS_SELECTOR, "tbody.projects-list") + projects_not_found = (By.CLASS_NAME, "none-panel") + + +class SearchLocators: + + search_issue_table = (By.ID, "issuetable") + search_issue_content = (By.ID, "issue-content") + search_no_issue_found = (By.ID, "issue-content") + + +class BoardsListLocators: + boards_list_url = UrlManager().boards_list_page_url() + boards_list_params = UrlManager().boards_list_params + + boards_list = (By.CSS_SELECTOR, "#ghx-content-main table.aui") + + +class BoardLocators: + # Scrum boards + scrum_board_backlog_content = (By.CSS_SELECTOR, "#ghx-backlog[data-rendered]:not(.browser-metrics-stale)") + board_columns = (By.CSS_SELECTOR, ".ghx-column") \ No newline at end of file diff --git a/app/selenium_ui/jira_ui.py b/app/selenium_ui/jira_ui.py index 6f80c870b..9e8772088 100644 --- a/app/selenium_ui/jira_ui.py +++ b/app/selenium_ui/jira_ui.py @@ -7,12 +7,12 @@ def test_0_selenium_a_login(webdriver, jira_datasets, jira_screen_shots): modules.login(webdriver, jira_datasets) -def test_1_selenium_browse_project(webdriver, jira_datasets, jira_screen_shots): - modules.browse_project(webdriver, jira_datasets) +def test_1_selenium_browse_projects_list(webdriver, jira_datasets, jira_screen_shots): + modules.browse_projects_list(webdriver, jira_datasets) -def test_1_selenium_browse_board(webdriver, jira_datasets, jira_screen_shots): - modules.browse_board(webdriver, jira_datasets) +def test_1_selenium_browse_boards_list(webdriver, jira_datasets, jira_screen_shots): + modules.browse_boards_list(webdriver, jira_datasets) def test_1_selenium_create_issue(webdriver, jira_datasets, jira_screen_shots): From f9ed1315bca7c256d7380a24c5a4451aa4d1069e Mon Sep 17 00:00:00 2001 From: mmizin <30836532+mmizin@users.noreply.github.com> Date: Thu, 2 Apr 2020 22:15:30 +0300 Subject: [PATCH 18/23] NONE: Remove unuseful step (#171) --- docs/dc-apps-performance-toolkit-user-guide-bitbucket.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 3b72fb931..50bc74ea7 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -516,7 +516,6 @@ Review `results_summary.log` file under artifacts dir location. Make sure that o To receive scalability benchmark results for four-node Bitbucket DC with app-specific actions: 1. Scale your Bitbucket Data Center deployment to 4 nodes the same way as in [Run 4](#run4). -1. Check Index is synchronized to new nodes the same way as in [Run 4](#run4). 1. Run bzt. ``` bash From 71e033713b52aaf437d038bb7ea96a8a7ca1383e Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Fri, 3 Apr 2020 16:58:22 +0300 Subject: [PATCH 19/23] libs versions bump, chromedriver version bump (#172) --- app/bitbucket.yml | 2 +- app/confluence.yml | 2 +- app/jira.yml | 2 +- requirements.txt | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/bitbucket.yml b/app/bitbucket.yml index 457de91f4..8caacdc6a 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -81,7 +81,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "80.0.3987.16" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads + version: "80.0.3987.106" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/app/confluence.yml b/app/confluence.yml index ae89c1468..016f802ba 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -88,7 +88,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "80.0.3987.16" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads + version: "80.0.3987.106" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/app/jira.yml b/app/jira.yml index 6358149bd..936fbe14b 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -90,7 +90,7 @@ modules: httpsampler.ignore_failed_embedded_resources: "true" selenium: chromedriver: - version: "80.0.3987.16" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads + version: "80.0.3987.106" # Supports Chrome version 80. You can refer to http://chromedriver.chromium.org/downloads reporting: - data-source: sample-labels module: junit-xml diff --git a/requirements.txt b/requirements.txt index 025237474..e7e2a530f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -matplotlib==3.1.3 -pandas==1.0.1 -importlib-metadata==1.5.0 -zipp==2.2.0 +matplotlib==3.2.1 +pandas==1.0.3 +importlib-metadata==1.6.0 +zipp==2.2.0 # hard requirement of bzt 1.14.1 bzt==1.14.1 \ No newline at end of file From 7a7b13a1a504b6c059abc7edb5e10a47f44b8b7b Mon Sep 17 00:00:00 2001 From: Sergey Moroz Date: Fri, 3 Apr 2020 19:46:05 +0300 Subject: [PATCH 20/23] None pageobject pattern confluence (#173) Refactored selenium test for Confluence to page object pattern Co-authored-by: Oleksandr Metelytsia --- app/extension/confluence/extension_ui.py | 16 +- app/selenium_ui/base_page.py | 11 + app/selenium_ui/confluence/modules.py | 217 +++++------------- app/selenium_ui/confluence/pages/pages.py | 120 ++++++++++ app/selenium_ui/confluence/pages/selectors.py | 81 +++++++ 5 files changed, 280 insertions(+), 165 deletions(-) create mode 100644 app/selenium_ui/confluence/pages/pages.py create mode 100644 app/selenium_ui/confluence/pages/selectors.py diff --git a/app/extension/confluence/extension_ui.py b/app/extension/confluence/extension_ui.py index 449260c1d..404e695e8 100644 --- a/app/extension/confluence/extension_ui.py +++ b/app/extension/confluence/extension_ui.py @@ -1,29 +1,25 @@ from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as ec - -from selenium_ui.confluence.modules import _wait_until from selenium_ui.conftest import print_timing from util.conf import CONFLUENCE_SETTINGS -APPLICATION_URL = CONFLUENCE_SETTINGS.server_url -timeout = 20 +from selenium_ui.base_page import BasePage def custom_action(webdriver, datasets): + page = BasePage(webdriver) @print_timing def measure(webdriver, interaction): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/reporter') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-element")), interaction) + page.go_to_url(f"{CONFLUENCE_SETTINGS.server_url}/plugin/report") + page.wait_until_visible((By.ID, 'report_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_report') @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/plugins/servlet/some-app/administration') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, "plugin-element")), interaction) + page.go_to_url(f"{CONFLUENCE_SETTINGS.server_url}/plugin/dashboard") + page.wait_until_visible((By.ID, 'dashboard_app_element_id'), interaction) measure(webdriver, 'selenium_app_custom_action:view_dashboard') - measure(webdriver, 'selenium_app_custom_action') diff --git a/app/selenium_ui/base_page.py b/app/selenium_ui/base_page.py index dbd9e6d1f..d0176bc63 100644 --- a/app/selenium_ui/base_page.py +++ b/app/selenium_ui/base_page.py @@ -77,6 +77,17 @@ def wait_until_any_ec_presented(self, selector_names, interaction): any_ec.ecs = tuple(ec.presence_of_element_located(origin_selector) for origin_selector in origin_selectors) return self.__wait_until(expected_condition=any_ec, interaction=interaction) + def wait_until_any_ec_text_presented_in_el(self, selector_names, interaction): + origin_selectors = [] + for selector_text in selector_names: + selector = self.get_selector(selector_text[0]) + text = selector_text[1] + origin_selectors.append((selector, text)) + any_ec = AnyEc() + any_ec.ecs = tuple(ec.text_to_be_present_in_element(locator=origin_selector[0], text_=origin_selector[1]) for + origin_selector in origin_selectors) + return self.__wait_until(expected_condition=any_ec, interaction=interaction) + def __wait_until(self, expected_condition, interaction, time_out=TIMEOUT): message = f"Interaction: {interaction}. " ec_type = type(expected_condition) diff --git a/app/selenium_ui/confluence/modules.py b/app/selenium_ui/confluence/modules.py index 077af842f..65e082565 100644 --- a/app/selenium_ui/confluence/modules.py +++ b/app/selenium_ui/confluence/modules.py @@ -1,230 +1,137 @@ import random -import time +from selenium_ui.conftest import print_timing -from selenium.webdriver.common.by import By -from selenium.webdriver.support import expected_conditions as ec -from selenium.webdriver.support.wait import WebDriverWait +from selenium_ui.confluence.pages.pages import Login, AllUpdates, PopupManager, Page, Dashboard, TopNavPanel, Editor, \ + Logout -from selenium_ui.conftest import AnyEc, generate_random_string, print_timing -from util.conf import CONFLUENCE_SETTINGS -timeout = 20 - -# TODO consider do not use conftest as utility class and do not import it in modules -APPLICATION_URL = CONFLUENCE_SETTINGS.server_url - - -def _dismiss_popup(webdriver, *args): - for elem in args: - try: - webdriver.execute_script(f"document.querySelector(\'{elem}\').click()") - except: - pass +def setup_run_data(datasets): + user = random.choice(datasets["users"]) + page = random.choice(datasets["pages"]) + blog = random.choice(datasets["blogs"]) + datasets['username'] = user[0] + datasets['password'] = user[1] + datasets['page_id'] = page[0] + datasets['blog_id'] = blog[0] def login(webdriver, datasets): + setup_run_data(datasets) + login_page = Login(webdriver) @print_timing def measure(webdriver, interaction): - @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/login.action') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'loginButton')), interaction) - + login_page.go_to() + login_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_login:open_login_page") - user = random.choice(datasets["users"]) - webdriver.find_element_by_id('os_username').send_keys(user[0]) - webdriver.find_element_by_id('os_password').send_keys(user[1]) - - def _setup_page_is_presented(): - elems = webdriver.find_elements_by_id('grow-ic-nav-container') - return True if elems else False - - def _user_setup(): - current_step_sel = 'grow-aui-progress-tracker-step-current' - if webdriver.find_element_by_class_name(current_step_sel).text == 'Welcome': - _wait_until(webdriver, - ec.element_to_be_clickable((By.ID, 'grow-intro-video-skip-button')), - interaction).click() - if webdriver.find_element_by_class_name(current_step_sel).text == 'Upload your photo': - _wait_until(webdriver, - ec.element_to_be_clickable((By.CSS_SELECTOR, '.aui-button-link')), - interaction).click() - if webdriver.find_element_by_class_name(current_step_sel).text == 'Find content': - _wait_until(webdriver, - ec.visibility_of_any_elements_located( - (By.CSS_SELECTOR, '.intro-find-spaces-space>.space-checkbox')), - interaction)[0].click() - _wait_until(webdriver, - ec.element_to_be_clickable((By.CSS_SELECTOR, '.intro-find-spaces-button-continue')), - interaction).click() + login_page.set_credentials(username=datasets['username'], password=datasets['password']) @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id('loginButton').click() - _wait_until(webdriver, ec.invisibility_of_element_located((By.ID, 'loginButton')), interaction) - if _setup_page_is_presented(): - _user_setup() - _wait_until(webdriver, ec.presence_of_element_located((By.CLASS_NAME, 'list-container-all-updates')), - interaction) - - measure(webdriver, "selenium_login:login_and_view_dashboard") # waits for all updates - + login_page.click_login_button(interaction) + if login_page.is_first_login(): + login_page.first_user_setup(interaction) + all_updates_page = AllUpdates(webdriver) + all_updates_page.wait_for_page_loaded(interaction) + measure(webdriver, "selenium_login:login_and_view_dashboard") measure(webdriver, 'selenium_login') - - _dismiss_popup(webdriver, - ".button-panel-button .set-timezone-button", - ".aui-button aui-button-link .skip-onboarding") + PopupManager(webdriver).dismiss_default_popup() def view_page(webdriver, datasets): - page = random.choice(datasets["pages"])[0] + page = Page(webdriver, page_id=datasets['page_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/pages/viewpage.action?pageId={page}') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'title-text')), interaction) - + page.go_to() + page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_page") def view_blog(webdriver, datasets): - blog = random.choice(datasets["blogs"])[0] + blog = Page(webdriver, page_id=datasets['blog_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/pages/viewpage.action?pageId={blog}') - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'title-text')), interaction) - + blog.go_to() + blog.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_blog") def view_dashboard(webdriver, datasets): + dashboard_page = Dashboard(webdriver) + @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/dashboard.action#all-updates') - _wait_until(webdriver, ec.visibility_of_element_located((By.CLASS_NAME, 'update-items')), interaction) - + dashboard_page.go_to() + dashboard_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_view_dashboard") def create_page(webdriver, datasets): + nav_panel = TopNavPanel(webdriver) + create_page = Editor(webdriver) + @print_timing def measure(webdriver, interaction): - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, 'quick-create-page-button')), interaction).click() - _dismiss_popup(webdriver, "#closeDisDialog") - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, 'rte-button-publish')), interaction) - + nav_panel.click_create(interaction) + PopupManager(webdriver).dismiss_default_popup() + create_page.wait_for_create_page_open(interaction) measure(webdriver, "selenium_create_page:open_create_page_editor") - _dismiss_popup(webdriver, "#closeDisDialog") - populate_page_title(webdriver) - populate_page_content(webdriver) + + PopupManager(webdriver).dismiss_default_popup() + + create_page.write_title() + create_page.write_content(interaction='create page') @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id("rte-button-publish").click() - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'title-text')), interaction) - + create_page.click_submit() + page = Page(webdriver) + page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_create_page:save_created_page") def edit_page(webdriver, datasets): - page = random.choice(datasets["pages"])[0] + edit_page = Editor(webdriver, page_id=datasets['page_id']) @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/pages/editpage.action?pageId={page}') - _wait_until(webdriver, - AnyEc(ec.text_to_be_present_in_element((By.CLASS_NAME, 'status-indicator-message'), 'Ready to go'), - ec.text_to_be_present_in_element((By.CLASS_NAME, 'status-indicator-message'), 'Changes saved') - ), interaction) - - _wait_until(webdriver, ec.element_to_be_clickable((By.ID, 'rte-button-publish')), interaction) - + edit_page.go_to() + edit_page.wait_for_page_loaded(interaction) measure(webdriver, "selenium_edit_page:open_create_page_editor") - populate_page_content(webdriver) + edit_page.write_content(interaction='edit page') @print_timing def measure(webdriver, interaction): - confirmation_button = "qed-publish-button" - webdriver.find_element_by_id("rte-button-publish").click() - if webdriver.find_elements_by_id(confirmation_button): - if webdriver.find_element_by_id('qed-publish-button').is_displayed(): - webdriver.find_element_by_id('qed-publish-button').click() - _wait_until(webdriver, ec.invisibility_of_element_located((By.ID, 'rte-spinner')), interaction) - _wait_until(webdriver, AnyEc(ec.presence_of_element_located((By.ID, "title-text")), - ec.presence_of_element_located((By.ID, confirmation_button)) - ), interaction) - + edit_page.save_edited_page(interaction) measure(webdriver, "selenium_edit_page:save_edited_page") def create_comment(webdriver, datasets): - view_page(webdriver, datasets) - + page = Page(webdriver, page_id=datasets['page_id']) + page.go_to() + page.wait_for_page_loaded(interaction='create comment') + edit_comment = Editor(webdriver) @print_timing def measure(webdriver, interaction): - webdriver.execute_script("document.querySelector('.quick-comment-prompt').click()") - _wait_until(webdriver, ec.frame_to_be_available_and_switch_to_it((By.ID, 'wysiwygTextarea_ifr')), interaction) - webdriver.find_element_by_id("tinymce").find_element_by_tag_name('p')\ - .send_keys(f"This is page comment from date {time.time()}") - webdriver.switch_to.parent_frame() - + page.click_add_comment() + edit_comment.write_content(interaction=interaction, text='This is selenium comment') measure(webdriver, 'selenium_create_comment:write_comment') @print_timing def measure(webdriver, interaction): - webdriver.find_element_by_id("rte-button-publish").click() - _wait_until(webdriver, ec.visibility_of_element_located((By.CSS_SELECTOR, '.quick-comment-prompt')), - interaction) - + edit_comment.click_submit() + page.wait_for_comment_field(interaction) measure(webdriver, "selenium_create_comment:save_comment") -def populate_page_title(webdriver): - _wait_until(webdriver, ec.visibility_of_element_located((By.ID, 'content-title')), 'populate page title') - title = "Selenium - " + generate_random_string(10) - webdriver.find_element_by_id("content-title").clear() - webdriver.find_element_by_id("content-title").send_keys(title) - - -def populate_page_content(webdriver): - _wait_until(webdriver, - ec.frame_to_be_available_and_switch_to_it((By.ID, 'wysiwygTextarea_ifr')), 'populate page content') - webdriver.find_element_by_id("tinymce").find_element_by_tag_name('p').send_keys(generate_random_string(30)) - webdriver.switch_to.parent_frame() - - def log_out(webdriver, datasets): @print_timing def measure(webdriver, interaction): - webdriver.get(f'{APPLICATION_URL}/logout.action') - + logout_page = Logout(webdriver) + logout_page.go_to() measure(webdriver, "selenium_log_out") - - -def _wait_until(webdriver, expected_condition, interaction, time_out=timeout): - message = f"Interaction: {interaction}. " - ec_type = type(expected_condition) - if ec_type == AnyEc: - conditions_text = "" - for ecs in expected_condition.ecs: - conditions_text = conditions_text + " " + f"Condition: {str(ecs)} Locator: {ecs.locator}\n" - - message += f"Timed out after {time_out} sec waiting for one of the conditions: \n{conditions_text}" - - elif ec_type == ec.invisibility_of_element_located: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.target}") - - elif ec_type == ec.frame_to_be_available_and_switch_to_it: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.frame_locator}") - - else: - message += (f"Timed out after {time_out} sec waiting for {str(expected_condition)}. \n" - f"Locator: {expected_condition.locator}") - - return WebDriverWait(webdriver, time_out).until(expected_condition, message=message) diff --git a/app/selenium_ui/confluence/pages/pages.py b/app/selenium_ui/confluence/pages/pages.py new file mode 100644 index 000000000..263e33ae7 --- /dev/null +++ b/app/selenium_ui/confluence/pages/pages.py @@ -0,0 +1,120 @@ + +from selenium_ui.base_page import BasePage + +from selenium_ui.confluence.pages.selectors import UrlManager, LoginPageLocators, AllUpdatesLocators, PopupLocators,\ + PageLocators, DashboardLocators, TopPanelLocators, EditorLocators + + +class Login(BasePage): + page_url = LoginPageLocators.login_page_url + page_loaded_selector = LoginPageLocators.login_button + + def set_credentials(self, username, password): + self.get_element(LoginPageLocators.login_username_field).send_keys(username) + self.get_element(LoginPageLocators.login_password_field).send_keys(password) + + def click_login_button(self, interaction): + self.wait_until_visible(LoginPageLocators.login_button, interaction).click() + self.wait_until_invisible(LoginPageLocators.login_button, interaction) + + def is_first_login(self): + elems = self.get_elements(LoginPageLocators.first_login_setup_page) + return True if elems else False + + def first_user_setup(self, interaction): + if self.get_element(LoginPageLocators.current_step_sel).text == 'Welcome': + self.wait_until_clickable(LoginPageLocators.skip_welcome_button, interaction).click() + if self.get_element(LoginPageLocators.current_step_sel).text == 'Upload your photo': + self.wait_until_clickable(LoginPageLocators.skip_photo_upload, interaction).click() + if self.get_element(LoginPageLocators.current_step_sel).text == 'Find content': + self.wait_until_any_element_visible(LoginPageLocators.skip_find_content, interaction)[0].click() + self.wait_until_clickable(LoginPageLocators.finish_setup, interaction).click() + + +class Logout(BasePage): + page_url = UrlManager().logout_url() + + +class AllUpdates(BasePage): + page_loaded_selector = AllUpdatesLocators.updates_content + + +class PopupManager(BasePage): + + def dismiss_default_popup(self): + return self.dismiss_popup(PopupLocators.timezone_popups, PopupLocators.skip_onbording_1, + PopupLocators.skip_onboarding_2, + PopupLocators.time_saving_template) + + +class Page(BasePage): + page_loaded_selector = PageLocators.page_title + + def __init__(self, driver, page_id=None): + BasePage.__init__(self, driver) + url_manager = UrlManager(page_id=page_id) + self.page_url = url_manager.page_url() + + def click_add_comment(self): + css_selector = PageLocators.comment_text_field[1] + self.execute_js(f"document.querySelector('{css_selector}').click()") + + def wait_for_comment_field(self, interaction): + self.wait_until_visible(PageLocators.comment_text_field, interaction) + + +class Dashboard(BasePage): + page_url = DashboardLocators.dashboard_url + page_loaded_selector = DashboardLocators.updated_items + + +class TopNavPanel(BasePage): + + def click_create(self, interaction): + self.wait_until_clickable(TopPanelLocators.create_button, interaction).click() + + +class Editor(BasePage): + + def __init__(self, driver, page_id=None): + BasePage.__init__(self, driver) + url_manager = UrlManager(page_id=page_id) + self.page_url = url_manager.edit_page_url() + + def wait_for_create_page_open(self, interaction): + self.wait_until_clickable(EditorLocators.publish_button, interaction) + + def wait_for_page_loaded(self, interaction): + self.wait_for_editor_open(interaction) + self.wait_until_clickable(EditorLocators.publish_button, interaction) + + def write_title(self): + title_field = self.wait_until_visible(EditorLocators.title_field, interaction='page title') + title = "Selenium - " + self.generate_random_string(10) + title_field.clear() + title_field.send_keys(title) + + def write_content(self, interaction, text=None): + self.wait_until_available_to_switch(EditorLocators.page_content_field, interaction=interaction) + text = self.generate_random_string(30) if not text else text + tinymce_text_el = self.get_element(EditorLocators.tinymce_page_content_field) + tinymce_text_el.find_element_by_tag_name('p').send_keys(text) + self.return_to_parent_frame() + + def click_submit(self): + self.get_element(EditorLocators.publish_button).click() + + def wait_for_editor_open(self, interaction): + self.wait_until_any_ec_text_presented_in_el(selector_names=[(EditorLocators.status_indicator, 'Ready to go'), + (EditorLocators.status_indicator, 'Changes saved')], + interaction=interaction) + + def save_edited_page(self, interaction): + self.get_element(EditorLocators.publish_button).click() + if self.get_elements(EditorLocators.confirm_publishing_button): + if self.get_element(EditorLocators.confirm_publishing_button).is_displayed(): + self.get_element(EditorLocators.confirm_publishing_button).click() + self.wait_until_invisible(EditorLocators.save_spinner, interaction) + self.wait_until_any_ec_presented(selector_names=[PageLocators.page_title, + EditorLocators.confirm_publishing_button], + interaction=interaction) diff --git a/app/selenium_ui/confluence/pages/selectors.py b/app/selenium_ui/confluence/pages/selectors.py new file mode 100644 index 000000000..762a16458 --- /dev/null +++ b/app/selenium_ui/confluence/pages/selectors.py @@ -0,0 +1,81 @@ +from selenium.webdriver.common.by import By +from util.conf import CONFLUENCE_SETTINGS + + +class UrlManager: + + def __init__(self, page_id=None): + self.host = CONFLUENCE_SETTINGS.server_url + self.login_params = '/login.action' + self.page_params = f"/pages/viewpage.action?pageId={page_id}" + self.dashboard_params = '/dashboard.action#all-updates' + self.edit_page_params = f'/pages/editpage.action?pageId={page_id}' + self.logout_params = "/logout.action" + + def login_url(self): + return f"{self.host}{self.login_params}" + + def dashboard_url(self): + return f"{self.host}{self.dashboard_params}" + + def page_url(self): + return f"{self.host}{self.page_params}" + + def edit_page_url(self): + return f"{self.host}{self.edit_page_params}" + + def logout_url(self): + return f"{self.host}{self.logout_params}" + + +class PopupLocators: + timezone_popups = '.button-panel-button .set-timezone-button' + skip_onbording_1 = '.aui-button aui-button-link .skip-onboarding' + skip_onboarding_2 = '.aui-button.aui-button-link.skip-onboarding' + time_saving_template = '#closeDisDialog' + + +class LoginPageLocators: + + login_page_url = UrlManager().login_url() + login_button = (By.ID, "loginButton") + login_username_field = (By.ID, "os_username") + login_password_field = (By.ID, "os_password") + + # Setup user page per first login + first_login_setup_page = (By.ID, "grow-ic-nav-container") + current_step_sel = (By.CLASS_NAME, "grow-aui-progress-tracker-step-current") + skip_welcome_button = (By.ID, "grow-intro-video-skip-button") + skip_photo_upload = (By.CSS_SELECTOR, ".aui-button-link") + skip_find_content = (By.CSS_SELECTOR, ".intro-find-spaces-space>.space-checkbox") + finish_setup = (By.CSS_SELECTOR, ".intro-find-spaces-button-continue") + + +class AllUpdatesLocators: + updates_content = (By.CLASS_NAME, "list-container-all-updates") + + +class PageLocators: + page_title = (By.ID, "title-text") + comment_text_field = (By.CSS_SELECTOR, ".quick-comment-prompt") + + +class DashboardLocators: + dashboard_url = UrlManager().dashboard_url() + updated_items = (By.CLASS_NAME, "update-items") + + +class TopPanelLocators: + create_button = (By.ID, "quick-create-page-button") + + +class EditorLocators: + publish_button = (By.ID, "rte-button-publish") + confirm_publishing_button = (By.ID, "qed-publish-button") + title_field = (By.ID, "content-title") + page_content_field = (By.ID, "wysiwygTextarea_ifr") + tinymce_page_content_field = (By.ID, "tinymce") + tinymce_page_content_parahraph = (By.TAG_NAME, 'p') + + status_indicator = (By.CLASS_NAME, "status-indicator-message") + save_spinner = (By.ID, "rte-spinner") From c98fc4e66bca4578eda55ddb65a2bda98a27a1cd Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Tue, 7 Apr 2020 11:59:25 +0300 Subject: [PATCH 21/23] Dca 413 summary scenario (#174) * dca-413 summary report * docs update * safe order --- app/reports_generation/README.md | 4 +- app/reports_generation/csv_chart_generator.py | 9 ++- .../scripts/config_provider.py | 2 +- .../scripts/summary_aggregator.py | 76 +++++++++++++++++++ app/util/analytics.py | 35 +++++---- ...erformance-toolkit-user-guide-bitbucket.md | 4 +- ...rformance-toolkit-user-guide-confluence.md | 4 +- ...pps-performance-toolkit-user-guide-jira.md | 4 +- 8 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 app/reports_generation/scripts/summary_aggregator.py diff --git a/app/reports_generation/README.md b/app/reports_generation/README.md index 9d5a54597..92b3f52c1 100644 --- a/app/reports_generation/README.md +++ b/app/reports_generation/README.md @@ -1,4 +1,4 @@ -## Reports generator - a tool that creates an aggregated .csv file and chart from multiple run results. +## Reports generator - a tool that creates an aggregated .csv file, chart and summary report from multiple run results. Before you start, make sure you have installed Python packages from [requirements.txt](../../requirements.txt). Otherwise, run the `pip install -r requirements.txt` command from DCAPT [root](../..) directory to install necessary packages to your virtual environment. @@ -6,7 +6,7 @@ Otherwise, run the `pip install -r requirements.txt` command from DCAPT [root](. To create reports, run the
`python csv_chart_generator.py [performance_profile.yml or scale_profile.yml]` command from the `reports_generation` folder. -The aggregated .csv files and charts are stored in the `results/reports` directory. +The aggregated .csv files, charts and summary report are stored in the `results/reports` directory. Before run, you should edit `performance_profile.yml` or `scale_profile.yml` and set appropriate `fullPath` values. **Configuration** diff --git a/app/reports_generation/csv_chart_generator.py b/app/reports_generation/csv_chart_generator.py index 13293d55a..70721f3ec 100644 --- a/app/reports_generation/csv_chart_generator.py +++ b/app/reports_generation/csv_chart_generator.py @@ -1,16 +1,17 @@ import datetime from pathlib import Path -from scripts import config_provider, csv_aggregator, chart_generator +from scripts import config_provider, csv_aggregator, chart_generator, summary_aggregator def main(): results_dir = __get_results_dir() - csv_aggregator_config = config_provider.get_csv_aggregator_config() - agg_csv = csv_aggregator.aggregate(csv_aggregator_config, results_dir) - chart_generator_config = config_provider.get_chart_generator_config(csv_aggregator_config, agg_csv) + config = config_provider.get_config() + agg_csv = csv_aggregator.aggregate(config, results_dir) + chart_generator_config = config_provider.get_chart_generator_config(config, agg_csv) chart_generator.perform_chart_creation(chart_generator_config, results_dir) + summary_aggregator.aggregate(config, results_dir) def __get_results_dir() -> Path: diff --git a/app/reports_generation/scripts/config_provider.py b/app/reports_generation/scripts/config_provider.py index c1ed0868c..c05433586 100644 --- a/app/reports_generation/scripts/config_provider.py +++ b/app/reports_generation/scripts/config_provider.py @@ -4,7 +4,7 @@ import yaml -def get_csv_aggregator_config() -> dict: +def get_config() -> dict: config_path = resolve_file_path(__get_config_file()) config = __read_config_file(config_path) config['profile'] = config_path.stem diff --git a/app/reports_generation/scripts/summary_aggregator.py b/app/reports_generation/scripts/summary_aggregator.py new file mode 100644 index 000000000..84c341b7c --- /dev/null +++ b/app/reports_generation/scripts/summary_aggregator.py @@ -0,0 +1,76 @@ +from pathlib import Path +from typing import List + +from scripts.utils import validate_str_is_not_blank, validate_file_exists, resolve_path + +SUMMARY_FILE_NAME = "results_summary.log" +DELIMITER = ('\n================================================================================' + '========================================\n') + + +def __validate_config(config: dict): + validate_str_is_not_blank(config, 'column_name') + validate_str_is_not_blank(config, 'profile') + + runs = config.get('runs') + if not isinstance(runs, list): + raise SystemExit(f'Config key "runs" should be a list') + + for run in runs: + if not isinstance(run, dict): + raise SystemExit(f'Config key "run" should be a dictionary') + + validate_str_is_not_blank(run, 'runName') + validate_str_is_not_blank(run, 'fullPath') + + +def __get_summary_files(config: dict) -> List[Path]: + summary_files = [] + for run in config['runs']: + file_path = resolve_path(run['fullPath']) / SUMMARY_FILE_NAME + validate_file_exists(file_path, f"File {file_path} does not exists") + summary_files.append(resolve_path(run['fullPath']) / SUMMARY_FILE_NAME) + return summary_files + + +def __get_run_names(config: dict) -> list: + run_names = [] + for run in config['runs']: + run_names.append(run['runName']) + return run_names + + +def __write_to_summary_report(file_names: List[Path], run_names: List, status: str, output_filename: Path): + with output_filename.open('a') as outfile: + outfile.write(f"Scenario status: {status}") + outfile.write(DELIMITER) + for file, run_name in zip(file_names, run_names): + with file.open('r') as infile: + outfile.write(f"Run name: {run_name}\n\n") + outfile.write(infile.read()) + outfile.write(DELIMITER) + + +def __get_output_file_path(config, results_dir) -> Path: + return results_dir / f"{config['profile']}_summary.log" + + +def __get_overall_status(files: List[Path]) -> bool: + for file in files: + with file.open('r') as f: + first_line = f.readline() + if 'FAIL' in first_line: + return False + return True + + +def aggregate(config: dict, results_dir: Path) -> Path: + __validate_config(config) + output_file_path = __get_output_file_path(config, results_dir) + summary_files = __get_summary_files(config) + run_names = __get_run_names(config) + status_message = 'OK' if __get_overall_status(summary_files) else "FAIL" + __write_to_summary_report(summary_files, run_names, status_message, output_file_path) + validate_file_exists(output_file_path, f"Results file {output_file_path} is not created") + print(f'Results file {output_file_path.absolute()} is created') + return output_file_path diff --git a/app/util/analytics.py b/app/util/analytics.py index cfac8b092..6f50fff7e 100644 --- a/app/util/analytics.py +++ b/app/util/analytics.py @@ -317,31 +317,34 @@ def generate_report_summary(self): if self.application_type == BITBUCKET: git_compliant = self.__is_git_operations_compliant() - overall_status = 'OK' if overall_status and git_compliant[0] else 'FAIL' + overall_status = 'OK' if finished[0] and success[0] and compliant[0] and git_compliant[0] else 'FAIL' - summary_report.append(self.format_string(f'Summary run status|{overall_status}\n')) - summary_report.append(self.format_string(f'OS|{self.os}')) - summary_report.append(self.format_string(f'DC Apps Performance Toolkit version|{self.tool_version}')) - summary_report.append(self.format_string(f'Application|{self.application_type} {self.application_version}')) - summary_report.append(self.format_string(f'Concurrency|{self.concurrency}')) - summary_report.append(self.format_string(f'Expected test run duration from yml file|{self.duration} sec')) - summary_report.append(self.format_string(f'Actual test run duration|{self.actual_duration} sec')) + summary_report.append(f'Summary run status|{overall_status}\n') + summary_report.append(f'Artifacts dir|{os.path.basename(self._log_dir)}') + summary_report.append(f'OS|{self.os}') + summary_report.append(f'DC Apps Performance Toolkit version|{self.tool_version}') + summary_report.append(f'Application|{self.application_type} {self.application_version}') + summary_report.append(f'Concurrency|{self.concurrency}') + summary_report.append(f'Expected test run duration from yml file|{self.duration} sec') + summary_report.append(f'Actual test run duration|{self.actual_duration} sec') if self.application_type == BITBUCKET: - summary_report.append(self.format_string(f'Total Git operations count|{self.actual_git_operations_count}')) - summary_report.append(self.format_string(f'Total Git operations compliant|{git_compliant}')) + summary_report.append(f'Total Git operations count|{self.actual_git_operations_count}') + summary_report.append(f'Total Git operations compliant|{git_compliant}') - summary_report.append(self.format_string(f'Finished|{finished}')) - summary_report.append(self.format_string(f'Compliant|{compliant}')) - summary_report.append(self.format_string(f'Success|{success}\n')) + summary_report.append(f'Finished|{finished}') + summary_report.append(f'Compliant|{compliant}') + summary_report.append(f'Success|{success}\n') - summary_report.append(self.format_string(f'Action|Success Rate|Status')) + summary_report.append(f'Action|Success Rate|Status') for key, value in {**self.jmeter_test_rates, **self.selenium_test_rates}.items(): status = 'OK' if value >= SUCCESS_TEST_RATE else 'Fail' - summary_report.append(self.format_string(f'{key}|{value}|{status}')) + summary_report.append(f'{key}|{value}|{status}') - self.__write_to_file(summary_report, summary_report_file) + pretty_report = map(self.format_string, summary_report) + + self.__write_to_file(pretty_report, summary_report_file) @staticmethod def __write_to_file(content, file): diff --git a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md index 50bc74ea7..84b266356 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md +++ b/docs/dc-apps-performance-toolkit-user-guide-bitbucket.md @@ -420,7 +420,7 @@ To generate a performance regression report: ``` bash python csv_chart_generator.py performance_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report @@ -546,7 +546,7 @@ To generate a scalability report: ``` bash python csv_chart_generator.py scale_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report diff --git a/docs/dc-apps-performance-toolkit-user-guide-confluence.md b/docs/dc-apps-performance-toolkit-user-guide-confluence.md index b1af4a383..a0d4fbcc1 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-confluence.md +++ b/docs/dc-apps-performance-toolkit-user-guide-confluence.md @@ -387,7 +387,7 @@ To generate a performance regression report: ``` bash python csv_chart_generator.py performance_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report @@ -601,7 +601,7 @@ To generate a scalability report: ``` bash python csv_chart_generator.py scale_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report diff --git a/docs/dc-apps-performance-toolkit-user-guide-jira.md b/docs/dc-apps-performance-toolkit-user-guide-jira.md index 8400b7ee3..d948e8400 100644 --- a/docs/dc-apps-performance-toolkit-user-guide-jira.md +++ b/docs/dc-apps-performance-toolkit-user-guide-jira.md @@ -425,7 +425,7 @@ To generate a performance regression report: ``` bash python csv_chart_generator.py performance_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report @@ -640,7 +640,7 @@ To generate a scalability report: ``` bash python csv_chart_generator.py scale_profile.yml ``` -1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results) and the `.png` file. +1. In the `dc-app-performance-toolkit/app/results/reports/YY-MM-DD-hh-mm-ss` folder, view the `.csv` file (with consolidated scenario results), the `.png` chart file and summary report. #### Analyzing report From dd903f9d5ce65ddaebc93efefced2070e01d4842 Mon Sep 17 00:00:00 2001 From: Oleksandr Metelytsia Date: Tue, 7 Apr 2020 12:09:52 +0300 Subject: [PATCH 22/23] release 2.0.0 --- app/bitbucket.yml | 2 +- app/confluence.yml | 2 +- app/jira.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/bitbucket.yml b/app/bitbucket.yml index 8caacdc6a..345689729 100644 --- a/app/bitbucket.yml +++ b/app/bitbucket.yml @@ -14,7 +14,7 @@ settings: test_duration: 50m WEBDRIVER_VISIBLE: False JMETER_VERSION: 5.2.1 - allow_analytics: No # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. + allow_analytics: Yes # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. services: - module: shellexec prepare: diff --git a/app/confluence.yml b/app/confluence.yml index 016f802ba..e86ef9538 100644 --- a/app/confluence.yml +++ b/app/confluence.yml @@ -14,7 +14,7 @@ settings: test_duration: 45m WEBDRIVER_VISIBLE: False JMETER_VERSION: 5.2.1 - allow_analytics: No # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. + allow_analytics: Yes # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. services: - module: shellexec prepare: diff --git a/app/jira.yml b/app/jira.yml index 936fbe14b..b84bd6aaa 100644 --- a/app/jira.yml +++ b/app/jira.yml @@ -14,7 +14,7 @@ settings: test_duration: 45m WEBDRIVER_VISIBLE: False JMETER_VERSION: 5.2.1 - allow_analytics: No # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. + allow_analytics: Yes # Allow sending basic run analytics to Atlassian. These analytics help us to understand how the tool is being used and help us to continue to invest in this tooling. For more details please see our README. services: - module: shellexec prepare: From d5d5a475bc17821626256c662df16fe9e4970f0f Mon Sep 17 00:00:00 2001 From: Alex Metelytsia Date: Tue, 7 Apr 2020 12:42:22 +0300 Subject: [PATCH 23/23] remove beta label (#177) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 499defc3b..79f9b9b01 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ The Data Center App Performance Toolkit extends [Taurus](https://gettaurus.org/) This repository contains Taurus scripts for performance testing of Atlassian Data Center products: Jira, Confluence, and Bitbucket. -At the moment, Jira DC, Confluence DC and Bitbucket DC support is in beta. - ## Supported versions * Supported Jira versions: * Jira [Enterprise Releases](https://confluence.atlassian.com/enterprise/atlassian-enterprise-releases-948227420.html): 7.13.6 and 8.5.0