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 691f35d88..0e1d19ecc 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 @@ -54,16 +54,17 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self._copies_columns() self._copies_row_using_keyboard_shortcut() self._copies_column_using_keyboard_shortcut() - self._copies_rectangular_selection() - self._shift_resizes_rectangular_selection() + # The below calls is commented since the new data grid does not + # support range selection. This can be enabled once the + # range selection is implemented. + # self._copies_rectangular_selection() + # self._shift_resizes_rectangular_selection() + self._shift_resizes_column_selection() - self._mouseup_outside_grid_still_makes_a_selection() + self._mouseup_outside_grid_does_not_make_a_selection() self._copies_rows_with_header() def paste_values_to_scratch_pad(self): - self.page.driver.switch_to.default_content() - self.page.driver.switch_to.frame( - self.page.driver.find_element(By.TAG_NAME, "iframe")) scratch_pad_ele = self.page.find_by_css_selector( QueryToolLocators.scratch_pad_css) self.page.paste_values(scratch_pad_ele) @@ -73,7 +74,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_rows(self): first_row = self.page.find_by_xpath( - QueryToolLocators.output_row_xpath.format(1)) + QueryToolLocators.output_cell_xpath.format(2, 1)) first_row.click() copy_button = self.page.find_by_css_selector( @@ -85,8 +86,10 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): clipboard_text) def _copies_rows_with_header(self): - self.page.find_by_css_selector('#btn-copy-row-dropdown').click() - self.page.find_by_css_selector('a#btn-copy-with-header').click() + self.page.find_by_css_selector(QueryToolLocators.copy_options_css)\ + .click() + self.page.find_by_css_selector(QueryToolLocators.copy_headers_btn_css)\ + .click() select_all = self.page.find_by_xpath( QueryToolLocators.select_all_column) @@ -122,7 +125,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): def _copies_row_using_keyboard_shortcut(self): first_row = self.page.find_by_xpath( - QueryToolLocators.output_row_xpath.format(1)) + QueryToolLocators.output_cell_xpath.format(2, 1)) first_row.click() ActionChains(self.page.driver).key_down( @@ -216,7 +219,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text) - def _mouseup_outside_grid_still_makes_a_selection(self): + def _mouseup_outside_grid_does_not_make_a_selection(self): bottom_right_cell = self.page.find_by_xpath( QueryToolLocators.output_column_data_xpath.format('cool info') ) @@ -233,7 +236,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): clipboard_text = self.paste_values_to_scratch_pad() - self.assertIn('"cool info"', clipboard_text) + self.assertNotIn('"cool info"', clipboard_text) def after(self): self.page.close_query_tool() diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py index a541f4171..0cb2c1c0d 100644 --- a/web/pgadmin/feature_tests/query_tool_journey_test.py +++ b/web/pgadmin/feature_tests/query_tool_journey_test.py @@ -35,7 +35,9 @@ class QueryToolJourneyTest(BaseFeatureTest): select_query = "SELECT * FROM %s" query_history_tab_name = "Query History" + query_history_tab_id = "id-history" query_editor_tab_name = "Query Editor" + query_editor_tab_id = "id-query" def before(self): self.test_table_name = "test_table" + str(random.randint(1000, 3000)) @@ -98,8 +100,10 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.driver.switch_to.frame( self.page.driver.find_element_by_tag_name("iframe")) - select_row = self.page.find_by_xpath( - QueryToolLocators.output_row_xpath.format('1')) + # row index starts with 2 + select_row = self.page.find_by_css_selector( + QueryToolLocators.output_row_col.format('2', '1')) + select_row.click() copy_row = self.page.find_by_css_selector( @@ -133,10 +137,6 @@ class QueryToolJourneyTest(BaseFeatureTest): QueryToolLocators.copy_button_css) copy_btn.click() - self.page.driver.switch_to.default_content() - self.page.driver.switch_to.frame( - self.page.driver.find_element_by_tag_name("iframe")) - scratch_pad_ele = self.page.find_by_css_selector( QueryToolLocators.scratch_pad_css) self.page.paste_values(scratch_pad_ele) @@ -149,16 +149,19 @@ class QueryToolJourneyTest(BaseFeatureTest): scratch_pad_ele.clear() def _test_history_tab(self): - self.page.clear_query_tool() + self.page.driver.switch_to.default_content() self.page.driver.switch_to.frame( self.page.driver.find_element_by_tag_name("iframe")) + self.page.clear_query_tool() editor_input = self.page.find_by_css_selector( QueryToolLocators.query_editor_panel) self.page.click_element(editor_input) self.page.execute_query(self.select_query % self.invalid_table_name) - self.page.click_tab(self.query_history_tab_name) + self.page.click_tab(self.query_history_tab_id, rc_dock=True) + self.page.wait_for_query_tool_loading_indicator_to_disappear( + container_id="id-history") selected_history_entry = self.page.find_by_css_selector( QueryToolLocators.query_history_selected) self.assertIn(self.select_query % self.invalid_table_name, @@ -168,7 +171,7 @@ class QueryToolJourneyTest(BaseFeatureTest): QueryToolLocators.query_history_detail) self.assertIn( - "Error Message relation \"%s\" does not exist" + "ERROR: relation \"%s\" does not exist" % self.invalid_table_name, failed_history_detail_pane.text ) @@ -198,10 +201,8 @@ class QueryToolJourneyTest(BaseFeatureTest): self.assertIn(self.select_query % self.invalid_table_name, invalid_history_entry.text) - self.page.click_tab(self.query_editor_tab_name) + self.page.click_tab(self.query_editor_tab_id, rc_dock=True) self.page.clear_query_tool() - self.page.driver.switch_to.frame( - self.page.driver.find_element_by_tag_name("iframe")) self.page.click_element(editor_input) # Check if 15 more query executed then the history should contain 17 @@ -212,7 +213,7 @@ class QueryToolJourneyTest(BaseFeatureTest): QueryToolLocators.btn_execute_query_css).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab(self.query_history_tab_name) + self.page.click_tab(self.query_history_tab_id, rc_dock=True) query_list = self.page.wait_for_elements( lambda driver: driver.find_elements( @@ -226,20 +227,18 @@ class QueryToolJourneyTest(BaseFeatureTest): self._test_toggle_generated_queries() def _test_history_query_sources(self): - self.page.driver.switch_to.frame( - self.page.driver.find_element_by_tag_name("iframe")) - self.page.click_tab(self.query_editor_tab_name) + self.page.click_tab(self.query_editor_tab_id, rc_dock=True) self._execute_sources_test_queries() - self.page.click_tab(self.query_history_tab_name) + self.page.click_tab(self.query_history_tab_id, rc_dock=True) history_entries_icons = [ - QueryToolLocators.commit_icon, - QueryToolLocators.save_data_icon, - QueryToolLocators.save_data_icon, - QueryToolLocators.execute_icon, - QueryToolLocators.explain_analyze_icon, - QueryToolLocators.explain_icon + 'CommitIcon', + 'SaveDataIcon', + 'SaveDataIcon', + 'ExecuteIcon', + 'ExplainAnalyzeIcon', + 'ExplainIcon', ] history_entries_queries = [ @@ -256,7 +255,7 @@ class QueryToolJourneyTest(BaseFeatureTest): history_entries_icons) def _test_toggle_generated_queries(self): - xpath = '//li[contains(@class, "pgadmin-query-history-entry")]' + xpath = "//li[@data-label='history-entry'][@data-pgadmin='true']" self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath)) self.page.set_switch_box_status( QueryToolLocators.show_query_internally_btn, 'No') @@ -266,9 +265,7 @@ class QueryToolJourneyTest(BaseFeatureTest): 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(self.query_editor_tab_name) + self.page.click_tab(self.query_editor_tab_id, rc_dock=True) # Select all data # (contains the primary key -> all columns should be editable) @@ -295,19 +292,18 @@ class QueryToolJourneyTest(BaseFeatureTest): self._check_query_results_editable(query, [True, True, False, False, False]) + # discard edits + self.page.execute_query('SELECT 1') + self.page.click_modal('Yes', True) + def _test_is_editable_columns_icons(self): if self.driver_version < 2.8: return - self.page.click_tab(self.query_editor_tab_name) + self.page.click_tab(self.query_editor_tab_id, rc_dock=True) self.page.clear_query_tool() query = "SELECT pk_column FROM %s" % self.test_editable_table_name self.page.execute_query(query) - # Discard changes made by previous test to data grid - self.page.driver.switch_to.default_content() - self.page.click_modal('Yes', True) - self.page.driver.switch_to.frame( - self.page.driver.find_element_by_tag_name("iframe")) icon_exists = self.page.check_if_element_exist_by_xpath( QueryToolLocators.editable_column_icon_xpath @@ -344,33 +340,38 @@ class QueryToolJourneyTest(BaseFeatureTest): query_options.click() self.page.uncheck_execute_option("auto_commit") - self._update_numeric_cell(2, 10) + self._update_numeric_cell(10) time.sleep(0.5) self._commit_transaction() + self.page.wait_for_spinner_to_disappear() # Turn on autocommit - retry = 3 - while retry > 0: - query_options = self.page.find_by_css_selector( - QueryToolLocators.btn_query_dropdown) - query_options.click() - expanded = query_options.get_attribute("aria-expanded") - if expanded == "false": - print("query option not yet expanded clicking commit again", - file=sys.stderr) - self._commit_transaction() - time.sleep(0.5) - query_options.click() - break - else: - retry -= 1 + # self.page.check_execute_option("auto_commit") + # query_options = self.page.find_by_css_selector( + # QueryToolLocators.btn_query_dropdown) + # query_options.click() + # retry = 3 + # while retry > 0: + # query_options = self.page.find_by_css_selector( + # QueryToolLocators.btn_query_dropdown) + # query_options.click() + # expanded = query_options.get_attribute("aria-expanded") + # if expanded == "false": + # print("query option not yet expanded clicking commit again", + # file=sys.stderr) + # self._commit_transaction() + # time.sleep(0.5) + # query_options.click() + # break + # else: + # retry -= 1 self.page.check_execute_option("auto_commit") def _check_history_queries_and_icons(self, history_queries, history_icons): # Select first query history entry self.page.find_by_css_selector( - QueryToolLocators.query_history_specific_entry.format(1)).click() + QueryToolLocators.query_history_specific_entry.format(2)).click() for icon, query in zip(history_icons, history_queries): # Check query query_history_selected_item = self.page.find_by_css_selector( @@ -382,27 +383,20 @@ class QueryToolJourneyTest(BaseFeatureTest): # Check source icon query_history_selected_icon = self.page.find_by_css_selector( 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 or 'icon-save_data_changes' in - icon_classes or 'icon-commit' in icon_classes or - 'fa-play' in icon_classes) + icon == query_history_selected_icon.get_attribute( + 'data-label')) # Move to next entry ActionChains(self.page.driver) \ .send_keys(Keys.ARROW_DOWN) \ .perform() - def _update_numeric_cell(self, cell_index, value): + def _update_numeric_cell(self, value): """ Updates a numeric cell in the first row of the resultset """ - cell_xpath = "//div[contains(@style, 'top:0px')]//" \ - "div[contains(@class,'l{0} r{1}')]". \ - format(cell_index, cell_index) - - self.page.check_if_element_exist_by_xpath(cell_xpath) - cell_el = self.page.find_by_xpath(cell_xpath) + cell_el = self.page.find_by_css_selector( + QueryToolLocators.output_row_col.format(2, 3)) ActionChains(self.driver).double_click(cell_el).perform() ActionChains(self.driver).send_keys(value). \ send_keys(Keys.ENTER).perform() @@ -410,7 +404,7 @@ class QueryToolJourneyTest(BaseFeatureTest): QueryToolLocators.btn_save_data).click() def _insert_data_into_test_editable_table(self): - self.page.click_tab(self.query_editor_tab_name) + self.page.click_tab(self.query_editor_tab_id, rc_dock=True) self.page.clear_query_tool() self.page.execute_query( "INSERT INTO %s VALUES (1, 1), (2, 2);" @@ -418,18 +412,13 @@ class QueryToolJourneyTest(BaseFeatureTest): ) def __clear_query_history(self): + self.page.click_tab(self.query_history_tab_id, rc_dock=True) + self.page.wait_for_query_tool_loading_indicator_to_disappear( + container_id="id-history") self.page.click_element( self.page.find_by_css_selector( - QueryToolLocators.btn_clear_dropdown) + QueryToolLocators.btn_history_remove_all) ) - ActionChains(self.driver) \ - .move_to_element( - self.page.find_by_css_selector( - QueryToolLocators.btn_clear_history)).perform() - self.page.click_element( - self.page.find_by_css_selector(QueryToolLocators.btn_clear_history) - ) - self.driver.switch_to.default_content() self.page.click_modal('Yes', True) def _navigate_to_query_tool(self): @@ -460,12 +449,10 @@ class QueryToolJourneyTest(BaseFeatureTest): discard_changes_modal=False): self.page.execute_query(query) if discard_changes_modal: - self.driver.switch_to.default_content() self.page.click_modal('Yes', True) - self.page.driver.switch_to.frame( - self.page.driver.find_element_by_tag_name("iframe")) - enumerated_should_be_editable = enumerate(cols_should_be_editable, 1) + # first column is rownum + enumerated_should_be_editable = enumerate(cols_should_be_editable, 2) import time time.sleep(0.5) @@ -475,9 +462,8 @@ class QueryToolJourneyTest(BaseFeatureTest): def _check_cell_editable(self, cell_index): """Checks if a cell in the first row of the resultset is editable""" - cell_el = self.page.find_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_css_selector( + QueryToolLocators.output_row_col.format(2, cell_index)) # Get existing value cell_value = int(cell_el.text) @@ -489,9 +475,9 @@ class QueryToolJourneyTest(BaseFeatureTest): # Check if the value was updated # Finding element again to avoid stale element reference exception - cell_el = self.page.find_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_css_selector(QueryToolLocators. + output_row_col.format(2, cell_index)) return int(cell_el.text) == new_value def _check_can_add_row(self): diff --git a/web/pgadmin/feature_tests/query_tool_tests.py b/web/pgadmin/feature_tests/query_tool_tests.py index 74e46878d..3915a29e7 100644 --- a/web/pgadmin/feature_tests/query_tool_tests.py +++ b/web/pgadmin/feature_tests/query_tool_tests.py @@ -30,7 +30,7 @@ class QueryToolFeatureTest(BaseFeatureTest): scenarios = [ ("Query tool feature test", dict()) ] - data_output_tab_name = 'Data Output' + data_output_tab_id = 'id-dataoutput' table_creation_fail_error = '"CREATE TABLE message does not displayed"' def before(self): @@ -45,10 +45,10 @@ class QueryToolFeatureTest(BaseFeatureTest): self.wait = WebDriverWait(self.page.driver, 10) def runTest(self): + skip_warning = "Skipped." # on demand result set on scrolling. print("\nOn demand query result... ", file=sys.stderr, end="") - skip_warning = "Skipped." self._on_demand_result() self.page.clear_query_tool() @@ -128,8 +128,7 @@ class QueryToolFeatureTest(BaseFeatureTest): 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'): + if btn.get_attribute('data-checked') == 'true': btn.click() query_op = self.page.find_by_css_selector( @@ -234,17 +233,16 @@ SELECT generate_series(1, {}) as id1, 'dummy' as id2""".format( self.page.find_by_css_selector( QueryToolLocators.output_column_header_css.format('id1')) column_1.click() - canvas_ele = self.page.find_by_css_selector('.grid-canvas') - scrolling_height = canvas_ele.size['height'] + grid = self.page.find_by_css_selector('.rdg') + scrolling_height = grid.size['height'] self.driver.execute_script( - "pgAdmin.SqlEditor.jquery('.slick-viewport')" - ".scrollTop(pgAdmin.SqlEditor.jquery('.grid-canvas')" - ".height());" + "document.querySelector('.rdg').scrollTop=" + "document.querySelector('.rdg').scrollHeight" ) # Table height takes some time to update, for which their is no # particular way time.sleep(2) - if canvas_ele.size['height'] == scrolling_height and \ + if grid.size['height'] == scrolling_height and \ self.page.check_if_element_exist_by_xpath( QueryToolLocators.output_column_data_xpath.format( row_id_to_find)): @@ -279,12 +277,16 @@ SELECT generate_series(1, 1000) as id order by id desc""" self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) canvas = self.wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) ) + self.wait.until(EC.presence_of_element_located( + (By.CSS_SELECTOR, + QueryToolLocators.query_output_cells))) + # Search for 'Output' word in result (verbose option) canvas.find_element(By.XPATH, "//*[contains(string(), 'Output')]") @@ -313,14 +315,14 @@ SELECT generate_series(1, 1000) as id order by id desc""" self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) self.wait.until(EC.presence_of_element_located( - (By.XPATH, QueryToolLocators.output_cell_xpath.format(1, 1))) + (By.XPATH, QueryToolLocators.output_cell_xpath.format(2, 2))) ) result = self.page.find_by_xpath( - QueryToolLocators.output_cell_xpath.format(1, 1)) + QueryToolLocators.output_cell_xpath.format(2, 2)) # Search for 'Shared Read Blocks' word in result (buffers option) self.assertIn('Shared Read Blocks', result.text) @@ -350,7 +352,7 @@ CREATE TABLE public.{}();""".format(table_name) self.page.click_execute_query_button() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('CREATE TABLE')), @@ -364,7 +366,7 @@ CREATE TABLE public.{}();""".format(table_name) -- 4. Check if table is *NOT* created. ROLLBACK;""" self.page.execute_query(query) - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('ROLLBACK')), "ROLLBACK message does not displayed") @@ -378,7 +380,7 @@ SELECT relname FROM pg_catalog.pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" self.page.execute_query(query) - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) canvas = self.wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) @@ -432,7 +434,7 @@ CREATE TABLE public.{}();""".format(table_name) self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('CREATE TABLE')), self.table_creation_fail_error) @@ -447,7 +449,7 @@ ROLLBACK;""" self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('ROLLBACK')), "ROLLBACK message does not displayed") @@ -462,7 +464,7 @@ SELECT relname FROM pg_catalog.pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" self.page.execute_query(query) - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) self.page.wait_for_query_tool_loading_indicator_to_disappear() canvas = self.wait.until(EC.presence_of_element_located( @@ -511,7 +513,7 @@ END;""" CREATE TABLE public.{}();""".format(table_name) self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('CREATE TABLE')), self.table_creation_fail_error) @@ -526,7 +528,7 @@ CREATE TABLE public.{}();""".format(table_name) SELECT 1/0;""" self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) 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") @@ -541,7 +543,7 @@ SELECT 1/0;""" END;""" self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message. format('Query returned successfully')), @@ -558,7 +560,7 @@ SELECT relname FROM pg_catalog.pg_class WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;""" self.page.execute_query(query) self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) canvas = self.wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) @@ -580,30 +582,20 @@ SELECT 1, pg_sleep(300)""" self.page.fill_codemirror_area_with(query) # query_button drop can be disabled so enable - commit_button = self.page.find_by_css_selector("#btn-commit") + commit_button = self.page.find_by_css_selector( + QueryToolLocators.btn_commit) if not commit_button.get_attribute('disabled'): commit_button.click() time.sleep(0.5) - query_op = self.page.find_by_css_selector( - QueryToolLocators.btn_query_dropdown) - query_op.click() - # enable auto-commit and disable auto-rollback self.page.check_execute_option('auto_commit') self.page.uncheck_execute_option('auto_rollback') - # close drop down - query_op.click() + # Execute query - retry = 5 - execute_button = self.page.find_by_css_selector( - QueryToolLocators.btn_execute_query_css) - while retry > 0: - execute_button.click() - if self.page.wait_for_query_tool_loading_indicator_to_appear(): - break - else: - retry -= 1 + self.page.find_by_css_selector( + QueryToolLocators.btn_execute_query_css).click() + # Providing a second of sleep since clicks on the execute and stop # query button is too quick that the query is not able run properly time.sleep(1) @@ -611,12 +603,15 @@ SELECT 1, pg_sleep(300)""" self.page.find_by_css_selector( QueryToolLocators.btn_cancel_query).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab('Messages') - 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!"))]' - )) + self.page.click_tab('id-messages', rc_dock=True) + self.assertTrue( + self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message + .format('canceling statement due to user request')) or + self.page.check_if_element_exist_by_xpath( + QueryToolLocators.sql_editor_message + .format('Execution Cancelled!')) + ) def _supported_server_version(self): connection = test_utils.get_db_connection( @@ -632,7 +627,7 @@ SELECT 1, pg_sleep(300)""" def _query_tool_notify_statements(self): print("\n\tListen on an event... ", file=sys.stderr, end="") self.page.execute_query("LISTEN foo;") - self.page.click_tab('Messages') + self.page.click_tab('id-messages', rc_dock=True) self.assertTrue(self.page.check_if_element_exist_by_xpath( QueryToolLocators.sql_editor_message.format('LISTEN')), @@ -642,9 +637,9 @@ SELECT 1, pg_sleep(300)""" print("\tNotify event without data... ", file=sys.stderr, end="") self.page.execute_query("NOTIFY foo;") - self.page.click_tab('Notifications') + self.page.click_tab('id-notifications', rc_dock=True) self.wait.until(EC.text_to_be_present_in_element( - (By.CSS_SELECTOR, "td.channel"), "foo") + (By.CSS_SELECTOR, "td[data-label='channel']"), "foo") ) print("OK.", file=sys.stderr) @@ -652,9 +647,9 @@ SELECT 1, pg_sleep(300)""" if self._supported_server_version(): self.page.clear_query_tool() self.page.execute_query("SELECT pg_notify('foo', 'Hello')") - self.page.click_tab('Notifications') + self.page.click_tab('id-notifications', rc_dock=True) self.wait.until(WaitForAnyElementWithText( - (By.CSS_SELECTOR, 'td.payload'), "Hello")) + (By.CSS_SELECTOR, "td[data-label='payload']"), "Hello")) print("OK.", file=sys.stderr) else: print("Skipped.", file=sys.stderr) @@ -700,8 +695,7 @@ SELECT 1, pg_sleep(300)""" 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'): + if btn.get_attribute('data-checked') == 'true': btn.click() # click cost button cost_btn = self.page.find_by_css_selector( @@ -715,7 +709,7 @@ SELECT 1, pg_sleep(300)""" QueryToolLocators.btn_explain_analyze).click() self.page.wait_for_query_tool_loading_indicator_to_disappear() - self.page.click_tab(self.data_output_tab_name) + self.page.click_tab(self.data_output_tab_id, rc_dock=True) canvas = self.wait.until(EC.presence_of_element_located( (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py index 7d174b7fe..566065be5 100644 --- a/web/pgadmin/feature_tests/view_data_dml_queries.py +++ b/web/pgadmin/feature_tests/view_data_dml_queries.py @@ -145,19 +145,7 @@ CREATE TABLE public.nonintpkey @staticmethod def _get_cell_xpath(cell, row): - - if row == 1: - xpath_grid_row = "//*[contains(@class, 'ui-widget-content') " \ - "and contains(@style, 'top:0px')]" - else: - xpath_grid_row = "//*[contains(@class, 'ui-widget-content') " \ - "and contains(@style, 'top:25px')]" - - xpath_row_cell = '//div[contains(@class, "' + cell + '")]' - - xpath_cell = '{0}{1}'.format(xpath_grid_row, xpath_row_cell) - - return xpath_cell + return QueryToolLocators.output_cell_xpath.format(row, cell) @staticmethod def _load_config_data(config_key): @@ -179,22 +167,22 @@ CREATE TABLE public.nonintpkey self.page.wait_for_query_tool_loading_indicator_to_disappear() # Run test to insert a new row in table with default values self._add_row(config_data_local) - self._verify_row_data(row_height=0, + self._verify_row_data(row=1, config_check_data=config_data_local['add']) # Run test to copy/paste a row self._copy_paste_row(config_data_local) self._update_row(config_data_local) - self.page.click_tab("Messages") + self.page.click_tab("id-messages", rc_dock=True) self._verify_messsages("") - self.page.click_tab("Data Output") + self.page.click_tab("id-dataoutput", rc_dock=True) updated_row_data = { i: config_data_local['update'][i] if i in config_data_local[ 'update'] else val for i, val in config_data_local['add'].items() } - self._verify_row_data(row_height=0, + self._verify_row_data(row=1, config_check_data=updated_row_data) self.page.close_data_grid() @@ -273,14 +261,12 @@ CREATE TABLE public.nonintpkey QueryToolLocators.text_editor_ok_btn_css).click() else: # Boolean editor test for to True click + checkbox_el = self.page.find_by_css_selector( + QueryToolLocators.row_editor_checkbox_css) if data[1] == 'true': - checkbox_el = cell_el.find_element( - By.XPATH, ".//*[contains(@class, 'multi-checkbox')]") checkbox_el.click() # Boolean editor test for to False click elif data[1] == 'false': - checkbox_el = cell_el.find_element( - By.XPATH, ".//*[contains(@class, 'multi-checkbox')]") # Sets true checkbox_el.click() # Sets false @@ -312,7 +298,7 @@ CREATE TABLE public.nonintpkey ) def _copy_paste_row(self, config_data_l): - row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath("r0", 1) + row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath(1, 1) self.page.find_by_xpath(row0_cell0_xpath).click() self.page.find_by_css_selector( @@ -321,14 +307,15 @@ CREATE TABLE public.nonintpkey QueryToolLocators.paste_button_css).click() # Update primary key of copied cell - self._add_update_save_row(config_data_l['copy'], row=2) + # Copy pasted rows go to first row + self._add_update_save_row(config_data_l['copy'], row=1) # Verify row 1 and row 2 data updated_row_data = { i: config_data_l['copy'][i] if i in config_data_l['copy'] else val for i, val in config_data_l['add'].items() } - self._verify_row_data(row_height=25, + self._verify_row_data(row=2, config_check_data=updated_row_data) def _add_update_save_row(self, data, row=1): @@ -337,9 +324,9 @@ CREATE TABLE public.nonintpkey items[item] = int(items[item]) items.sort(reverse=False) for idx in items: - cell_xpath = CheckForViewDataTest._get_cell_xpath( - 'r' + str(idx), row - ) + # rowindex starts with 2 and 1st colindex is rownum + cell_xpath = CheckForViewDataTest\ + ._get_cell_xpath(str(idx + 1), row + 1) time.sleep(0.2) self._update_cell(cell_xpath, data[str(idx)]) self.page.find_by_css_selector( @@ -351,6 +338,9 @@ CREATE TABLE public.nonintpkey time.sleep(2) def _add_row(self, config_data_l): + self.page.find_by_css_selector( + QueryToolLocators.btn_add_row).click() + time.sleep(1) self._add_update_save_row(config_data_l['add'], 1) def _update_row(self, config_data_l): @@ -361,11 +351,13 @@ CREATE TABLE public.nonintpkey QueryToolLocators.query_messages_panel) self.assertEqual(text, messages_ele.text) - def _verify_row_data(self, row_height, config_check_data): + def _verify_row_data(self, row, config_check_data): self.page.click_execute_query_button() + self.driver.execute_script( + "document.querySelector('.rdg').scrollLeft=0" + ) - xpath = "//*[contains(@class, 'ui-widget-content') and " \ - "contains(@style, 'top:" + str(row_height) + "px')]" + xpath = QueryToolLocators.output_row_xpath.format(2) scroll_on_arg_for_js = "arguments[0].scrollIntoView(false)" self.page.wait_for_query_tool_loading_indicator_to_disappear() @@ -379,9 +371,10 @@ CREATE TABLE public.nonintpkey for idx in actual_list: while retry > 0: try: - result_row = self.page.find_by_xpath(xpath) - element = \ - result_row.find_element(By.CLASS_NAME, "r" + str(idx)) + element = self.page.find_by_xpath( + QueryToolLocators.output_cell_xpath + .format(row + 1, idx + 1) + ) self.page.driver.execute_script( scroll_on_arg_for_js, element) break @@ -399,6 +392,7 @@ CREATE TABLE public.nonintpkey list_item.sort(reverse=True) for idx in list_item: time.sleep(0.4) - element = result_row.find_element(By.CLASS_NAME, "r" + str(idx)) + element = self.page.find_by_xpath( + QueryToolLocators.output_cell_xpath.format(2, idx + 1)) self.page.driver.execute_script( scroll_on_arg_for_js, element) 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 efe4a9a40..96bb00522 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 @@ -196,17 +196,12 @@ 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() - result_row = self.page.find_by_xpath( - "//*[contains(@class, 'ui-widget-content') and " - "contains(@style, 'top:0px')]" - ) - - cells = result_row.find_elements(By.TAG_NAME, 'div') - - # remove first element as it is row number. - source_code = cells[1].get_attribute('innerHTML') + source_code = self.page\ + .find_by_xpath(QueryToolLocators.output_cell_xpath.format(2, 2))\ + .get_attribute('innerHTML') self._check_escaped_characters( source_code, @@ -225,13 +220,12 @@ class CheckForXssFeatureTest(BaseFeatureTest): self.page.find_by_css_selector( QueryToolLocators.btn_execute_query_css).click() - self.page.click_tab('Query History') + self.page.click_tab('id-history', rc_dock=True) # Check for history entry - history_ele = self.page.find_by_css_selector( - ".query-history div.query-group:first-child" - " .list-item.selected .query" - ) + history_ele = self.page\ + .find_by_css_selector( + QueryToolLocators.query_history_specific_entry.format(2)) source_code = history_ele.get_attribute('innerHTML') @@ -246,7 +240,7 @@ class CheckForXssFeatureTest(BaseFeatureTest): try: history_ele = self.driver \ .find_element(By.CSS_SELECTOR, - ".query-detail .content-value") + QueryToolLocators.query_history_detail) source_code = history_ele.get_attribute('innerHTML') break except StaleElementReferenceException: @@ -258,25 +252,7 @@ class CheckForXssFeatureTest(BaseFeatureTest): "Query tool (History Details-Message)" ) - 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, - self.check_xss_chars_set2, - "Query tool (History Details-Error)" - ) - - self.page.click_tab('Query Editor') + self.page.click_tab('id-query', rc_dock=True) def _check_xss_view_data(self): print( @@ -284,13 +260,11 @@ class CheckForXssFeatureTest(BaseFeatureTest): file=sys.stderr, end="" ) - self.page.find_by_css_selector(".slick-header-column") - cells = self.driver. \ - find_elements(By.CSS_SELECTOR, ".slick-header-column") - # remove first element as it is row number. # currently 4th col - source_code = cells[4].get_attribute('innerHTML') + source_code = self.page \ + .find_by_xpath(QueryToolLocators.output_cell_xpath.format(1, 5)) \ + .get_attribute('innerHTML') self._check_escaped_characters( source_code, @@ -310,7 +284,7 @@ class CheckForXssFeatureTest(BaseFeatureTest): 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') + self.page.click_tab('id-explain', rc_dock=True) for idx in range(3): # Re-try logic @@ -318,7 +292,7 @@ class CheckForXssFeatureTest(BaseFeatureTest): ActionChains(self.driver).move_to_element( self.driver.find_element( By.CSS_SELECTOR, - 'div.pgadmin-explain-container > svg > g > g > image') + 'div#id-explain svg > g > g > image') ).click().perform() break except Exception: @@ -333,8 +307,8 @@ class CheckForXssFeatureTest(BaseFeatureTest): raise source_code = self.driver.find_element( - By.CSS_SELECTOR, - '.pgadmin-explain-details:not(.d-none)').get_attribute('innerHTML') + By.CSS_SELECTOR, QueryToolLocators.explain_details)\ + .get_attribute('innerHTML') self._check_escaped_characters( source_code, diff --git a/web/pgadmin/static/js/Explain/Graphical.jsx b/web/pgadmin/static/js/Explain/Graphical.jsx index 0ce0c6068..c402e7245 100644 --- a/web/pgadmin/static/js/Explain/Graphical.jsx +++ b/web/pgadmin/static/js/Explain/Graphical.jsx @@ -411,7 +411,7 @@ export default function Graphical({planData, ctx}) { onNodeClick={onNodeClick} /> {Boolean(explainPlanDetails) && - + {explainPlanTitle} diff --git a/web/pgadmin/static/js/components/Buttons.jsx b/web/pgadmin/static/js/components/Buttons.jsx index ae20f1045..eeafc7739 100644 --- a/web/pgadmin/static/js/components/Buttons.jsx +++ b/web/pgadmin/static/js/components/Buttons.jsx @@ -94,8 +94,9 @@ export const PrimaryButton = forwardRef((props, ref)=>{ allClassName.push(classes.xsButton); } noBorder && allClassName.push(classes.noBorder); + const dataLabel = typeof(children) == 'string' ? children : undefined; return ( - + ); }); PrimaryButton.displayName = 'PrimaryButton'; @@ -116,8 +117,9 @@ export const DefaultButton = forwardRef((props, ref)=>{ allClassName.push(classes.xsButton); } noBorder && allClassName.push(classes.noBorder); + const dataLabel = typeof(children) == 'string' ? children : undefined; return ( - + ); }); DefaultButton.displayName = 'DefaultButton'; @@ -144,7 +146,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split return ( + accessKey={accesskey} data-label={title || ''} {...props}> {icon} ); @@ -152,7 +154,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split return ( + accessKey={accesskey} data-label={title || ''} {...props}> {icon} ); @@ -163,7 +165,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split + accessKey={accesskey} data-label={title || ''} {...props}> {icon} @@ -173,7 +175,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split + accessKey={accesskey} data-label={title || ''} {...props}> {icon} diff --git a/web/pgadmin/static/js/components/ExternalIcon.jsx b/web/pgadmin/static/js/components/ExternalIcon.jsx index b43f61317..0edd5eac3 100644 --- a/web/pgadmin/static/js/components/ExternalIcon.jsx +++ b/web/pgadmin/static/js/components/ExternalIcon.jsx @@ -24,47 +24,47 @@ ExternalIcon.propTypes = { Icon: PropTypes.elementType.isRequired, }; -export const QueryToolIcon = ({style})=>; +export const QueryToolIcon = ({style})=>; QueryToolIcon.propTypes = {style: PropTypes.object}; -export const ViewDataIcon = ({style})=>; +export const ViewDataIcon = ({style})=>; ViewDataIcon.propTypes = {style: PropTypes.object}; -export const SaveDataIcon = ({style})=>; +export const SaveDataIcon = ({style})=>; SaveDataIcon.propTypes = {style: PropTypes.object}; -export const PasteIcon = ({style})=>; +export const PasteIcon = ({style})=>; PasteIcon.propTypes = {style: PropTypes.object}; -export const FilterIcon = ({style})=>; +export const FilterIcon = ({style})=>; FilterIcon.propTypes = {style: PropTypes.object}; -export const CommitIcon = ({style})=>; +export const CommitIcon = ({style})=>; CommitIcon.propTypes = {style: PropTypes.object}; -export const RollbackIcon = ({style})=>; +export const RollbackIcon = ({style})=>; RollbackIcon.propTypes = {style: PropTypes.object}; -export const ClearIcon = ({style})=>; +export const ClearIcon = ({style})=>; ClearIcon.propTypes = {style: PropTypes.object}; -export const ConnectedIcon = ({style})=>; +export const ConnectedIcon = ({style})=>; ConnectedIcon.propTypes = {style: PropTypes.object}; -export const DisonnectedIcon = ({style})=>; +export const DisonnectedIcon = ({style})=>; DisonnectedIcon.propTypes = {style: PropTypes.object}; -export const RegexIcon = ({style})=>; +export const RegexIcon = ({style})=>; RegexIcon.propTypes = {style: PropTypes.object}; -export const FormatCaseIcon = ({style})=>; +export const FormatCaseIcon = ({style})=>; FormatCaseIcon.propTypes = {style: PropTypes.object}; -export const ExpandDialogIcon = ({style})=>;QueryToolIcon.propTypes = {style: PropTypes.object}; +export const ExpandDialogIcon = ({style})=>; ExpandDialogIcon.propTypes = {style: PropTypes.object}; -export const MinimizeDialogIcon = ({style})=>; +export const MinimizeDialogIcon = ({style})=>; MinimizeDialogIcon.propTypes = {style: PropTypes.object}; -export const AWSIcon = ({style})=>; +export const AWSIcon = ({style})=>; AWSIcon.propTypes = {style: PropTypes.object}; diff --git a/web/pgadmin/static/js/components/Loader.jsx b/web/pgadmin/static/js/components/Loader.jsx index edb384e92..0232090be 100644 --- a/web/pgadmin/static/js/components/Loader.jsx +++ b/web/pgadmin/static/js/components/Loader.jsx @@ -40,13 +40,13 @@ const useStyles = makeStyles((theme)=>({ } })); -export default function Loader({message, style}) { +export default function Loader({message, style, ...props}) { const classes = useStyles(); if(!message) { return <>; } return ( - + {message} diff --git a/web/pgadmin/static/js/components/Menu.jsx b/web/pgadmin/static/js/components/Menu.jsx index 84f51f65c..585c5a9a2 100644 --- a/web/pgadmin/static/js/components/Menu.jsx +++ b/web/pgadmin/static/js/components/Menu.jsx @@ -24,6 +24,7 @@ const useStyles = makeStyles((theme)=>({ }, '& .szh-menu__divider': { margin: 0, + background: theme.otherVars.borderColor, } }, menuItem: { @@ -47,13 +48,17 @@ const useStyles = makeStyles((theme)=>({ } })); -export function PgMenu({open, className, ...props}) { +export function PgMenu({open, className, label, ...props}) { const classes = useStyles(); + const state = open ? 'open' : 'closed'; + props.anchorRef?.current?.setAttribute('data-state', state); return ( ); } @@ -61,6 +66,8 @@ export function PgMenu({open, className, ...props}) { PgMenu.propTypes = { open: PropTypes.bool, className: CustomPropTypes.className, + label: PropTypes.string, + anchorRef: CustomPropTypes.ref, }; export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false, accesskey, shortcut, children, ...props})=>{ @@ -72,7 +79,8 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false props.onClick(e); }; } - return + const dataLabel = typeof(children) == 'string' ? children : undefined; + return {hasCheck && } {children} {(shortcut || accesskey) &&
({shortcutToString(shortcut, accesskey)})
} diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx index 8c7ecedaf..b6204724c 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolComponent.jsx @@ -53,18 +53,23 @@ function initConnection(api, params, passdata) { } function setPanelTitle(panel, title, qtState, dirty=false) { - if(title) { - title =title.split('\\').pop().split('/').pop(); - } else if(qtState.current_file) { + if(qtState.current_file) { title = qtState.current_file.split('\\').pop().split('/').pop(); - } else { - title = qtState.params.title || 'Untitled'; + } else if (!qtState.is_new_tab) { + if(!title) { + title = panel.$titleText?.[0].textContent; + if(panel.is_dirty_editor) { + // remove asterisk + title = title.slice(0, -1); + } + } } title = title + (dirty ? '*': ''); if (qtState.is_new_tab) { window.document.title = title; } else { + panel.is_dirty_editor = dirty; setQueryToolDockerTitle(panel, true, title, qtState.current_file ? true : false); } } @@ -109,6 +114,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN const setQtState = (state)=>{ _setQtState((prev)=>({...prev,...evalFunc(null, state, prev)})); }; + const isDirtyRef = useRef(false); // usefull when conn change. const eventBus = useRef(eventBusObj || (new EventBus())); const docker = useRef(null); const api = useMemo(()=>getApiInstance(), []); @@ -411,6 +417,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN [QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileDone], [QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileDone], [QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{ + isDirtyRef.current = isDirty; if(qtState.params.is_query_tool) { setPanelTitle(panel, null, qtState, isDirty); } @@ -511,6 +518,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN obtaining_conn: false, }; }); + setPanelTitle(panel, connectionData.title, qtState, isDirtyRef.current); let msg = `${connectionData['server_name']}/${connectionData['database_name']} - Database connected`; Notifier.success(msg); resolve(); diff --git a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx index a37a98c63..6839cf65b 100644 --- a/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx +++ b/web/pgadmin/tools/sqleditor/static/js/components/QueryToolDataGrid/Editors.jsx @@ -186,7 +186,7 @@ export function TextEditor({row, column, onRowChange, onClose}) { { setEditorPosition(getCellElement(column.idx), ele); - }} className={classes.textEditor}> + }} className={classes.textEditor} data-label="pg-editor">