Fixed the following issues:

1. Feature test cases for the new query tool.
 2. Query tool panel rename is not persisting.
This commit is contained in:
Aditya Toshniwal 2022-05-12 18:29:09 +05:30 committed by Akshay Joshi
parent 31be1ae026
commit ca8e14455f
21 changed files with 424 additions and 414 deletions

View File

@ -54,16 +54,17 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
self._copies_columns() self._copies_columns()
self._copies_row_using_keyboard_shortcut() self._copies_row_using_keyboard_shortcut()
self._copies_column_using_keyboard_shortcut() self._copies_column_using_keyboard_shortcut()
self._copies_rectangular_selection() # The below calls is commented since the new data grid does not
self._shift_resizes_rectangular_selection() # 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._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() self._copies_rows_with_header()
def paste_values_to_scratch_pad(self): 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( scratch_pad_ele = self.page.find_by_css_selector(
QueryToolLocators.scratch_pad_css) QueryToolLocators.scratch_pad_css)
self.page.paste_values(scratch_pad_ele) self.page.paste_values(scratch_pad_ele)
@ -73,7 +74,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
def _copies_rows(self): def _copies_rows(self):
first_row = self.page.find_by_xpath( first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1)) QueryToolLocators.output_cell_xpath.format(2, 1))
first_row.click() first_row.click()
copy_button = self.page.find_by_css_selector( copy_button = self.page.find_by_css_selector(
@ -85,8 +86,10 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
clipboard_text) clipboard_text)
def _copies_rows_with_header(self): def _copies_rows_with_header(self):
self.page.find_by_css_selector('#btn-copy-row-dropdown').click() self.page.find_by_css_selector(QueryToolLocators.copy_options_css)\
self.page.find_by_css_selector('a#btn-copy-with-header').click() .click()
self.page.find_by_css_selector(QueryToolLocators.copy_headers_btn_css)\
.click()
select_all = self.page.find_by_xpath( select_all = self.page.find_by_xpath(
QueryToolLocators.select_all_column) QueryToolLocators.select_all_column)
@ -122,7 +125,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
def _copies_row_using_keyboard_shortcut(self): def _copies_row_using_keyboard_shortcut(self):
first_row = self.page.find_by_xpath( first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1)) QueryToolLocators.output_cell_xpath.format(2, 1))
first_row.click() first_row.click()
ActionChains(self.page.driver).key_down( 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', '"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
clipboard_text) 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( bottom_right_cell = self.page.find_by_xpath(
QueryToolLocators.output_column_data_xpath.format('cool info') QueryToolLocators.output_column_data_xpath.format('cool info')
) )
@ -233,7 +236,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
clipboard_text = self.paste_values_to_scratch_pad() clipboard_text = self.paste_values_to_scratch_pad()
self.assertIn('"cool info"', clipboard_text) self.assertNotIn('"cool info"', clipboard_text)
def after(self): def after(self):
self.page.close_query_tool() self.page.close_query_tool()

View File

@ -35,7 +35,9 @@ class QueryToolJourneyTest(BaseFeatureTest):
select_query = "SELECT * FROM %s" select_query = "SELECT * FROM %s"
query_history_tab_name = "Query History" query_history_tab_name = "Query History"
query_history_tab_id = "id-history"
query_editor_tab_name = "Query Editor" query_editor_tab_name = "Query Editor"
query_editor_tab_id = "id-query"
def before(self): def before(self):
self.test_table_name = "test_table" + str(random.randint(1000, 3000)) 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.switch_to.frame(
self.page.driver.find_element_by_tag_name("iframe")) self.page.driver.find_element_by_tag_name("iframe"))
select_row = self.page.find_by_xpath( # row index starts with 2
QueryToolLocators.output_row_xpath.format('1')) select_row = self.page.find_by_css_selector(
QueryToolLocators.output_row_col.format('2', '1'))
select_row.click() select_row.click()
copy_row = self.page.find_by_css_selector( copy_row = self.page.find_by_css_selector(
@ -133,10 +137,6 @@ class QueryToolJourneyTest(BaseFeatureTest):
QueryToolLocators.copy_button_css) QueryToolLocators.copy_button_css)
copy_btn.click() 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( scratch_pad_ele = self.page.find_by_css_selector(
QueryToolLocators.scratch_pad_css) QueryToolLocators.scratch_pad_css)
self.page.paste_values(scratch_pad_ele) self.page.paste_values(scratch_pad_ele)
@ -149,16 +149,19 @@ class QueryToolJourneyTest(BaseFeatureTest):
scratch_pad_ele.clear() scratch_pad_ele.clear()
def _test_history_tab(self): 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.switch_to.frame(
self.page.driver.find_element_by_tag_name("iframe")) self.page.driver.find_element_by_tag_name("iframe"))
self.page.clear_query_tool()
editor_input = self.page.find_by_css_selector( editor_input = self.page.find_by_css_selector(
QueryToolLocators.query_editor_panel) QueryToolLocators.query_editor_panel)
self.page.click_element(editor_input) self.page.click_element(editor_input)
self.page.execute_query(self.select_query % self.invalid_table_name) 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( selected_history_entry = self.page.find_by_css_selector(
QueryToolLocators.query_history_selected) QueryToolLocators.query_history_selected)
self.assertIn(self.select_query % self.invalid_table_name, self.assertIn(self.select_query % self.invalid_table_name,
@ -168,7 +171,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
QueryToolLocators.query_history_detail) QueryToolLocators.query_history_detail)
self.assertIn( self.assertIn(
"Error Message relation \"%s\" does not exist" "ERROR: relation \"%s\" does not exist"
% self.invalid_table_name, % self.invalid_table_name,
failed_history_detail_pane.text failed_history_detail_pane.text
) )
@ -198,10 +201,8 @@ class QueryToolJourneyTest(BaseFeatureTest):
self.assertIn(self.select_query % self.invalid_table_name, self.assertIn(self.select_query % self.invalid_table_name,
invalid_history_entry.text) 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.clear_query_tool()
self.page.driver.switch_to.frame(
self.page.driver.find_element_by_tag_name("iframe"))
self.page.click_element(editor_input) self.page.click_element(editor_input)
# Check if 15 more query executed then the history should contain 17 # 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() QueryToolLocators.btn_execute_query_css).click()
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( query_list = self.page.wait_for_elements(
lambda driver: driver.find_elements( lambda driver: driver.find_elements(
@ -226,20 +227,18 @@ class QueryToolJourneyTest(BaseFeatureTest):
self._test_toggle_generated_queries() self._test_toggle_generated_queries()
def _test_history_query_sources(self): def _test_history_query_sources(self):
self.page.driver.switch_to.frame( self.page.click_tab(self.query_editor_tab_id, rc_dock=True)
self.page.driver.find_element_by_tag_name("iframe"))
self.page.click_tab(self.query_editor_tab_name)
self._execute_sources_test_queries() 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 = [ history_entries_icons = [
QueryToolLocators.commit_icon, 'CommitIcon',
QueryToolLocators.save_data_icon, 'SaveDataIcon',
QueryToolLocators.save_data_icon, 'SaveDataIcon',
QueryToolLocators.execute_icon, 'ExecuteIcon',
QueryToolLocators.explain_analyze_icon, 'ExplainAnalyzeIcon',
QueryToolLocators.explain_icon 'ExplainIcon',
] ]
history_entries_queries = [ history_entries_queries = [
@ -256,7 +255,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
history_entries_icons) history_entries_icons)
def _test_toggle_generated_queries(self): 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.assertTrue(self.page.check_if_element_exist_by_xpath(xpath))
self.page.set_switch_box_status( self.page.set_switch_box_status(
QueryToolLocators.show_query_internally_btn, 'No') QueryToolLocators.show_query_internally_btn, 'No')
@ -266,9 +265,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath)) self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath))
def _test_updatable_resultset(self): def _test_updatable_resultset(self):
if self.driver_version < 2.8: self.page.click_tab(self.query_editor_tab_id, rc_dock=True)
return
self.page.click_tab(self.query_editor_tab_name)
# Select all data # Select all data
# (contains the primary key -> all columns should be editable) # (contains the primary key -> all columns should be editable)
@ -295,19 +292,18 @@ class QueryToolJourneyTest(BaseFeatureTest):
self._check_query_results_editable(query, self._check_query_results_editable(query,
[True, True, False, False, False]) [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): def _test_is_editable_columns_icons(self):
if self.driver_version < 2.8: if self.driver_version < 2.8:
return 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() self.page.clear_query_tool()
query = "SELECT pk_column FROM %s" % self.test_editable_table_name query = "SELECT pk_column FROM %s" % self.test_editable_table_name
self.page.execute_query(query) 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( icon_exists = self.page.check_if_element_exist_by_xpath(
QueryToolLocators.editable_column_icon_xpath QueryToolLocators.editable_column_icon_xpath
@ -344,33 +340,38 @@ class QueryToolJourneyTest(BaseFeatureTest):
query_options.click() query_options.click()
self.page.uncheck_execute_option("auto_commit") self.page.uncheck_execute_option("auto_commit")
self._update_numeric_cell(2, 10) self._update_numeric_cell(10)
time.sleep(0.5) time.sleep(0.5)
self._commit_transaction() self._commit_transaction()
self.page.wait_for_spinner_to_disappear()
# Turn on autocommit # Turn on autocommit
retry = 3 # self.page.check_execute_option("auto_commit")
while retry > 0: # query_options = self.page.find_by_css_selector(
query_options = self.page.find_by_css_selector( # QueryToolLocators.btn_query_dropdown)
QueryToolLocators.btn_query_dropdown) # query_options.click()
query_options.click() # retry = 3
expanded = query_options.get_attribute("aria-expanded") # while retry > 0:
if expanded == "false": # query_options = self.page.find_by_css_selector(
print("query option not yet expanded clicking commit again", # QueryToolLocators.btn_query_dropdown)
file=sys.stderr) # query_options.click()
self._commit_transaction() # expanded = query_options.get_attribute("aria-expanded")
time.sleep(0.5) # if expanded == "false":
query_options.click() # print("query option not yet expanded clicking commit again",
break # file=sys.stderr)
else: # self._commit_transaction()
retry -= 1 # time.sleep(0.5)
# query_options.click()
# break
# else:
# retry -= 1
self.page.check_execute_option("auto_commit") self.page.check_execute_option("auto_commit")
def _check_history_queries_and_icons(self, history_queries, history_icons): def _check_history_queries_and_icons(self, history_queries, history_icons):
# Select first query history entry # Select first query history entry
self.page.find_by_css_selector( 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): for icon, query in zip(history_icons, history_queries):
# Check query # Check query
query_history_selected_item = self.page.find_by_css_selector( query_history_selected_item = self.page.find_by_css_selector(
@ -382,27 +383,20 @@ class QueryToolJourneyTest(BaseFeatureTest):
# Check source icon # Check source icon
query_history_selected_icon = self.page.find_by_css_selector( query_history_selected_icon = self.page.find_by_css_selector(
QueryToolLocators.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( self.assertTrue(
icon in icon_classes or 'icon-save_data_changes' in icon == query_history_selected_icon.get_attribute(
icon_classes or 'icon-commit' in icon_classes or 'data-label'))
'fa-play' in icon_classes)
# Move to next entry # Move to next entry
ActionChains(self.page.driver) \ ActionChains(self.page.driver) \
.send_keys(Keys.ARROW_DOWN) \ .send_keys(Keys.ARROW_DOWN) \
.perform() .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 Updates a numeric cell in the first row of the resultset
""" """
cell_xpath = "//div[contains(@style, 'top:0px')]//" \ cell_el = self.page.find_by_css_selector(
"div[contains(@class,'l{0} r{1}')]". \ QueryToolLocators.output_row_col.format(2, 3))
format(cell_index, cell_index)
self.page.check_if_element_exist_by_xpath(cell_xpath)
cell_el = self.page.find_by_xpath(cell_xpath)
ActionChains(self.driver).double_click(cell_el).perform() ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(value). \ ActionChains(self.driver).send_keys(value). \
send_keys(Keys.ENTER).perform() send_keys(Keys.ENTER).perform()
@ -410,7 +404,7 @@ class QueryToolJourneyTest(BaseFeatureTest):
QueryToolLocators.btn_save_data).click() QueryToolLocators.btn_save_data).click()
def _insert_data_into_test_editable_table(self): 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.clear_query_tool()
self.page.execute_query( self.page.execute_query(
"INSERT INTO %s VALUES (1, 1), (2, 2);" "INSERT INTO %s VALUES (1, 1), (2, 2);"
@ -418,18 +412,13 @@ class QueryToolJourneyTest(BaseFeatureTest):
) )
def __clear_query_history(self): 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.click_element(
self.page.find_by_css_selector( 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) self.page.click_modal('Yes', True)
def _navigate_to_query_tool(self): def _navigate_to_query_tool(self):
@ -460,12 +449,10 @@ class QueryToolJourneyTest(BaseFeatureTest):
discard_changes_modal=False): discard_changes_modal=False):
self.page.execute_query(query) self.page.execute_query(query)
if discard_changes_modal: if discard_changes_modal:
self.driver.switch_to.default_content()
self.page.click_modal('Yes', True) 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 import time
time.sleep(0.5) time.sleep(0.5)
@ -475,9 +462,8 @@ class QueryToolJourneyTest(BaseFeatureTest):
def _check_cell_editable(self, cell_index): def _check_cell_editable(self, cell_index):
"""Checks if a cell in the first row of the resultset is editable""" """Checks if a cell in the first row of the resultset is editable"""
cell_el = self.page.find_by_xpath( cell_el = self.page.find_by_css_selector(
"//div[contains(@style, 'top:0px')]//div[contains(@class, " QueryToolLocators.output_row_col.format(2, cell_index))
"'l{0} r{1}')]".format(cell_index, cell_index))
# Get existing value # Get existing value
cell_value = int(cell_el.text) cell_value = int(cell_el.text)
@ -489,9 +475,9 @@ class QueryToolJourneyTest(BaseFeatureTest):
# Check if the value was updated # Check if the value was updated
# Finding element again to avoid stale element reference exception # Finding element again to avoid stale element reference exception
cell_el = self.page.find_by_xpath( cell_el = self.page.\
"//div[contains(@style, 'top:0px')]//div[contains(@class, " find_by_css_selector(QueryToolLocators.
"'l{0} r{1}')]".format(cell_index, cell_index)) output_row_col.format(2, cell_index))
return int(cell_el.text) == new_value return int(cell_el.text) == new_value
def _check_can_add_row(self): def _check_can_add_row(self):

View File

@ -30,7 +30,7 @@ class QueryToolFeatureTest(BaseFeatureTest):
scenarios = [ scenarios = [
("Query tool feature test", dict()) ("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"' table_creation_fail_error = '"CREATE TABLE message does not displayed"'
def before(self): def before(self):
@ -45,10 +45,10 @@ class QueryToolFeatureTest(BaseFeatureTest):
self.wait = WebDriverWait(self.page.driver, 10) self.wait = WebDriverWait(self.page.driver, 10)
def runTest(self): def runTest(self):
skip_warning = "Skipped."
# on demand result set on scrolling. # on demand result set on scrolling.
print("\nOn demand query result... ", print("\nOn demand query result... ",
file=sys.stderr, end="") file=sys.stderr, end="")
skip_warning = "Skipped."
self._on_demand_result() self._on_demand_result()
self.page.clear_query_tool() self.page.clear_query_tool()
@ -128,8 +128,7 @@ class QueryToolFeatureTest(BaseFeatureTest):
QueryToolLocators.btn_explain_buffers, QueryToolLocators.btn_explain_buffers,
QueryToolLocators.btn_explain_timing): QueryToolLocators.btn_explain_timing):
btn = self.page.find_by_css_selector(op) btn = self.page.find_by_css_selector(op)
check = btn.find_element(By.TAG_NAME, 'i') if btn.get_attribute('data-checked') == 'true':
if 'visibility-hidden' not in check.get_attribute('class'):
btn.click() btn.click()
query_op = self.page.find_by_css_selector( 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( self.page.find_by_css_selector(
QueryToolLocators.output_column_header_css.format('id1')) QueryToolLocators.output_column_header_css.format('id1'))
column_1.click() column_1.click()
canvas_ele = self.page.find_by_css_selector('.grid-canvas') grid = self.page.find_by_css_selector('.rdg')
scrolling_height = canvas_ele.size['height'] scrolling_height = grid.size['height']
self.driver.execute_script( self.driver.execute_script(
"pgAdmin.SqlEditor.jquery('.slick-viewport')" "document.querySelector('.rdg').scrollTop="
".scrollTop(pgAdmin.SqlEditor.jquery('.grid-canvas')" "document.querySelector('.rdg').scrollHeight"
".height());"
) )
# Table height takes some time to update, for which their is no # Table height takes some time to update, for which their is no
# particular way # particular way
time.sleep(2) 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( self.page.check_if_element_exist_by_xpath(
QueryToolLocators.output_column_data_xpath.format( QueryToolLocators.output_column_data_xpath.format(
row_id_to_find)): 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.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( canvas = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) (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) # Search for 'Output' word in result (verbose option)
canvas.find_element(By.XPATH, "//*[contains(string(), 'Output')]") 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.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( 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( 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) # Search for 'Shared Read Blocks' word in result (buffers option)
self.assertIn('Shared Read Blocks', result.text) 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.click_execute_query_button()
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('CREATE TABLE')), QueryToolLocators.sql_editor_message.format('CREATE TABLE')),
@ -364,7 +366,7 @@ CREATE TABLE public.{}();""".format(table_name)
-- 4. Check if table is *NOT* created. -- 4. Check if table is *NOT* created.
ROLLBACK;""" ROLLBACK;"""
self.page.execute_query(query) 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('ROLLBACK')), QueryToolLocators.sql_editor_message.format('ROLLBACK')),
"ROLLBACK message does not displayed") "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;""" WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
self.page.execute_query(query) 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( canvas = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) (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.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('CREATE TABLE')), QueryToolLocators.sql_editor_message.format('CREATE TABLE')),
self.table_creation_fail_error) self.table_creation_fail_error)
@ -447,7 +449,7 @@ ROLLBACK;"""
self.page.execute_query(query) self.page.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('ROLLBACK')), QueryToolLocators.sql_editor_message.format('ROLLBACK')),
"ROLLBACK message does not displayed") "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;""" WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
self.page.execute_query(query) 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() self.page.wait_for_query_tool_loading_indicator_to_disappear()
canvas = self.wait.until(EC.presence_of_element_located( canvas = self.wait.until(EC.presence_of_element_located(
@ -511,7 +513,7 @@ END;"""
CREATE TABLE public.{}();""".format(table_name) CREATE TABLE public.{}();""".format(table_name)
self.page.execute_query(query) self.page.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('CREATE TABLE')), QueryToolLocators.sql_editor_message.format('CREATE TABLE')),
self.table_creation_fail_error) self.table_creation_fail_error)
@ -526,7 +528,7 @@ CREATE TABLE public.{}();""".format(table_name)
SELECT 1/0;""" SELECT 1/0;"""
self.page.execute_query(query) self.page.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('division by zero')), QueryToolLocators.sql_editor_message.format('division by zero')),
"division by zero message does not displayed") "division by zero message does not displayed")
@ -541,7 +543,7 @@ SELECT 1/0;"""
END;""" END;"""
self.page.execute_query(query) self.page.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message. QueryToolLocators.sql_editor_message.
format('Query returned successfully')), 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;""" WHERE relkind IN ('r','s','t') and relnamespace = 2200::oid;"""
self.page.execute_query(query) self.page.execute_query(query)
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( canvas = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))) (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)))
@ -580,30 +582,20 @@ SELECT 1, pg_sleep(300)"""
self.page.fill_codemirror_area_with(query) self.page.fill_codemirror_area_with(query)
# query_button drop can be disabled so enable # 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'): if not commit_button.get_attribute('disabled'):
commit_button.click() commit_button.click()
time.sleep(0.5) 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 # enable auto-commit and disable auto-rollback
self.page.check_execute_option('auto_commit') self.page.check_execute_option('auto_commit')
self.page.uncheck_execute_option('auto_rollback') self.page.uncheck_execute_option('auto_rollback')
# close drop down
query_op.click()
# Execute query # Execute query
retry = 5 self.page.find_by_css_selector(
execute_button = self.page.find_by_css_selector( QueryToolLocators.btn_execute_query_css).click()
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
# Providing a second of sleep since clicks on the execute and stop # 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 # query button is too quick that the query is not able run properly
time.sleep(1) time.sleep(1)
@ -611,12 +603,15 @@ SELECT 1, pg_sleep(300)"""
self.page.find_by_css_selector( self.page.find_by_css_selector(
QueryToolLocators.btn_cancel_query).click() QueryToolLocators.btn_cancel_query).click()
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( self.assertTrue(
'//div[contains(@class, "sql-editor-message") and ' self.page.check_if_element_exist_by_xpath(
'(contains(string(), "canceling statement due to user request") ' QueryToolLocators.sql_editor_message
'or contains(string(), "Execution Cancelled!"))]' .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): def _supported_server_version(self):
connection = test_utils.get_db_connection( connection = test_utils.get_db_connection(
@ -632,7 +627,7 @@ SELECT 1, pg_sleep(300)"""
def _query_tool_notify_statements(self): def _query_tool_notify_statements(self):
print("\n\tListen on an event... ", file=sys.stderr, end="") print("\n\tListen on an event... ", file=sys.stderr, end="")
self.page.execute_query("LISTEN foo;") 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( self.assertTrue(self.page.check_if_element_exist_by_xpath(
QueryToolLocators.sql_editor_message.format('LISTEN')), QueryToolLocators.sql_editor_message.format('LISTEN')),
@ -642,9 +637,9 @@ SELECT 1, pg_sleep(300)"""
print("\tNotify event without data... ", file=sys.stderr, end="") print("\tNotify event without data... ", file=sys.stderr, end="")
self.page.execute_query("NOTIFY foo;") 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( 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) print("OK.", file=sys.stderr)
@ -652,9 +647,9 @@ SELECT 1, pg_sleep(300)"""
if self._supported_server_version(): if self._supported_server_version():
self.page.clear_query_tool() self.page.clear_query_tool()
self.page.execute_query("SELECT pg_notify('foo', 'Hello')") 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( self.wait.until(WaitForAnyElementWithText(
(By.CSS_SELECTOR, 'td.payload'), "Hello")) (By.CSS_SELECTOR, "td[data-label='payload']"), "Hello"))
print("OK.", file=sys.stderr) print("OK.", file=sys.stderr)
else: else:
print("Skipped.", file=sys.stderr) print("Skipped.", file=sys.stderr)
@ -700,8 +695,7 @@ SELECT 1, pg_sleep(300)"""
QueryToolLocators.btn_explain_buffers, QueryToolLocators.btn_explain_buffers,
QueryToolLocators.btn_explain_timing): QueryToolLocators.btn_explain_timing):
btn = self.page.find_by_css_selector(op) btn = self.page.find_by_css_selector(op)
check = btn.find_element(By.TAG_NAME, 'i') if btn.get_attribute('data-checked') == 'true':
if 'visibility-hidden' not in check.get_attribute('class'):
btn.click() btn.click()
# click cost button # click cost button
cost_btn = self.page.find_by_css_selector( cost_btn = self.page.find_by_css_selector(
@ -715,7 +709,7 @@ SELECT 1, pg_sleep(300)"""
QueryToolLocators.btn_explain_analyze).click() QueryToolLocators.btn_explain_analyze).click()
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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( canvas = self.wait.until(EC.presence_of_element_located(
(By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css)) (By.CSS_SELECTOR, QueryToolLocators.query_output_canvas_css))

View File

@ -145,19 +145,7 @@ CREATE TABLE public.nonintpkey
@staticmethod @staticmethod
def _get_cell_xpath(cell, row): def _get_cell_xpath(cell, row):
return QueryToolLocators.output_cell_xpath.format(row, cell)
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
@staticmethod @staticmethod
def _load_config_data(config_key): def _load_config_data(config_key):
@ -179,22 +167,22 @@ CREATE TABLE public.nonintpkey
self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.wait_for_query_tool_loading_indicator_to_disappear()
# Run test to insert a new row in table with default values # Run test to insert a new row in table with default values
self._add_row(config_data_local) 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']) config_check_data=config_data_local['add'])
# Run test to copy/paste a row # Run test to copy/paste a row
self._copy_paste_row(config_data_local) self._copy_paste_row(config_data_local)
self._update_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._verify_messsages("")
self.page.click_tab("Data Output") self.page.click_tab("id-dataoutput", rc_dock=True)
updated_row_data = { updated_row_data = {
i: config_data_local['update'][i] if i in config_data_local[ i: config_data_local['update'][i] if i in config_data_local[
'update'] else val 'update'] else val
for i, val in config_data_local['add'].items() 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) config_check_data=updated_row_data)
self.page.close_data_grid() self.page.close_data_grid()
@ -273,14 +261,12 @@ CREATE TABLE public.nonintpkey
QueryToolLocators.text_editor_ok_btn_css).click() QueryToolLocators.text_editor_ok_btn_css).click()
else: else:
# Boolean editor test for to True click # Boolean editor test for to True click
checkbox_el = self.page.find_by_css_selector(
QueryToolLocators.row_editor_checkbox_css)
if data[1] == 'true': if data[1] == 'true':
checkbox_el = cell_el.find_element(
By.XPATH, ".//*[contains(@class, 'multi-checkbox')]")
checkbox_el.click() checkbox_el.click()
# Boolean editor test for to False click # Boolean editor test for to False click
elif data[1] == 'false': elif data[1] == 'false':
checkbox_el = cell_el.find_element(
By.XPATH, ".//*[contains(@class, 'multi-checkbox')]")
# Sets true # Sets true
checkbox_el.click() checkbox_el.click()
# Sets false # Sets false
@ -312,7 +298,7 @@ CREATE TABLE public.nonintpkey
) )
def _copy_paste_row(self, config_data_l): 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_xpath(row0_cell0_xpath).click()
self.page.find_by_css_selector( self.page.find_by_css_selector(
@ -321,14 +307,15 @@ CREATE TABLE public.nonintpkey
QueryToolLocators.paste_button_css).click() QueryToolLocators.paste_button_css).click()
# Update primary key of copied cell # 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 # Verify row 1 and row 2 data
updated_row_data = { updated_row_data = {
i: config_data_l['copy'][i] if i in config_data_l['copy'] else val i: config_data_l['copy'][i] if i in config_data_l['copy'] else val
for i, val in config_data_l['add'].items() 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) config_check_data=updated_row_data)
def _add_update_save_row(self, data, row=1): def _add_update_save_row(self, data, row=1):
@ -337,9 +324,9 @@ CREATE TABLE public.nonintpkey
items[item] = int(items[item]) items[item] = int(items[item])
items.sort(reverse=False) items.sort(reverse=False)
for idx in items: for idx in items:
cell_xpath = CheckForViewDataTest._get_cell_xpath( # rowindex starts with 2 and 1st colindex is rownum
'r' + str(idx), row cell_xpath = CheckForViewDataTest\
) ._get_cell_xpath(str(idx + 1), row + 1)
time.sleep(0.2) time.sleep(0.2)
self._update_cell(cell_xpath, data[str(idx)]) self._update_cell(cell_xpath, data[str(idx)])
self.page.find_by_css_selector( self.page.find_by_css_selector(
@ -351,6 +338,9 @@ CREATE TABLE public.nonintpkey
time.sleep(2) time.sleep(2)
def _add_row(self, config_data_l): 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) self._add_update_save_row(config_data_l['add'], 1)
def _update_row(self, config_data_l): def _update_row(self, config_data_l):
@ -361,11 +351,13 @@ CREATE TABLE public.nonintpkey
QueryToolLocators.query_messages_panel) QueryToolLocators.query_messages_panel)
self.assertEqual(text, messages_ele.text) 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.page.click_execute_query_button()
self.driver.execute_script(
"document.querySelector('.rdg').scrollLeft=0"
)
xpath = "//*[contains(@class, 'ui-widget-content') and " \ xpath = QueryToolLocators.output_row_xpath.format(2)
"contains(@style, 'top:" + str(row_height) + "px')]"
scroll_on_arg_for_js = "arguments[0].scrollIntoView(false)" scroll_on_arg_for_js = "arguments[0].scrollIntoView(false)"
self.page.wait_for_query_tool_loading_indicator_to_disappear() self.page.wait_for_query_tool_loading_indicator_to_disappear()
@ -379,9 +371,10 @@ CREATE TABLE public.nonintpkey
for idx in actual_list: for idx in actual_list:
while retry > 0: while retry > 0:
try: try:
result_row = self.page.find_by_xpath(xpath) element = self.page.find_by_xpath(
element = \ QueryToolLocators.output_cell_xpath
result_row.find_element(By.CLASS_NAME, "r" + str(idx)) .format(row + 1, idx + 1)
)
self.page.driver.execute_script( self.page.driver.execute_script(
scroll_on_arg_for_js, element) scroll_on_arg_for_js, element)
break break
@ -399,6 +392,7 @@ CREATE TABLE public.nonintpkey
list_item.sort(reverse=True) list_item.sort(reverse=True)
for idx in list_item: for idx in list_item:
time.sleep(0.4) 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( self.page.driver.execute_script(
scroll_on_arg_for_js, element) scroll_on_arg_for_js, element)

View File

@ -196,17 +196,12 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self.page.fill_codemirror_area_with( self.page.fill_codemirror_area_with(
"select '<img src=\"x\" onerror=\"console.log(1)\">'" "select '<img src=\"x\" onerror=\"console.log(1)\">'"
) )
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( source_code = self.page\
"//*[contains(@class, 'ui-widget-content') and " .find_by_xpath(QueryToolLocators.output_cell_xpath.format(2, 2))\
"contains(@style, 'top:0px')]" .get_attribute('innerHTML')
)
cells = result_row.find_elements(By.TAG_NAME, 'div')
# remove first element as it is row number.
source_code = cells[1].get_attribute('innerHTML')
self._check_escaped_characters( self._check_escaped_characters(
source_code, source_code,
@ -225,13 +220,12 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self.page.find_by_css_selector( self.page.find_by_css_selector(
QueryToolLocators.btn_execute_query_css).click() 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 # Check for history entry
history_ele = self.page.find_by_css_selector( history_ele = self.page\
".query-history div.query-group:first-child" .find_by_css_selector(
" .list-item.selected .query" QueryToolLocators.query_history_specific_entry.format(2))
)
source_code = history_ele.get_attribute('innerHTML') source_code = history_ele.get_attribute('innerHTML')
@ -246,7 +240,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
try: try:
history_ele = self.driver \ history_ele = self.driver \
.find_element(By.CSS_SELECTOR, .find_element(By.CSS_SELECTOR,
".query-detail .content-value") QueryToolLocators.query_history_detail)
source_code = history_ele.get_attribute('innerHTML') source_code = history_ele.get_attribute('innerHTML')
break break
except StaleElementReferenceException: except StaleElementReferenceException:
@ -258,25 +252,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
"Query tool (History Details-Message)" "Query tool (History Details-Message)"
) )
retry = 2 self.page.click_tab('id-query', rc_dock=True)
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')
def _check_xss_view_data(self): def _check_xss_view_data(self):
print( print(
@ -284,13 +260,11 @@ class CheckForXssFeatureTest(BaseFeatureTest):
file=sys.stderr, end="" 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. # remove first element as it is row number.
# currently 4th col # 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( self._check_escaped_characters(
source_code, source_code,
@ -310,7 +284,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
self.page.find_by_css_selector( self.page.find_by_css_selector(
QueryToolLocators.btn_explain).click() QueryToolLocators.btn_explain).click()
self.page.wait_for_query_tool_loading_indicator_to_disappear() 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): for idx in range(3):
# Re-try logic # Re-try logic
@ -318,7 +292,7 @@ class CheckForXssFeatureTest(BaseFeatureTest):
ActionChains(self.driver).move_to_element( ActionChains(self.driver).move_to_element(
self.driver.find_element( self.driver.find_element(
By.CSS_SELECTOR, By.CSS_SELECTOR,
'div.pgadmin-explain-container > svg > g > g > image') 'div#id-explain svg > g > g > image')
).click().perform() ).click().perform()
break break
except Exception: except Exception:
@ -333,8 +307,8 @@ class CheckForXssFeatureTest(BaseFeatureTest):
raise raise
source_code = self.driver.find_element( source_code = self.driver.find_element(
By.CSS_SELECTOR, By.CSS_SELECTOR, QueryToolLocators.explain_details)\
'.pgadmin-explain-details:not(.d-none)').get_attribute('innerHTML') .get_attribute('innerHTML')
self._check_escaped_characters( self._check_escaped_characters(
source_code, source_code,

View File

@ -411,7 +411,7 @@ export default function Graphical({planData, ctx}) {
onNodeClick={onNodeClick} onNodeClick={onNodeClick}
/> />
{Boolean(explainPlanDetails) && {Boolean(explainPlanDetails) &&
<Card className={classes.explainDetails}> <Card className={classes.explainDetails} data-label="explain-details">
<CardHeader title={<Box display="flex"> <CardHeader title={<Box display="flex">
{explainPlanTitle} {explainPlanTitle}
<Box marginLeft="auto"> <Box marginLeft="auto">

View File

@ -94,8 +94,9 @@ export const PrimaryButton = forwardRef((props, ref)=>{
allClassName.push(classes.xsButton); allClassName.push(classes.xsButton);
} }
noBorder && allClassName.push(classes.noBorder); noBorder && allClassName.push(classes.noBorder);
const dataLabel = typeof(children) == 'string' ? children : undefined;
return ( return (
<Button ref={ref} size={size} className={clsx(allClassName)} {...otherProps} variant="contained" color="primary" >{children}</Button> <Button ref={ref} size={size} className={clsx(allClassName)} data-label={dataLabel} {...otherProps} variant="contained" color="primary">{children}</Button>
); );
}); });
PrimaryButton.displayName = 'PrimaryButton'; PrimaryButton.displayName = 'PrimaryButton';
@ -116,8 +117,9 @@ export const DefaultButton = forwardRef((props, ref)=>{
allClassName.push(classes.xsButton); allClassName.push(classes.xsButton);
} }
noBorder && allClassName.push(classes.noBorder); noBorder && allClassName.push(classes.noBorder);
const dataLabel = typeof(children) == 'string' ? children : undefined;
return ( return (
<Button variant="outlined" color="default" ref={ref} size={size} className={clsx(allClassName)} {...otherProps} >{children}</Button> <Button variant="outlined" color="default" ref={ref} size={size} className={clsx(allClassName)} data-label={dataLabel} {...otherProps}>{children}</Button>
); );
}); });
DefaultButton.displayName = 'DefaultButton'; DefaultButton.displayName = 'DefaultButton';
@ -144,7 +146,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
return ( return (
<PrimaryButton ref={ref} style={style} <PrimaryButton ref={ref} style={style}
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)} className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)}
accessKey={accesskey} {...props}> accessKey={accesskey} data-label={title || ''} {...props}>
{icon} {icon}
</PrimaryButton> </PrimaryButton>
); );
@ -152,7 +154,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
return ( return (
<DefaultButton ref={ref} style={style} <DefaultButton ref={ref} style={style}
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)} className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)}
accessKey={accesskey} {...props}> accessKey={accesskey} data-label={title || ''} {...props}>
{icon} {icon}
</DefaultButton> </DefaultButton>
); );
@ -163,7 +165,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}> <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
<PrimaryButton ref={ref} style={style} <PrimaryButton ref={ref} style={style}
className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)} className={clsx(classes.iconButton, (splitButton ? classes.splitButton : ''), className)}
accessKey={accesskey} {...props}> accessKey={accesskey} data-label={title || ''} {...props}>
{icon} {icon}
</PrimaryButton> </PrimaryButton>
</Tooltip> </Tooltip>
@ -173,7 +175,7 @@ export const PgIconButton = forwardRef(({icon, title, shortcut, className, split
<Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}> <Tooltip title={shortcutTitle || title || ''} aria-label={title || ''}>
<DefaultButton ref={ref} style={style} <DefaultButton ref={ref} style={style}
className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)} className={clsx(classes.iconButton, classes.iconButtonDefault, (splitButton ? classes.splitButton : ''), className)}
accessKey={accesskey} {...props}> accessKey={accesskey} data-label={title || ''} {...props}>
{icon} {icon}
</DefaultButton> </DefaultButton>
</Tooltip> </Tooltip>

View File

@ -24,47 +24,47 @@ ExternalIcon.propTypes = {
Icon: PropTypes.elementType.isRequired, Icon: PropTypes.elementType.isRequired,
}; };
export const QueryToolIcon = ({style})=><ExternalIcon Icon={QueryToolSvg} style={{height: '1rem', ...style}} />; export const QueryToolIcon = ({style})=><ExternalIcon Icon={QueryToolSvg} style={{height: '1rem', ...style}} data-label="QueryToolIcon" />;
QueryToolIcon.propTypes = {style: PropTypes.object}; QueryToolIcon.propTypes = {style: PropTypes.object};
export const ViewDataIcon = ({style})=><ExternalIcon Icon={ViewDataSvg} style={{height: '0.8rem', ...style}} />; export const ViewDataIcon = ({style})=><ExternalIcon Icon={ViewDataSvg} style={{height: '0.8rem', ...style}} data-label="ViewDataIcon" />;
ViewDataIcon.propTypes = {style: PropTypes.object}; ViewDataIcon.propTypes = {style: PropTypes.object};
export const SaveDataIcon = ({style})=><ExternalIcon Icon={SaveDataSvg} style={{height: '1rem', ...style}} />; export const SaveDataIcon = ({style})=><ExternalIcon Icon={SaveDataSvg} style={{height: '1rem', ...style}} data-label="SaveDataIcon" />;
SaveDataIcon.propTypes = {style: PropTypes.object}; SaveDataIcon.propTypes = {style: PropTypes.object};
export const PasteIcon = ({style})=><ExternalIcon Icon={PasteSvg} style={style} />; export const PasteIcon = ({style})=><ExternalIcon Icon={PasteSvg} style={style} data-label="PasteIcon" />;
PasteIcon.propTypes = {style: PropTypes.object}; PasteIcon.propTypes = {style: PropTypes.object};
export const FilterIcon = ({style})=><ExternalIcon Icon={FilterSvg} style={style} />; export const FilterIcon = ({style})=><ExternalIcon Icon={FilterSvg} style={style} data-label="FilterIcon" />;
FilterIcon.propTypes = {style: PropTypes.object}; FilterIcon.propTypes = {style: PropTypes.object};
export const CommitIcon = ({style})=><ExternalIcon Icon={CommitSvg} style={style} />; export const CommitIcon = ({style})=><ExternalIcon Icon={CommitSvg} style={{height: '1.2rem', ...style}} data-label="CommitIcon" />;
CommitIcon.propTypes = {style: PropTypes.object}; CommitIcon.propTypes = {style: PropTypes.object};
export const RollbackIcon = ({style})=><ExternalIcon Icon={RollbackSvg} style={style} />; export const RollbackIcon = ({style})=><ExternalIcon Icon={RollbackSvg} style={{height: '1.2rem', ...style}} data-label="RollbackIcon" />;
RollbackIcon.propTypes = {style: PropTypes.object}; RollbackIcon.propTypes = {style: PropTypes.object};
export const ClearIcon = ({style})=><ExternalIcon Icon={ClearSvg} style={style} />; export const ClearIcon = ({style})=><ExternalIcon Icon={ClearSvg} style={style} data-label="ClearIcon" />;
ClearIcon.propTypes = {style: PropTypes.object}; ClearIcon.propTypes = {style: PropTypes.object};
export const ConnectedIcon = ({style})=><ExternalIcon Icon={ConnectedSvg} style={{height: '1rem', ...style}} />; export const ConnectedIcon = ({style})=><ExternalIcon Icon={ConnectedSvg} style={{height: '1rem', ...style}} data-label="ConnectedIcon" />;
ConnectedIcon.propTypes = {style: PropTypes.object}; ConnectedIcon.propTypes = {style: PropTypes.object};
export const DisonnectedIcon = ({style})=><ExternalIcon Icon={DisconnectedSvg} style={{height: '1rem', ...style}} />; export const DisonnectedIcon = ({style})=><ExternalIcon Icon={DisconnectedSvg} style={{height: '1rem', ...style}} data-label="DisonnectedIcon" />;
DisonnectedIcon.propTypes = {style: PropTypes.object}; DisonnectedIcon.propTypes = {style: PropTypes.object};
export const RegexIcon = ({style})=><ExternalIcon Icon={RegexSvg} style={style} />; export const RegexIcon = ({style})=><ExternalIcon Icon={RegexSvg} style={style} data-label="RegexIcon" />;
RegexIcon.propTypes = {style: PropTypes.object}; RegexIcon.propTypes = {style: PropTypes.object};
export const FormatCaseIcon = ({style})=><ExternalIcon Icon={FormatCaseSvg} style={style} />; export const FormatCaseIcon = ({style})=><ExternalIcon Icon={FormatCaseSvg} style={style} data-label="FormatCaseIcon" />;
FormatCaseIcon.propTypes = {style: PropTypes.object}; FormatCaseIcon.propTypes = {style: PropTypes.object};
export const ExpandDialogIcon = ({style})=><ExternalIcon Icon={Expand} style={{height: '1.2rem', ...style}} />;QueryToolIcon.propTypes = {style: PropTypes.object}; export const ExpandDialogIcon = ({style})=><ExternalIcon Icon={Expand} style={{height: '1.2rem', ...style}} data-label="ExpandDialogIcon" />;
ExpandDialogIcon.propTypes = {style: PropTypes.object}; ExpandDialogIcon.propTypes = {style: PropTypes.object};
export const MinimizeDialogIcon = ({style})=><ExternalIcon Icon={Collapse} style={{height: '1.4rem', ...style}} />; export const MinimizeDialogIcon = ({style})=><ExternalIcon Icon={Collapse} style={{height: '1.4rem', ...style}} data-label="MinimizeDialogIcon" />;
MinimizeDialogIcon.propTypes = {style: PropTypes.object}; MinimizeDialogIcon.propTypes = {style: PropTypes.object};
export const AWSIcon = ({style})=><ExternalIcon Icon={AWS} style={{height: '1.4rem', ...style}} />; export const AWSIcon = ({style})=><ExternalIcon Icon={AWS} style={{height: '1.4rem', ...style}} data-label="AWSIcon" />;
AWSIcon.propTypes = {style: PropTypes.object}; AWSIcon.propTypes = {style: PropTypes.object};

View File

@ -40,13 +40,13 @@ const useStyles = makeStyles((theme)=>({
} }
})); }));
export default function Loader({message, style}) { export default function Loader({message, style, ...props}) {
const classes = useStyles(); const classes = useStyles();
if(!message) { if(!message) {
return <></>; return <></>;
} }
return ( return (
<Box className={classes.root} style={style}> <Box className={classes.root} style={style} data-label="loader" {...props}>
<Box className={classes.loaderRoot}> <Box className={classes.loaderRoot}>
<CircularProgress className={classes.loader} /> <CircularProgress className={classes.loader} />
<Typography className={classes.message}>{message}</Typography> <Typography className={classes.message}>{message}</Typography>

View File

@ -24,6 +24,7 @@ const useStyles = makeStyles((theme)=>({
}, },
'& .szh-menu__divider': { '& .szh-menu__divider': {
margin: 0, margin: 0,
background: theme.otherVars.borderColor,
} }
}, },
menuItem: { 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 classes = useStyles();
const state = open ? 'open' : 'closed';
props.anchorRef?.current?.setAttribute('data-state', state);
return ( return (
<ControlledMenu <ControlledMenu
state={open ? 'open' : 'closed'} state={state}
{...props} {...props}
className={clsx(classes.menu, className)} className={clsx(classes.menu, className)}
aria-label={label || 'Menu'}
data-state={state}
/> />
); );
} }
@ -61,6 +66,8 @@ export function PgMenu({open, className, ...props}) {
PgMenu.propTypes = { PgMenu.propTypes = {
open: PropTypes.bool, open: PropTypes.bool,
className: CustomPropTypes.className, className: CustomPropTypes.className,
label: PropTypes.string,
anchorRef: CustomPropTypes.ref,
}; };
export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false, accesskey, shortcut, children, ...props})=>{ 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); props.onClick(e);
}; };
} }
return <MenuItem {...props} onClick={onClick} className={classes.menuItem}> const dataLabel = typeof(children) == 'string' ? children : undefined;
return <MenuItem {...props} onClick={onClick} className={classes.menuItem} data-label={dataLabel} data-checked={checked}>
{hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} />} {hasCheck && <CheckIcon className={classes.checkIcon} style={checked ? {} : {visibility: 'hidden'}} />}
{children} {children}
{(shortcut || accesskey) && <div className={classes.shortcut}>({shortcutToString(shortcut, accesskey)})</div>} {(shortcut || accesskey) && <div className={classes.shortcut}>({shortcutToString(shortcut, accesskey)})</div>}

View File

@ -53,18 +53,23 @@ function initConnection(api, params, passdata) {
} }
function setPanelTitle(panel, title, qtState, dirty=false) { function setPanelTitle(panel, title, qtState, dirty=false) {
if(title) { if(qtState.current_file) {
title =title.split('\\').pop().split('/').pop();
} else if(qtState.current_file) {
title = qtState.current_file.split('\\').pop().split('/').pop(); title = qtState.current_file.split('\\').pop().split('/').pop();
} else { } else if (!qtState.is_new_tab) {
title = qtState.params.title || 'Untitled'; if(!title) {
title = panel.$titleText?.[0].textContent;
if(panel.is_dirty_editor) {
// remove asterisk
title = title.slice(0, -1);
}
}
} }
title = title + (dirty ? '*': ''); title = title + (dirty ? '*': '');
if (qtState.is_new_tab) { if (qtState.is_new_tab) {
window.document.title = title; window.document.title = title;
} else { } else {
panel.is_dirty_editor = dirty;
setQueryToolDockerTitle(panel, true, title, qtState.current_file ? true : false); setQueryToolDockerTitle(panel, true, title, qtState.current_file ? true : false);
} }
} }
@ -109,6 +114,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
const setQtState = (state)=>{ const setQtState = (state)=>{
_setQtState((prev)=>({...prev,...evalFunc(null, state, prev)})); _setQtState((prev)=>({...prev,...evalFunc(null, state, prev)}));
}; };
const isDirtyRef = useRef(false); // usefull when conn change.
const eventBus = useRef(eventBusObj || (new EventBus())); const eventBus = useRef(eventBusObj || (new EventBus()));
const docker = useRef(null); const docker = useRef(null);
const api = useMemo(()=>getApiInstance(), []); 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.LOAD_FILE_DONE, fileDone],
[QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileDone], [QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileDone],
[QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{ [QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{
isDirtyRef.current = isDirty;
if(qtState.params.is_query_tool) { if(qtState.params.is_query_tool) {
setPanelTitle(panel, null, qtState, isDirty); setPanelTitle(panel, null, qtState, isDirty);
} }
@ -511,6 +518,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
obtaining_conn: false, obtaining_conn: false,
}; };
}); });
setPanelTitle(panel, connectionData.title, qtState, isDirtyRef.current);
let msg = `${connectionData['server_name']}/${connectionData['database_name']} - Database connected`; let msg = `${connectionData['server_name']}/${connectionData['database_name']} - Database connected`;
Notifier.success(msg); Notifier.success(msg);
resolve(); resolve();

View File

@ -186,7 +186,7 @@ export function TextEditor({row, column, onRowChange, onClose}) {
<Portal container={document.body}> <Portal container={document.body}>
<Box ref={(ele)=>{ <Box ref={(ele)=>{
setEditorPosition(getCellElement(column.idx), ele); setEditorPosition(getCellElement(column.idx), ele);
}} className={classes.textEditor}> }} className={classes.textEditor} data-label="pg-editor">
<textarea ref={autoFocusAndSelect} className={classes.textarea} value={localVal} onChange={onChange} /> <textarea ref={autoFocusAndSelect} className={classes.textarea} value={localVal} onChange={onChange} />
<Box display="flex" justifyContent="flex-end"> <Box display="flex" justifyContent="flex-end">
<DefaultButton startIcon={<CloseIcon />} onClick={()=>onClose(false)} size="small"> <DefaultButton startIcon={<CloseIcon />} onClick={()=>onClose(false)} size="small">
@ -287,7 +287,7 @@ export function CheckboxEditor({row, column, onRowChange, onClose}) {
className = 'intermediate'; className = 'intermediate';
} }
return ( return (
<div onClick={changeValue} tabIndex="0" onBlur={onBlur}> <div onClick={changeValue} tabIndex="0" onBlur={onBlur} data-label="pg-checkbox-editor">
<span className={clsx(classes.check, className)}></span> <span className={clsx(classes.check, className)}></span>
</div> </div>
); );
@ -334,7 +334,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
<Portal container={document.body}> <Portal container={document.body}>
<Box ref={(ele)=>{ <Box ref={(ele)=>{
setEditorPosition(getCellElement(column.idx), ele); setEditorPosition(getCellElement(column.idx), ele);
}} className={classes.jsonEditor}> }} className={classes.jsonEditor} data-label="pg-editor">
<JsonEditor <JsonEditor
value={localVal} value={localVal}
options={{ options={{

View File

@ -8,7 +8,7 @@
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
import { Box, makeStyles } from '@material-ui/core'; import { Box, makeStyles } from '@material-ui/core';
import _ from 'lodash'; import _ from 'lodash';
import React, {useState, useEffect, useContext, useRef} from 'react'; import React, {useState, useEffect, useContext, useRef, useLayoutEffect} from 'react';
import ReactDataGrid, {Row, useRowSelection} from 'react-data-grid'; import ReactDataGrid, {Row, useRowSelection} from 'react-data-grid';
import LockIcon from '@material-ui/icons/Lock'; import LockIcon from '@material-ui/icons/Lock';
import EditIcon from '@material-ui/icons/Edit'; import EditIcon from '@material-ui/icons/Edit';
@ -130,23 +130,42 @@ CustomRow.propTypes = {
viewportColumns: PropTypes.array, viewportColumns: PropTypes.array,
}; };
function SelectAllHeaderRenderer(props) { function getCopyShortcutHandler(handleCopy) {
return (e)=>{
if((e.ctrlKey || e.metaKey) && e.key !== 'Control' && e.keyCode == 67) {
handleCopy();
}
};
}
function SelectAllHeaderRenderer({onAllRowsSelectionChange, isCellSelected}) {
const [checked, setChecked] = useState(false); const [checked, setChecked] = useState(false);
const cellRef = useRef();
const eventBus = useContext(QueryToolEventsContext); const eventBus = useContext(QueryToolEventsContext);
const dataGridExtras = useContext(DataGridExtrasContext);
const onClick = ()=>{ const onClick = ()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, true, ()=>{ eventBus.fireEvent(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, true, ()=>{
setChecked(!checked); setChecked(!checked);
props.onAllRowsSelectionChange(!checked); onAllRowsSelectionChange(!checked);
}); });
}; };
return <div style={{widht: '100%', height: '100%'}} onClick={onClick}></div>;
useLayoutEffect(() => {
if (!isCellSelected) return;
cellRef.current?.focus({ preventScroll: true });
}, [isCellSelected]);
return <div ref={cellRef} style={{width: '100%', height: '100%'}} onClick={onClick}
tabIndex="0" onKeyDown={getCopyShortcutHandler(dataGridExtras.handleCopy)}></div>;
} }
SelectAllHeaderRenderer.propTypes = { SelectAllHeaderRenderer.propTypes = {
onAllRowsSelectionChange: PropTypes.func, onAllRowsSelectionChange: PropTypes.func,
isCellSelected: PropTypes.bool,
}; };
function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsChange, isCellSelected}) { function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsChange, isCellSelected}) {
const classes = useStyles(); const classes = useStyles();
const cellRef = useRef();
const eventBus = useContext(QueryToolEventsContext); const eventBus = useContext(QueryToolEventsContext);
const dataGridExtras = useContext(DataGridExtrasContext); const dataGridExtras = useContext(DataGridExtrasContext);
@ -168,11 +187,17 @@ function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsCha
const isSelected = selectedColumns.has(column.idx); const isSelected = selectedColumns.has(column.idx);
useLayoutEffect(() => {
if (!isCellSelected) return;
cellRef.current?.focus({ preventScroll: true });
}, [isCellSelected]);
return ( return (
<Box className={clsx(classes.columnHeader, isSelected ? classes.colHeaderSelected : null)} onClick={onClick}> <Box ref={cellRef} className={clsx(classes.columnHeader, isSelected ? classes.colHeaderSelected : null)} onClick={onClick} tabIndex="0"
onKeyDown={getCopyShortcutHandler(dataGridExtras.handleCopy)} data-column-key={column.key}>
{(column.column_type_internal == 'geometry' || column.column_type_internal == 'geography') && {(column.column_type_internal == 'geometry' || column.column_type_internal == 'geography') &&
<Box> <Box>
<PgIconButton title={gettext('View all geometries in this column')} icon={<MapIcon />} size="small" style={{marginRight: '0.25rem'}} onClick={(e)=>{ <PgIconButton title={gettext('View all geometries in this column')} icon={<MapIcon data-label="MapIcon"/>} size="small" style={{marginRight: '0.25rem'}} onClick={(e)=>{
e.stopPropagation(); e.stopPropagation();
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, column); eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, column);
}}/> }}/>
@ -182,8 +207,8 @@ function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsCha
<span>{column.display_type}</span> <span>{column.display_type}</span>
</Box> </Box>
<Box marginLeft="4px">{column.can_edit ? <Box marginLeft="4px">{column.can_edit ?
<EditIcon fontSize="small" style={{fontSize: '0.875rem'}} />: <EditIcon fontSize="small" style={{fontSize: '0.875rem'}} data-label="EditIcon"/>:
<LockIcon fontSize="small" style={{fontSize: '0.875rem'}} /> <LockIcon fontSize="small" style={{fontSize: '0.875rem'}} data-label="LockIcon"/>
}</Box> }</Box>
</Box> </Box>
); );
@ -372,7 +397,7 @@ export default function QueryToolDataGrid({columns, rows, totalRowCount, dataCha
} }
return ( return (
<DataGridExtrasContext.Provider value={{onSelectedCellChange}}> <DataGridExtrasContext.Provider value={{onSelectedCellChange, handleCopy}}>
<ReactDataGrid <ReactDataGrid
id="datagrid" id="datagrid"
columns={readyColumns} columns={readyColumns}

View File

@ -537,6 +537,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={saveAsMenuRef} anchorRef={saveAsMenuRef}
open={openMenuName=='menu-saveas'} open={openMenuName=='menu-saveas'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('File Menu')}
> >
<PgMenuItem onClick={()=>{saveFile(true);}}>{gettext('Save as')}</PgMenuItem> <PgMenuItem onClick={()=>{saveFile(true);}}>{gettext('Save as')}</PgMenuItem>
</PgMenu> </PgMenu>
@ -544,6 +545,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={editMenuRef} anchorRef={editMenuRef}
open={openMenuName=='menu-edit'} open={openMenuName=='menu-edit'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('Edit Menu')}
> >
<PgMenuItem shortcut={FIXED_PREF.find} <PgMenuItem shortcut={FIXED_PREF.find}
onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, false);}}>{gettext('Find')}</PgMenuItem> onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.EDITOR_FIND_REPLACE, false);}}>{gettext('Find')}</PgMenuItem>
@ -567,6 +569,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={filterMenuRef} anchorRef={filterMenuRef}
open={openMenuName=='menu-filter'} open={openMenuName=='menu-filter'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('Filter Options Menu')}
> >
<PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, true);}}>{gettext('Filter by Selection')}</PgMenuItem> <PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, true);}}>{gettext('Filter by Selection')}</PgMenuItem>
<PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, false);}}>{gettext('Exclude by Selection')}</PgMenuItem> <PgMenuItem onClick={()=>{eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_INCLUDE_EXCLUDE_FILTER, false);}}>{gettext('Exclude by Selection')}</PgMenuItem>
@ -576,6 +579,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={autoCommitMenuRef} anchorRef={autoCommitMenuRef}
open={openMenuName=='menu-autocommit'} open={openMenuName=='menu-autocommit'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('Execute Options Menu')}
> >
<PgMenuItem hasCheck value="auto_commit" checked={checkedMenuItems['auto_commit']} <PgMenuItem hasCheck value="auto_commit" checked={checkedMenuItems['auto_commit']}
onClick={checkMenuClick}>{gettext('Auto commit?')}</PgMenuItem> onClick={checkMenuClick}>{gettext('Auto commit?')}</PgMenuItem>
@ -586,6 +590,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={explainMenuRef} anchorRef={explainMenuRef}
open={openMenuName=='menu-explain'} open={openMenuName=='menu-explain'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('Explain Options Menu')}
> >
<PgMenuItem hasCheck value="explain_verbose" checked={checkedMenuItems['explain_verbose']} <PgMenuItem hasCheck value="explain_verbose" checked={checkedMenuItems['explain_verbose']}
onClick={checkMenuClick}>{gettext('Verbose')}</PgMenuItem> onClick={checkMenuClick}>{gettext('Verbose')}</PgMenuItem>
@ -604,6 +609,7 @@ export function MainToolBar({containerRef, onFilterClick, onManageMacros}) {
anchorRef={macrosMenuRef} anchorRef={macrosMenuRef}
open={openMenuName=='menu-macros'} open={openMenuName=='menu-macros'}
onClose={onMenuClose} onClose={onMenuClose}
label={gettext('Macros Menu')}
> >
<PgMenuItem onClick={onManageMacros}>{gettext('Manage macros')}</PgMenuItem> <PgMenuItem onClick={onManageMacros}>{gettext('Manage macros')}</PgMenuItem>
<PgMenuDivider /> <PgMenuDivider />

View File

@ -47,10 +47,10 @@ export function Notifications() {
<tbody> <tbody>
{notices.map((notice, i)=>{ {notices.map((notice, i)=>{
return <tr key={i}> return <tr key={i}>
<td>{notice.recorded_time}</td> <td data-label="recorded_time">{notice.recorded_time}</td>
<td>{notice.channel}</td> <td data-label="channel">{notice.channel}</td>
<td>{notice.pid}</td> <td data-label="pid">{notice.pid}</td>
<td>{notice.payload}</td> <td data-label="payload">{notice.payload}</td>
</tr>; </tr>;
})} })}
</tbody> </tbody>

View File

@ -239,11 +239,11 @@ class QueryHistoryUtils {
function QuerySourceIcon({source}) { function QuerySourceIcon({source}) {
switch(JSON.stringify(source)) { switch(JSON.stringify(source)) {
case JSON.stringify(QuerySources.EXECUTE): case JSON.stringify(QuerySources.EXECUTE):
return <PlayArrowRoundedIcon style={{marginLeft: '-4px'}}/>; return <PlayArrowRoundedIcon style={{marginLeft: '-4px'}} data-label="ExecuteIcon" />;
case JSON.stringify(QuerySources.EXPLAIN): case JSON.stringify(QuerySources.EXPLAIN):
return <ExplicitRoundedIcon/>; return <ExplicitRoundedIcon data-label="ExplainIcon" />;
case JSON.stringify(QuerySources.EXPLAIN_ANALYZE): case JSON.stringify(QuerySources.EXPLAIN_ANALYZE):
return <AssessmentRoundedIcon/>; return <AssessmentRoundedIcon data-label="ExplainAnalyzeIcon" />;
case JSON.stringify(QuerySources.COMMIT): case JSON.stringify(QuerySources.COMMIT):
return <CommitIcon style={{marginLeft: '-4px'}}/>; return <CommitIcon style={{marginLeft: '-4px'}}/>;
case JSON.stringify(QuerySources.ROLLBACK): case JSON.stringify(QuerySources.ROLLBACK):
@ -262,7 +262,7 @@ QuerySourceIcon.propTypes = {
function HistoryEntry({entry, formatEntryDate, itemKey, selectedItemKey, onClick}) { function HistoryEntry({entry, formatEntryDate, itemKey, selectedItemKey, onClick}) {
const classes = useStyles(); const classes = useStyles();
return <ListItem tabIndex="0" ref={(ele)=>{ return <ListItem tabIndex="0" data-label="history-entry" data-pgadmin={entry.is_pgadmin_query} ref={(ele)=>{
selectedItemKey==itemKey && ele && ele.scrollIntoView({ selectedItemKey==itemKey && ele && ele.scrollIntoView({
block: 'center', block: 'center',
behavior: 'smooth', behavior: 'smooth',
@ -324,7 +324,7 @@ function QueryHistoryDetails({entry}) {
return ( return (
<> <>
{entry.info && <Box className={classes.infoHeader}>{entry.info}</Box>} {entry.info && <Box className={classes.infoHeader}>{entry.info}</Box>}
<Box padding="0.5rem"> <Box padding="0.5rem" data-label="history-detail">
<Grid container> <Grid container>
<Grid item sm={4}>{entry.start_time.toLocaleDateString() + ' ' + entry.start_time.toLocaleTimeString()}</Grid> <Grid item sm={4}>{entry.start_time.toLocaleDateString() + ' ' + entry.start_time.toLocaleTimeString()}</Grid>
<Grid item sm={4}>{entry?.row_affected > 0 && entry.row_affected}</Grid> <Grid item sm={4}>{entry?.row_affected > 0 && entry.row_affected}</Grid>

View File

@ -1225,7 +1225,7 @@ export function ResultSet() {
return ( return (
<Box className={classes.root} ref={containerRef} tabIndex="0"> <Box className={classes.root} ref={containerRef} tabIndex="0">
<Loader message={loaderText} /> <Loader message={loaderText} />
<Loader message={isLoadingMore ? gettext('Loading more rows...') : null} style={{top: 'unset', right: 'unset', padding: '0.5rem 1rem'}}/> <Loader data-label="loader-more-rows" message={isLoadingMore ? gettext('Loading more rows...') : null} style={{top: 'unset', right: 'unset', padding: '0.5rem 1rem'}}/>
{!queryData && {!queryData &&
<EmptyPanelMessage text={gettext('No data output. Execute a query to get output.')}/> <EmptyPanelMessage text={gettext('No data output. Execute a query to get output.')}/>
} }

View File

@ -163,6 +163,7 @@ export function ResultSetToolbar({containerRef, canEdit, totalRowCount}) {
anchorRef={copyMenuRef} anchorRef={copyMenuRef}
open={menuOpenId=='menu-copyheader'} open={menuOpenId=='menu-copyheader'}
onClose={handleMenuClose} onClose={handleMenuClose}
label={gettext('Copy Options Menu')}
> >
<PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>{gettext('Copy with headers')}</PgMenuItem> <PgMenuItem hasCheck value="copy_with_headers" checked={checkedMenuItems['copy_with_headers']} onClick={checkMenuClick}>{gettext('Copy with headers')}</PgMenuItem>
</PgMenu> </PgMenu>

View File

@ -108,105 +108,127 @@ class NavMenuLocators:
"//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') " \ "//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') " \
"and contains(.,'{}')]" "and contains(.,'{}')]"
rcdock_tab = "div.dock-tab-btn[id$='{0}']"
process_watcher_error_close_xpath = \ process_watcher_error_close_xpath = \
".btn.btn-sm-sq.btn-primary.pg-bg-close > i" ".btn.btn-sm-sq.btn-primary.pg-bg-close > i"
class QueryToolLocators: class QueryToolLocators:
btn_save_file = "#btn-save-file" btn_save_file = "button[data-label='Save File']"
btn_save_data = "#btn-save-data" btn_save_data = "button[data-label='Save Data Changes']"
btn_query_dropdown = "#btn-query-dropdown" btn_query_dropdown = "button[data-label='Execute options']"
btn_auto_rollback = "#btn-auto-rollback" btn_auto_rollback = "li[data-label='Auto rollback on error?']"
btn_auto_rollback_check_status = "#btn-auto-rollback > i" btn_auto_rollback_check_status = "#btn-auto-rollback > i"
btn_auto_commit = "#btn-auto-commit" btn_auto_commit = "li[data-label='Auto commit?']"
btn_auto_commit_check_status = "#btn-auto-commit > i" btn_auto_commit_check_status = "#btn-auto-commit > i"
btn_cancel_query = "#btn-cancel-query" btn_cancel_query = "button[data-label='Cancel query']"
btn_explain = "#btn-explain" btn_explain = "button[data-label='Explain']"
btn_explain_analyze = "#btn-explain-analyze" btn_explain_analyze = "button[data-label='Explain Analyze']"
btn_explain_options_dropdown = "#btn-explain-options-dropdown" btn_explain_options_dropdown = "button[data-label='Explain Settings']"
btn_explain_verbose = "#btn-explain-verbose" btn_explain_verbose = "li[data-label='Verbose']"
btn_explain_costs = "#btn-explain-costs" btn_explain_costs = "li[data-label='Costs']"
btn_explain_buffers = "#btn-explain-buffers" btn_explain_buffers = "li[data-label='Buffers']"
btn_explain_timing = "#btn-explain-timing" btn_explain_timing = "li[data-label='Timing']"
btn_clear_dropdown = "#btn-clear-dropdown" btn_edit_dropdown = "button[data-label='Edit']"
btn_clear_history = "#btn-clear-history" btn_clear_history = "#btn-clear-history"
btn_clear = "#btn-clear" btn_clear = "li[data-label='Clear Query']"
query_editor_panel = "#output-panel" btn_add_row = "button[data-label='Add row']"
query_history_selected = "#query_list .selected" query_tool_menu = "ul[aria-label='{0}']"
query_history_entries = "#query_list>.query-group>ul>li" query_editor_panel = "#id-query"
query_history_selected = \
"#id-history li[data-label='history-entry'].Mui-selected"
query_history_entries = "#id-history li[data-label='history-entry']"
query_history_specific_entry = \ query_history_specific_entry = \
"#query_list>.query-group>ul>li:nth-child({})" "#id-history li[data-label='history-entry']:nth-child({0})"
query_history_detail = "#query_detail" query_history_detail = "#id-history div[data-label='history-detail']"
invalid_query_history_entry_css = "#query_list .entry.error .query" query_history_selected_icon = query_history_selected + ' svg'
invalid_query_history_entry_css = \
"#id-history li[data-label='history-entry'][class*='itemError']"
explain_details = "#id-explain div[data-label='explain-details']"
editor_panel = "#output-panel" editor_panel = "#output-panel"
query_messages_panel = ".sql-editor-message" query_messages_panel = "#id-messages"
output_row_xpath = "//div[contains(@class, 'slick-row')][{}]/*[1]" output_row = "#id-dataoutput div.rdg-row[aria-rowindex={0}]"
output_column_header_css = "[data-column-id='{}']" output_row_col = "#id-dataoutput div.rdg-row[aria-rowindex='{0}']" \
" div.rdg-cell[aria-colindex='{1}']"
output_column_data_xpath = "//div[contains(@class, 'slick-cell')]" \ output_column_header_css = \
"#id-dataoutput div.rdg-cell div[data-column-key='{0}']"
output_column_data_xpath = "//div[contains(@class, 'rdg-cell')]" \
"[contains(., '{}')]" "[contains(., '{}')]"
output_cell_xpath = "//div[contains(@class, 'slick-cell') and " \ output_row_xpath = "//div[@aria-rowindex='{0}']"
"contains(@class, 'l{0} r{1}')]" output_cell_xpath = "//div[@aria-rowindex='{0}']/div[@aria-colindex='{1}']"
select_all_column = \ select_all_column = \
"//div[contains(@id,'row-header-column')]" "//div[@role='columnheader'][@aria-colindex='1']"
new_row_xpath = "//div[contains(@class, 'new-row')]" new_row_xpath = "//div[contains(@class, 'new-row')]"
scratch_pad_css = ".sql-scratch > textarea" scratch_pad_css = "#id-scratch textarea"
copy_button_css = "#btn-copy-row" copy_button_css = "#id-dataoutput button[data-label='Copy']"
paste_button_css = "#btn-paste-row" copy_options_css = "#id-dataoutput button[data-label='Copy options']"
row_editor_text_area_css = ".pg-text-editor > textarea" copy_headers_btn_css = "li[data-label='Copy with headers']"
paste_button_css = "#id-dataoutput button[data-label='Paste']"
row_editor_text_area_css = "div[data-label='pg-editor'] textarea"
json_editor_text_area_css = \ json_editor_text_area_css = \
"div.ace_layer.ace_text-layer .ace_line_group .ace_line" "div.ace_layer.ace_text-layer .ace_line_group .ace_line"
text_editor_ok_btn_css = ".btn.btn-primary.long_text_editor" row_editor_checkbox_css = "div[data-label='pg-checkbox-editor']"
btn_load_file_css = "#btn-load-file" text_editor_ok_btn_css = \
"div[data-label='pg-editor'] button[data-label='OK']"
btn_execute_query_css = "#btn-flash" btn_load_file_css = "button[data-label='Open File']"
btn_execute_query_css = "button[data-label='Execute/Refresh']"
input_file_path_css = "input#file-input-path" input_file_path_css = "input#file-input-path"
select_file_content_css = "table#contents" select_file_content_css = "table#contents"
query_output_canvas_css = "#datagrid .slick-viewport .grid-canvas" query_output_canvas_css = "#id-dataoutput .rdg"
query_output_cells = ".slick-cell" query_output_cells = ".rdg-cell[role='gridcell']"
sql_editor_message = "//div[contains(@class, 'sql-editor-message') and " \ sql_editor_message = "//div[@id='id-messages'][contains(string(), '{}')]"
"contains(string(), '{}')]"
code_mirror_hint_box_xpath = "//ul[@class='CodeMirror-hints default']" code_mirror_hint_box_xpath = "//ul[@class='CodeMirror-hints default']"
@ -215,32 +237,19 @@ class QueryToolLocators:
code_mirror_data_xpath = "//pre[@class=' CodeMirror-line ']/span" code_mirror_data_xpath = "//pre[@class=' CodeMirror-line ']/span"
save_data_icon = "icon-save-data-changes" btn_commit = "button[data-label='Commit']"
commit_icon = "icon-commit" btn_history_remove_all = "#id-history button[data-label='Remove All']"
execute_icon = "fa-play"
explain_icon = "fa-hand-pointer"
explain_analyze_icon = "fa-list-alt"
query_history_selected_icon = '#query_list .selected #query_source_icon'
btn_commit = "#btn-commit"
show_query_internally_btn = \ show_query_internally_btn = \
"//div[label[contains(normalize-space(text())," \ "//div[contains(normalize-space(text())," \
"'Show queries generated internally by')]]//" \ "'Show queries generated internally by')]/span/span[1]"
"div[contains(@class,'toggle btn')]"
editable_column_icon_xpath = "//div[contains(@class," \ editable_column_icon_xpath = \
" 'editable-column-header-icon')]" \ "//div[@role='columnheader']/div/div/*[@data-label='EditIcon']"
"/i[contains(@class, 'fa-pencil-alt')]"
read_only_column_icon_xpath = "//div[contains(@class," \ read_only_column_icon_xpath = \
" 'editable-column-header-icon')]" \ "//div[@role='columnheader']/div/div/*[@data-label='LockIcon']"
"/i[contains(@class, 'fa-lock')]"
class ConnectToServerDiv: class ConnectToServerDiv:

View File

@ -87,9 +87,9 @@ class PgadminPage:
# In case of react dialog we use different xpath # In case of react dialog we use different xpath
if react_dialog: if react_dialog:
modal_button = self.find_by_xpath( modal_button = self.find_by_css_selector(
"//button[contains(@class,'MuiButtonBase-root')]" ".react-draggable button[data-label='{0}']"
"//span[text()='%s']" % button_text) .format(button_text))
else: else:
modal_button = self.find_by_xpath( modal_button = self.find_by_xpath(
"//div[contains(@class, 'alertify') and " "//div[contains(@class, 'alertify') and "
@ -203,9 +203,7 @@ class PgadminPage:
self.driver.switch_to.frame( self.driver.switch_to.frame(
self.driver.find_elements(By.TAG_NAME, "iframe")[0]) self.driver.find_elements(By.TAG_NAME, "iframe")[0])
time.sleep(.5) time.sleep(.5)
self.click_element(self.find_by_xpath( self.find_by_css_selector("button[data-test='dont-save']").click()
'//button[contains(@class, "ajs-button") and '
'contains(.,"Don\'t save")]'))
if self.check_if_element_exist_by_xpath( if self.check_if_element_exist_by_xpath(
"//button[text()='Rollback']", 1): "//button[text()='Rollback']", 1):
@ -214,15 +212,22 @@ class PgadminPage:
self.driver.switch_to.default_content() self.driver.switch_to.default_content()
def clear_query_tool(self): def clear_query_tool(self):
self.click_element( retry = 3
self.find_by_css_selector(QueryToolLocators.btn_clear_dropdown) edit_options = self.find_by_css_selector(
) QueryToolLocators.btn_edit_dropdown)
while retry > 0:
self.click_element(edit_options)
time.sleep(0.3)
if edit_options.get_attribute("data-state") == "open":
break
else:
retry -= 1
ActionChains(self.driver).move_to_element( ActionChains(self.driver).move_to_element(
self.find_by_css_selector(QueryToolLocators.btn_clear)).perform() self.find_by_css_selector(QueryToolLocators.btn_clear)).perform()
self.click_element( self.click_element(
self.find_by_css_selector(QueryToolLocators.btn_clear) self.find_by_css_selector(QueryToolLocators.btn_clear)
) )
self.driver.switch_to.default_content()
self.click_modal('Yes', True) self.click_modal('Yes', True)
def execute_query(self, query): def execute_query(self, query):
@ -230,90 +235,76 @@ class PgadminPage:
self.click_execute_query_button() self.click_execute_query_button()
def click_execute_query_button(self, timeout=20): def click_execute_query_button(self, timeout=20):
retry = 5
execute_button = self.find_by_css_selector( execute_button = self.find_by_css_selector(
QueryToolLocators.btn_execute_query_css) QueryToolLocators.btn_execute_query_css)
first_click = execute_button.get_attribute('data-click-counter')
while retry > 0:
execute_button.click() execute_button.click()
execute_button = self.find_by_css_selector(
QueryToolLocators.btn_execute_query_css)
second_click = execute_button.get_attribute(
'data-click-counter')
if first_click != second_click:
self.wait_for_query_tool_loading_indicator_to_appear()
break
else:
retry -= 1
self.wait_for_query_tool_loading_indicator_to_disappear(timeout) self.wait_for_query_tool_loading_indicator_to_disappear(timeout)
def check_execute_option(self, option): def check_execute_option(self, option):
""""This function will check auto commit or auto roll back based on """"This function will check auto commit or auto roll back based on
user input. If button is already checked, no action will be taken""" user input. If button is already checked, no action will be taken"""
query_options = self.driver.find_element( menu_btn = self.driver.find_element_by_css_selector(
By.CSS_SELECTOR, QueryToolLocators.btn_query_dropdown) QueryToolLocators.btn_query_dropdown)
expanded = query_options.get_attribute("aria-expanded") if menu_btn.get_attribute('data-state') == "closed":
if expanded == "false": menu_btn.click()
query_options.click()
def update_execute_option_setting( def update_execute_option_setting(css_selector_of_option):
css_selector_of_option_status, css_selector_of_option,):
retry = 3 retry = 3
check_status = self.driver.find_element( menu_option = self.driver.find_element_by_css_selector(
By.CSS_SELECTOR, css_selector_of_option_status) css_selector_of_option)
if 'visibility-hidden' in check_status.get_attribute('class'): if menu_option.get_attribute('data-checked') == 'false':
while retry > 0: while retry > 0:
self.find_by_css_selector(css_selector_of_option).click() menu_option.click()
time.sleep(0.2) time.sleep(0.2)
if 'visibility-hidden' not in \ if menu_option.get_attribute('data-checked') == 'true':
check_status.get_attribute('class'):
break break
else: else:
retry -= 1 retry -= 1
if option == 'auto_commit': if option == 'auto_commit':
update_execute_option_setting( update_execute_option_setting(
QueryToolLocators.btn_auto_commit_check_status,
QueryToolLocators.btn_auto_commit) QueryToolLocators.btn_auto_commit)
if option == 'auto_rollback': if option == 'auto_rollback':
update_execute_option_setting( update_execute_option_setting(
QueryToolLocators.btn_auto_rollback_check_status,
QueryToolLocators.btn_auto_rollback) QueryToolLocators.btn_auto_rollback)
if menu_btn.get_attribute('data-state') == "open":
menu_btn.click()
def uncheck_execute_option(self, option): def uncheck_execute_option(self, option):
""""This function will uncheck auto commit or auto roll back based on """"This function will uncheck auto commit or auto roll back based on
user input. If button is already unchecked, no action will be taken""" user input. If button is already unchecked, no action will be taken"""
query_options = self.driver.find_element_by_css_selector( menu = self.driver.find_element_by_css_selector(
QueryToolLocators.btn_query_dropdown) QueryToolLocators.query_tool_menu.format('Execute Options Menu'))
expanded = query_options.get_attribute("aria-expanded")
if expanded == "false":
query_options.click()
def update_execute_option_setting( if menu.get_attribute('data-state') == "closed":
css_selector_of_option_status, css_selector_of_option): self.driver.find_element_by_css_selector(
QueryToolLocators.btn_query_dropdown).click()
def update_execute_option_setting(css_selector_of_option):
retry = 3 retry = 3
check_status = self.driver.find_element_by_css_selector( menu_option = self.driver.find_element_by_css_selector(
css_selector_of_option_status) css_selector_of_option)
if 'visibility-hidden' not in check_status.get_attribute('class'): if menu_option.get_attribute('data-checked') == 'true':
while retry > 0: while retry > 0:
self.find_by_css_selector( menu_option.click()
css_selector_of_option).click()
time.sleep(0.2) time.sleep(0.2)
if 'visibility-hidden' in \ if menu_option.get_attribute('data-checked') == 'false':
check_status.get_attribute('class'):
break break
else: else:
retry -= 1 retry -= 1
if option == 'auto_commit': if option == 'auto_commit':
update_execute_option_setting( update_execute_option_setting(
QueryToolLocators.btn_auto_commit_check_status,
QueryToolLocators.btn_auto_commit) QueryToolLocators.btn_auto_commit)
if option == 'auto_rollback': if option == 'auto_rollback':
update_execute_option_setting( update_execute_option_setting(
QueryToolLocators.btn_auto_rollback_check_status,
QueryToolLocators.btn_auto_rollback) QueryToolLocators.btn_auto_rollback)
if menu.get_attribute('data-state') == "open":
self.driver.find_element_by_css_selector(
QueryToolLocators.btn_query_dropdown).click()
def close_data_grid(self): def close_data_grid(self):
self.driver.switch_to.default_content() self.driver.switch_to.default_content()
xpath = "//*[@id='dockerContainer']/div/div[3]/div/div[2]/div[1]" xpath = "//*[@id='dockerContainer']/div/div[3]/div/div[2]/div[1]"
@ -331,6 +322,7 @@ class PgadminPage:
self.click_element(object_menu_item) self.click_element(object_menu_item)
delete_menu_item = self.find_by_partial_link_text("Remove Server") delete_menu_item = self.find_by_partial_link_text("Remove Server")
self.click_element(delete_menu_item) self.click_element(delete_menu_item)
self.driver.switch_to.default_content()
self.click_modal('Yes', True) self.click_modal('Yes', True)
time.sleep(1) time.sleep(1)
else: else:
@ -1054,15 +1046,15 @@ class PgadminPage:
driver.switch_to.default_content() driver.switch_to.default_content()
driver.switch_to.frame( driver.switch_to.frame(
driver.find_element_by_tag_name("iframe")) driver.find_element_by_tag_name("iframe"))
element = driver.find_element_by_css_selector( element = driver.find_element(
"#output-panel .CodeMirror") By.CSS_SELECTOR, "#sqleditor-container .CodeMirror")
if element.is_displayed() and element.is_enabled(): if element.is_displayed() and element.is_enabled():
return element return element
except (NoSuchElementException, WebDriverException): except (NoSuchElementException, WebDriverException):
return False return False
time.sleep(1) time.sleep(1)
self.wait_for_query_tool_loading_indicator_to_disappear(12) # self.wait_for_query_tool_loading_indicator_to_disappear(12)
retry = 2 retry = 2
while retry > 0: while retry > 0:
@ -1071,7 +1063,9 @@ class PgadminPage:
WebDriverWait(self.driver, 10).until( WebDriverWait(self.driver, 10).until(
EC.frame_to_be_available_and_switch_to_it( EC.frame_to_be_available_and_switch_to_it(
(By.TAG_NAME, "iframe"))) (By.TAG_NAME, "iframe")))
self.find_by_xpath("//a[text()='Query Editor']").click() self.find_by_css_selector(
"div.dock-tab-btn[id$=\"id-query\"]").click()
# self.find_by_xpath("//div[text()='Query Editor']").click()
codemirror_ele = WebDriverWait( codemirror_ele = WebDriverWait(
self.driver, timeout=self.timeout, poll_frequency=0.01) \ self.driver, timeout=self.timeout, poll_frequency=0.01) \
@ -1099,11 +1093,16 @@ class PgadminPage:
"arguments[0].CodeMirror.lineCount(),0);", "arguments[0].CodeMirror.lineCount(),0);",
codemirror_ele, field_content) codemirror_ele, field_content)
def click_tab(self, tab_name): def click_tab(self, tab_name, rc_dock=False):
if rc_dock:
tab = self.find_by_css_selector(
NavMenuLocators.rcdock_tab.format(tab_name))
self.click_element(tab)
return
WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable( WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable(
(By.XPATH, NavMenuLocators.select_tab_xpath.format(tab_name)))) (By.XPATH, NavMenuLocators.select_tab_xpath.format(tab_name))))
click_tab = True while True:
while click_tab:
tab = self.find_by_xpath( tab = self.find_by_xpath(
NavMenuLocators.select_tab_xpath.format(tab_name)) NavMenuLocators.select_tab_xpath.format(tab_name))
self.click_element(tab) self.click_element(tab)
@ -1188,15 +1187,16 @@ class PgadminPage:
self._wait_for("spinner to disappear", spinner_has_disappeared, 20) self._wait_for("spinner to disappear", spinner_has_disappeared, 20)
def wait_for_query_tool_loading_indicator_to_disappear(self, timeout=20): def wait_for_query_tool_loading_indicator_to_disappear(
self, timeout=20, container_id="id-dataoutput"):
def spinner_has_disappeared(driver): def spinner_has_disappeared(driver):
try: try:
# Refer the status message as spinner appears only on the # Refer the status message as spinner appears only on the
# the data output panel # the data output panel
spinner = driver.find_element( driver.find_element(
By.CSS_SELECTOR, By.CSS_SELECTOR,
".sql-editor .sql-editor-busy-text-status") "#{0} div[data-label='loader']".format(container_id))
return "d-none" in spinner.get_attribute("class") return False
except NoSuchElementException: except NoSuchElementException:
# wait for loading indicator disappear animation to complete. # wait for loading indicator disappear animation to complete.
time.sleep(0.5) time.sleep(0.5)
@ -1207,8 +1207,7 @@ class PgadminPage:
def wait_for_query_tool_loading_indicator_to_appear(self): def wait_for_query_tool_loading_indicator_to_appear(self):
status = self.check_if_element_exist_by_xpath( status = self.check_if_element_exist_by_xpath(
"//div[@id='editor-panel']//" "//div[@id='id-dataoutput']//div[@data-label='loader']", 1)
"div[@class='pg-sp-container sql-editor-busy-fetching']", 1)
return status return status
def wait_for_app(self): def wait_for_app(self):
@ -1340,14 +1339,15 @@ class PgadminPage:
if required_status == 'Yes': if required_status == 'Yes':
status_changed_successfully = \ status_changed_successfully = \
self.toggle_switch_box(switch_box_element, self.toggle_switch_box(
expected_attr_in_class_tag='success', switch_box_element,
unexpected_attr_in_class_tag='off') expected_attr_in_class_tag='Mui-checked',
unexpected_attr_in_class_tag='')
else: else:
status_changed_successfully = \ status_changed_successfully = \
self.toggle_switch_box(switch_box_element, self.toggle_switch_box(
expected_attr_in_class_tag='off', switch_box_element, expected_attr_in_class_tag='',
unexpected_attr_in_class_tag='success') unexpected_attr_in_class_tag='Mui-checked')
return status_changed_successfully return status_changed_successfully
def toggle_switch_box(self, switch_box_ele, expected_attr_in_class_tag, def toggle_switch_box(self, switch_box_ele, expected_attr_in_class_tag,

View File

@ -298,7 +298,7 @@ def setup_webdriver_specification(arguments):
options.add_argument("--window-size=1790,1080") options.add_argument("--window-size=1790,1080")
options.add_argument("--disable-infobars") options.add_argument("--disable-infobars")
# options.add_experimental_option('w3c', False) # options.add_experimental_option('w3c', False)
driver_local = webdriver.Chrome(chrome_options=options) driver_local = webdriver.Chrome(options=options)
# maximize browser window # maximize browser window
driver_local.maximize_window() driver_local.maximize_window()