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_row_using_keyboard_shortcut()
self._copies_column_using_keyboard_shortcut()
self._copies_rectangular_selection()
self._shift_resizes_rectangular_selection()
# The below calls is commented since the new data grid does not
# support range selection. This can be enabled once the
# range selection is implemented.
# self._copies_rectangular_selection()
# self._shift_resizes_rectangular_selection()
self._shift_resizes_column_selection()
self._mouseup_outside_grid_still_makes_a_selection()
self._mouseup_outside_grid_does_not_make_a_selection()
self._copies_rows_with_header()
def paste_values_to_scratch_pad(self):
self.page.driver.switch_to.default_content()
self.page.driver.switch_to.frame(
self.page.driver.find_element(By.TAG_NAME, "iframe"))
scratch_pad_ele = self.page.find_by_css_selector(
QueryToolLocators.scratch_pad_css)
self.page.paste_values(scratch_pad_ele)
@ -73,7 +74,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
def _copies_rows(self):
first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1))
QueryToolLocators.output_cell_xpath.format(2, 1))
first_row.click()
copy_button = self.page.find_by_css_selector(
@ -85,8 +86,10 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
clipboard_text)
def _copies_rows_with_header(self):
self.page.find_by_css_selector('#btn-copy-row-dropdown').click()
self.page.find_by_css_selector('a#btn-copy-with-header').click()
self.page.find_by_css_selector(QueryToolLocators.copy_options_css)\
.click()
self.page.find_by_css_selector(QueryToolLocators.copy_headers_btn_css)\
.click()
select_all = self.page.find_by_xpath(
QueryToolLocators.select_all_column)
@ -122,7 +125,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
def _copies_row_using_keyboard_shortcut(self):
first_row = self.page.find_by_xpath(
QueryToolLocators.output_row_xpath.format(1))
QueryToolLocators.output_cell_xpath.format(2, 1))
first_row.click()
ActionChains(self.page.driver).key_down(
@ -216,7 +219,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
'"Some-Name"\t6\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14',
clipboard_text)
def _mouseup_outside_grid_still_makes_a_selection(self):
def _mouseup_outside_grid_does_not_make_a_selection(self):
bottom_right_cell = self.page.find_by_xpath(
QueryToolLocators.output_column_data_xpath.format('cool info')
)
@ -233,7 +236,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest):
clipboard_text = self.paste_values_to_scratch_pad()
self.assertIn('"cool info"', clipboard_text)
self.assertNotIn('"cool info"', clipboard_text)
def after(self):
self.page.close_query_tool()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,47 +24,47 @@ ExternalIcon.propTypes = {
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};
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};

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();
if(!message) {
return <></>;
}
return (
<Box className={classes.root} style={style}>
<Box className={classes.root} style={style} data-label="loader" {...props}>
<Box className={classes.loaderRoot}>
<CircularProgress className={classes.loader} />
<Typography className={classes.message}>{message}</Typography>

View File

@ -24,6 +24,7 @@ const useStyles = makeStyles((theme)=>({
},
'& .szh-menu__divider': {
margin: 0,
background: theme.otherVars.borderColor,
}
},
menuItem: {
@ -47,13 +48,17 @@ const useStyles = makeStyles((theme)=>({
}
}));
export function PgMenu({open, className, ...props}) {
export function PgMenu({open, className, label, ...props}) {
const classes = useStyles();
const state = open ? 'open' : 'closed';
props.anchorRef?.current?.setAttribute('data-state', state);
return (
<ControlledMenu
state={open ? 'open' : 'closed'}
state={state}
{...props}
className={clsx(classes.menu, className)}
aria-label={label || 'Menu'}
data-state={state}
/>
);
}
@ -61,6 +66,8 @@ export function PgMenu({open, className, ...props}) {
PgMenu.propTypes = {
open: PropTypes.bool,
className: CustomPropTypes.className,
label: PropTypes.string,
anchorRef: CustomPropTypes.ref,
};
export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false, accesskey, shortcut, children, ...props})=>{
@ -72,7 +79,8 @@ export const PgMenuItem = applyStatics(MenuItem)(({hasCheck=false, checked=false
props.onClick(e);
};
}
return <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'}} />}
{children}
{(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) {
if(title) {
title =title.split('\\').pop().split('/').pop();
} else if(qtState.current_file) {
if(qtState.current_file) {
title = qtState.current_file.split('\\').pop().split('/').pop();
} else {
title = qtState.params.title || 'Untitled';
} else if (!qtState.is_new_tab) {
if(!title) {
title = panel.$titleText?.[0].textContent;
if(panel.is_dirty_editor) {
// remove asterisk
title = title.slice(0, -1);
}
}
}
title = title + (dirty ? '*': '');
if (qtState.is_new_tab) {
window.document.title = title;
} else {
panel.is_dirty_editor = dirty;
setQueryToolDockerTitle(panel, true, title, qtState.current_file ? true : false);
}
}
@ -109,6 +114,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
const setQtState = (state)=>{
_setQtState((prev)=>({...prev,...evalFunc(null, state, prev)}));
};
const isDirtyRef = useRef(false); // usefull when conn change.
const eventBus = useRef(eventBusObj || (new EventBus()));
const docker = useRef(null);
const api = useMemo(()=>getApiInstance(), []);
@ -411,6 +417,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
[QUERY_TOOL_EVENTS.LOAD_FILE_DONE, fileDone],
[QUERY_TOOL_EVENTS.SAVE_FILE_DONE, fileDone],
[QUERY_TOOL_EVENTS.QUERY_CHANGED, (isDirty)=>{
isDirtyRef.current = isDirty;
if(qtState.params.is_query_tool) {
setPanelTitle(panel, null, qtState, isDirty);
}
@ -511,6 +518,7 @@ export default function QueryToolComponent({params, pgWindow, pgAdmin, selectedN
obtaining_conn: false,
};
});
setPanelTitle(panel, connectionData.title, qtState, isDirtyRef.current);
let msg = `${connectionData['server_name']}/${connectionData['database_name']} - Database connected`;
Notifier.success(msg);
resolve();

View File

@ -186,7 +186,7 @@ export function TextEditor({row, column, onRowChange, onClose}) {
<Portal container={document.body}>
<Box ref={(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} />
<Box display="flex" justifyContent="flex-end">
<DefaultButton startIcon={<CloseIcon />} onClick={()=>onClose(false)} size="small">
@ -287,7 +287,7 @@ export function CheckboxEditor({row, column, onRowChange, onClose}) {
className = 'intermediate';
}
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>
</div>
);
@ -334,7 +334,7 @@ export function JsonTextEditor({row, column, onRowChange, onClose}) {
<Portal container={document.body}>
<Box ref={(ele)=>{
setEditorPosition(getCellElement(column.idx), ele);
}} className={classes.jsonEditor}>
}} className={classes.jsonEditor} data-label="pg-editor">
<JsonEditor
value={localVal}
options={{

View File

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////
import { Box, makeStyles } from '@material-ui/core';
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 LockIcon from '@material-ui/icons/Lock';
import EditIcon from '@material-ui/icons/Edit';
@ -130,23 +130,42 @@ CustomRow.propTypes = {
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 cellRef = useRef();
const eventBus = useContext(QueryToolEventsContext);
const dataGridExtras = useContext(DataGridExtrasContext);
const onClick = ()=>{
eventBus.fireEvent(QUERY_TOOL_EVENTS.FETCH_MORE_ROWS, true, ()=>{
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 = {
onAllRowsSelectionChange: PropTypes.func,
isCellSelected: PropTypes.bool,
};
function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsChange, isCellSelected}) {
const classes = useStyles();
const cellRef = useRef();
const eventBus = useContext(QueryToolEventsContext);
const dataGridExtras = useContext(DataGridExtrasContext);
@ -168,11 +187,17 @@ function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsCha
const isSelected = selectedColumns.has(column.idx);
useLayoutEffect(() => {
if (!isCellSelected) return;
cellRef.current?.focus({ preventScroll: true });
}, [isCellSelected]);
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') &&
<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();
eventBus.fireEvent(QUERY_TOOL_EVENTS.TRIGGER_RENDER_GEOMETRIES, column);
}}/>
@ -182,8 +207,8 @@ function SelectableHeaderRenderer({column, selectedColumns, onSelectedColumnsCha
<span>{column.display_type}</span>
</Box>
<Box marginLeft="4px">{column.can_edit ?
<EditIcon fontSize="small" style={{fontSize: '0.875rem'}} />:
<LockIcon fontSize="small" style={{fontSize: '0.875rem'}} />
<EditIcon fontSize="small" style={{fontSize: '0.875rem'}} data-label="EditIcon"/>:
<LockIcon fontSize="small" style={{fontSize: '0.875rem'}} data-label="LockIcon"/>
}</Box>
</Box>
);
@ -372,7 +397,7 @@ export default function QueryToolDataGrid({columns, rows, totalRowCount, dataCha
}
return (
<DataGridExtrasContext.Provider value={{onSelectedCellChange}}>
<DataGridExtrasContext.Provider value={{onSelectedCellChange, handleCopy}}>
<ReactDataGrid
id="datagrid"
columns={readyColumns}

View File

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

View File

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

View File

@ -239,11 +239,11 @@ class QueryHistoryUtils {
function QuerySourceIcon({source}) {
switch(JSON.stringify(source)) {
case JSON.stringify(QuerySources.EXECUTE):
return <PlayArrowRoundedIcon style={{marginLeft: '-4px'}}/>;
return <PlayArrowRoundedIcon style={{marginLeft: '-4px'}} data-label="ExecuteIcon" />;
case JSON.stringify(QuerySources.EXPLAIN):
return <ExplicitRoundedIcon/>;
return <ExplicitRoundedIcon data-label="ExplainIcon" />;
case JSON.stringify(QuerySources.EXPLAIN_ANALYZE):
return <AssessmentRoundedIcon/>;
return <AssessmentRoundedIcon data-label="ExplainAnalyzeIcon" />;
case JSON.stringify(QuerySources.COMMIT):
return <CommitIcon style={{marginLeft: '-4px'}}/>;
case JSON.stringify(QuerySources.ROLLBACK):
@ -262,7 +262,7 @@ QuerySourceIcon.propTypes = {
function HistoryEntry({entry, formatEntryDate, itemKey, selectedItemKey, onClick}) {
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({
block: 'center',
behavior: 'smooth',
@ -324,7 +324,7 @@ function QueryHistoryDetails({entry}) {
return (
<>
{entry.info && <Box className={classes.infoHeader}>{entry.info}</Box>}
<Box padding="0.5rem">
<Box padding="0.5rem" data-label="history-detail">
<Grid container>
<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>

View File

@ -1225,7 +1225,7 @@ export function ResultSet() {
return (
<Box className={classes.root} ref={containerRef} tabIndex="0">
<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 &&
<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}
open={menuOpenId=='menu-copyheader'}
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>
</PgMenu>

View File

@ -108,105 +108,127 @@ class NavMenuLocators:
"//*[contains(@class,'wcTabTop')]//*[contains(@class,'wcPanelTab') " \
"and contains(.,'{}')]"
rcdock_tab = "div.dock-tab-btn[id$='{0}']"
process_watcher_error_close_xpath = \
".btn.btn-sm-sq.btn-primary.pg-bg-close > i"
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_commit = "#btn-auto-commit"
btn_auto_commit = "li[data-label='Auto commit?']"
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 = "#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_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"
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(., '{}')]"
output_cell_xpath = "//div[contains(@class, 'slick-cell') and " \
"contains(@class, 'l{0} r{1}')]"
output_row_xpath = "//div[@aria-rowindex='{0}']"
output_cell_xpath = "//div[@aria-rowindex='{0}']/div[@aria-colindex='{1}']"
select_all_column = \
"//div[contains(@id,'row-header-column')]"
"//div[@role='columnheader'][@aria-colindex='1']"
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 = \
"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"
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 " \
"contains(string(), '{}')]"
sql_editor_message = "//div[@id='id-messages'][contains(string(), '{}')]"
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"
save_data_icon = "icon-save-data-changes"
btn_commit = "button[data-label='Commit']"
commit_icon = "icon-commit"
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"
btn_history_remove_all = "#id-history button[data-label='Remove All']"
show_query_internally_btn = \
"//div[label[contains(normalize-space(text())," \
"'Show queries generated internally by')]]//" \
"div[contains(@class,'toggle btn')]"
"//div[contains(normalize-space(text())," \
"'Show queries generated internally by')]/span/span[1]"
editable_column_icon_xpath = "//div[contains(@class," \
" 'editable-column-header-icon')]" \
"/i[contains(@class, 'fa-pencil-alt')]"
editable_column_icon_xpath = \
"//div[@role='columnheader']/div/div/*[@data-label='EditIcon']"
read_only_column_icon_xpath = "//div[contains(@class," \
" 'editable-column-header-icon')]" \
"/i[contains(@class, 'fa-lock')]"
read_only_column_icon_xpath = \
"//div[@role='columnheader']/div/div/*[@data-label='LockIcon']"
class ConnectToServerDiv:

View File

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