pgadmin4/web/pgadmin/feature_tests/query_tool_journey_test.py

469 lines
18 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2020, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from __future__ import print_function
import sys
import random
from selenium.webdriver import ActionChains
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from regression.python_test_utils import test_utils
from regression.feature_utils.base_feature_test import BaseFeatureTest
from regression.feature_utils.locators import QueryToolLocators
class QueryToolJourneyTest(BaseFeatureTest):
"""
Tests the path through the query tool
"""
scenarios = [
("Tests the path through the query tool", dict())
]
test_table_name = ""
test_editable_table_name = ""
invalid_table_name = ""
def before(self):
self.test_table_name = "test_table" + str(random.randint(1000, 3000))
self.invalid_table_name = \
"table_that_doesnt_exist_" + str(random.randint(1000, 3000))
test_utils.create_table(
self.server, self.test_db, self.test_table_name)
self.test_editable_table_name = "test_editable_table" + \
str(random.randint(1000, 3000))
create_sql = '''
CREATE TABLE "%s" (
pk_column NUMERIC PRIMARY KEY,
normal_column NUMERIC
);
''' % self.test_editable_table_name
test_utils.create_table_with_query(
self.server, self.test_db, create_sql)
self.page.add_server(self.server)
driver_version = test_utils.get_driver_version()
self.driver_version = float('.'.join(driver_version.split('.')[:2]))
self.wait = WebDriverWait(self.page.driver, 10)
def runTest(self):
self._navigate_to_query_tool()
self.page.execute_query(
"SELECT * FROM %s ORDER BY value " % self.test_table_name)
print("Copy rows...", file=sys.stderr, end="")
self._test_copies_rows()
print(" OK.", file=sys.stderr)
print("Copy columns...", file=sys.stderr, end="")
self._test_copies_columns()
print(" OK.", file=sys.stderr)
print("History tab...", file=sys.stderr, end="")
self._test_history_tab()
print(" OK.", file=sys.stderr)
self._insert_data_into_test_editable_table()
print("History query source icons and generated queries toggle...",
file=sys.stderr, end="")
self._test_query_sources_and_generated_queries()
print(" OK.", file=sys.stderr)
print("Updatable result sets...", file=sys.stderr, end="")
self._test_updatable_resultset()
print(" OK.", file=sys.stderr)
print("Is editable column header icons...", file=sys.stderr, end="")
self._test_is_editable_columns_icons()
print(" OK.", file=sys.stderr)
def _test_copies_rows(self):
self.page.driver.switch_to.default_content()
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'))
select_row.click()
copy_row = self.page.find_by_css_selector(
QueryToolLocators.copy_button_css)
copy_row.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)
clipboard_text = scratch_pad_ele.get_attribute("value")
self.assertEqual('"Some-Name"\t6\t"some info"',
clipboard_text)
scratch_pad_ele.clear()
def _test_copies_columns(self):
self.page.driver.switch_to.default_content()
self.page.driver.switch_to_frame(
self.page.driver.find_element_by_tag_name("iframe"))
column_header = self.page.find_by_css_selector(
QueryToolLocators.output_column_header_css.format('some_column'))
column_header.click()
copy_btn = self.page.find_by_css_selector(
QueryToolLocators.copy_button_css)
copy_btn.click()
self.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)
clipboard_text = scratch_pad_ele.get_attribute("value")
self.assertTrue('"Some-Name"' in clipboard_text)
self.assertTrue('"Some-Other-Name"' in clipboard_text)
self.assertTrue('"Yet-Another-Name"' in clipboard_text)
scratch_pad_ele.clear()
def _test_history_tab(self):
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("SELECT * FROM %s" % self.invalid_table_name)
self.page.click_tab("Query History")
selected_history_entry = self.page.find_by_css_selector(
QueryToolLocators.query_history_selected)
self.assertIn("SELECT * FROM %s" % self.invalid_table_name,
selected_history_entry.text)
failed_history_detail_pane = self.page.find_by_css_selector(
QueryToolLocators.query_history_detail)
self.assertIn(
"Error Message relation \"%s\" does not exist"
% self.invalid_table_name,
failed_history_detail_pane.text
)
self.page.wait_for_elements(
lambda driver: driver.find_elements_by_css_selector(
QueryToolLocators.query_history_entries))
# get the query history rows and click the previous query row which
# was executed and verify it
history_rows = self.driver.find_elements_by_css_selector(
QueryToolLocators.query_history_entries)
history_rows[1].click()
selected_history_entry = self.page.find_by_css_selector(
QueryToolLocators.query_history_selected)
self.assertIn(("SELECT * FROM %s ORDER BY value" %
self.test_table_name),
selected_history_entry.text)
# check second(invalid) query also exist in the history tab with error
newly_selected_history_entry = history_rows[0]
self.page.click_element(newly_selected_history_entry)
invalid_history_entry = self.page.find_by_css_selector(
QueryToolLocators.invalid_query_history_entry_css)
self.assertIn("SELECT * FROM %s" % self.invalid_table_name,
invalid_history_entry.text)
self.page.click_tab("Query Editor")
self.page.clear_query_tool()
self.page.click_element(editor_input)
# Check if 15 more query executed then the history should contain 17
# entries.
self.page.fill_codemirror_area_with("SELECT * FROM hats")
for _ in range(15):
self.page.find_by_css_selector(
QueryToolLocators.btn_execute_query_css).click()
self.page.wait_for_query_tool_loading_indicator_to_disappear()
self.page.click_tab("Query History")
query_list = self.page.wait_for_elements(
lambda driver: driver.find_elements_by_css_selector(
QueryToolLocators.query_history_entries))
self.assertTrue(17, len(query_list))
def _test_query_sources_and_generated_queries(self):
self.__clear_query_history()
self._test_history_query_sources()
self._test_toggle_generated_queries()
def _test_history_query_sources(self):
self.page.click_tab("Query Editor")
self._execute_sources_test_queries()
self.page.click_tab("Query History")
history_entries_icons = [
QueryToolLocators.commit_icon,
QueryToolLocators.save_data_icon,
QueryToolLocators.save_data_icon,
QueryToolLocators.execute_icon,
QueryToolLocators.explain_analyze_icon,
QueryToolLocators.explain_icon
]
history_entries_queries = [
"COMMIT;",
"UPDATE public.%s SET normal_column = '10'::numeric "
"WHERE pk_column = '1';" % self.test_editable_table_name,
"BEGIN;",
"SELECT * FROM %s" % self.test_editable_table_name,
"SELECT * FROM %s" % self.test_editable_table_name,
"SELECT * FROM %s" % self.test_editable_table_name
]
self._check_history_queries_and_icons(history_entries_queries,
history_entries_icons)
def _test_toggle_generated_queries(self):
xpath = '//li[contains(@class, "pgadmin-query-history-entry")]'
self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath))
self.page.set_switch_box_status(
QueryToolLocators.show_query_internally_btn, 'No')
self.assertFalse(self.page.check_if_element_exist_by_xpath(xpath))
self.page.set_switch_box_status(
QueryToolLocators.show_query_internally_btn, 'Yes')
self.assertTrue(self.page.check_if_element_exist_by_xpath(xpath))
def _test_updatable_resultset(self):
if self.driver_version < 2.8:
return
self.page.click_tab("Query Editor")
# Select all data
# (contains the primary key -> all columns should be editable)
self.page.clear_query_tool()
query = "SELECT pk_column, normal_column FROM %s" \
% self.test_editable_table_name
self._check_query_results_editable(query, [True, True])
# Select data without primary keys -> should not be editable
self.page.clear_query_tool()
query = "SELECT normal_column FROM %s" % self.test_editable_table_name
self._check_query_results_editable(query, [False],
discard_changes_modal=True)
# Select all data in addition to duplicate, renamed, and out-of-table
# columns
self.page.clear_query_tool()
query = """
SELECT pk_column, normal_column, normal_column,
normal_column as pk_column,
(normal_column::text || normal_column::text)::int
FROM %s
""" % self.test_editable_table_name
self._check_query_results_editable(query,
[True, True, False, False, False])
def _test_is_editable_columns_icons(self):
if self.driver_version < 2.8:
return
self.page.click_tab("Query Editor")
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.click_modal('Yes')
icon_exists = self.page.check_if_element_exist_by_xpath(
QueryToolLocators.editable_column_icon_xpath
)
self.assertTrue(icon_exists)
self.page.clear_query_tool()
query = "SELECT normal_column FROM %s" % self.test_editable_table_name
self.page.execute_query(query)
icon_exists = self.page.check_if_element_exist_by_xpath(
QueryToolLocators.read_only_column_icon_xpath
)
self.assertTrue(icon_exists)
def _execute_sources_test_queries(self):
self.page.clear_query_tool()
self._explain_query(
"SELECT * FROM %s;"
% self.test_editable_table_name
)
self._explain_analyze_query(
"SELECT * FROM %s;"
% self.test_editable_table_name
)
self.page.execute_query(
"SELECT * FROM %s;"
% self.test_editable_table_name
)
# Turn off autocommit
query_options = self.page.find_by_css_selector(
QueryToolLocators.btn_query_dropdown)
query_options.click()
self.page.uncheck_execute_option("auto_commit")
self._update_numeric_cell(2, 10)
self._commit_transaction()
# Turn on autocommit
query_options = self.page.find_by_css_selector(
QueryToolLocators.btn_query_dropdown)
query_options.click()
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()
for icon, query in zip(history_icons, history_queries):
# Check query
query_history_selected_item = self.page.find_by_css_selector(
QueryToolLocators.query_history_selected
)
self.assertIn(query, query_history_selected_item.text)
# Check source icon
query_history_selected_icon = self.page.find_by_css_selector(
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)
# Move to next entry
ActionChains(self.page.driver) \
.send_keys(Keys.ARROW_DOWN) \
.perform()
def _update_numeric_cell(self, cell_index, value):
"""
Updates a numeric cell in the first row of the resultset
"""
self.page.check_if_element_exist_by_xpath(
"//div[contains(@style, 'top:0px')]//div[contains(@class, "
"'l{0} r{1}')]".format(cell_index, cell_index))
cell_el = self.page.find_by_xpath(
"//div[contains(@style, 'top:0px')]//div[contains(@class, "
"'l{0} r{1}')]".format(cell_index, cell_index))
ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(value). \
send_keys(Keys.ENTER).perform()
self.page.find_by_css_selector(
QueryToolLocators.btn_save_data).click()
def _insert_data_into_test_editable_table(self):
self.page.click_tab("Query Editor")
self.page.clear_query_tool()
self.page.execute_query(
"INSERT INTO %s VALUES (1, 1), (2, 2);"
% self.test_editable_table_name
)
def __clear_query_history(self):
self.page.click_element(
self.page.find_by_css_selector(
QueryToolLocators.btn_clear_dropdown)
)
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.page.click_modal('Yes')
def _navigate_to_query_tool(self):
self.page.expand_database_node(
self.server['name'],
self.server['db_password'], self.test_db)
self.page.open_query_tool()
self.page.wait_for_spinner_to_disappear()
def _explain_query(self, query):
self.page.fill_codemirror_area_with(query)
self.page.find_by_css_selector(
QueryToolLocators.btn_explain).click()
def _explain_analyze_query(self, query):
self.page.fill_codemirror_area_with(query)
self.page.find_by_css_selector(
QueryToolLocators.btn_explain_analyze).click()
def _commit_transaction(self):
self.page.find_by_css_selector(
QueryToolLocators.btn_commit).click()
def _assert_clickable(self, element):
self.page.click_element(element)
def _check_query_results_editable(self, query, cols_should_be_editable,
discard_changes_modal=False):
self.page.execute_query(query)
if discard_changes_modal:
self.page.click_modal('Yes')
enumerated_should_be_editable = enumerate(cols_should_be_editable, 1)
import time
time.sleep(0.5)
for column_index, should_be_editable in enumerated_should_be_editable:
is_editable = self._check_cell_editable(column_index)
self.assertEqual(is_editable, should_be_editable)
def _check_cell_editable(self, cell_index):
"""Checks if a cell in the first row of the resultset is editable"""
cell_el = self.page.find_by_xpath(
"//div[contains(@style, 'top:0px')]//div[contains(@class, "
"'l{0} r{1}')]".format(cell_index, cell_index))
# Get existing value
cell_value = int(cell_el.text)
new_value = cell_value + 1
# Try to update value
ActionChains(self.driver).double_click(cell_el).perform()
ActionChains(self.driver).send_keys(new_value). \
send_keys(Keys.ENTER).perform()
# 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))
return int(cell_el.text) == new_value
def _check_can_add_row(self):
return self.page.check_if_element_exist_by_xpath(
QueryToolLocators.new_row_xpath)
def after(self):
self.page.close_query_tool()
test_utils.delete_table(
self.server, self.test_db, self.test_table_name)
test_utils.delete_table(
self.server, self.test_db, self.test_editable_table_name)
self.page.remove_server(self.server)