diff --git a/web/pgadmin/feature_tests/test_data.json b/web/pgadmin/feature_tests/test_data.json new file mode 100644 index 0000000..672b217 --- /dev/null +++ b/web/pgadmin/feature_tests/test_data.json @@ -0,0 +1,88 @@ +{ + "table_insert_update_cases": { + + "defaults_text": { + "insert": { + "insert_with_defaults": { + "id": "1", + "data_default_nulls": "abc123", + "data_default_no_nulls": "def456", + "data_no_nulls": "test string" + } + }, + "update": { + "update_with_null_value": { + "id": "12", + "data_default_nulls": "", + "data_nulls": "" + }, + "update_with_empty_string": { + "id": "15", + "data_default_nulls": "''", + "data_nulls": "''" + } + } + }, + "defaults_boolean": { + "insert": { + "insert_with_defaults": { + "id": "1", + "data_default_nulls": "false", + "data_default_no_nulls": "false", + "data_no_nulls": "true" + } + }, + "update": { + "update_with_boolean_value": { + "id": "12", + "data_default_nulls": "true", + "data_nulls": "true" + } + } + }, + "defaults_number": { + "insert": { + "insert_with_defaults": { + "id": "1", + "data_default_nulls": "10", + "data_default_no_nulls": "20", + "data_no_nulls": "10" + } + }, + "update": { + "update_with_null_value": { + "id": "12", + "data_default_nulls": "", + "data_nulls": "" + }, + "update_with_numeric_value": { + "id": "15", + "data_default_nulls": "12", + "data_nulls": "13" + } + } + }, + "defaults_json": { + "insert": { + "insert_with_defaults": { + "id": "1", + "data_default_nulls": "[0,1]", + "data_default_no_nulls": "[2,3]", + "data_no_nulls": "[4,5]" + } + }, + "update": { + "update_with_json_data": { + "id": "12", + "data_default_nulls": "[10,11,12]", + "data_nulls": "[3,4]" + }, + "update_with_null_value": { + "id": "15", + "data_default_nulls": "", + "data_nulls": "" + } + } + } + } +} \ No newline at end of file diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py new file mode 100644 index 0000000..56d89a4 --- /dev/null +++ b/web/pgadmin/feature_tests/view_data_dml_queries.py @@ -0,0 +1,486 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2017, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +import json +import os +from selenium.webdriver import ActionChains +from regression.python_test_utils import test_utils +from regression.feature_utils.base_feature_test import BaseFeatureTest +import time +from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import NoSuchElementException + + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + +try: + with open(CURRENT_PATH + '/test_data.json') as data_file: + config_data = json.load(data_file)['table_insert_update_cases'] +except Exception as e: + print(str(e)) + + +class CheckForViewDataTest(BaseFeatureTest): + """ + Test cases to validate insert, update operations in table + with input test data + + First of all, the test data is inserted/updated into table and then + inserted data is compared with original data to check if expected data + is returned from table or not. + + We will cover test cases for, + 1) Insert with default values + 2) Update with null values + 3) Update with blank string + """ + + scenarios = [ + ("Validate Insert, Update operations in View data with given test " + "data", + dict()) + ] + + # To create column id with nextval, first a sequence must be created. + create_sequence = """ +CREATE SEQUENCE public.defaults_text_id_seq + INCREMENT 1 + START 9 + MINVALUE 1 + MAXVALUE 9223372036854775807 + CACHE 1; + +ALTER SEQUENCE public.defaults_text_id_seq + OWNER TO postgres; +""" + + # query for creating 'defaults_text' table + defaults_text_query = """ +CREATE TABLE public.defaults_text +( + id bigint NOT NULL DEFAULT nextval('defaults_text_id_seq'::regclass), + data_default_nulls text COLLATE pg_catalog."default" DEFAULT 'abc123'::text, + data_default_no_nulls text COLLATE pg_catalog."default" NOT NULL +DEFAULT 'def456'::text, + data_nulls text COLLATE pg_catalog."default", + data_no_nulls text COLLATE pg_catalog."default" NOT NULL, + CONSTRAINT defaults_text_pkey PRIMARY KEY (id) +) +""" + + # query for creating 'defaults_boolean' table + defaults_boolean_query = """ +CREATE TABLE public.defaults_boolean +( + id bigint NOT NULL, + data_default_nulls boolean DEFAULT false, + data_default_no_nulls boolean NOT NULL DEFAULT false, + data_nulls boolean, + data_no_nulls boolean NOT NULL, + CONSTRAINT default_boolean_pkey PRIMARY KEY (id) +) +""" + + # query for creating 'defaults_number' table + defaults_number_query = """ +CREATE TABLE public.defaults_number +( + id bigint NOT NULL, + data_default_nulls numeric(100) DEFAULT 10, + data_default_no_nulls numeric(100) NOT NULL DEFAULT 20, + data_nulls numeric(100), + data_no_nulls numeric(100) NOT NULL, + CONSTRAINT default_number_pkey PRIMARY KEY (id) +) +""" + + # query for creating 'defaults_json' table + defaults_json_query = """ +CREATE TABLE public.defaults_json +( + id bigint NOT NULL, + data_default_nulls json DEFAULT '[0,1]'::json, + data_default_no_nulls json NOT NULL DEFAULT '[2,3]'::json, + data_nulls json, + data_no_nulls json NOT NULL, + CONSTRAINT defaults_json_pkey PRIMARY KEY (id) +) +""" + + def before(self): + connection = test_utils.get_db_connection(self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port']) + test_utils.drop_database(connection, "acceptance_test_db") + test_utils.create_database(self.server, "acceptance_test_db") + test_utils.create_table_with_query( + self.server, + "acceptance_test_db", + CheckForViewDataTest.create_sequence) + + # Create pre-requisite tables + for key in config_data.keys(): + test_utils.create_table_with_query( + self.server, + "acceptance_test_db", + getattr(CheckForViewDataTest, key+'_query')) + + def runTest(self): + self.page.wait_for_spinner_to_disappear() + self._connects_to_server() + + self._tables_node_expandable() + for key in config_data.keys(): + self.driver.switch_to.default_content() + self.page.select_tree_item(key) + # Open Object -> View data + self._check_xss_in_view_data() + + # Run test to insert a new row in table with default values + self._insert_row_in_table(key) + self._verify_insert_data(key) + self._close_query_tool() + + # Run update cells test cases + for key in config_data.keys(): + self.driver.switch_to.default_content() + self.page.select_tree_item(key) + # Open Object -> View data + self._check_xss_in_view_data() + + # Run test to update existing rows in table with given values + self._update_row_in_table(key) + self._close_query_tool() + + def after(self): + time.sleep(1) + self.page.remove_server(self.server) + connection = test_utils.get_db_connection(self.server['db'], + self.server['username'], + self.server['db_password'], + self.server['host'], + self.server['port']) + test_utils.drop_database(connection, "acceptance_test_db") + + def _connects_to_server(self): + self.page.find_by_xpath("//*[@class='aciTreeText' and .='Servers']").click() + self.page.driver.find_element_by_link_text("Object").click() + ActionChains(self.page.driver) \ + .move_to_element(self.page.driver.find_element_by_link_text("Create")) \ + .perform() + self.page.find_by_partial_link_text("Server...").click() + + server_config = self.server + self.page.fill_input_by_field_name("name", server_config['name']) + self.page.find_by_partial_link_text("Connection").click() + self.page.fill_input_by_field_name("host", server_config['host']) + self.page.fill_input_by_field_name("port", server_config['port']) + self.page.fill_input_by_field_name("username", server_config['username']) + self.page.fill_input_by_field_name("password", server_config['db_password']) + self.page.find_by_xpath("//button[contains(.,'Save')]").click() + + def _tables_node_expandable(self): + self.page.toggle_open_tree_item(self.server['name']) + self.page.toggle_open_tree_item('Databases') + self.page.toggle_open_tree_item('acceptance_test_db') + self.page.toggle_open_tree_item('Schemas') + self.page.toggle_open_tree_item('public') + self.page.toggle_open_tree_item('Tables') + + def _check_xss_in_view_data(self): + self.page.driver.find_element_by_link_text("Object").click() + ActionChains(self.page.driver) \ + .move_to_element( + self.page.driver.find_element_by_link_text("View Data")) \ + .perform() + self.page.find_by_partial_link_text("View All Rows").click() + time.sleep(3) + self.page.driver.switch_to.frame( + self.page.driver.find_element_by_tag_name('iframe') + ) + + def _insert_row_in_table(self, table): + xpath_cell1 = "//div[contains(@class, 'new-row')]//div[" \ + "contains(@class, 'r1')]" + + cell1_val = config_data[table]['insert']['insert_with_defaults'][ + 'id'] + + cell2_val = config_data[table]['insert']['insert_with_defaults'][ + 'data_no_nulls'] + + time.sleep(1) + # Insert into first cell + new_row = self.page.find_by_xpath(xpath_cell1) + new_row.click() + field = new_row.find_element_by_xpath(".//input") + field.click() + ActionChains(self.driver).send_keys(cell1_val).perform() + field.send_keys(Keys.TAB) + + # # Insert into last cell. To insert into cells like boolean, + # number, json has different process, so the code is conditional + if table == 'defaults_boolean': + xpath_cell5 = "//*[@id='datagrid']//div[" \ + "contains(@class, 'new_row')]//div[" \ + "contains(@class, 'r5')]" + cell5_el = self.page.find_by_xpath(xpath_cell5) + ActionChains(self.driver).move_to_element(cell5_el).double_click( + cell5_el + ).perform() + time.sleep(0.4) + + checkbox_el = cell5_el.find_element_by_xpath(".//input") + checkbox_el.click() + ActionChains(self.driver).move_to_element(checkbox_el).double_click( + checkbox_el + ).perform() + checkbox_el.send_keys(Keys.TAB) # Press tab key + + elif table == 'defaults_number': + xpath_cell5 = "//*[@id='datagrid']//div[" \ + "contains(@class,'new_row')]//div[" \ + "contains(@class, 'r5')]" + cell5_el = self.page.find_by_xpath(xpath_cell5) + + ActionChains(self.driver).move_to_element(cell5_el).double_click( + cell5_el + ).perform() + + time.sleep(0.5) + cell5_input_el = cell5_el.find_element_by_xpath('.//input') + cell5_input_el.click() + ActionChains(self.driver).send_keys(cell2_val).perform() + cell5_input_el.send_keys(Keys.TAB) # Press tab key + else: + xpath_cell5 = "//*[@id='datagrid']//div[contains(@class, " \ + "'new_row')]//div[contains(@class, 'r5')]" + cell_el = self.page.find_by_xpath(xpath_cell5) + + ActionChains(self.driver).move_to_element(cell_el).double_click( + cell_el + ).perform() + + cell5_el = self.page.driver.find_element_by_css_selector( + "div[style*='z-index: 1000'] textarea" + ) + cell5_el.clear() + cell5_el.click() + time.sleep(0.5) + + ActionChains(self.driver).send_keys(cell2_val).perform() + + self.page.driver.find_element_by_css_selector( + "div[style*='z-index: 1000'] div button:first-child" + ).click() # Click on editor's Save button + + self.page.find_by_id("btn-save").click() # Save data + + def _verify_insert_data(self, table): + time.sleep(0.5) + self.page.find_by_id("btn-flash").click() + time.sleep(1) + main_el = self.page.find_by_xpath('//*[@id="datagrid"]') + cell1 = main_el.find_element_by_xpath( + './/div[contains(@class, "r1")]//span' + ).get_attribute('innerHTML') + try: + cell2 = main_el.find_element_by_xpath( + './/div[contains(@class, "r2")]//span' + ).get_attribute('innerHTML') + except NoSuchElementException: + cell2 = main_el.find_element_by_xpath( + './/div[contains(@class, "r2")]' + ).get_attribute('innerHTML') + + try: + cell3 = main_el.find_element_by_xpath( + './/div[contains(@class, "r3")]//span' + ).get_attribute('innerHTML') + except NoSuchElementException: + cell3 = main_el.find_element_by_xpath( + './/div[contains(@class, "r3")]' + ).get_attribute('innerHTML') + + try: + cell5 = main_el.find_element_by_xpath( + './/div[contains(@class, "r5")]//span' + ).get_attribute('innerHTML') + except NoSuchElementException: + cell5 = main_el.find_element_by_xpath( + './/div[contains(@class, "r5")]' + ).get_attribute('innerHTML') + + test_verify_data = config_data[table]['insert'][ + 'insert_with_defaults'] + + # compare updated cell values with original values + self.assertEquals(cell1, test_verify_data['id']) + self.assertEquals(cell2, test_verify_data['data_default_nulls']) + self.assertEquals(cell3, test_verify_data['data_default_no_nulls']) + self.assertEquals(cell5, test_verify_data['data_no_nulls']) + + def _update_numeric_cell(self, xpath, value): + """ + This function updates the given cell(xpath) with + given value + Args: + xpath: xpath of cell element + value: cell value to update + + Returns: None + + """ + time.sleep(2) + cell_el = self.page.find_by_xpath(xpath) + ActionChains(self.driver).move_to_element(cell_el).double_click( + cell_el + ).perform() + field = cell_el.find_element_by_xpath(".//input") + field.clear() + field.click() + ActionChains(self.driver).send_keys(value).perform() + field.send_keys(Keys.TAB) # Press tab key + + def _update_text_cell(self, xpath, value): + """ + This function updates the given cell(xpath) with + given value + Args: + xpath: xpath of cell element + value: cell value to update + + Returns: None + + """ + cell_el = self.page.find_by_xpath(xpath) + ActionChains(self.driver).move_to_element(cell_el).double_click( + cell_el).perform() + field = self.page.driver.find_element_by_css_selector( + "div[style*='z-index: 1000'] textarea" + ) + field.clear() + field.click() + time.sleep(1) + ActionChains(self.driver).send_keys(value).perform() + time.sleep(1) + self.page.driver.find_element_by_css_selector( + "div[style*='z-index: 1000'] div button:first-child" + ).click() # Click on editor's Save button + + def _update_boolean_cell(self, xpath, value): + """ + This function updates the given cell(xpath) with + given value + Args: + xpath: xpath of cell element + value: cell value to update + + Returns: None + + """ + cell_el = self.page.find_by_xpath(xpath) + ActionChains(self.driver).move_to_element(cell_el).double_click( + cell_el + ).perform() + time.sleep(0.4) + + checkbox_el = cell_el.find_element_by_xpath(".//input") + checkbox_el.click() + ActionChains(self.driver).move_to_element(checkbox_el).double_click( + checkbox_el + ).perform() + checkbox_el.send_keys(Keys.TAB) # Press tab key + + def _update_row_in_table(self, table): + xpath_cell1 = "//div[contains(@class, 'even')]//div[" \ + "contains(@class, 'r1')]" + xpath_cell2 = "//div[contains(@class, 'even')]//div[" \ + "contains(@class, 'r2')]" + xpath_cell3 = "//div[contains(@class, 'even')]//div[" \ + "contains(@class, 'r4')]" + + update_cases = config_data[table]['update'] + + for row in update_cases: + cell1 = update_cases[row]['id'] + cell2 = update_cases[row]['data_default_nulls'] + cell3 = update_cases[row]['data_nulls'] + + if table == 'defaults_boolean': + self._update_numeric_cell(xpath_cell1, cell1) + self._update_boolean_cell(xpath_cell2, cell2) + self._update_boolean_cell(xpath_cell3, cell3) + elif table == 'defaults_number': + self._update_numeric_cell(xpath_cell1, cell1) + self._update_numeric_cell(xpath_cell2, cell2) + self._update_numeric_cell(xpath_cell3, cell3) + else: + self._update_numeric_cell(xpath_cell1, cell1) + self._update_text_cell(xpath_cell2, cell2) + self._update_text_cell(xpath_cell3, cell3) + + self.page.find_by_id("btn-save").click() # Save data + self._verify_update_data(table, row) + + def _get_cell_value(self, table, xpath): + """ + This function extracts the value from markup and then + manipulate if necessary + Args: + table: test case type [we are using table_name] + xpath: xpath for dom element + + Returns: element value + + """ + + cell = self.page.find_by_xpath(xpath).get_attribute('innerHTML') + if cell.find('