From 9f455a514e317412b03b9b6adac55cd601284098 Mon Sep 17 00:00:00 2001 From: Shubham Agarwal Date: Thu, 22 Aug 2019 14:50:51 +0530 Subject: [PATCH] Feature test improvement and fix intermittent failures part of #3936 --- .../feature_tests/browser_tool_bar_test.py | 36 +- ...opy_selected_query_results_feature_test.py | 73 +-- .../feature_tests/file_manager_test.py | 50 +- .../feature_tests/keyboard_shortcut_test.py | 40 +- web/pgadmin/feature_tests/locators.py | 32 -- .../pg_datatype_validation_test.py | 78 +-- .../pg_utilities_backup_restore_test.py | 111 +++-- .../pg_utilities_maintenance_test.py | 69 ++- .../query_tool_auto_complete_tests.py | 97 ++-- .../feature_tests/query_tool_journey_test.py | 214 ++++---- web/pgadmin/feature_tests/query_tool_tests.py | 471 ++++++++---------- .../feature_tests/table_ddl_feature_test.py | 8 +- .../feature_tests/view_data_dml_queries.py | 49 +- .../xss_checks_panels_and_query_tool_test.py | 34 +- .../xss_checks_pgadmin_debugger_test.py | 13 +- .../xss_checks_roles_control_test.py | 13 +- web/regression/feature_utils/locators.py | 221 ++++++++ web/regression/feature_utils/pgadmin_page.py | 194 +++++++- .../feature_utils/tree_area_locators.py | 31 ++ .../python_test_utils/test_gui_helper.py | 40 +- .../python_test_utils/test_utils.py | 65 ++- 21 files changed, 1213 insertions(+), 726 deletions(-) delete mode 100644 web/pgadmin/feature_tests/locators.py create mode 100644 web/regression/feature_utils/locators.py create mode 100644 web/regression/feature_utils/tree_area_locators.py diff --git a/web/pgadmin/feature_tests/browser_tool_bar_test.py b/web/pgadmin/feature_tests/browser_tool_bar_test.py index 01c7bc548..2d504d8ae 100644 --- a/web/pgadmin/feature_tests/browser_tool_bar_test.py +++ b/web/pgadmin/feature_tests/browser_tool_bar_test.py @@ -10,9 +10,8 @@ from __future__ import print_function import sys import random -import time - from regression.python_test_utils import test_utils +from regression.feature_utils.locators import BrowserToolBarLocators from regression.feature_utils.base_feature_test import BaseFeatureTest from selenium.common.exceptions import TimeoutException, \ StaleElementReferenceException @@ -57,27 +56,25 @@ class BrowserToolBarFeatureTest(BaseFeatureTest): def after(self): self.page.remove_server(self.server) + test_utils.delete_table(self.server, self.test_db, + self.test_table_name) - def _locate_database_tree_node(self): + def test_query_tool_button(self): self.page.toggle_open_tree_item(self.server['name']) self.page.toggle_open_tree_item('Databases') self.page.toggle_open_tree_item(self.test_db) - - def test_query_tool_button(self): - self._locate_database_tree_node() - retry_count = 0 while retry_count < 5: try: self.page.find_by_css_selector( - ".wcFrameButton[title='Query Tool']:not(.disabled)")\ + BrowserToolBarLocators.open_query_tool_button_css)\ .click() break - except StaleElementReferenceException: + except (StaleElementReferenceException, TimeoutException): retry_count += 1 - time.sleep(0.5) - self.page.find_by_css_selector(".wcPanelTab .wcTabIcon.fa.fa-bolt") + self.page.find_by_css_selector( + BrowserToolBarLocators.query_tool_panel_css) def test_view_data_tool_button(self): self.page.select_tree_item(self.test_db) @@ -90,26 +87,23 @@ class BrowserToolBarFeatureTest(BaseFeatureTest): while retry_count < 5: try: self.page.find_by_css_selector( - ".wcFrameButton[title='View Data']:not(.disabled)").click() + BrowserToolBarLocators.view_table_data_button_css).click() break - except StaleElementReferenceException: + except (StaleElementReferenceException, TimeoutException): retry_count += 1 - - time.sleep(0.5) - self.page.find_by_css_selector(".wcPanelTab .wcTabIcon.fa.fa-bolt") + self.page.find_by_css_selector( + BrowserToolBarLocators.view_data_panel_css) def test_filtered_rows_tool_button(self): retry_count = 0 while retry_count < 5: try: self.page.find_by_css_selector( - ".wcFrameButton[title='Filtered Rows']:not(.disabled)")\ + BrowserToolBarLocators.filter_data_button_css)\ .click() break - except StaleElementReferenceException: + except (StaleElementReferenceException, TimeoutException): retry_count += 1 - - time.sleep(0.5) self.page.find_by_css_selector( - ".alertify .ajs-header[data-title~='Filter']") + BrowserToolBarLocators.filter_alertify_box_css) self.page.click_modal('Cancel') diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py index 0837b84a9..1b97b3e9c 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py @@ -9,11 +9,11 @@ import pyperclip import random - from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.feature_utils.locators import QueryToolLocators class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): @@ -32,10 +32,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): # Create test table with random name to avoid same name conflicts in # parallel execution self.test_table_name = "test_table" + str(random.randint(1000, 3000)) - + self.page.add_server(self.server) test_utils.create_table( self.server, self.test_db, self.test_table_name) - self.page.add_server(self.server) def runTest(self): self.page.toggle_open_tree_item(self.server['name']) @@ -46,7 +45,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.fill_codemirror_area_with( "SELECT * FROM %s ORDER BY some_column" % self.test_table_name) - self.page.find_by_id("btn-flash").click() + self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css).click() self._copies_rows() self._copies_columns() @@ -59,21 +59,26 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_rows(self): pyperclip.copy("old clipboard contents") - self.page.find_by_xpath( - "//*[contains(@class, 'slick-row')]/*[1]").click() + first_row = self.page.find_by_xpath( + QueryToolLocators.output_row_xpath.format(1)) + first_row.click() - self.page.find_by_xpath("//*[@id='btn-copy-row']").click() + copy_button = self.page.find_by_css_selector( + QueryToolLocators.copy_button_css) + copy_button.click() self.assertEqual('"Some-Name"\t"6"\t"some info"', pyperclip.paste()) def _copies_columns(self): pyperclip.copy("old clipboard contents") - self.page.find_by_xpath( - "//*[@data-test='output-column-header' and " - "contains(., 'some_column')]" - ).click() - self.page.find_by_xpath("//*[@id='btn-copy-row']").click() + column = self.page.find_by_css_selector( + QueryToolLocators.output_column_header_css.format('some_column')) + column.click() + + copy_button = self.page.find_by_css_selector( + QueryToolLocators.copy_button_css) + copy_button.click() self.assertEqual( """\"Some-Name" @@ -83,8 +88,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_row_using_keyboard_shortcut(self): pyperclip.copy("old clipboard contents") - self.page.find_by_xpath( - "//*[contains(@class, 'slick-row')]/*[1]").click() + first_row = self.page.find_by_xpath( + QueryToolLocators.output_row_xpath.format(1)) + first_row.click() ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() @@ -94,10 +100,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_column_using_keyboard_shortcut(self): pyperclip.copy("old clipboard contents") - self.page.find_by_xpath( - "//*[@data-test='output-column-header' and " - "contains(., 'some_column')]" - ).click() + column = self.page.find_by_css_selector( + QueryToolLocators.output_column_header_css.format('some_column')) + column.click() ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() @@ -111,12 +116,12 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_rectangular_selection(self): pyperclip.copy("old clipboard contents") - top_left_cell = self.page.find_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(., 'Some-Other-Name')]" - ) + top_left_cell = \ + self.page.find_by_xpath( + QueryToolLocators.output_column_data_xpath. + format('Some-Other-Name')) bottom_right_cell = self.page.find_by_xpath( - "//div[contains(@class, 'slick-cell') and contains(., '14')]") + QueryToolLocators.output_column_data_xpath.format('14')) ActionChains( self.page.driver @@ -135,11 +140,11 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): pyperclip.copy("old clipboard contents") top_left_cell = self.page.find_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(., 'Some-Other-Name')]" + QueryToolLocators.output_column_data_xpath. + format('Some-Other-Name') ) initial_bottom_right_cell = self.page.find_by_xpath( - "//div[contains(@class, 'slick-cell') and contains(., '14')]") + QueryToolLocators.output_column_data_xpath.format('14')) ActionChains( self.page.driver ).click_and_hold(top_left_cell).move_to_element( @@ -160,10 +165,10 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _shift_resizes_column_selection(self): pyperclip.copy("old clipboard contents") - self.page.find_by_xpath( - "//*[@data-test='output-column-header' and " - "contains(., 'value')]" - ).click() + column = self.page.find_by_css_selector( + QueryToolLocators.output_column_header_css.format('value') + ) + column.click() ActionChains(self.page.driver).key_down( Keys.SHIFT).send_keys(Keys.ARROW_LEFT).key_up(Keys.SHIFT).perform() @@ -181,11 +186,11 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): pyperclip.copy("old clipboard contents") bottom_right_cell = self.page.find_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(., 'cool info')]" + QueryToolLocators.output_column_data_xpath.format('cool info') ) - load_button = self.page.find_by_xpath("//button[@id='btn-load-file']") + load_button = self.page.find_by_css_selector( + QueryToolLocators.btn_load_file_css) ActionChains(self.page.driver).click_and_hold(bottom_right_cell) \ .move_to_element(load_button) \ .release(load_button) \ @@ -199,3 +204,5 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def after(self): self.page.close_query_tool() self.page.remove_server(self.server) + test_utils.delete_table(self.server, self.test_db, + self.test_table_name) diff --git a/web/pgadmin/feature_tests/file_manager_test.py b/web/pgadmin/feature_tests/file_manager_test.py index b6b35c047..3df37af84 100644 --- a/web/pgadmin/feature_tests/file_manager_test.py +++ b/web/pgadmin/feature_tests/file_manager_test.py @@ -9,15 +9,16 @@ from __future__ import print_function import os -import time import sys - +import time from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By +from selenium.common.exceptions import StaleElementReferenceException, \ + TimeoutException from selenium.webdriver.support import expected_conditions as EC from regression.feature_utils.base_feature_test import BaseFeatureTest -from .locators import QueryToolLocatorsCss +from regression.feature_utils.locators import QueryToolLocators class CheckFileManagerFeatureTest(BaseFeatureTest): @@ -65,32 +66,40 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): self.page.open_query_tool() def _create_new_file(self): - self.page.find_by_css_selector(QueryToolLocatorsCss.btn_save_file)\ + self.page.find_by_css_selector(QueryToolLocators.btn_save_file)\ .click() # Set the XSS value in input self.page.find_by_css_selector('.change_file_types') - self.page.fill_input_by_css_selector("input#file-input-path", - self.XSS_FILE) + self.page.fill_input_by_css_selector( + QueryToolLocators.input_file_path_css, self.XSS_FILE) # Save the file self.page.click_modal('Create') self.page.wait_for_query_tool_loading_indicator_to_disappear() def _open_file_manager_and_check_xss_file(self): - self.page.find_by_id("btn-load-file").click() + load_file = self.page.find_by_css_selector( + QueryToolLocators.btn_load_file_css) + load_file.click() self.page.find_by_css_selector('.change_file_types') - self.page.fill_input_by_css_selector("#file-input-path", "/tmp/", - key_after_input=Keys.RETURN) + self.page.fill_input_by_css_selector( + QueryToolLocators.input_file_path_css, + "/tmp/", key_after_input=Keys.RETURN) if self.page.driver.capabilities['browserName'] == 'firefox': table = self.page.wait_for_element_to_reload( - lambda driver: - driver.find_element_by_css_selector("table#contents") + lambda driver: driver.find_element_by_css_selector( + QueryToolLocators.select_file_content_css) ) else: - table = self.page.driver \ - .find_element_by_css_selector("table#contents") - - contents = table.get_attribute('innerHTML') + table = self.page.driver.find_element_by_css_selector( + QueryToolLocators.select_file_content_css) + retry_count = 0 + while retry_count < 5: + try: + contents = table.get_attribute('innerHTML') + break + except (StaleElementReferenceException, TimeoutException): + retry_count += 1 self.page.click_modal('Cancel') self.page.wait_for_query_tool_loading_indicator_to_disappear() @@ -107,7 +116,9 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): ) != -1, "{0} might be vulnerable to XSS ".format(source) def _check_file_sorting(self): - self.page.find_by_id("btn-load-file").click() + load_file = self.page.find_by_css_selector( + QueryToolLocators.btn_load_file_css) + load_file.click() self.page.find_by_css_selector("#contents th[data-column='0']") # Added time.sleep so that the element to be clicked. @@ -134,7 +145,7 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): if not success: raise Exception("Unable to sort in ascending order while clicked " "on 'Name' column") - + # Added time.sleep so that the element to be clicked. time.sleep(0.05) # Click and Check for sort Descending @@ -143,9 +154,10 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): iteration = 0 success = False while not success and iteration < 4: - self.page.find_by_xpath("//th[@data-column='0']" - "/div/span[text()='Name']").click() + try: + self.page.find_by_xpath("//th[@data-column='0']" + "/div/span[text()='Name']").click() self.wait.until( EC.presence_of_element_located(( By.CSS_SELECTOR, diff --git a/web/pgadmin/feature_tests/keyboard_shortcut_test.py b/web/pgadmin/feature_tests/keyboard_shortcut_test.py index bbf0c7963..9ed841337 100644 --- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py +++ b/web/pgadmin/feature_tests/keyboard_shortcut_test.py @@ -15,13 +15,14 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains -from regression.feature_utils.base_feature_test import BaseFeatureTest from selenium.webdriver.common.keys import Keys +from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.feature_utils.locators import NavMenuLocators class KeyboardShortcutFeatureTest(BaseFeatureTest): """ - This feature test will test the keyboard short is working + This feature test will test the keyboard shortcut is working properly. """ @@ -85,23 +86,36 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest): print("OK", file=sys.stderr) def _update_preferences(self): - self.page.find_by_id("mnu_file").click() - self.page.find_by_id("mnu_preferences").click() + file_menu = self.page.find_by_css_selector( + NavMenuLocators.file_menu_css) + file_menu.click() + pref_menu_item = self.page.find_by_css_selector( + NavMenuLocators.preference_menu_item_css) + pref_menu_item.click() + + # Wait till the preference dialogue box is displayed by checking the + # visibility of Show System Object label self.wait.until(EC.presence_of_element_located( - (By.XPATH, "//*[contains(string(), 'Show system objects?')]")) + (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath)) ) - self.page.find_by_css_selector( - ".ajs-dialog.pg-el-container .ajs-maximize" - ).click() + maximize_button = self.page.find_by_css_selector( + NavMenuLocators.maximize_pref_dialogue_css) + maximize_button.click() - browser = self.page.find_by_xpath( - "//*[contains(@class,'aciTreeLi') and contains(.,'Browser')]") + browser_node = self.page.find_by_xpath( + NavMenuLocators.specified_preference_tree_node.format('Browser')) + if self.page.find_by_xpath( + NavMenuLocators.specified_pref_node_exp_status. + format('Browser')).get_attribute('aria-expanded') == 'false': - browser.find_element_by_xpath( - "//*[contains(@class,'aciTreeText') and " - "contains(.,'Keyboard shortcuts')]").click() + ActionChains(self.driver).double_click(browser_node).perform() + + keyboard_node = self.page.find_by_xpath( + NavMenuLocators.specified_sub_node_of_pref_tree_node.format( + 'Browser', 'Keyboard shortcuts')) + keyboard_node.click() for s in self.new_shortcuts: key = self.new_shortcuts[s]['shortcut'][2] diff --git a/web/pgadmin/feature_tests/locators.py b/web/pgadmin/feature_tests/locators.py deleted file mode 100644 index 4fda2b4e5..000000000 --- a/web/pgadmin/feature_tests/locators.py +++ /dev/null @@ -1,32 +0,0 @@ -class QueryToolLocatorsCss: - btn_save_file = "#btn-save-file" - btn_save_data = "#btn-save-data" - btn_execute_query = "#btn-flash" - btn_query_dropdown = "#btn-query-dropdown" - btn_auto_rollback = "#btn-auto-rollback" - btn_auto_rollback_check_status = "#btn-auto-rollback > i" - btn_auto_commit = "#btn-auto-commit" - btn_auto_commit_check_status = "#btn-auto-commit > i" - btn_cancel_query = "#btn-cancel-query" - btn_explain = "#btn-explain" - btn_explain_analyze = "#btn-explain-analyze" - btn_explain_options_dropdown = "#btn-explain-options-dropdown" - btn_explain_verbose = "#btn-explain-verbose" - btn_explain_costs = "#btn-explain-costs" - btn_explain_buffers = "#btn-explain-buffers" - btn_explain_timing = "#btn-explain-timing" - btn_clear_dropdown = "#btn-clear-dropdown" - btn_clear = "#btn-clear" - btn_commit = "#btn-commit" - query_editor_panel = "#output-panel" - query_history_selected = "#query_list .selected" - query_history_selected_icon = '#query_list .selected #query_source_icon' - query_history_detail = "#query_detail" - query_history_generated_queries_toggle = '#generated-queries-toggle' - editor_panel = "#output-panel" - query_messages_panel = ".sql-editor-message" - execute_icon = "fa-bolt" - explain_icon = "fa-hand-pointer-o" - explain_analyze_icon = "fa-list-alt" - save_data_icon = "icon-save-data-changes" - commit_icon = "icon-commit" diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/pgadmin/feature_tests/pg_datatype_validation_test.py index 8a5b293be..83a245e57 100644 --- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py +++ b/web/pgadmin/feature_tests/pg_datatype_validation_test.py @@ -18,6 +18,8 @@ from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.feature_utils.locators import NavMenuLocators, \ + QueryToolLocators CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) @@ -78,35 +80,42 @@ class PGDataypeFeatureTest(BaseFeatureTest): connection.close() def _update_preferences(self): - self.page.find_by_id("mnu_file").click() - self.page.find_by_id("mnu_preferences").click() + file_menu = self.page.find_by_css_selector( + NavMenuLocators.file_menu_css) + file_menu.click() + + pref_menu_item = self.page.find_by_css_selector( + NavMenuLocators.preference_menu_item_css) + pref_menu_item.click() + wait = WebDriverWait(self.page.driver, 10) + # Wait till the preference dialogue box is displayed by checking the + # visibility of Show System Object label wait.until(EC.presence_of_element_located( - (By.XPATH, "//*[contains(string(), 'Show system objects?')]")) + (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath)) ) - self.page.find_by_css_selector( - ".ajs-dialog.pg-el-container .ajs-maximize").click() + maximize_button = self.page.find_by_css_selector( + NavMenuLocators.maximize_pref_dialogue_css) + maximize_button.click() sql_editor = self.page.find_by_xpath( - "//*[contains(@class,'aciTreeLi') and contains(.,'Query Tool')]") + NavMenuLocators.specified_preference_tree_node. + format('Query Tool')) + if self.page.find_by_xpath( + NavMenuLocators.specified_pref_node_exp_status. + format('Query Tool')).\ + get_attribute('aria-expanded') == 'false': + ActionChains(self.driver).double_click(sql_editor).perform() - sql_editor.find_element_by_xpath( - "//*[contains(@class,'aciTreeText') and contains(.,'Options')]" - ).click() + option_node = self.page.find_by_xpath( + NavMenuLocators.specified_sub_node_of_pref_tree_node.format( + 'Query Tool', 'Options')) + option_node.click() - insert_bracket_pairs_control = self.page.find_by_xpath( - "//div[contains(@class,'pgadmin-control-group') and " - "contains(.,'Insert bracket pairs?')]" - ) - - switch_btn = insert_bracket_pairs_control.\ - find_element_by_class_name('toggle') - - # check if switch is on then only toggle. - if 'off' not in switch_btn.get_attribute('class'): - switch_btn.click() + self.page.set_switch_box_status( + NavMenuLocators.insert_bracket_pair_switch_btn, 'No') # save and close the preference dialog. self.page.click_modal('Save') @@ -121,8 +130,10 @@ class PGDataypeFeatureTest(BaseFeatureTest): 'yellow','green','blue','purple'); """ self.page.fill_codemirror_area_with(query) - self.page.find_by_id("btn-flash").click() - self._clear_query_tool() + execute_query = self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css) + execute_query.click() + self.page.clear_query_tool() def runTest(self): self.page.wait_for_spinner_to_disappear() @@ -150,7 +161,9 @@ class PGDataypeFeatureTest(BaseFeatureTest): for batch in config_data: query = self.construct_select_query(batch) self.page.fill_codemirror_area_with(query) - self.page.find_by_id("btn-flash").click() + execute_query = self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css) + execute_query.click() wait = WebDriverWait(self.page.driver, 5) @@ -168,12 +181,13 @@ class PGDataypeFeatureTest(BaseFeatureTest): )) canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) ) # For every sample data-type value, check the expected output. cnt = 2 - cells = canvas.find_elements_by_css_selector('.slick-cell') + cells = canvas.find_elements_by_css_selector( + QueryToolLocators.query_output_cells) # remove first element as it is row number. cells.pop(0) for val, cell, datatype in zip( @@ -202,7 +216,7 @@ class PGDataypeFeatureTest(BaseFeatureTest): "for datatype {0}\n{1} does not match with {2}".format( datatype, val, expected_output ) - self._clear_query_tool() + self.page.clear_query_tool() def construct_select_query(self, batch): query = 'SELECT ' @@ -233,18 +247,6 @@ class PGDataypeFeatureTest(BaseFeatureTest): datatype, source_code, string_to_find ) - def _clear_query_tool(self): - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") - ) - ActionChains(self.driver)\ - .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']"))\ - .perform() - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear']") - ) - self.page.click_modal('Yes') - def _is_datatype_available_in_current_database(self, datatype): if datatype == '': return True diff --git a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py index 9afe007b5..0d4d8e9f6 100644 --- a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py +++ b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py @@ -11,9 +11,11 @@ import os from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import ElementClickInterceptedException from regression.feature_utils.base_feature_test import BaseFeatureTest from regression.python_test_utils import test_utils from regression.python_test_utils import test_gui_helper +from regression.feature_utils.locators import NavMenuLocators class PGUtilitiesBackupFeatureTest(BaseFeatureTest): @@ -61,22 +63,47 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self.page.toggle_open_tree_item(self.database_name) # Backup - self.driver.find_element_by_link_text("Tools").click() + retry = 3 + while retry > 0: + try: + self.driver.find_element_by_link_text( + NavMenuLocators.tools_menu_link_text).click() + break + except ElementClickInterceptedException: + retry -= 1 - self.page.find_by_partial_link_text("Backup...").click() + backup_object = self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, NavMenuLocators.backup_obj_css))) + backup_object.click() - self.wait.until(EC.element_to_be_clickable( - (By.CSS_SELECTOR, ".file [name='file']"))) - - self.wait.until(EC.element_to_be_clickable( - (By.CSS_SELECTOR, ".file [name='file']"))).click() + # Enter the file name of the backup to be taken + self.wait.until(EC.visibility_of_element_located( + (By.NAME, NavMenuLocators.backup_filename_txt_box_name))) + element = self.wait.until(EC.element_to_be_clickable( + (By.NAME, NavMenuLocators.backup_filename_txt_box_name))) + element.click() self.page.fill_input_by_field_name( - "file", "test_backup", loose_focus=True) + NavMenuLocators.backup_filename_txt_box_name, + "test_backup", loose_focus=True) - self.page.find_by_xpath("//button[contains(@class,'fa-save') " - "and contains(.,'Backup')]").click() + # Click on the take Backup button + take_bckup = self.page.find_by_xpath( + NavMenuLocators.backup_btn_xpath) + click = True + while click: + try: + take_bckup.click() + if self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_name( + NavMenuLocators.backup_filename_txt_box_name)): + click = False + except Exception as e: + pass - self.page.find_by_css_selector('.ajs-bg-bgprocess') + # Wait for the backup status alertfier + self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, + NavMenuLocators.bcg_process_status_alertifier_css))) status = test_utils.get_watcher_dialogue_status(self) @@ -86,7 +113,10 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self.assertEquals(status, "Successfully completed.") self.page.find_by_css_selector( - ".pg-bg-more-details").click() + NavMenuLocators.status_alertifier_more_btn_css).click() + + self.wait.until(EC.visibility_of_element_located( + (By.XPATH, NavMenuLocators.process_watcher_alertfier))) backup_file = None # Check for XSS in Backup details @@ -94,7 +124,8 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self._check_detailed_window_for_xss('Backup') else: command = self.page.find_by_css_selector( - ".bg-process-details .bg-detailed-desc").text + NavMenuLocators.process_watcher_detailed_command_canvas_css).\ + text self.assertIn(self.server['name'], str(command)) self.assertIn("from database 'pg_utility_test_db'", str(command)) @@ -109,26 +140,41 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): backup_file = command[int(command.find('--file')) + 8:int(command.find('--host')) - 2] - self.page.find_by_xpath("//div[contains(@class,'wcFloatingFocus')" - "]//div[contains(@class,'fa-close')]").click() + close_btn = self.page.find_by_xpath( + NavMenuLocators.process_watcher_close_button_xpath) + close_btn.click() # Restore - self.driver.find_element_by_link_text("Tools").click() - self.page.find_by_partial_link_text("Restore...").click() + tools_menu = self.driver.find_element_by_link_text( + NavMenuLocators.tools_menu_link_text) + tools_menu.click() + + restore_obj = self.page.find_by_css_selector( + NavMenuLocators.restore_obj_css) + restore_obj.click() + + self.wait.until(EC.visibility_of_element_located( + (By.NAME, NavMenuLocators.restore_file_name_txt_box_name))) self.wait.until(EC.element_to_be_clickable( - (By.CSS_SELECTOR, ".file [name='file']"))) - - self.wait.until(EC.element_to_be_clickable( - (By.CSS_SELECTOR, ".file [name='file']"))).click() + (By.NAME, NavMenuLocators.restore_file_name_txt_box_name))).click() self.page.fill_input_by_field_name( - "file", "test_backup", loose_focus=True) + NavMenuLocators.restore_file_name_txt_box_name, + "test_backup", loose_focus=True) - self.page.find_by_xpath("//button[contains(@class,'fa-upload')" - " and contains(.,'Restore')]").click() + restore_btn = self.page.find_by_xpath( + NavMenuLocators.restore_button_xpath) + restore_btn.click() - self.page.find_by_css_selector('.ajs-bg-bgprocess') + self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_css_selector( + NavMenuLocators.restore_file_name_txt_box_name)) + + # Wait for the backup status alertfier + self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, + NavMenuLocators.bcg_process_status_alertifier_css))) status = test_utils.get_watcher_dialogue_status(self) @@ -138,14 +184,18 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self.assertEquals(status, "Successfully completed.") self.page.find_by_css_selector( - ".pg-bg-more-details").click() + NavMenuLocators.status_alertifier_more_btn_css).click() + + self.wait.until(EC.visibility_of_element_located( + (By.XPATH, NavMenuLocators.process_watcher_alertfier))) # Check for XSS in Restore details if self.is_xss_check: self._check_detailed_window_for_xss('Restore') else: command = self.page.find_by_css_selector( - ".bg-process-details .bg-detailed-desc").text + NavMenuLocators.process_watcher_detailed_command_canvas_css).\ + text self.assertIn(self.server['name'], str(command)) if os.name is not 'nt': @@ -153,8 +203,9 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self.assertIn("pg_restore", str(command)) - self.page.find_by_xpath("//div[contains(@class,'wcFloatingFocus')]" - "//div[contains(@class,'fa-close')]").click() + close_watcher = self.page.find_by_xpath( + NavMenuLocators.process_watcher_close_button_xpath) + close_watcher.click() if backup_file is not None: if os.path.isfile(backup_file): @@ -175,7 +226,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): def _check_detailed_window_for_xss(self, tool_name): source_code = self.page.find_by_css_selector( - ".bg-process-details .bg-detailed-desc" + NavMenuLocators.process_watcher_detailed_command_canvas_css ).get_attribute('innerHTML') self._check_escaped_characters( source_code, diff --git a/web/pgadmin/feature_tests/pg_utilities_maintenance_test.py b/web/pgadmin/feature_tests/pg_utilities_maintenance_test.py index 129f63478..7524f141d 100644 --- a/web/pgadmin/feature_tests/pg_utilities_maintenance_test.py +++ b/web/pgadmin/feature_tests/pg_utilities_maintenance_test.py @@ -6,13 +6,15 @@ # This software is released under the PostgreSQL Licence # ########################################################################## - -import time - from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By +from selenium.common.exceptions import ElementClickInterceptedException from regression.feature_utils.base_feature_test import BaseFeatureTest from regression.python_test_utils import test_utils from regression.python_test_utils import test_gui_helper +from regression.feature_utils.locators import NavMenuLocators +import random class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): @@ -55,6 +57,8 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): self.server['port'], self.server['sslmode'] ) + + self.table_name = self.table_name + str(random.randint(1000, 3000)) test_utils.drop_database(connection, self.database_name) test_utils.create_database(self.server, self.database_name) test_utils.create_table(self.server, self.database_name, @@ -66,8 +70,16 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): def runTest(self): self._open_maintenance_dialogue() self.page.click_modal('OK') - self.page.find_by_css_selector('.ajs-bg-bgprocess') - self._verify_command() + self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_xpath( + NavMenuLocators.maintenance_operation)) + + # Wait for the backup status alertfier + self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, + NavMenuLocators.bcg_process_status_alertifier_css))) + + self.verify_command() def _open_maintenance_dialogue(self): self.page.toggle_open_server(self.server['name']) @@ -78,30 +90,49 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): self.page.toggle_open_tree_item('public') self.page.toggle_open_tables_node() self.page.select_tree_item(self.table_name) + retry = 3 + while retry > 0: + try: + tools_menu = self.driver.find_element_by_link_text( + NavMenuLocators.tools_menu_link_text) + tools_menu.click() + break + except ElementClickInterceptedException: + retry -= 1 + maintenance_obj = self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, NavMenuLocators.maintenance_obj_css))) + maintenance_obj.click() - self.driver.find_element_by_link_text("Tools").click() - self.page.find_by_partial_link_text("Maintenance...").click() - time.sleep(0.5) + self.page.check_if_element_exist_by_xpath( + NavMenuLocators.maintenance_operation, 10) - def _verify_command(self): + def verify_command(self): status = test_utils.get_watcher_dialogue_status(self) if status != "Successfully completed.": + test_gui_helper.close_bgprocess_popup(self) self.assertEquals(status, "Successfully completed.") - self.page.find_by_css_selector(".pg-bg-more-details").click() + self.page.find_by_css_selector( + NavMenuLocators.status_alertifier_more_btn_css).click() + + self.wait.until(EC.visibility_of_element_located( + (By.XPATH, NavMenuLocators.process_watcher_alertfier))) + command = self.page.find_by_css_selector( - ".bg-process-details .bg-detailed-desc").text + NavMenuLocators. + process_watcher_detailed_command_canvas_css).text + if self.test_level == 'database': - self.assertEquals(command, "VACUUM " - "(VERBOSE)\nRunning Query:" + self.assertEquals(command, "VACUUM (VERBOSE)\nRunning Query:" "\nVACUUM VERBOSE;") elif self.is_xss_check and self.test_level == 'table': # Check for XSS in the dialog source_code = self.page.find_by_css_selector( - ".bg-process-details .bg-detailed-desc" + NavMenuLocators. + process_watcher_detailed_command_canvas_css ).get_attribute('innerHTML') - self._check_escaped_characters( + self.check_escaped_characters( source_code, '<h1>test_me</h1>', 'Maintenance detailed window' @@ -112,12 +143,14 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): "\nVACUUM VERBOSE" " public." + self.table_name + ";") - self.page.find_by_css_selector( - "div.wcFloatingFocus div.fa-close").click() + self.page.find_by_xpath( + NavMenuLocators.process_watcher_close_button_xpath).click() def after(self): test_gui_helper.close_bgprocess_popup(self) self.page.remove_server(self.server) + test_utils.delete_table(self.server, self.test_db, + self.table_name) connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -128,7 +161,7 @@ class PGUtilitiesMaintenanceFeatureTest(BaseFeatureTest): ) test_utils.drop_database(connection, self.database_name) - def _check_escaped_characters(self, source_code, string_to_find, source): + def check_escaped_characters(self, source_code, string_to_find, source): # For XSS we need to search against element's html code assert source_code.find(string_to_find) != - \ 1, "{0} might be vulnerable to XSS ".format(source) diff --git a/web/pgadmin/feature_tests/query_tool_auto_complete_tests.py b/web/pgadmin/feature_tests/query_tool_auto_complete_tests.py index b36d23437..1f6e248ce 100644 --- a/web/pgadmin/feature_tests/query_tool_auto_complete_tests.py +++ b/web/pgadmin/feature_tests/query_tool_auto_complete_tests.py @@ -15,6 +15,7 @@ from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.feature_utils.locators import QueryToolLocators class QueryToolAutoCompleteFeatureTest(BaseFeatureTest): @@ -55,7 +56,10 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest): test_utils.create_table(self.server, self.test_db, self.second_table_name) - self._locate_database_tree_node() + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item(self.test_db) + self.page.open_query_tool() self.page.wait_for_spinner_to_disappear() @@ -64,64 +68,64 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest): print("\nAuto complete ALTER keyword... ", file=sys.stderr, end="") self._auto_complete("A", "ALTER") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete BEGIN keyword... ", file=sys.stderr, end="") self._auto_complete("BE", "BEGIN") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete CASCADED keyword... ", file=sys.stderr, end="") self._auto_complete("CAS", "CASCADED") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete SELECT keyword... ", file=sys.stderr, end="") self._auto_complete("SE", "SELECT") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete pg_backend_pid() function ... ", file=sys.stderr, end="") self._auto_complete("SELECT pg_", "pg_backend_pid()") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete current_query() function ... ", file=sys.stderr, end="") self._auto_complete("SELECT current_", "current_query()") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete function with argument ... ", file=sys.stderr, end="") self._auto_complete("SELECT pg_st", "pg_stat_file(filename)") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete schema other than default start with test_ ... ", file=sys.stderr, end="") self._auto_complete("SELECT * FROM te", self.first_schema_name) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete schema other than default starts with comp_ ... ", file=sys.stderr, end="") self._auto_complete("SELECT * FROM co", self.second_schema_name) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete first table in public schema ... ", file=sys.stderr, end="") self._auto_complete("SELECT * FROM public.", self.first_table_name) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete second table in public schema ... ", file=sys.stderr, end="") self._auto_complete("SELECT * FROM public.", self.second_table_name) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete JOIN second table with after schema name ... ", file=sys.stderr, end="") @@ -129,7 +133,7 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest): " JOIN public." self._auto_complete(query, self.second_table_name) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("Auto complete JOIN ON some columns ... ", file=sys.stderr, end="") @@ -140,54 +144,51 @@ class QueryToolAutoCompleteFeatureTest(BaseFeatureTest): ".some_column" self._auto_complete(query, expected_string) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() - print("Auto complete JOIN ON some columns using tabel alias ... ", + print("Auto complete JOIN ON some columns using table alias ... ", file=sys.stderr, end="") query = "SELECT * FROM public." + self.first_table_name + \ " t1 JOIN public." + self.second_table_name + " t2 ON t2." self._auto_complete(query, "some_column = t1.some_column") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() def after(self): self.page.remove_server(self.server) - - def _locate_database_tree_node(self): - self.page.toggle_open_tree_item(self.server['name']) - self.page.toggle_open_tree_item('Databases') - self.page.toggle_open_tree_item(self.test_db) - - def _clear_query_tool(self): - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") - ) - ActionChains(self.driver) \ - .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']")) \ - .perform() - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear']") - ) - self.page.click_modal('Yes') + test_utils.delete_table(self.server, self.test_db, + self.first_table_name) + test_utils.delete_table(self.server, self.test_db, + self.second_table_name) def _auto_complete(self, word, expected_string): self.page.fill_codemirror_area_with(word) - ActionChains(self.page.driver).key_down( - Keys.CONTROL).send_keys(Keys.SPACE).key_up(Keys.CONTROL).perform() - - # if IntelliSense is present then verify this - if self.page.check_if_element_exist_by_xpath( - "//ul[@class='CodeMirror-hints default']", 2): + hint_displayed = False + retry = 3 + while retry > 0: + ActionChains(self.page.driver).key_down( + Keys.CONTROL).send_keys(Keys.SPACE).key_up( + Keys.CONTROL).perform() + if self.page.check_if_element_exist_by_xpath( + QueryToolLocators.code_mirror_hint_box_xpath, 20): + hint_displayed = True + break + else: + retry -= 1 + if hint_displayed: + # if IntelliSense is present then verify this self.page.find_by_xpath( - "//ul[contains(@class, 'CodeMirror-hints') and " - "contains(., '" + expected_string + "')]") + QueryToolLocators.code_mirror_hint_item_xpath.format( + expected_string)) else: # if no IntelliSense is present it means there is only one option # so check if required string is present in codeMirror - code_mirror = self.page.find_by_xpath( - "//pre[@class=' CodeMirror-line ']/span") - code_mirror_text = code_mirror.text - - if expected_string not in code_mirror_text: - raise Exception("Required String %s is not " - "present" % expected_string) + code_mirror = self.driver.find_elements_by_xpath( + QueryToolLocators.code_mirror_data_xpath) + for data in code_mirror: + code_mirror_text = data.text + print("Single entry..........") + if expected_string not in code_mirror_text: + print("single entry exception.........") + raise Exception("Required String %s is not " + "present" % expected_string) diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py index 42253c6f7..8d8bfe34f 100644 --- a/web/pgadmin/feature_tests/query_tool_journey_test.py +++ b/web/pgadmin/feature_tests/query_tool_journey_test.py @@ -14,10 +14,10 @@ import random from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys - +from selenium.webdriver.support.ui import WebDriverWait from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest -from .locators import QueryToolLocatorsCss +from regression.feature_utils.locators import QueryToolLocators class QueryToolJourneyTest(BaseFeatureTest): @@ -31,9 +31,12 @@ class QueryToolJourneyTest(BaseFeatureTest): test_table_name = "" test_editable_table_name = "" + invalid_table_name = "" def before(self): self.test_table_name = "test_table" + str(random.randint(1000, 3000)) + self.invalid_table_name = \ + "table_that_doesnt_exist_" + str(random.randint(1000, 3000)) test_utils.create_table( self.server, self.test_db, self.test_table_name) @@ -52,10 +55,11 @@ class QueryToolJourneyTest(BaseFeatureTest): driver_version = test_utils.get_driver_version() self.driver_version = float('.'.join(driver_version.split('.')[:2])) + self.wait = WebDriverWait(self.page.driver, 10) def runTest(self): self._navigate_to_query_tool() - self._execute_query( + self.page.execute_query( "SELECT * FROM %s ORDER BY value " % self.test_table_name) print("Copy rows...", file=sys.stderr, end="") @@ -78,7 +82,7 @@ class QueryToolJourneyTest(BaseFeatureTest): self._test_query_sources_and_generated_queries() print(" OK.", file=sys.stderr) - print("Updatable resultsets...", file=sys.stderr, end="") + print("Updatable result sets...", file=sys.stderr, end="") self._test_updatable_resultset() print(" OK.", file=sys.stderr) @@ -87,9 +91,14 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.driver.switch_to.default_content() self.page.driver.switch_to_frame( self.page.driver.find_element_by_tag_name("iframe")) - self.page.find_by_xpath( - "//*[contains(@class, 'slick-row')]/*[1]").click() - self.page.find_by_xpath("//*[@id='btn-copy-row']").click() + + select_row = self.page.find_by_xpath( + QueryToolLocators.output_row_xpath.format('1')) + select_row.click() + + copy_row = self.page.find_by_css_selector( + QueryToolLocators.copy_button_css) + copy_row.click() self.assertEqual('"Some-Name"\t"6"\t"some info"', pyperclip.paste()) @@ -100,86 +109,85 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.driver.switch_to.default_content() self.page.driver.switch_to_frame( self.page.driver.find_element_by_tag_name("iframe")) - self.page.find_by_xpath( - "//*[@data-test='output-column-header' and " - "contains(., 'some_column')]" - ).click() - self.page.find_by_xpath("//*[@id='btn-copy-row']").click() + + column_header = self.page.find_by_css_selector( + QueryToolLocators.output_column_header_css.format('some_column')) + column_header.click() + + copy_btn = self.page.find_by_css_selector( + QueryToolLocators.copy_button_css) + copy_btn.click() self.assertTrue('"Some-Name"' in pyperclip.paste()) self.assertTrue('"Some-Other-Name"' in pyperclip.paste()) self.assertTrue('"Yet-Another-Name"' in pyperclip.paste()) def _test_history_tab(self): - self.__clear_query_tool() + self.page.clear_query_tool() editor_input = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_editor_panel) + QueryToolLocators.query_editor_panel) self.page.click_element(editor_input) - self._execute_query("SELECT * FROM table_that_doesnt_exist") + self.page.execute_query("SELECT * FROM %s" % self.invalid_table_name) self.page.click_tab("Query History") selected_history_entry = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_history_selected) - self.assertIn("SELECT * FROM table_that_doesnt_exist", + QueryToolLocators.query_history_selected) + self.assertIn("SELECT * FROM %s" % self.invalid_table_name, selected_history_entry.text) failed_history_detail_pane = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_history_detail) + QueryToolLocators.query_history_detail) self.assertIn( - "Error Message relation \"table_that_doesnt_exist\" " - "does not exist", failed_history_detail_pane.text + "Error Message relation \"%s\" does not exist" + % self.invalid_table_name, + failed_history_detail_pane.text ) - self.page.wait_for_element(lambda driver: driver - .find_element_by_css_selector( - "#query_list> .query-group>ul>li")) + self.page.wait_for_elements( + lambda driver: driver.find_elements_by_css_selector( + QueryToolLocators.query_history_entries)) # get the query history rows and click the previous query row which # was executed and verify it history_rows = self.driver.find_elements_by_css_selector( - "#query_list> .query-group>ul>li") + QueryToolLocators.query_history_entries) history_rows[1].click() selected_history_entry = self.page.find_by_css_selector( - "#query_list .selected") + QueryToolLocators.query_history_selected) self.assertIn(("SELECT * FROM %s ORDER BY value" % self.test_table_name), selected_history_entry.text) # check second(invalid) query also exist in the history tab with error - newly_selected_history_entry = self.page.find_by_xpath( - "//*[@id='query_list']/div/ul/li[1]") + newly_selected_history_entry = history_rows[0] self.page.click_element(newly_selected_history_entry) - selected_invalid_history_entry = self.page.find_by_css_selector( - "#query_list .selected .entry.error .query") + invalid_history_entry = self.page.find_by_css_selector( + QueryToolLocators.invalid_query_history_entry_css) - self.assertIn("SELECT * FROM table_that_doesnt_exist", - selected_invalid_history_entry.text) + self.assertIn("SELECT * FROM %s" % self.invalid_table_name, + invalid_history_entry.text) self.page.click_tab("Query Editor") - self.__clear_query_tool() + self.page.clear_query_tool() self.page.click_element(editor_input) + # Check if 15 more query executed then the history should contain 17 + # entries. self.page.fill_codemirror_area_with("SELECT * FROM hats") for _ in range(15): self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab("Query History") - query_we_need_to_scroll_to = self.page.find_by_xpath( - "//*[@id='query_list']/div/ul/li[17]") + query_list = self.page.wait_for_elements( + lambda driver: driver.find_elements_by_css_selector( + QueryToolLocators.query_history_entries)) - self.page.click_element(query_we_need_to_scroll_to) - - for _ in range(17): - ActionChains(self.page.driver) \ - .send_keys(Keys.ARROW_DOWN) \ - .perform() - - self._assert_clickable(query_we_need_to_scroll_to) + self.assertTrue(17, len(query_list)) def _test_query_sources_and_generated_queries(self): self.__clear_query_history() @@ -193,12 +201,12 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.click_tab("Query History") history_entries_icons = [ - QueryToolLocatorsCss.commit_icon, - QueryToolLocatorsCss.save_data_icon, - QueryToolLocatorsCss.save_data_icon, - QueryToolLocatorsCss.execute_icon, - QueryToolLocatorsCss.explain_analyze_icon, - QueryToolLocatorsCss.explain_icon + QueryToolLocators.commit_icon, + QueryToolLocators.save_data_icon, + QueryToolLocators.save_data_icon, + QueryToolLocators.execute_icon, + QueryToolLocators.explain_analyze_icon, + QueryToolLocators.explain_icon ] history_entries_queries = [ @@ -217,33 +225,31 @@ class QueryToolJourneyTest(BaseFeatureTest): def _test_toggle_generated_queries(self): xpath = '//li[contains(@class, "pgadmin-query-history-entry")]' self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath)) - toggle_el = self.page.find_by_xpath( - '//input[@id ="generated-queries-toggle"]/..' - ) - toggle_el.click() + self.page.set_switch_box_status( + QueryToolLocators.show_query_internally_btn, 'No') self.assertFalse(self.page.check_if_element_exist_by_xpath(xpath)) - toggle_el.click() + self.page.set_switch_box_status( + QueryToolLocators.show_query_internally_btn, 'Yes') self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath)) def _test_updatable_resultset(self): if self.driver_version < 2.8: return - self.page.click_tab("Query Editor") # Select all data (contains the primary key -> should be editable) - self.__clear_query_tool() + self.page.clear_query_tool() query = "SELECT pk_column, normal_column FROM %s" \ % self.test_editable_table_name self._check_query_results_editable(query, True) # Select data without primary keys -> should not be editable - self.__clear_query_tool() + self.page.clear_query_tool() query = "SELECT normal_column FROM %s" % self.test_editable_table_name self._check_query_results_editable(query, False) def _execute_sources_test_queries(self): - self.__clear_query_tool() + self.page.clear_query_tool() self._explain_query( "SELECT * FROM %s;" @@ -253,17 +259,17 @@ class QueryToolJourneyTest(BaseFeatureTest): "SELECT * FROM %s;" % self.test_editable_table_name ) - self._execute_query( + self.page.execute_query( "SELECT * FROM %s;" % self.test_editable_table_name ) # Turn off autocommit query_options = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_options.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit).click() + QueryToolLocators.btn_auto_commit).click() query_options.click() # Click again to close dropdown self._update_numeric_cell(2, 10) @@ -272,24 +278,25 @@ class QueryToolJourneyTest(BaseFeatureTest): # Turn on autocommit query_options = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_options.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit).click() + QueryToolLocators.btn_auto_commit).click() query_options.click() # Click again to close dropdown def _check_history_queries_and_icons(self, history_queries, history_icons): # Select first query history entry - self.page.find_by_xpath("//*[@id='query_list']/div/ul/li[1]").click() + self.page.find_by_css_selector( + QueryToolLocators.query_history_specific_entry.format(1)).click() for icon, query in zip(history_icons, history_queries): # Check query query_history_selected_item = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_history_selected + QueryToolLocators.query_history_selected ) self.assertIn(query, query_history_selected_item.text) # Check source icon query_history_selected_icon = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_history_selected_icon) + QueryToolLocators.query_history_selected_icon) icon_classes = query_history_selected_icon.get_attribute('class') icon_classes = icon_classes.split(" ") self.assertTrue(icon in icon_classes) @@ -302,47 +309,37 @@ class QueryToolJourneyTest(BaseFeatureTest): """ Updates a numeric cell in the first row of the resultset """ - xpath = '//div[contains(@class, "slick-row") and ' \ - 'contains(@style, "top:0px")]' - xpath += '/div[contains(@class, "slick-cell") and ' \ - 'contains(@class, "r' + str(cell_index) + '")]' - cell_el = self.page.find_by_xpath(xpath) + self.page.check_if_element_exist_by_xpath( + "//div[contains(@style, 'top:0px')]//div[contains(@class, " + "'l{0} r{1}')]".format(cell_index, cell_index)) + cell_el = self.page.find_by_xpath( + "//div[contains(@style, 'top:0px')]//div[contains(@class, " + "'l{0} r{1}')]".format(cell_index, cell_index)) ActionChains(self.driver).double_click(cell_el).perform() ActionChains(self.driver).send_keys(value). \ send_keys(Keys.ENTER).perform() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_save_data).click() + QueryToolLocators.btn_save_data).click() def _insert_data_into_test_editable_table(self): self.page.click_tab("Query Editor") - self.__clear_query_tool() - self._execute_query( + self.page.clear_query_tool() + self.page.execute_query( "INSERT INTO %s VALUES (1, 1), (2, 2);" % self.test_editable_table_name ) - def __clear_query_tool(self): - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") - ) - ActionChains(self.driver)\ - .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']"))\ - .perform() - self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear']") - ) - self.page.click_modal('Yes') - def __clear_query_history(self): self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") + self.page.find_by_css_selector( + QueryToolLocators.btn_clear_dropdown) ) ActionChains(self.driver)\ .move_to_element( - self.page.find_by_xpath( - "//*[@id='btn-clear-history']")).perform() + self.page.find_by_css_selector( + QueryToolLocators.btn_clear_history)).perform() self.page.click_element( - self.page.find_by_xpath("//*[@id='btn-clear-history']") + self.page.find_by_css_selector(QueryToolLocators.btn_clear_history) ) self.page.click_modal('Yes') @@ -353,44 +350,39 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.open_query_tool() self.page.wait_for_spinner_to_disappear() - def _execute_query(self, query): - self.page.fill_codemirror_area_with(query) - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - def _explain_query(self, query): self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain).click() + QueryToolLocators.btn_explain).click() def _explain_analyze_query(self, query): self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_analyze).click() + QueryToolLocators.btn_explain_analyze).click() def _commit_transaction(self): self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_commit).click() + QueryToolLocators.btn_commit).click() def _assert_clickable(self, element): self.page.click_element(element) def _check_query_results_editable(self, query, should_be_editable): - self._execute_query(query) - self.page.wait_for_spinner_to_disappear() + self.page.execute_query(query) # Check if the first cell in the first row is editable is_editable = self._check_cell_editable(1) self.assertEqual(is_editable, should_be_editable) def _check_cell_editable(self, cell_index): - """ - Checks if a cell in the first row of the resultset is editable - """ - xpath = '//div[contains(@class, "slick-row") and ' \ - 'contains(@style, "top:0px")]' - xpath += '/div[contains(@class, "slick-cell") and ' \ - 'contains(@class, "r' + str(cell_index) + '")]' - cell_el = self.page.find_by_xpath(xpath) + """Checks if a cell in the first row of the resultset is editable""" + + self.page.check_if_element_exist_by_xpath( + "//div[contains(@style, 'top:0px')]//div[contains(@class, " + "'l{0} r{1}')]".format(cell_index, cell_index)) + cell_el = self.page.find_by_xpath( + "//div[contains(@style, 'top:0px')]//div[contains(@class, " + "'l{0} r{1}')]".format(cell_index, cell_index)) + # Get existing value cell_value = int(cell_el.text) new_value = cell_value + 1 @@ -401,6 +393,12 @@ class QueryToolJourneyTest(BaseFeatureTest): # Check if the value was updated return int(cell_el.text) == new_value + def _check_can_add_row(self): + return self.page.check_if_element_exist_by_xpath( + QueryToolLocators.new_row_xpath) + def after(self): self.page.close_query_tool() self.page.remove_server(self.server) + test_utils.delete_table( + self.server, self.test_db, self.test_table_name) diff --git a/web/pgadmin/feature_tests/query_tool_tests.py b/web/pgadmin/feature_tests/query_tool_tests.py index 88ea9e151..9252b93ca 100644 --- a/web/pgadmin/feature_tests/query_tool_tests.py +++ b/web/pgadmin/feature_tests/query_tool_tests.py @@ -10,15 +10,16 @@ from __future__ import print_function import sys -from selenium.common.exceptions import StaleElementReferenceException -from selenium.webdriver import ActionChains +from selenium.common.exceptions import StaleElementReferenceException, \ + ElementClickInterceptedException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest import config -from .locators import QueryToolLocatorsCss +from regression.feature_utils.locators import \ + QueryToolLocators class QueryToolFeatureTest(BaseFeatureTest): @@ -33,17 +34,20 @@ class QueryToolFeatureTest(BaseFeatureTest): def before(self): self.page.wait_for_spinner_to_disappear() self.page.add_server(self.server) - self._locate_database_tree_node() + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item(self.test_db) self.page.open_query_tool() self.page.wait_for_spinner_to_disappear() self._reset_options() + self.wait = WebDriverWait(self.page.driver, 10) def runTest(self): # on demand result set on scrolling. print("\nOn demand query result... ", file=sys.stderr, end="") self._on_demand_result() - self._clear_query_tool() + self.page.clear_query_tool() # explain query with verbose and cost print("Explain query with verbose and cost... ", @@ -51,7 +55,7 @@ class QueryToolFeatureTest(BaseFeatureTest): if self._supported_server_version(): self._query_tool_explain_with_verbose_and_cost() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() else: print("Skipped.", file=sys.stderr) @@ -61,7 +65,7 @@ class QueryToolFeatureTest(BaseFeatureTest): if self._supported_server_version(): self._query_tool_explain_analyze_with_buffers_and_timing() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() else: print("Skipped.", file=sys.stderr) @@ -69,30 +73,30 @@ class QueryToolFeatureTest(BaseFeatureTest): print("Auto commit disabled... ", file=sys.stderr, end="") self._query_tool_auto_commit_disabled() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() # auto commit enabled. print("Auto commit enabled... ", file=sys.stderr, end="") self._query_tool_auto_commit_enabled() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() # auto rollback enabled. print("Auto rollback enabled...", file=sys.stderr, end="") self._query_tool_auto_rollback_enabled() print(" OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() # cancel query. print("Cancel query... ", file=sys.stderr, end="") self._query_tool_cancel_query() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() # Notify Statements. print("Capture Notify Statements... ", file=sys.stderr, end="") self._query_tool_notify_statements() - self._clear_query_tool() + self.page.clear_query_tool() # explain query with JIT stats print("Explain query with JIT stats... ", @@ -100,7 +104,7 @@ class QueryToolFeatureTest(BaseFeatureTest): if self._supported_jit_on_server(): self._query_tool_explain_check_jit_stats() print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() else: print("Skipped.", file=sys.stderr) @@ -112,33 +116,33 @@ class QueryToolFeatureTest(BaseFeatureTest): self.page.fill_codemirror_area_with('') explain_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_options_dropdown) + QueryToolLocators.btn_explain_options_dropdown) explain_op.click() # disable Explain options and auto rollback only if they are enabled. - for op in (QueryToolLocatorsCss.btn_explain_verbose, - QueryToolLocatorsCss.btn_explain_costs, - QueryToolLocatorsCss.btn_explain_buffers, - QueryToolLocatorsCss.btn_explain_timing): + for op in (QueryToolLocators.btn_explain_verbose, + QueryToolLocators.btn_explain_costs, + QueryToolLocators.btn_explain_buffers, + QueryToolLocators.btn_explain_timing): btn = self.page.find_by_css_selector(op) check = btn.find_element_by_tag_name('i') if 'visibility-hidden' not in check.get_attribute('class'): btn.click() query_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_op.click() # disable auto rollback only if they are enabled btn = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_rollback) + QueryToolLocators.btn_auto_rollback) check = btn.find_element_by_tag_name('i') if 'visibility-hidden' not in check.get_attribute('class'): btn.click() # enable autocommit only if it's disabled btn = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit) + QueryToolLocators.btn_auto_commit) check = btn.find_element_by_tag_name('i') if 'visibility-hidden' in check.get_attribute('class'): btn.click() @@ -146,23 +150,6 @@ class QueryToolFeatureTest(BaseFeatureTest): # close menu query_op.click() - def _locate_database_tree_node(self): - self.page.toggle_open_tree_item(self.server['name']) - self.page.toggle_open_tree_item('Databases') - self.page.toggle_open_tree_item(self.test_db) - - def _clear_query_tool(self): - self.page.click_element(self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_clear_dropdown) - ) - ActionChains(self.driver) \ - .move_to_element(self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_clear)).perform() - self.page.click_element( - self.page.find_by_css_selector(QueryToolLocatorsCss.btn_clear) - ) - self.page.click_modal('Yes') - def _on_demand_result(self): ON_DEMAND_CHUNKS = 2 row_id_to_find = config.ON_DEMAND_RECORD_COUNT * ON_DEMAND_CHUNKS @@ -175,126 +162,133 @@ SELECT generate_series(1, {}) as id1, 'dummy' as id2""".format( print("\nOn demand result set on scrolling... ", file=sys.stderr, end="") - wait = WebDriverWait(self.page.driver, 10) - self.page.fill_codemirror_area_with(query) - - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + self.page.execute_query(query) # wait for header of the table to be visible - wait.until(EC.visibility_of_element_located( - (By.XPATH, '//div[@class="slick-header-columns"]'))) + self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) - wait.until(EC.presence_of_element_located( - (By.XPATH, - '//span[@data-row="0" and text()="1"]')) - ) + self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, + QueryToolLocators.query_output_cells))) - # scroll to bottom to fetch next chunk of result set. - self.driver.execute_script( - "pgAdmin.SqlEditor.jquery('.slick-viewport')" - ".scrollTop(pgAdmin.SqlEditor.jquery('.grid-canvas').height());" - ) + canvas = self.page.find_by_css_selector( + QueryToolLocators.query_output_canvas_css) - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) - - self._check_ondemand_result(row_id_to_find, canvas) + self._check_ondemand_result(row_id_to_find) print("OK.", file=sys.stderr) print("On demand result set on grid select all... ", file=sys.stderr, end="") self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() # wait for header of the table to be visible - wait.until(EC.visibility_of_element_located( - (By.XPATH, '//div[@class="slick-header-columns"]'))) + canvas = self.page.find_by_css_selector( + QueryToolLocators.query_output_canvas_css) - # wait for first row to contain value - wait.until(EC.presence_of_element_located( - (By.XPATH, - '//span[@data-row="0" and text()="1"]')) + # wait for the rows in the table to be displayed + self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, + QueryToolLocators.query_output_cells)) ) - wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, ".slick-header-column"))).click() + # Select all rows in a table + multiple_check = True + count = 0 + while multiple_check: + try: + select_all = self.wait.until(EC.element_to_be_clickable( + (By.XPATH, QueryToolLocators.select_all_column))) + select_all.click() + multiple_check = False + except (StaleElementReferenceException, + ElementClickInterceptedException): + count += 1 + pass + print(count) - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) - - self._check_ondemand_result(row_id_to_find, canvas) + self._check_ondemand_result(row_id_to_find) print("OK.", file=sys.stderr) print("On demand result set on column select all... ", file=sys.stderr, end="") self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() # wait for header of the table to be visible - wait.until(EC.visibility_of_element_located( - (By.XPATH, '//div[@class="slick-header-columns"]'))) + self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) - wait.until(EC.presence_of_element_located( - (By.XPATH, - '//span[@data-row="0" and text()="1"]')) + # wait for the rows in the table to be displayed + self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, + QueryToolLocators.query_output_cells)) ) # click on first data column to select all column. - wait.until(EC.presence_of_element_located( - ( - By.XPATH, - "//span[contains(@class, 'column-name') " - "and contains(., 'id1')]")) - ).click() + column_1 = \ + self.page.find_by_css_selector( + QueryToolLocators.output_column_header_css.format('id1')) + column_1.click() - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) - self._check_ondemand_result(row_id_to_find, canvas) + self._check_ondemand_result(row_id_to_find) print("OK.", file=sys.stderr) - def _check_ondemand_result(self, row_id_to_find, canvas): + def _check_ondemand_result(self, row_id_to_find): # scroll to bottom to bring last row of next chunk in viewport. - self.driver.execute_script( - "pgAdmin.SqlEditor.jquery('.slick-viewport')" - ".scrollTop(pgAdmin.SqlEditor.jquery('.grid-canvas').height());" - ) + # canvas_ele = self.page.find_by_css_selector() + scroll = 10 + while scroll: + canvas_ele = self.page.find_by_css_selector('.grid-canvas') + scrolling_height = canvas_ele.size['height'] + self.driver.execute_script( + "pgAdmin.SqlEditor.jquery('.slick-viewport')" + ".scrollTop(pgAdmin.SqlEditor.jquery('.grid-canvas')" + ".height());" + ) + import time + time.sleep(0.5) + if canvas_ele.size['height'] == scrolling_height: + break + else: + scroll -= 1 - canvas.find_element_by_xpath( - '//span[text()="{}"]'.format(row_id_to_find) - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.output_column_data_xpath.format(row_id_to_find) + )) def _query_tool_explain_with_verbose_and_cost(self): query = """-- Explain query with verbose and cost SELECT generate_series(1, 1000) as id order by id desc""" - wait = WebDriverWait(self.page.driver, 10) - self.page.fill_codemirror_area_with(query) explain_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_options_dropdown) + QueryToolLocators.btn_explain_options_dropdown) explain_op.click() # disable Explain options and auto rollback only if they are enabled. - for op in (QueryToolLocatorsCss.btn_explain_verbose, - QueryToolLocatorsCss.btn_explain_costs): + for op in (QueryToolLocators.btn_explain_verbose, + QueryToolLocators.btn_explain_costs): self.page.find_by_css_selector(op).click() explain_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain).click() + QueryToolLocators.btn_explain).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Data Output') - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) ) # Search for 'Output' word in result (verbose option) @@ -307,40 +301,38 @@ SELECT generate_series(1, 1000) as id order by id desc""" query = """-- Explain analyze query with buffers and timing SELECT generate_series(1, 1000) as id order by id desc""" - wait = WebDriverWait(self.page.driver, 10) - self.page.fill_codemirror_area_with(query) explain_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_options_dropdown) + QueryToolLocators.btn_explain_options_dropdown) explain_op.click() # disable Explain options and auto rollback only if they are enabled. - for op in (QueryToolLocatorsCss.btn_explain_buffers, - QueryToolLocatorsCss.btn_explain_timing): + for op in (QueryToolLocators.btn_explain_buffers, + QueryToolLocators.btn_explain_timing): self.page.find_by_css_selector(op).click() explain_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_analyze).click() + QueryToolLocators.btn_explain_analyze).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Data Output') - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) - ) - # Search for 'Shared Read Blocks' word in result (buffers option) - canvas.find_element_by_xpath( - "//*[contains(string(), 'Shared Read Blocks')]" + self.wait.until(EC.presence_of_element_located( + (By.XPATH, QueryToolLocators.output_cell_xpath.format(1, 1))) ) + result = self.page.find_by_xpath( + QueryToolLocators.output_cell_xpath.format(1, 1)) + + # Search for 'Shared Read Blocks' word in result (buffers option) + self.assertIn('Shared Read Blocks', result.text) + # Search for 'Actual Total Time' word in result (timing option) - canvas.find_element_by_xpath( - "//*[contains(string(), 'Actual Total Time')]" - ) + self.assertIn('Actual Total Time', result.text) def _query_tool_auto_commit_disabled(self): table_name = 'query_tool_auto_commit_disabled_table' @@ -349,32 +341,31 @@ SELECT generate_series(1, 1000) as id order by id desc""" -- 3. ROLLBACK transaction. -- 4. Check if table is *NOT* created. CREATE TABLE public.{}();""".format(table_name) - wait = WebDriverWait(self.page.driver, 10) self.page.fill_codemirror_area_with(query) # open auto commit option and disable it query_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit).click() + QueryToolLocators.btn_auto_commit).click() # close option query_op.click() # execute query self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "CREATE TABLE")]' - ) + + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('CREATE TABLE')), + "CREATE TABLE message does not displayed") # do the ROLLBACK and check if the table is present or not - self._clear_query_tool() + self.page.clear_query_tool() query = """-- 1. (Done) Disable auto commit. -- 2. (Done) Create table in public schema. -- 3. ROLLBACK transaction. @@ -382,16 +373,15 @@ CREATE TABLE public.{}();""".format(table_name) ROLLBACK;""" self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "ROLLBACK")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('ROLLBACK')), + "ROLLBACK message does not displayed") - self._clear_query_tool() + self.page.clear_query_tool() query = """-- 1. (Done) Disable auto commit. -- 2. (Done) Create table in public schema. -- 3. (Done) ROLLBACK transaction. @@ -400,16 +390,15 @@ SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Data Output') - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) el = canvas.find_elements_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(text(), '{}')]".format(table_name)) + QueryToolLocators.output_column_data_xpath.format(table_name)) assert len(el) == 0, "Table '{}' created with auto commit disabled " \ "and without any explicit commit.".format( @@ -423,7 +412,7 @@ SELECT relname FROM pg_class ROLLBACK;""" self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() @@ -437,23 +426,21 @@ END;""" self.page.fill_codemirror_area_with(query) - wait = WebDriverWait(self.page.driver, 10) - query_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit).click() + QueryToolLocators.btn_auto_commit).click() query_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self._clear_query_tool() + self.page.clear_query_tool() table_name = 'query_tool_auto_commit_enabled_table' query = """-- 1. (Done) END any open transaction. @@ -463,37 +450,29 @@ END;""" -- 5. Check if table is created event after ROLLBACK. CREATE TABLE public.{}();""".format(table_name) - self.page.fill_codemirror_area_with(query) - - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "CREATE TABLE")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('CREATE TABLE')), + "CREATE TABLE message does not displayed") - self._clear_query_tool() + self.page.clear_query_tool() query = """-- 1. (Done) END any open transaction if any. -- 2. (Done) Enable auto commit. -- 3. (Done) Create table in public schema. -- 4. ROLLBACK transaction -- 5. Check if table is created event after ROLLBACK. ROLLBACK;""" - self.page.fill_codemirror_area_with(query) - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "ROLLBACK")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('ROLLBACK')), + "ROLLBACK message does not displayed") - self._clear_query_tool() + self.page.clear_query_tool() query = """-- 1. (Done) END any open transaction if any. -- 2. (Done) Enable auto commit. -- 3. (Done) Create table in public schema. @@ -503,17 +482,16 @@ SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" self.page.fill_codemirror_area_with(query) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.click_tab('Data Output') self.page.wait_for_query_tool_loading_indicator_to_disappear() - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) el = canvas.find_elements_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(text(), '{}')]".format(table_name)) + QueryToolLocators.output_column_data_xpath.format(table_name)) assert len(el) != 0, "Table '{}' is not created with auto " \ "commit enabled.".format(table_name) @@ -527,12 +505,11 @@ SELECT relname FROM pg_class -- 5. END transaction. -- 6. Check if table is *NOT* created after ending transaction. END;""" - wait = WebDriverWait(self.page.driver, 10) self.page.fill_codemirror_area_with(query) query_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_op.click() # uncheckt auto commit and check auto-rollback @@ -542,10 +519,10 @@ END;""" query_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self._clear_query_tool() + self.page.clear_query_tool() query = """-- 1. (Done) END any open transaction. -- 2. Enable auto rollback and disable auto commit. @@ -554,21 +531,14 @@ END;""" -- 5. END transaction. -- 6. Check if table is *NOT* created after ending transaction. CREATE TABLE public.{}();""".format(table_name) - wait = WebDriverWait(self.page.driver, 10) - - self.page.fill_codemirror_area_with(query) - - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "CREATE TABLE")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('CREATE TABLE')), + "CREATE TABLE message does not displayed") + self.page.clear_query_tool() - self._clear_query_tool() query = """-- 1. (Done) END any open transaction. -- 2. (Done) Enable auto rollback and disable auto commit. -- 3. (Done) Create table in public schema. @@ -576,18 +546,14 @@ CREATE TABLE public.{}();""".format(table_name) -- 5. END transaction. -- 6. Check if table is *NOT* created after ending transaction. SELECT 1/0;""" - self.page.fill_codemirror_area_with(query) - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "division by zero")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('division by zero')), + "division by zero message does not displayed") + self.page.clear_query_tool() - self._clear_query_tool() query = """-- 1. (Done) END any open transaction. -- 2. (Done) Enable auto rollback and disable auto commit. -- 3. (Done) Create table in public schema. @@ -595,19 +561,15 @@ SELECT 1/0;""" -- 5. END transaction. -- 6. Check if table is *NOT* created after ending transaction. END;""" - - self.page.fill_codemirror_area_with(query) - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( - '//div[contains(@class, "sql-editor-message") and ' - 'contains(string(), "Query returned successfully")]' - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message. + format('Query returned successfully')), + "Query returned successfully message does not displayed") + self.page.clear_query_tool() - self._clear_query_tool() query = """-- 1. (Done) END any open transaction. -- 2. (Done) Enable auto rollback and disable auto commit. -- 3. (Done) Create table in public schema. @@ -616,18 +578,14 @@ END;""" -- 6. Check if table is *NOT* created after ending transaction. SELECT relname FROM pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" - self.page.fill_codemirror_area_with(query) - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - + self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Data Output') - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas"))) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) el = canvas.find_elements_by_xpath( - "//div[contains(@class, 'slick-cell') and " - "contains(text(), '{}')]".format(table_name)) + QueryToolLocators.output_column_data_xpath.format(table_name)) assert len(el) == 0, "Table '{}' created even after ROLLBACK due to " \ "sql error.".format(table_name) @@ -648,7 +606,7 @@ SELECT 1, pg_sleep(300)""" commit_button.click() query_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_query_dropdown) + QueryToolLocators.btn_query_dropdown) query_op.click() # enable auto-commit and disable auto-rollback @@ -658,18 +616,18 @@ SELECT 1, pg_sleep(300)""" query_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() + QueryToolLocators.btn_execute_query_css).click() self.page.find_by_xpath("//*[@id='fetching_data']") self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_cancel_query).click() + QueryToolLocators.btn_cancel_query).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Messages') - self.page.find_by_xpath( + self.assertTrue(self.page.check_if_element_exist_by_xpath( '//div[contains(@class, "sql-editor-message") and ' '(contains(string(), "canceling statement due to user request") ' 'or contains(string(), "Execution Cancelled!"))]' - ) + )) def _supported_server_version(self): connection = test_utils.get_db_connection( @@ -683,48 +641,33 @@ SELECT 1, pg_sleep(300)""" return connection.server_version > 90100 def _query_tool_notify_statements(self): - wait = WebDriverWait(self.page.driver, 60) - print("\n\tListen on an event... ", file=sys.stderr, end="") - self.page.fill_codemirror_area_with("LISTEN foo;") - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - - self.page.wait_for_query_tool_loading_indicator_to_disappear() + self.page.execute_query("LISTEN foo;") self.page.click_tab('Messages') - wait.until(EC.text_to_be_present_in_element( - (By.CSS_SELECTOR, ".sql-editor-message"), "LISTEN") - ) + self.assertTrue(self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message.format('LISTEN')), + "LISTEN message does not displayed") print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("\tNotify event without data... ", file=sys.stderr, end="") - self.page.fill_codemirror_area_with("NOTIFY foo;") - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - - self.page.wait_for_query_tool_loading_indicator_to_disappear() + self.page.execute_query("NOTIFY foo;") self.page.click_tab('Notifications') - wait.until(EC.text_to_be_present_in_element( + self.wait.until(EC.text_to_be_present_in_element( (By.CSS_SELECTOR, "td.channel"), "foo") ) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() print("\tNotify event with data... ", file=sys.stderr, end="") if self._supported_server_version(): - self.page.fill_codemirror_area_with("SELECT pg_notify('foo', " - "'Hello')") - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - - self.page.wait_for_query_tool_loading_indicator_to_disappear() + self.page.execute_query("SELECT pg_notify('foo', 'Hello')") self.page.click_tab('Notifications') - wait.until(WaitForAnyElementWithText( + self.wait.until(WaitForAnyElementWithText( (By.CSS_SELECTOR, 'td.payload'), "Hello")) print("OK.", file=sys.stderr) - self._clear_query_tool() + self.page.clear_query_tool() else: print("Skipped.", file=sys.stderr) @@ -756,69 +699,63 @@ SELECT 1, pg_sleep(300)""" is_edb = 'EnterpriseDB' in version_string[0] connection.close() - return connection.server_version >= 110000 and jit_enabled def _query_tool_explain_check_jit_stats(self): - wait = WebDriverWait(self.page.driver, 10) - self.page.fill_codemirror_area_with("SET jit_above_cost=10;") - self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_execute_query).click() - self.page.wait_for_query_tool_loading_indicator_to_disappear() - self._clear_query_tool() + self.page.execute_query("SET jit_above_cost=10;") + self.page.clear_query_tool() self.page.fill_codemirror_area_with("SELECT count(*) FROM pg_class;") - explain_op = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_options_dropdown) + QueryToolLocators.btn_explain_options_dropdown) explain_op.click() # disable Explain options and only enable COST option - for op in (QueryToolLocatorsCss.btn_explain_verbose, - QueryToolLocatorsCss.btn_explain_costs, - QueryToolLocatorsCss.btn_explain_buffers, - QueryToolLocatorsCss.btn_explain_timing): + for op in (QueryToolLocators.btn_explain_verbose, + QueryToolLocators.btn_explain_costs, + QueryToolLocators.btn_explain_buffers, + QueryToolLocators.btn_explain_timing): btn = self.page.find_by_css_selector(op) check = btn.find_element_by_tag_name('i') if 'visibility-hidden' not in check.get_attribute('class'): btn.click() # click cost button cost_btn = self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_costs) + QueryToolLocators.btn_explain_costs) cost_btn.click() # close explain options explain_op.click() self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_explain_analyze).click() + QueryToolLocators.btn_explain_analyze).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Data Output') - canvas = wait.until(EC.presence_of_element_located( - (By.CSS_SELECTOR, "#datagrid .slick-viewport .grid-canvas")) + canvas = self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) ) # Search for 'Output' word in result (verbose option) canvas.find_element_by_xpath("//*[contains(string(), 'JIT')]") - self._clear_query_tool() + self.page.clear_query_tool() def check_execute_option(self, option): """"This function will check auto commit or auto roll back based on user input. If button is already checked, no action will be taken""" if option == 'auto_commit': check_status = self.driver.find_element_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit_check_status) + QueryToolLocators.btn_auto_commit_check_status) if 'visibility-hidden' in check_status.get_attribute('class'): - self.page.find_by_css_selector(QueryToolLocatorsCss. + self.page.find_by_css_selector(QueryToolLocators. btn_auto_commit).click() if option == 'auto_rollback': check_status = self.driver.find_element_by_css_selector( - QueryToolLocatorsCss.btn_auto_rollback_check_status) + QueryToolLocators.btn_auto_rollback_check_status) if 'visibility-hidden' in check_status.get_attribute('class'): - self.page.find_by_css_selector(QueryToolLocatorsCss. + self.page.find_by_css_selector(QueryToolLocators. btn_auto_rollback).click() def uncheck_execute_option(self, option): @@ -826,15 +763,15 @@ SELECT 1, pg_sleep(300)""" user input. If button is already unchecked, no action will be taken""" if option == 'auto_commit': check_status = self.driver.find_element_by_css_selector( - QueryToolLocatorsCss.btn_auto_commit_check_status) + QueryToolLocators.btn_auto_commit_check_status) if 'visibility-hidden' not in check_status.get_attribute('class'): - self.page.find_by_css_selector(QueryToolLocatorsCss. + self.page.find_by_css_selector(QueryToolLocators. btn_auto_commit).click() if option == 'auto_rollback': check_status = self.driver.find_element_by_css_selector( - QueryToolLocatorsCss.btn_auto_rollback_check_status) + QueryToolLocators.btn_auto_rollback_check_status) if 'visibility-hidden' not in check_status.get_attribute('class'): - self.page.find_by_css_selector(QueryToolLocatorsCss. + self.page.find_by_css_selector(QueryToolLocators. btn_auto_rollback).click() diff --git a/web/pgadmin/feature_tests/table_ddl_feature_test.py b/web/pgadmin/feature_tests/table_ddl_feature_test.py index 2537bcfe2..bd9d45fa1 100644 --- a/web/pgadmin/feature_tests/table_ddl_feature_test.py +++ b/web/pgadmin/feature_tests/table_ddl_feature_test.py @@ -40,9 +40,13 @@ class TableDdlFeatureTest(BaseFeatureTest): self.page.select_tree_item(self.test_table_name) self.page.click_tab("SQL") - self.page.find_by_xpath( + # Wait till data is displayed in SQL Tab + self.assertTrue(self.page.check_if_element_exist_by_xpath( "//*[contains(@class,'CodeMirror-lines') and " - "contains(.,'CREATE TABLE public.%s')]" % self.test_table_name) + "contains(.,'CREATE TABLE public.%s')]" % self.test_table_name, + 10), "No data displayed in SQL tab") def after(self): self.page.remove_server(self.server) + test_utils.delete_table( + self.server, self.test_db, self.test_table_name) diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py index 6152e85ee..a9e9f48ec 100644 --- a/web/pgadmin/feature_tests/view_data_dml_queries.py +++ b/web/pgadmin/feature_tests/view_data_dml_queries.py @@ -18,7 +18,8 @@ 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 WebDriverWait -from .locators import QueryToolLocatorsCss +from regression.feature_utils.locators import QueryToolLocators, \ + NavMenuLocators CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) @@ -26,8 +27,6 @@ config_data = config_data_json = {} # try: with open(CURRENT_PATH + '/test_data.json') as data_file: config_data_json = json.load(data_file) -# except Exception as e: -# print(str(e)) class CheckForViewDataTest(BaseFeatureTest): @@ -121,7 +120,13 @@ CREATE TABLE public.nonintpkey def runTest(self): self.page.wait_for_spinner_to_disappear() self.page.add_server(self.server) - self._tables_node_expandable() + + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item(self.test_db) + self.page.toggle_open_tree_item('Schemas') + self.page.toggle_open_tree_item('public') + self.page.toggle_open_tree_item('Tables') self._load_config_data('table_insert_update_cases') # iterate on both tables @@ -134,6 +139,10 @@ CREATE TABLE public.nonintpkey def after(self): self.page.remove_server(self.server) + for cnt in (1, 2): + test_utils.delete_table( + self.server, self.test_db, 'defaults_{0}'.format(str(cnt))) + test_utils.delete_table(self.server, self.test_db, 'nonintpkey') @staticmethod def _get_cell_xpath(cell, row): @@ -227,14 +236,14 @@ CREATE TABLE public.nonintpkey send_keys(Keys.ENTER).perform() elif cell_type in ['text', 'json', 'text[]', 'boolean[]']: text_area_ele = self.page.find_by_css_selector( - ".pg-text-editor > textarea") + QueryToolLocators.row_editor_text_area_css) text_area_ele.clear() text_area_ele.click() text_area_ele.send_keys(value) # Click on editor's Save button self.page.find_by_css_selector( - '.btn.btn-primary.long_text_editor').click() + QueryToolLocators.text_editor_ok_btn_css).click() else: # Boolean editor test for to True click if data[1] == 'true': @@ -250,24 +259,19 @@ CREATE TABLE public.nonintpkey # Sets false ActionChains(self.driver).click(checkbox_el).perform() - def _tables_node_expandable(self): - self.page.toggle_open_tree_item(self.server['name']) - self.page.toggle_open_tree_item('Databases') - self.page.toggle_open_tree_item(self.test_db) - self.page.toggle_open_tree_item('Schemas') - self.page.toggle_open_tree_item('public') - self.page.toggle_open_tree_item('Tables') - def _view_data_grid(self, table_name): self.page.driver.find_element_by_link_text("Object").click() ActionChains( self.page.driver ).move_to_element( - self.page.driver.find_element_by_link_text("View/Edit Data") + self.page.driver.find_element_by_link_text( + NavMenuLocators.view_data_link_text) ).perform() self.page.find_by_partial_link_text("All Rows").click() - time.sleep(1) + # wait until datagrid frame is loaded. + self.page.wait_for_query_tool_loading_indicator_to_appear() + self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab(table_name) @@ -284,8 +288,10 @@ CREATE TABLE public.nonintpkey row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath("r0", 1) self.page.find_by_xpath(row0_cell0_xpath).click() - self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.page.find_by_xpath("//*[@id='btn-paste-row']").click() + self.page.find_by_css_selector( + QueryToolLocators.copy_button_css).click() + self.page.find_by_css_selector( + QueryToolLocators.paste_button_css).click() # Update primary key of copied cell self._add_update_save_row(config_data['copy'], row=2) @@ -305,7 +311,7 @@ CREATE TABLE public.nonintpkey time.sleep(0.2) self._update_cell(cell_xpath, data[str(idx)]) self.page.find_by_css_selector( - QueryToolLocatorsCss.btn_save_data).click() + QueryToolLocators.btn_save_data).click() # There should be some delay after save button is clicked, as it # takes some time to complete save ajax call otherwise discard unsaved # changes dialog will appear if we try to execute query before previous @@ -320,11 +326,12 @@ CREATE TABLE public.nonintpkey def _verify_messsages(self, text): messages_ele = self.page.find_by_css_selector( - QueryToolLocatorsCss.query_messages_panel) + QueryToolLocators.query_messages_panel) self.assertEquals(text, messages_ele.text) def _verify_row_data(self, is_new_row, config_check_data): - self.page.find_by_id("btn-flash").click() + self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css).click() # First row if row height = 0, second row if its 25 row_height = 0 if is_new_row else 25 diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py index 049f4f5f2..9b2f31d39 100644 --- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py +++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py @@ -8,10 +8,13 @@ ########################################################################## from __future__ import print_function +import sys +import random from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest from selenium.webdriver import ActionChains -import sys +from selenium.common.exceptions import StaleElementReferenceException +from regression.feature_utils.locators import QueryToolLocators class CheckForXssFeatureTest(BaseFeatureTest): @@ -32,7 +35,8 @@ class CheckForXssFeatureTest(BaseFeatureTest): scenarios = [ ("Test XSS check for panels and query tool", dict()) ] - test_table_name = "

X" + test_table_name = "

X" + str(random.randint(1000, 3000)) + # test_table_name = "

X" test_type_name = '""' def before(self): @@ -85,6 +89,8 @@ class CheckForXssFeatureTest(BaseFeatureTest): def after(self): self.page.remove_server(self.server) + test_utils.delete_table( + self.server, self.test_db, self.test_table_name) def _tables_node_expandable(self): self.page.toggle_open_server(self.server['name']) @@ -198,7 +204,8 @@ class CheckForXssFeatureTest(BaseFeatureTest): self.page.fill_codemirror_area_with( "select '" ) - self.page.find_by_id("btn-flash").click() + self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css).click() self.page.click_tab('Query History') @@ -227,13 +234,17 @@ class CheckForXssFeatureTest(BaseFeatureTest): '<script>alert(1)</script>', "Query tool (History Details-Message)" ) - - # Check for history details error message - history_ele = self.page.find_by_css_selector( - ".query-detail .history-error-text" - ) - - source_code = history_ele.get_attribute('innerHTML') + retry = 2 + while retry > 0: + try: + # Check for history details error message + history_ele = self.page.find_by_css_selector( + ".query-detail .history-error-text" + ) + source_code = history_ele.get_attribute('innerHTML') + break + except StaleElementReferenceException: + retry -= 1 self._check_escaped_characters( source_code, @@ -272,7 +283,8 @@ class CheckForXssFeatureTest(BaseFeatureTest): 'select * from "{0}"'.format(self.test_table_name) ) - self.page.find_by_id("btn-explain").click() + self.page.find_by_css_selector( + QueryToolLocators.btn_explain).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.click_tab('Explain') diff --git a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py index 5f27d97c6..be275913f 100644 --- a/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py +++ b/web/pgadmin/feature_tests/xss_checks_pgadmin_debugger_test.py @@ -88,7 +88,18 @@ class CheckDebuggerForXssFeatureTest(BaseFeatureTest): # If debugger plugin is not found if is_error and is_error.text == "Debugger Error": - self.page.click_modal('OK') + click = True + while click: + try: + self.page.click_modal('OK') + wait.until(EC.invisibility_of_element( + (By.XPATH, "//div[contains(@class, 'alertify') and " + "not(contains(@class, 'ajs-hidden'))]//div[" + "contains(@class,'ajs-header')]") + )) + click = False + except TimeoutException: + pass self.skipTest( "Please make sure that debugger plugin is properly configured" ) diff --git a/web/pgadmin/feature_tests/xss_checks_roles_control_test.py b/web/pgadmin/feature_tests/xss_checks_roles_control_test.py index 2dac1dde2..6673390a2 100644 --- a/web/pgadmin/feature_tests/xss_checks_roles_control_test.py +++ b/web/pgadmin/feature_tests/xss_checks_roles_control_test.py @@ -10,6 +10,10 @@ import random from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.feature_utils.locators import NavMenuLocators +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait class CheckRoleMembershipControlFeatureTest(BaseFeatureTest): @@ -36,6 +40,7 @@ class CheckRoleMembershipControlFeatureTest(BaseFeatureTest): self.role) test_utils.create_role(self.server, "postgres", "

test

") + self.wait = WebDriverWait(self.page.driver, 20) def runTest(self): self.page.wait_for_spinner_to_disappear() @@ -56,9 +61,11 @@ class CheckRoleMembershipControlFeatureTest(BaseFeatureTest): self.page.select_tree_item(role) def _check_role_membership_control(self): - self.page.driver.find_element_by_link_text("Object").click() - self.page.driver.find_element_by_link_text("Properties...").click() - # self.page.find_by_partial_link_text("Membership").click() + self.page.driver.find_element_by_link_text( + NavMenuLocators.object_menu_link_text).click() + property_object = self.wait.until(EC.visibility_of_element_located( + (By.CSS_SELECTOR, NavMenuLocators.properties_obj_css))) + property_object.click() self.click_membership_tab() # Fetch the source code for our custom control source_code = self.page.find_by_xpath( diff --git a/web/regression/feature_utils/locators.py b/web/regression/feature_utils/locators.py new file mode 100644 index 000000000..b2bcb6224 --- /dev/null +++ b/web/regression/feature_utils/locators.py @@ -0,0 +1,221 @@ +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +class BrowserToolBarLocators(): + """This will contains element locators for browser tool bar""" + + open_query_tool_button_css = \ + ".wcFrameButton[title='Query Tool']:not(.disabled)" + + query_tool_panel_css = ".wcPanelTab .wcTabIcon.fa.fa-bolt" + + view_table_data_button_css = \ + ".wcFrameButton[title='View Data']:not(.disabled)" + + view_data_panel_css = ".wcPanelTab .wcTabIcon.fa.fa-table" + + filter_data_button_css = \ + ".wcFrameButton[title='Filtered Rows']:not(.disabled)" + + filter_alertify_box_css = ".alertify .ajs-header[data-title~='Filter']" + + +class NavMenuLocators: + "This will contains element locators of navigation menu bar" + + file_menu_css = "#mnu_file" + + preference_menu_item_css = "#mnu_preferences" + + tools_menu_link_text = "Tools" + + view_data_link_text = "View/Edit Data" + + object_menu_link_text = "Object" + + properties_obj_css = "#show_obj_properties.dropdown-item:not(.disabled)" + + backup_obj_css = "#backup_object.dropdown-item:not(.disabled)" + + restore_obj_css = "#restore_object.dropdown-item:not(.disabled)" + + maintenance_obj_css = "#maintenance.dropdown-item:not(.disabled)" + + show_system_objects_pref_label_xpath = \ + "//label[contains(text(), 'Show system objects?')]" + + maximize_pref_dialogue_css = ".ajs-dialog.pg-el-container .ajs-maximize" + + specified_pref_node_exp_status = \ + "//div[div[span[span[(@class='aciTreeText')and " \ + "(text()='{0} ' or text()='{0}')]]]]" + + specified_preference_tree_node = \ + "//div//span[(@class='aciTreeText')and " \ + "(text()='{0} ' or text()='{0}')]" + + specified_sub_node_of_pref_tree_node = \ + "//span[text()='{0}']//following::span[text()='{1}']" + + insert_bracket_pair_switch_btn = \ + "//div[label[normalize-space(text())='Insert bracket pairs?']]" \ + "//div[contains(@class,'toggle btn')]" + + backup_filename_txt_box_name = "file" + + restore_file_name_txt_box_name = "file" + + backup_btn_xpath = \ + "//button[contains(@class,'fa-save')and contains(.,'Backup')]" + + bcg_process_status_alertifier_css = \ + ".ajs-message.ajs-bg-bgprocess.ajs-visible" + + status_alertifier_more_btn_css = ".pg-bg-more-details" + + process_watcher_alertfier = \ + "//div[contains(@class,'wcFrameTitleBar')]" \ + "//div[contains(text(),'Process Watcher')]" + + process_watcher_detailed_command_canvas_css = \ + ".bg-process-details .bg-detailed-desc" + + process_watcher_close_button_xpath = \ + "//div[contains(@class,'wcFloatingFocus')]//" \ + "div[contains(@class,'fa-close')]" + + restore_file_name_xpath = "//div[contains(text(),'Restore')]" \ + "//following::input[@name='file']" + + restore_button_xpath = \ + "//button[contains(@class,'fa-upload') and contains(.,'Restore')]" + + maintenance_operation = "//label[text()='Maintenance operation']" + + select_tab_xpath = \ + "//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') " \ + "and contains(.,'{}')]" + + +class QueryToolLocators: + btn_save_file = "#btn-save-file" + + btn_save_data = "#btn-save-data" + + btn_query_dropdown = "#btn-query-dropdown" + + btn_auto_rollback = "#btn-auto-rollback" + + btn_auto_rollback_check_status = "#btn-auto-rollback > i" + + btn_auto_commit = "#btn-auto-commit" + + btn_auto_commit_check_status = "#btn-auto-commit > i" + + btn_cancel_query = "#btn-cancel-query" + + btn_explain = "#btn-explain" + + btn_explain_analyze = "#btn-explain-analyze" + + btn_explain_options_dropdown = "#btn-explain-options-dropdown" + + btn_explain_verbose = "#btn-explain-verbose" + + btn_explain_costs = "#btn-explain-costs" + + btn_explain_buffers = "#btn-explain-buffers" + + btn_explain_timing = "#btn-explain-timing" + + btn_clear_dropdown = "#btn-clear-dropdown" + + btn_clear_history = "#btn-clear-history" + + btn_clear = "#btn-clear" + + query_editor_panel = "#output-panel" + + query_history_selected = "#query_list .selected" + + query_history_entries = "#query_list>.query-group>ul>li" + + query_history_specific_entry = \ + "#query_list>.query-group>ul>li:nth-child({})" + + query_history_detail = "#query_detail" + + invalid_query_history_entry_css = "#query_list .entry.error .query" + + editor_panel = "#output-panel" + + query_messages_panel = ".sql-editor-message" + + output_row_xpath = "//div[contains(@class, 'slick-row')][{}]/*[1]" + + output_column_header_css = "[data-column-id='{}']" + + output_column_data_xpath = "//div[contains(@class, 'slick-cell')]" \ + "[contains(., '{}')]" + output_cell_xpath = "//div[contains(@class, 'slick-cell') and " \ + "contains(@class, 'l{0} r{1}')]" + + select_all_column = \ + "//div[contains(@id,'row-header-column')]" + + new_row_xpath = "//div[contains(@class, 'new-row')]" + + copy_button_css = "#btn-copy-row" + + paste_button_css = "#btn-paste-row" + + row_editor_text_area_css = ".pg-text-editor > textarea" + + text_editor_ok_btn_css = ".btn.btn-primary.long_text_editor" + + btn_load_file_css = "#btn-load-file" + + btn_execute_query_css = "#btn-flash" + + input_file_path_css = "input#file-input-path" + + select_file_content_css = "table#contents" + + query_output_canvas_css = "#datagrid .slick-viewport .grid-canvas" + + query_output_cells = ".slick-cell" + + sql_editor_message = "//div[contains(@class, 'sql-editor-message') and " \ + "contains(string(), '{}')]" + + code_mirror_hint_box_xpath = "//ul[@class='CodeMirror-hints default']" + + code_mirror_hint_item_xpath = \ + "//ul[contains(@class, 'CodeMirror-hints') and contains(., '{}')]" + + code_mirror_data_xpath = "//pre[@class=' CodeMirror-line ']/span" + + save_data_icon = "icon-save-data-changes" + + commit_icon = "icon-commit" + + execute_icon = "fa-bolt" + + explain_icon = "fa-hand-pointer-o" + + explain_analyze_icon = "fa-list-alt" + + query_history_selected_icon = '#query_list .selected #query_source_icon' + + btn_commit = "#btn-commit" + + show_query_internally_btn = \ + "//div[label[normalize-space(" \ + "text())='Show queries generated internally by pgAdmin?']]" \ + "//div[contains(@class,'toggle btn')]" diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index 33cc8fc4c..b5b8fb5d7 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -8,6 +8,7 @@ ########################################################################## import time +import sys from selenium import webdriver from selenium.common.exceptions import NoSuchElementException, \ @@ -18,6 +19,9 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.common.by import By +from regression.feature_utils.locators import QueryToolLocators, \ + NavMenuLocators +from regression.feature_utils.tree_area_locators import TreeAreaLocators class PgadminPage: @@ -84,8 +88,10 @@ class PgadminPage: self.find_by_css_selector("button[type='save'].btn.btn-primary").\ click() - self.find_by_xpath( - "//*[@id='tree']//*[.='" + server_config['name'] + "']") + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located( + (By.XPATH, + "//*[@id='tree']//*[.='" + server_config['name'] + "']"))) def open_query_tool(self): self.driver.find_element_by_link_text("Tools").click() @@ -151,6 +157,24 @@ class PgadminPage: 'contains(.,"Don\'t save")]')) self.driver.switch_to.default_content() + def clear_query_tool(self): + self.click_element( + self.find_by_css_selector(QueryToolLocators.btn_clear_dropdown) + ) + ActionChains(self.driver).move_to_element( + self.find_by_css_selector(QueryToolLocators.btn_clear)).perform() + self.click_element( + self.find_by_css_selector(QueryToolLocators.btn_clear) + ) + self.click_modal('Yes') + + def execute_query(self, query): + self.fill_codemirror_area_with(query) + execute_button = self.find_by_css_selector( + QueryToolLocators.btn_execute_query_css) + execute_button.click() + self.wait_for_query_tool_loading_indicator_to_disappear() + def close_data_grid(self): self.driver.switch_to_default_content() xpath = "//*[@id='dockerContainer']/div/div[3]/div/div[2]/div[1]" @@ -188,14 +212,40 @@ class PgadminPage: if attempts == 0: raise Exception(e) + def get_expansion_status_of_node(self, xpath_node): + """get the expansion status for a node through xpath""" + node_is_expanded = False + element = self.find_by_xpath(xpath_node) + if element.get_attribute("aria-expanded") == 'true': + node_is_expanded = True + return node_is_expanded + def toggle_open_servers_group(self): """This will open Servers group to display underlying nodes""" + is_expanded = False self.wait_for_spinner_to_disappear() - server_group = self.find_by_xpath( - "//div[@id='tree']//span[@class='aciTreeItem']" - "/span[(@class='aciTreeText') and starts-with(text(),'Servers ') " - "or starts-with(text(), 'Servers')]") - ActionChains(self.driver).double_click(server_group).perform() + if self.check_if_element_exist_by_xpath( + TreeAreaLocators.server_group_node): + if self.get_expansion_status_of_node( + TreeAreaLocators.server_group_node_exp_status): + is_expanded = True + else: + webdriver.ActionChains(self.driver).double_click( + self.find_by_xpath( + TreeAreaLocators. + server_group_node)).perform() + if self.check_if_element_exist_by_xpath( + TreeAreaLocators.server_group_sub_nodes): + is_expanded = True + else: + print( + "(toggle_open_servers_group)The Server Group " + "node is clicked to expand but it is not expanded", + file=sys.stderr) + else: + print("The Server Group node is not visible", + file=sys.stderr) + return is_expanded def toggle_open_tree_item(self, tree_item_text): # 'sleep' here helps in cases where underlying nodes are auto opened. @@ -203,8 +253,7 @@ class PgadminPage: # even if the underlying node to be clicked was Opened. time.sleep(.6) item_with_text = self.find_by_xpath( - "//div[@id='tree']//span[@class='aciTreeItem']/span[" - "(@class='aciTreeText') and text()='" + tree_item_text + "']") + TreeAreaLocators.specified_tree_node.format(tree_item_text)) self.driver.execute_script("arguments[0].scrollIntoView()", item_with_text) @@ -215,6 +264,17 @@ class PgadminPage: item = item_with_text.find_element_by_xpath( ".//parent::*[@class='aciTreeItem']") ActionChains(self.driver).double_click(item).perform() + retry = 3 + while retry > 0: + try: + WebDriverWait(self.driver, 5).until((lambda item_with_text: ( + item_with_text.find_element_by_xpath( + ".//ancestor::*[@class='aciTreeLine']"). + get_attribute("aria-expanded") == 'true'))) + break + except TimeoutException: + retry -= 1 + pass def toggle_open_tables_node(self): """The function will be used for opening Tables node only""" @@ -454,15 +514,14 @@ class PgadminPage: def click_tab(self, tab_name): WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable( - (By.XPATH, "//*[contains(@class,'wcTabTop')]//" - "*[contains(@class,'wcPanelTab') " - "and contains(.,'" + tab_name + "')]"))) - - tab = self.find_by_xpath("//*[contains(@class,'wcTabTop')]//" - "*[contains(@class,'wcPanelTab') " - "and contains(.,'" + tab_name + "')]") - - self.click_element(tab) + (By.XPATH, NavMenuLocators.select_tab_xpath.format(tab_name)))) + click_tab = True + while click_tab: + tab = self.find_by_xpath( + NavMenuLocators.select_tab_xpath.format(tab_name)) + self.click_element(tab) + if 'wcPanelTabActive' in tab.get_attribute('class'): + break def wait_for_input_by_element(self, element, content): def input_field_has_content(driver): @@ -539,7 +598,7 @@ class PgadminPage: except NoSuchElementException: return True - self._wait_for("spinner to disappear", spinner_has_disappeared) + self._wait_for("spinner to disappear", spinner_has_disappeared, 20) def wait_for_query_tool_loading_indicator_to_disappear(self): def spinner_has_disappeared(driver): @@ -553,7 +612,12 @@ class PgadminPage: time.sleep(0.5) return True - self._wait_for("spinner to disappear", spinner_has_disappeared) + self._wait_for("spinner to disappear", spinner_has_disappeared, 20) + + def wait_for_query_tool_loading_indicator_to_appear(self): + self.check_if_element_exist_by_xpath( + "//div[@id='editor-panel']//" + "div[@class='pg-sp-container sql-editor-busy-fetching']") def wait_for_app(self): def page_shows_app(driver): @@ -574,10 +638,98 @@ class PgadminPage: return element_selector(self.driver) def _wait_for(self, waiting_for_message, condition_met_function, - timeout=3): + timeout=5): if timeout is None: timeout = self.timeout return WebDriverWait(self.driver, timeout, 0.01).until( condition_met_function, "Timed out waiting for " + waiting_for_message ) + + def wait_for_elements(self, find_method_with_args): + """Using xpath, it will wait for elements""" + def element_if_it_exists(driver): + try: + element = find_method_with_args(driver) + if len(element) > 0 and element[0].is_displayed() and element[ + 0].is_enabled(): + return element + except NoSuchElementException: + return False + + return self._wait_for("element to exist", element_if_it_exists) + + def find_by_xpath_list(self, xpath): + """This will find out list of elements through a single xpath""" + return self.wait_for_elements( + lambda driver: driver.find_elements_by_xpath(xpath)) + + def get_index_of_element(self, element_list, target_string): + """it will return index of an element from provided element list""" + index_of_required_server = -1 + if len(element_list) > 0: + for index, element in enumerate(element_list): + if element.text.startswith(target_string) and ( + target_string in element.text): + index_of_required_server = index + break + else: + print("There seems no record in the provided element list") + return index_of_required_server + + def set_switch_box_status(self, switch_box, required_status): + """it will change switch box status to required one. Two elements + of the switch boxes are to be provided i) button which is needed to + toggle ii) Yes for True or No for False""" + status_changed_successfully = False + switch_box_element = self.find_by_xpath(switch_box) + + if required_status == 'Yes': + if 'off' in switch_box_element.get_attribute("class"): + switch_box_element.click() + time.sleep(1) + if 'success' in switch_box_element.get_attribute("class"): + status_changed_successfully = True + else: + print( + "(set_switch_box_status)Clicked the " + "element to change its status but " + "it did not changed", + file=sys.stderr) + elif 'success' in switch_box_element.get_attribute("class"): + status_changed_successfully = True + else: + if 'success' in switch_box_element.get_attribute("class"): + switch_box_element.click() + if 'off' in switch_box_element.get_attribute("class"): + status_changed_successfully = True + else: + print( + "(set_switch_box_status)Clicked the element to " + "change its status but it did not changed", + file=sys.stderr) + elif 'off' in switch_box_element.get_attribute("class"): + status_changed_successfully = True + return status_changed_successfully + + def retry_click_operation(self, element_to_click, + element_to_verify_after_click): + """This will attempt to click add button multiple time, + some different exception encountered while clicking, so handled + through this""" + + click_status = False + attempt = 0 + + while click_status is not True and attempt < 5: + try: + if element_to_verify_after_click.is_displayed(): + click_status = True + element_to_click.click() + if element_to_verify_after_click.is_displayed(): + click_status = True + except Exception: + print("The click operation is not performed for " + "attempt %s, will try 5 attempts" % attempt) + attempt = +1 + return click_status diff --git a/web/regression/feature_utils/tree_area_locators.py b/web/regression/feature_utils/tree_area_locators.py new file mode 100644 index 000000000..524058f09 --- /dev/null +++ b/web/regression/feature_utils/tree_area_locators.py @@ -0,0 +1,31 @@ +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2019, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + + +class TreeAreaLocators(): + """This will contains element locators for tree area, will also contain + parametrized xpath where applicable""" + + server_group_node = \ + "//div[@id='tree']//span[@class='aciTreeItem']" \ + "/span[(@class='aciTreeText') and starts-with(text(),'Servers ') or " \ + "starts-with(text(), 'Servers')]" + + server_group_node_exp_status = "//div[div[span[span[" \ + "(@class='aciTreeText') and " \ + "(text()='Servers ' or " \ + "text()='Servers')]]]]" + + server_group_sub_nodes = \ + "//div[div[span[span[contains(text(),'Servers')]]]]/" \ + "following-sibling::ul/li/div/div/div/span[2]/" \ + "span[@class='aciTreeText']" + + specified_tree_node = \ + "//div[@id='tree']//span[@class='aciTreeItem']/" \ + "span[(@class='aciTreeText') and text()='{}']" diff --git a/web/regression/python_test_utils/test_gui_helper.py b/web/regression/python_test_utils/test_gui_helper.py index 5174492e2..c31787cc7 100644 --- a/web/regression/python_test_utils/test_gui_helper.py +++ b/web/regression/python_test_utils/test_gui_helper.py @@ -12,42 +12,32 @@ def close_bgprocess_popup(tester): """ Allows us to close the background process popup window """ - screen_shot_taken = False # In cases where backup div is not closed (sometime due to some error) try: - if tester.driver.find_element_by_css_selector( - ".ajs-message.ajs-bg-bgprocess.ajs-visible"): - tester._screenshot() - screen_shot_taken = True - tester.driver.find_element_by_css_selector( - ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() + tester.page.wait_for_element_to_disappear( + lambda x: tester.driver.find_element_by_xpath( + ".ajs-message.ajs-bg-bgprocess.ajs-visible")) except Exception: - pass + tester.driver.find_element_by_css_selector( + ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() # In cases where restore div is not closed (sometime due to some error) try: - if tester.driver.find_element_by_xpath( + tester.page.wait_for_element_to_disappear( + lambda x: tester.driver.find_element_by_xpath( "//div[@class='card-header bg-primary d-flex']/div" - "[contains(text(), 'Restoring backup')]"): - tester._screenshot() - screen_shot_taken = True - tester.driver.find_element_by_css_selector( - ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() + "[contains(text(), 'Restoring backup')]")) except Exception: - pass + tester.driver.find_element_by_css_selector( + ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() # In cases where maintenance window is not closed (sometime due to some # error) try: - if tester.driver.find_element_by_xpath( + tester.page.wait_for_element_to_disappear( + lambda x: tester.driver.find_element_by_xpath( "//div[@class='card-header bg-primary d-flex']/div" - "[contains(text(), 'Maintenance')]"): - tester._screenshot() - screen_shot_taken = True - tester.driver.find_element_by_css_selector( - ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() + "[contains(text(), 'Maintenance')]")) except Exception: - pass - - if not screen_shot_taken: - tester._screenshot() + tester.driver.find_element_by_css_selector( + ".btn.btn-sm-sq.btn-primary.pg-bg-close > i").click() diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index 54be70a98..2e8a0eb8c 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -194,6 +194,35 @@ def create_table(server, db_name, table_name, extra_columns=[]): traceback.print_exc(file=sys.stderr) +def delete_table(server, db_name, table_name): + """ + This function delete the table in given database + :param server: server details + :type server: dict + :param db_name: database name + :type db_name: str + :param table_name: table name + :type table_name: str + :return: None + """ + try: + connection = get_db_connection( + db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode'] + ) + pg_cursor = connection.cursor() + pg_cursor.execute( + '''DROP TABLE IF EXISTS "%s"''' % table_name) + connection.commit() + + except Exception: + traceback.print_exc(file=sys.stderr) + + def create_table_with_query(server, db_name, query): """ This function create the table in given database name @@ -774,22 +803,28 @@ def configure_preferences(default_binary_path=None): def reset_layout_db(user_id=None): - conn = sqlite3.connect(config.TEST_SQLITE_PATH) - cur = conn.cursor() + retry = 3 + while retry > 0: + try: + conn = sqlite3.connect(config.TEST_SQLITE_PATH) + cur = conn.cursor() - if user_id is None: - cur.execute( - 'DELETE FROM SETTING WHERE SETTING in ' - '("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")' - ) - else: - cur.execute( - 'DELETE FROM SETTING WHERE SETTING in ' - '("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")' - ' AND USER_ID=?', user_id - ) - conn.commit() - conn.close() + if user_id is None: + cur.execute( + 'DELETE FROM SETTING WHERE SETTING in ' + '("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")' + ) + else: + cur.execute( + 'DELETE FROM SETTING WHERE SETTING in ' + '("Browser/Layout", "SQLEditor/Layout", "Debugger/Layout")' + ' AND USER_ID=?', user_id + ) + conn.commit() + conn.close() + break + except Exception: + retry -= 1 def remove_db_file():