diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py index 3cd6b3b86..5b24e6e8f 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_add.py @@ -43,31 +43,30 @@ class IndexConstraintAddTestCase(BaseTestGenerator): dict(url='/browser/unique_constraint/obj/', data=unique_key_data)) ] - @classmethod - def setUpClass(cls): - cls.db_name = parent_node_dict["database"][-1]["db_name"] + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] - cls.server_id = schema_info["server_id"] - cls.db_id = schema_info["db_id"] - db_con = database_utils.connect_database(cls, utils.SERVER_GROUP, - cls.server_id, cls.db_id) + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) if not db_con['data']["connected"]: raise Exception("Could not connect to database to add a " "index constraint(primary key or unique key).") - cls.schema_id = schema_info["schema_id"] - cls.schema_name = schema_info["schema_name"] - schema_response = schema_utils.verify_schemas(cls.server, - cls.db_name, - cls.schema_name) + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) if not schema_response: raise Exception("Could not find the schema to add a index " "constraint(primary key or unique key).") - cls.table_name = "table_indexconstraint_%s" % \ - (str(uuid.uuid4())[1:8]) - cls.table_id = tables_utils.create_table(cls.server, - cls.db_name, - cls.schema_name, - cls.table_name) + self.table_name = "table_indexconstraint_%s" % \ + (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) def runTest(self): """This function will add index constraint(primary key or unique key) @@ -80,7 +79,6 @@ class IndexConstraintAddTestCase(BaseTestGenerator): content_type='html/json') self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(cls): + def tearDown(self): # Disconnect the database - database_utils.disconnect_database(cls, cls.server_id, cls.db_id) + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py index 7eea5e9bb..f35a00b4a 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_delete.py @@ -38,31 +38,30 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator): type="UNIQUE")) ] - @classmethod - def setUpClass(cls): - cls.db_name = parent_node_dict["database"][-1]["db_name"] + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] - cls.server_id = schema_info["server_id"] - cls.db_id = schema_info["db_id"] - db_con = database_utils.connect_database(cls, utils.SERVER_GROUP, - cls.server_id, cls.db_id) + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) if not db_con['data']["connected"]: raise Exception("Could not connect to database to add a " "index constraint(primary key or unique key).") - cls.schema_id = schema_info["schema_id"] - cls.schema_name = schema_info["schema_name"] - schema_response = schema_utils.verify_schemas(cls.server, - cls.db_name, - cls.schema_name) + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) if not schema_response: raise Exception("Could not find the schema to add a index " "constraint(primary key or unique key).") - cls.table_name = "table_indexconstraint_%s" % \ - (str(uuid.uuid4())[1:8]) - cls.table_id = tables_utils.create_table(cls.server, - cls.db_name, - cls.schema_name, - cls.table_name) + self.table_name = "table_indexconstraint_%s" % \ + (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) def runTest(self): """This function will delete index constraint(primary key or @@ -81,7 +80,6 @@ class IndexConstraintDeleteTestCase(BaseTestGenerator): ) self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(cls): + def tearDown(self): # Disconnect the database - database_utils.disconnect_database(cls, cls.server_id, cls.db_id) + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py index c08c2acb3..f838a2f73 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_get.py @@ -38,31 +38,30 @@ class IndexConstraintGetTestCase(BaseTestGenerator): type="UNIQUE")) ] - @classmethod - def setUpClass(cls): - cls.db_name = parent_node_dict["database"][-1]["db_name"] + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] - cls.server_id = schema_info["server_id"] - cls.db_id = schema_info["db_id"] - db_con = database_utils.connect_database(cls, utils.SERVER_GROUP, - cls.server_id, cls.db_id) + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) if not db_con['data']["connected"]: raise Exception("Could not connect to database to add a " "index constraint(primary key or unique key).") - cls.schema_id = schema_info["schema_id"] - cls.schema_name = schema_info["schema_name"] - schema_response = schema_utils.verify_schemas(cls.server, - cls.db_name, - cls.schema_name) + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) if not schema_response: raise Exception("Could not find the schema to add a index " "constraint(primary key or unique key).") - cls.table_name = "table_indexconstraint_%s" % \ - (str(uuid.uuid4())[1:8]) - cls.table_id = tables_utils.create_table(cls.server, - cls.db_name, - cls.schema_name, - cls.table_name) + self.table_name = "table_indexconstraint_%s" % \ + (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) def runTest(self): """This function will fetch the index constraint(primary key or @@ -81,7 +80,6 @@ class IndexConstraintGetTestCase(BaseTestGenerator): ) self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(cls): + def tearDown(self): # Disconnect the database - database_utils.disconnect_database(cls, cls.server_id, cls.db_id) + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py index 87289a620..6bdf70308 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/tests/test_index_constraint_put.py @@ -40,31 +40,30 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator): type="UNIQUE", data=data)) ] - @classmethod - def setUpClass(cls): - cls.db_name = parent_node_dict["database"][-1]["db_name"] + def setUp(self): + self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] - cls.server_id = schema_info["server_id"] - cls.db_id = schema_info["db_id"] - db_con = database_utils.connect_database(cls, utils.SERVER_GROUP, - cls.server_id, cls.db_id) + self.server_id = schema_info["server_id"] + self.db_id = schema_info["db_id"] + db_con = database_utils.connect_database(self, utils.SERVER_GROUP, + self.server_id, self.db_id) if not db_con['data']["connected"]: raise Exception("Could not connect to database to add a " "index constraint(primary key or unique key).") - cls.schema_id = schema_info["schema_id"] - cls.schema_name = schema_info["schema_name"] - schema_response = schema_utils.verify_schemas(cls.server, - cls.db_name, - cls.schema_name) + self.schema_id = schema_info["schema_id"] + self.schema_name = schema_info["schema_name"] + schema_response = schema_utils.verify_schemas(self.server, + self.db_name, + self.schema_name) if not schema_response: raise Exception("Could not find the schema to add a index " "constraint(primary key or unique key).") - cls.table_name = "table_indexconstraint_%s" % \ - (str(uuid.uuid4())[1:8]) - cls.table_id = tables_utils.create_table(cls.server, - cls.db_name, - cls.schema_name, - cls.table_name) + self.table_name = "table_indexconstraint_%s" % \ + (str(uuid.uuid4())[1:8]) + self.table_id = tables_utils.create_table(self.server, + self.db_name, + self.schema_name, + self.table_name) def runTest(self): """This function will update index constraint(primary key or @@ -84,7 +83,6 @@ class IndexConstraintUpdateTestCase(BaseTestGenerator): follow_redirects=True) self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(cls): + def tearDown(self): # Disconnect the database - database_utils.disconnect_database(cls, cls.server_id, cls.db_id) + database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py index bbed26998..02203d2f5 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/test_table_parameters.py @@ -46,8 +46,9 @@ class TableUpdateParameterTestCase(BaseTestGenerator): ) ] - @classmethod - def setUpClass(self): + table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8]) + + def setUp(self): self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] self.server_id = schema_info["server_id"] @@ -63,12 +64,14 @@ class TableUpdateParameterTestCase(BaseTestGenerator): self.schema_name) if not schema_response: raise Exception("Could not find the schema to add a table.") - self.table_name = "test_table_parameters_%s" % (str(uuid.uuid4())[1:8]) - self.table_id = tables_utils.create_table( - self.server, self.db_name, - self.schema_name, - self.table_name) + self.table_id = tables_utils.get_table_id(self.server, self.db_name, + self.table_name) + if self.table_id is None: + self.table_id = tables_utils.create_table( + self.server, self.db_name, + self.schema_name, + self.table_name) def runTest(self): """This function will fetch added table under schema node.""" @@ -130,7 +133,6 @@ class TableUpdateParameterTestCase(BaseTestGenerator): follow_redirects=True) self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(self): + def tearDown(self): # Disconnect the database database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py index 0a2963aab..8e5f665f3 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/tables/tests/utils.py @@ -483,3 +483,26 @@ def get_hash_partitions_data(data): }] data['partition_keys'] = \ [{'key_type': 'column', 'pt_column': 'empno'}] + + +def get_table_id(server, db_name, table_name): + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + pg_cursor = connection.cursor() + pg_cursor.execute("select oid from pg_class where relname='%s'" % + table_name) + table = pg_cursor.fetchone() + if table: + table_id = table[0] + else: + table_id = None + connection.close() + return table_id + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py index c84276ef6..1f63ef564 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/test_mviews_parameters.py @@ -45,8 +45,9 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator): ) ] - @classmethod - def setUpClass(self): + m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8]) + + def setUp(self): self.db_name = parent_node_dict["database"][-1]["db_name"] schema_info = parent_node_dict["schema"][-1] self.server_id = schema_info["server_id"] @@ -70,17 +71,19 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator): if not schema_response: raise Exception("Could not find the schema to update a mview.") - self.m_view_name = "test_mview_put_%s" % (str(uuid.uuid4())[1:8]) - m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE pg_default " \ - "AS SELECT 'test_pgadmin' WITH NO DATA;ALTER TABLE " \ - "%s.%s OWNER TO %s" - - self.m_view_id = views_utils.create_view(self.server, - self.db_name, - self.schema_name, - m_view_sql, + self.m_view_id = views_utils.get_view_id(self.server, self.db_name, self.m_view_name) + if self.m_view_id is None: + m_view_sql = "CREATE MATERIALIZED VIEW %s.%s TABLESPACE " \ + "pg_default AS SELECT 'test_pgadmin' WITH NO " \ + "DATA;ALTER TABLE %s.%s OWNER TO %s" + self.m_view_id = views_utils.create_view(self.server, + self.db_name, + self.schema_name, + m_view_sql, + self.m_view_name) + def runTest(self): """This function will update the view/mview under schema node.""" mview_response = views_utils.verify_view(self.server, self.db_name, @@ -141,7 +144,6 @@ class MViewsUpdateParameterTestCase(BaseTestGenerator): follow_redirects=True) self.assertEquals(response.status_code, 200) - @classmethod - def tearDownClass(self): + def tearDown(self): # Disconnect the database database_utils.disconnect_database(self, self.server_id, self.db_id) diff --git a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py index d6180c4fc..7619ee673 100644 --- a/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py +++ b/web/pgadmin/browser/server_groups/servers/databases/schemas/views/tests/utils.py @@ -86,3 +86,28 @@ def verify_view(server, db_name, view_name): except Exception: traceback.print_exc(file=sys.stderr) raise + + +def get_view_id(server, db_name, view_name): + try: + connection = utils.get_db_connection(db_name, + server['username'], + server['db_password'], + server['host'], + server['port'], + server['sslmode']) + old_isolation_level = connection.isolation_level + connection.set_isolation_level(0) + pg_cursor = connection.cursor() + # Get 'oid' from newly created view + pg_cursor.execute("select oid from pg_class where relname='%s'" % + view_name) + view = pg_cursor.fetchone() + view_id = None + if view: + view_id = view[0] + connection.close() + return view_id + except Exception: + traceback.print_exc(file=sys.stderr) + raise diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py index 7091ffa42..7475970ab 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py @@ -8,7 +8,6 @@ ########################################################################## from __future__ import print_function -import pyperclip import random from selenium.webdriver import ActionChains @@ -61,7 +60,6 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self._copies_rows_with_header() def _copies_rows(self): - pyperclip.copy("old clipboard contents") first_row = self.page.find_by_xpath( QueryToolLocators.output_row_xpath.format(1)) first_row.click() @@ -70,14 +68,23 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): QueryToolLocators.copy_button_css) copy_button.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"', - pyperclip.paste()) + clipboard_text) + scratch_pad_ele.clear() 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() - pyperclip.copy("old clipboard contents") select_all = self.page.find_by_xpath( QueryToolLocators.select_all_column) select_all.click() @@ -86,13 +93,22 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): QueryToolLocators.copy_button_css) copy_button.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_column"\t"value"\t"details" \"Some-Name"\t6\t"some info" \"Some-Other-Name"\t22\t"some other info" -\"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste()) +\"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text) + scratch_pad_ele.clear() def _copies_columns(self): - pyperclip.copy("old clipboard contents") column = self.page.find_by_css_selector( QueryToolLocators.output_column_header_css.format('some_column')) column.click() @@ -101,14 +117,23 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): QueryToolLocators.copy_button_css) copy_button.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" "Some-Other-Name" "Yet-Another-Name\"""", - pyperclip.paste()) + clipboard_text) + scratch_pad_ele.clear() def _copies_row_using_keyboard_shortcut(self): - pyperclip.copy("old clipboard contents") first_row = self.page.find_by_xpath( QueryToolLocators.output_row_xpath.format(1)) first_row.click() @@ -116,27 +141,43 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + 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"', - pyperclip.paste()) + clipboard_text) + scratch_pad_ele.clear() def _copies_column_using_keyboard_shortcut(self): - pyperclip.copy("old clipboard contents") column = self.page.find_by_css_selector( QueryToolLocators.output_column_header_css.format('some_column')) column.click() ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + 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" "Some-Other-Name" "Yet-Another-Name\"""", - pyperclip.paste()) + clipboard_text) + scratch_pad_ele.clear() def _copies_rectangular_selection(self): - pyperclip.copy("old clipboard contents") - top_left_cell = \ self.page.find_by_xpath( QueryToolLocators.output_column_data_xpath. @@ -154,12 +195,20 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.driver ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + 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-Other-Name"\t22\n"Yet-Another-Name"\t14', pyperclip.paste()) + '"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', clipboard_text) + scratch_pad_ele.clear() def _shift_resizes_rectangular_selection(self): - pyperclip.copy("old clipboard contents") - top_left_cell = self.page.find_by_xpath( QueryToolLocators.output_column_data_xpath. format('Some-Other-Name') @@ -180,12 +229,20 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): Keys.CONTROL ).send_keys('c').key_up(Keys.CONTROL).perform() + 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-Other-Name"\t22\t"some other info" -"Yet-Another-Name"\t14\t"cool info\"""", pyperclip.paste()) +"Yet-Another-Name"\t14\t"cool info\"""", clipboard_text) + scratch_pad_ele.clear() def _shift_resizes_column_selection(self): - pyperclip.copy("old clipboard contents") - column = self.page.find_by_css_selector( QueryToolLocators.output_column_header_css.format('value') ) @@ -197,13 +254,21 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + 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\n"Some-Other-Name"\t22\n"Yet-Another-Name"\t14', - pyperclip.paste()) + clipboard_text) + scratch_pad_ele.clear() def _mouseup_outside_grid_still_makes_a_selection(self): - pyperclip.copy("old clipboard contents") - bottom_right_cell = self.page.find_by_xpath( QueryToolLocators.output_column_data_xpath.format('cool info') ) @@ -218,7 +283,17 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertIn('"cool info"', pyperclip.paste()) + 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.assertIn('"cool info"', clipboard_text) + scratch_pad_ele.clear() def after(self): self.page.close_query_tool() diff --git a/web/pgadmin/feature_tests/file_manager_test.py b/web/pgadmin/feature_tests/file_manager_test.py index 6d012869b..33f86ad86 100644 --- a/web/pgadmin/feature_tests/file_manager_test.py +++ b/web/pgadmin/feature_tests/file_manager_test.py @@ -38,7 +38,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): self.page.add_server(self.server) self.wait = WebDriverWait(self.page.driver, 10) - self.XSS_FILE = '/tmp/.sql' + self.XSS_FILE = '/tmp/.sql' # Remove any previous file if os.path.isfile(self.XSS_FILE): os.remove(self.XSS_FILE) @@ -67,7 +68,7 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): self.page.open_query_tool() def _create_new_file(self): - self.page.find_by_css_selector(QueryToolLocators.btn_save_file)\ + self.page.find_by_css_selector(QueryToolLocators.btn_save_file) \ .click() # Set the XSS value in input self.page.find_by_css_selector('.change_file_types') @@ -112,8 +113,8 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): self.page.wait_for_query_tool_loading_indicator_to_disappear() self._check_escaped_characters( contents, - '<img src=x onmouseover=alert("1")>.sql', - 'File manager' + '<img src=x ' + self.server['name'][:13] + + '=alert("1")>.sql', 'File manager' ) def _check_escaped_characters(self, source_code, string_to_find, source): diff --git a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py index 1d6d7a43e..98b8a38b8 100644 --- a/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py +++ b/web/pgadmin/feature_tests/pg_utilities_backup_restore_test.py @@ -18,6 +18,7 @@ from regression.python_test_utils import test_utils from regression.python_test_utils import test_gui_helper from regression.feature_utils.locators import NavMenuLocators from regression.feature_utils.tree_area_locators import TreeAreaLocators +from selenium.webdriver import ActionChains class PGUtilitiesBackupFeatureTest(BaseFeatureTest): @@ -56,6 +57,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self.server['sslmode'] ) test_utils.drop_database(connection, self.database_name) + self._update_preferences() db_id = test_utils.create_database(self.server, self.database_name) if not db_id: self.assertTrue(False, "Database {} is not " @@ -130,7 +132,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self._check_detailed_window_for_xss('Backup') else: command = self.page.find_by_css_selector( - NavMenuLocators.process_watcher_detailed_command_canvas_css).\ + NavMenuLocators.process_watcher_detailed_command_canvas_css). \ text self.assertIn(self.server['name'], str(command)) @@ -199,7 +201,7 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): self._check_detailed_window_for_xss('Restore') else: command = self.page.find_by_css_selector( - NavMenuLocators.process_watcher_detailed_command_canvas_css).\ + NavMenuLocators.process_watcher_detailed_command_canvas_css). \ text self.assertIn(self.server['name'], str(command)) @@ -242,3 +244,71 @@ class PGUtilitiesBackupFeatureTest(BaseFeatureTest): # For XSS we need to search against element's html code assert source_code.find(string_to_find) != - \ 1, "{0} might be vulnerable to XSS ".format(source) + + def _update_preferences(self): + file_menu = self.page.find_by_css_selector( + NavMenuLocators.file_menu_css) + file_menu.click() + + pref_menu_item = self.page.find_by_css_selector( + NavMenuLocators.preference_menu_item_css) + pref_menu_item.click() + + wait = WebDriverWait(self.page.driver, 10) + + # Wait till the preference dialogue box is displayed by checking the + # visibility of Show System Object label + wait.until(EC.presence_of_element_located( + (By.XPATH, NavMenuLocators.show_system_objects_pref_label_xpath)) + ) + + maximize_button = self.page.find_by_css_selector( + NavMenuLocators.maximize_pref_dialogue_css) + maximize_button.click() + + path = self.page.find_by_xpath( + NavMenuLocators.specified_preference_tree_node.format('Paths')) + if self.page.find_by_xpath( + NavMenuLocators.specified_pref_node_exp_status.format('Paths')). \ + get_attribute('aria-expanded') == 'false': + ActionChains(self.driver).double_click(path).perform() + + binary_path = self.page.find_by_xpath( + NavMenuLocators.specified_sub_node_of_pref_tree_node.format( + 'Paths', 'Binary paths')) + binary_path.click() + + default_binary_path = self.server['default_binary_paths'] + if default_binary_path is not None: + server_types = default_binary_path.keys() + for serv in server_types: + if serv == 'pg': + path_input = self.page.find_by_xpath( + "//label[text()='PostgreSQL Binary " + "Path']/following-sibling::div//input") + path_input.clear() + path_input.click() + path_input.send_keys(default_binary_path['pg']) + elif serv == 'gpdb': + path_input = self.page.find_by_xpath( + "//label[text()='Greenplum Database Binary " + "Path']/following-sibling::div//input") + path_input.clear() + path_input.click() + path_input.send_keys(default_binary_path['gpdb']) + elif serv == 'ppas': + path_input = self.page.find_by_xpath( + "//label[text()='EDB Advanced Server Binary " + "Path']/following-sibling::div//input") + path_input.clear() + path_input.click() + path_input.send_keys(default_binary_path['ppas']) + else: + print('Binary path Key is Incorrect') + + # save and close the preference dialog. + self.page.click_modal('Save') + + self.page.wait_for_element_to_disappear( + lambda driver: driver.find_element_by_css_selector(".ajs-modal") + ) diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/pgadmin/feature_tests/query_tool_journey_test.py index 2d7c4ef98..ddb4e3caf 100644 --- a/web/pgadmin/feature_tests/query_tool_journey_test.py +++ b/web/pgadmin/feature_tests/query_tool_journey_test.py @@ -9,7 +9,6 @@ from __future__ import print_function import sys -import pyperclip import random from selenium.webdriver import ActionChains @@ -90,7 +89,6 @@ class QueryToolJourneyTest(BaseFeatureTest): print(" OK.", file=sys.stderr) def _test_copies_rows(self): - pyperclip.copy("old clipboard contents") self.page.driver.switch_to.default_content() self.page.driver.switch_to_frame( self.page.driver.find_element_by_tag_name("iframe")) @@ -103,12 +101,21 @@ class QueryToolJourneyTest(BaseFeatureTest): 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"', - pyperclip.paste()) + clipboard_text) - def _test_copies_columns(self): - pyperclip.copy("old clipboard contents") + 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")) @@ -121,9 +128,20 @@ class QueryToolJourneyTest(BaseFeatureTest): QueryToolLocators.copy_button_css) copy_btn.click() - self.assertTrue('"Some-Name"' in pyperclip.paste()) - self.assertTrue('"Some-Other-Name"' in pyperclip.paste()) - self.assertTrue('"Yet-Another-Name"' in pyperclip.paste()) + 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() @@ -370,10 +388,10 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.find_by_css_selector( QueryToolLocators.btn_clear_dropdown) ) - ActionChains(self.driver)\ + ActionChains(self.driver) \ .move_to_element( - self.page.find_by_css_selector( - QueryToolLocators.btn_clear_history)).perform() + 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) ) diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/pgadmin/feature_tests/view_data_dml_queries.py index 0155cdc32..db788bf24 100644 --- a/web/pgadmin/feature_tests/view_data_dml_queries.py +++ b/web/pgadmin/feature_tests/view_data_dml_queries.py @@ -131,13 +131,15 @@ CREATE TABLE public.nonintpkey self.test_db, 'public') self._load_config_data('table_insert_update_cases') + data_local = config_data # iterate on both tables for cnt in (1, 2): - self._perform_test_for_table('defaults_{0}'.format(str(cnt))) - + self._perform_test_for_table('defaults_{0}'.format(str(cnt)), + data_local) # test nonint pkey table self._load_config_data('table_insert_update_nonint') - self._perform_test_for_table('nonintpkey') + data_local = config_data + self._perform_test_for_table('nonintpkey', data_local) def after(self): self.page.remove_server(self.server) @@ -167,7 +169,7 @@ CREATE TABLE public.nonintpkey global config_data config_data = config_data_json[config_key] - def _perform_test_for_table(self, table_name): + def _perform_test_for_table(self, table_name, config_data_local): self.page.click_a_tree_node( table_name, TreeAreaLocators.sub_nodes_of_tables_node) @@ -176,19 +178,20 @@ 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() - self._verify_row_data(True, config_data['add']) + self._add_row(config_data_local) + self._verify_row_data(True, config_data_local['add']) # Run test to copy/paste a row - self._copy_paste_row() + self._copy_paste_row(config_data_local) - self._update_row() + self._update_row(config_data_local) self.page.click_tab("Messages") self._verify_messsages("") self.page.click_tab("Data Output") updated_row_data = { - i: config_data['update'][i] if i in config_data['update'] else val - for i, val in config_data['add'].items() + 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(False, updated_row_data) @@ -219,7 +222,6 @@ CREATE TABLE public.nonintpkey Returns: None """ - self.wait.until(EC.visibility_of_element_located( (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING ) @@ -236,7 +238,7 @@ CREATE TABLE public.nonintpkey if value == 'clear': cell_el.find_element_by_css_selector('input').clear() else: - ActionChains(self.driver).send_keys(value).\ + ActionChains(self.driver).send_keys(value). \ send_keys(Keys.ENTER).perform() elif cell_type in ['text', 'json', 'text[]', 'boolean[]']: text_area_ele = self.page.find_by_css_selector( @@ -288,7 +290,7 @@ CREATE TABLE public.nonintpkey self.page.driver.find_element_by_tag_name('iframe') ) - def _copy_paste_row(self): + def _copy_paste_row(self, config_data_l): row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath("r0", 1) self.page.find_by_xpath(row0_cell0_xpath).click() @@ -298,12 +300,12 @@ CREATE TABLE public.nonintpkey QueryToolLocators.paste_button_css).click() # Update primary key of copied cell - self._add_update_save_row(config_data['copy'], row=2) + self._add_update_save_row(config_data_l['copy'], row=2) # Verify row 1 and row 2 data updated_row_data = { - i: config_data['copy'][i] if i in config_data['copy'] else val - for i, val in config_data['add'].items() + 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(False, updated_row_data) @@ -326,11 +328,11 @@ CREATE TABLE public.nonintpkey # save ajax is completed. time.sleep(2) - def _add_row(self): - self._add_update_save_row(config_data['add'], 1) + def _add_row(self, config_data_l): + self._add_update_save_row(config_data_l['add'], 1) - def _update_row(self): - self._add_update_save_row(config_data['update'], 1) + def _update_row(self, config_data_l): + self._add_update_save_row(config_data_l['update'], 1) def _verify_messsages(self, text): messages_ele = self.page.find_by_css_selector( diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py index 9efad8a36..69c3cba80 100644 --- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py +++ b/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py @@ -211,17 +211,23 @@ class CheckForXssFeatureTest(BaseFeatureTest): "Query tool (History Entry)" ) - # Check for history details message - history_ele = self.driver\ - .find_element_by_css_selector(".query-detail .content-value") - - source_code = history_ele.get_attribute('innerHTML') + retry = 2 + while retry > 0: + try: + history_ele = self.driver \ + .find_element_by_css_selector( + ".query-detail .content-value") + source_code = history_ele.get_attribute('innerHTML') + break + except StaleElementReferenceException: + retry -= 1 self._check_escaped_characters( source_code, '<script>alert(1)</script>', "Query tool (History Details-Message)" ) + retry = 2 while retry > 0: try: diff --git a/web/pgadmin/utils/route.py b/web/pgadmin/utils/route.py index 35493e95d..98be31328 100644 --- a/web/pgadmin/utils/route.py +++ b/web/pgadmin/utils/route.py @@ -118,9 +118,8 @@ class BaseTestGenerator(unittest.TestCase): self.skipTest('cannot run in: %s' % server_con['data']['type']) - @classmethod - def setTestServer(cls, server): - cls.server = server + def setTestServer(self, server): + self.server = server @abstractmethod def runTest(self): @@ -135,17 +134,14 @@ class BaseTestGenerator(unittest.TestCase): def setTestClient(cls, test_client): cls.tester = test_client - @classmethod - def setDriver(cls, driver): - cls.driver = driver + def setDriver(self, driver): + self.driver = driver - @classmethod - def setServerInformation(cls, server_information): - cls.server_information = server_information + def setServerInformation(self, server_information): + self.server_information = server_information - @classmethod - def setTestDatabaseName(cls, database_name): - cls.test_db = database_name + def setTestDatabaseName(self, database_name): + self.test_db = database_name @classmethod def setReSQLModuleList(cls, module_list): diff --git a/web/regression/README b/web/regression/README index a4591acb2..314e58da9 100644 --- a/web/regression/README +++ b/web/regression/README @@ -141,6 +141,28 @@ Python Tests: and registered automatically by its module name in 'pgadmin4/web/pgadmin/utils/test.py' file. +- To run Feature Tests in parallel using selenoid(grid + docker), selenoid + need to be installed. Steps to install selenoid - + - Install & Start docker + $yum -y install docker docker-registry + $vi /etc/sysconfig/docker # in OPTIONS add ‘--selinux-enabled=false’ + $systemctl enable docker.service + $systemctl start docker.service + $systemctl status docker.service + - Install & Start Selenoid + $curl -s https://aerokube.com/cm/bash | bash + $./cm selenoid start --vnc -g -limit 3 -cpu 1.5 + $./cm selenoid-ui start + Check selenoid status - + http://:4444/status + - Should show json with browsers details + http://:8080/#/ + - Capabilities shows available browser + - Update config_local.py if exists else create new with 'DEFAULT_SERVER' value + DEFAULT_SERVER = '' + - Update 'test_config.json' with selenoid related info like selenoid url + & browser details where tests need to be executed + - Change to the regression test directory: run 'cd web/regression' @@ -190,6 +212,9 @@ Python Tests: Example 2) Execute only reverse engineered SQL test framework for some modules run 'python runtests.py --pkg resql --modules sequences,functions' +- Execute ui selenium tests in parallel using selenoid(selenium grid + docker) + Example : --pkg feature_tests --parallel + Code Coverage: --------------- diff --git a/web/regression/feature_utils/app_starter.py b/web/regression/feature_utils/app_starter.py index fe4c441a1..7de10b25c 100644 --- a/web/regression/feature_utils/app_starter.py +++ b/web/regression/feature_utils/app_starter.py @@ -61,11 +61,16 @@ class AppStarter: raise Exception('Unable to start python server even after ' 'retrying 60 times.') - launch_browser(0) + if self.driver is not None: + launch_browser(0) + else: + return "http://" + self.app_config.DEFAULT_SERVER + ":" \ + + random_server_port def stop_app(self): """ This function stop the started app by killing process """ - self.driver.quit() + if self.driver is not None: + self.driver.quit() # os.killpg supported in Mac and Unix as this function not supported in # Windows try: diff --git a/web/regression/feature_utils/locators.py b/web/regression/feature_utils/locators.py index b8169fa59..0df8e61dd 100644 --- a/web/regression/feature_utils/locators.py +++ b/web/regression/feature_utils/locators.py @@ -172,6 +172,8 @@ class QueryToolLocators: new_row_xpath = "//div[contains(@class, 'new-row')]" + scratch_pad_css = ".sql-scratch > textarea" + copy_button_css = "#btn-copy-row" paste_button_css = "#btn-paste-row" diff --git a/web/regression/feature_utils/pgadmin_page.py b/web/regression/feature_utils/pgadmin_page.py index aef031fd0..4d6f15e50 100644 --- a/web/regression/feature_utils/pgadmin_page.py +++ b/web/regression/feature_utils/pgadmin_page.py @@ -88,11 +88,17 @@ class PgadminPage: (By.CSS_SELECTOR, "button[type='save'].btn.btn-primary"))) self.find_by_css_selector("button[type='save'].btn.btn-primary").\ click() - - WebDriverWait(self.driver, 10).until( - EC.visibility_of_element_located( - (By.XPATH, - "//*[@id='tree']//*[.='" + server_config['name'] + "']"))) + try: + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located( + (By.XPATH, + "//*[@id='tree']//*[.='" + server_config['name'] + "']"))) + except TimeoutException: + self.toggle_open_servers_group() + WebDriverWait(self.driver, 10).until( + EC.visibility_of_element_located( + (By.XPATH, + "//*[@id='tree']//*[.='" + server_config['name'] + "']"))) def open_query_tool(self): self.driver.find_element_by_link_text("Tools").click() @@ -910,7 +916,11 @@ class PgadminPage: return element except (NoSuchElementException, WebDriverException): return False - + time.sleep(1) + self.driver.switch_to.default_content() + self.driver.switch_to_frame( + self.driver.find_element_by_tag_name("iframe")) + self.find_by_xpath("//a[text()='Query Editor']").click() codemirror_ele = WebDriverWait( self.driver, timeout=self.timeout, poll_frequency=0.01)\ .until(find_codemirror, @@ -1161,3 +1171,12 @@ class PgadminPage: except Exception: attempt += 1 return click_status + + def paste_values(self, el=None): + actions = ActionChains(self.driver) + if el: + el.click() + actions.key_down(Keys.SHIFT) + actions.send_keys(Keys.INSERT) + actions.key_up(Keys.SHIFT) + actions.perform() diff --git a/web/regression/python_test_utils/test_utils.py b/web/regression/python_test_utils/test_utils.py index 971fc2403..71b75df7b 100644 --- a/web/regression/python_test_utils/test_utils.py +++ b/web/regression/python_test_utils/test_utils.py @@ -17,6 +17,13 @@ import sqlite3 import shutil from functools import partial from testtools.testcase import clone_test_with_new_id +import re +import time +from selenium.common.exceptions import WebDriverException +import urllib.request as urllib +import json +from selenium import webdriver +from selenium.webdriver.chrome.options import Options import config import regression @@ -1216,3 +1223,144 @@ def create_expected_output(parameters, actual_data): actual_data.remove(value) break return expected_output + + +def is_parallel_ui_tests(args): + """ + This function checks for coverage args exists in command line args + :return: boolean + """ + if "parallel" in args and args["parallel"]: + return True + return False + + +def get_selenium_grid_status_and_browser_list(selrnoid_url): + """ + This function checks selenoid status for given url + :param selrnoid_url: + :return: status of selenoid & list of browsers available with selenoid if + status is up + """ + selenoid_status = False + browser_list = [] + try: + selenoid_status = urllib.urlopen( + "http://" + re.split('/', (re.split('//', selrnoid_url, 1)[1]))[ + 0] + "/status", timeout=10) + selenoid_status = json.load(selenoid_status) + if isinstance(selenoid_status, dict): + available_browsers = selenoid_status["browsers"] + selenoid_status = True + + list_of_browsers = test_setup.config_data['selenoid_info'][ + 'cross_Browsers'] + + for browser in list_of_browsers: + if browser["name"].lower() in available_browsers.keys(): + versions = available_browsers[(browser["name"].lower())] + if browser["version"] in versions.keys(): + browser_list.append(browser) + else: + print("Available {0} versions {1}".format(browser["name"], + versions.keys())) + print("Specified Version = {0}".format(browser["version"])) + else: + print("{0} is NOT available".format(browser["name"])) + except Exception as e: + print(str(e)) + print("Unable to find Selenoid Status") + + return selenoid_status, browser_list + + +def is_feature_test_included(arguments): + """ + :param arguments: his is command line arguments for module name to + which test suite will run + :return: boolean value whether to execute feature tests or NOT & + browser name if feature_test_tobe_included = True + """ + exclude_pkgs = [] + if arguments['exclude'] is not None: + exclude_pkgs += arguments['exclude'].split(',') + + feature_test_tobe_included = 'feature_tests' not in exclude_pkgs and \ + (arguments['pkg'] is None or arguments[ + 'pkg'] == "all" or + arguments['pkg'] == "feature_tests") + return feature_test_tobe_included + + +def launch_url_in_browser(driver_instance, url, retry_count): + """ + Function launches urls in sepecided driver instance + :param driver_instance:browser instance + :param url:url to be launched + :param retry_count: + :return: + """ + try: + driver_instance.get(url) + except WebDriverException as e: + # In case of WebDriverException sleep for 1 second and retry + # again. Retry 10 times and if still app will not start then + # raise exception. + time.sleep(1) + if retry_count < 60: + retry_count = retry_count + 1 + launch_url_in_browser(driver_instance, url, retry_count) + else: + raise Exception('Unable to start python server even after ' + 'retrying 60 times.') + + +def get_remote_webdriver(browser, browser_ver, test_name): + hub_url = test_setup.config_data['selenoid_info']['selenoid_url'] + test_name = browser + browser_ver + "_" + test_name + "-" + time.strftime( + "%m_%d_%y_%H_%M_%S", time.localtime()) + driver_local = None + + desired_capabilities = { + "version": browser_ver, + "enableVNC": True, + "enableVideo": True, + "enableLog": True, + "videoName": test_name + ".mp4", + "logName": test_name + ".log", + "name": test_name, + "timeZone": "Asia/Kolkata" + } + + if browser == 'firefox': + profile = webdriver.FirefoxProfile() + profile.set_preference("dom.disable_beforeunload", True) + desired_capabilities["browserName"] = "firefox" + desired_capabilities["requireWindowFocus"] = True + desired_capabilities["enablePersistentHover"] = False + driver_local = webdriver.Remote( + command_executor=hub_url, + desired_capabilities=desired_capabilities, browser_profile=profile) + driver_local.implicitly_wait(1) + + elif browser == 'chrome': + options = Options() + if test_setup.config_data: + if 'headless_chrome' in test_setup.config_data: + if test_setup.config_data['headless_chrome']: + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-setuid-sandbox") + options.add_argument("--window-size=1280,1024") + options.add_argument("--disable-infobars") + desired_capabilities["browserName"] = "chrome" + driver_local = webdriver.Remote( + command_executor=hub_url, + desired_capabilities=desired_capabilities, options=options) + else: + print("Specified browser does not exist.") + + # maximize browser window + driver_local.maximize_window() + driver_local.implicitly_wait(2) + return driver_local diff --git a/web/regression/runtests.py b/web/regression/runtests.py index fcf73a886..d41c962a2 100644 --- a/web/regression/runtests.py +++ b/web/regression/runtests.py @@ -21,7 +21,8 @@ import traceback import json import random import coverage - +import threading +import time import unittest if sys.version_info[0] >= 3: @@ -136,7 +137,7 @@ scenarios.apply_scenario = test_utils.apply_scenario def get_suite(module_list, test_server, test_app_client, server_information, - test_db_name): + test_db_name, driver_passed): """ This function add the tests to test suite and return modified test suite variable. @@ -166,7 +167,7 @@ def get_suite(module_list, test_server, test_app_client, server_information, obj.setApp(app) obj.setTestClient(test_app_client) obj.setTestServer(test_server) - obj.setDriver(driver) + obj.setDriver(driver_passed) obj.setServerInformation(server_information) obj.setTestDatabaseName(test_db_name) scenario = scenarios.generate_scenarios(obj) @@ -207,57 +208,59 @@ def get_test_modules(arguments): exclude_pkgs += arguments['exclude'].split(',') if 'feature_tests' not in exclude_pkgs and \ - (arguments['pkg'] is None or arguments['pkg'] == "all" or - arguments['pkg'] == "feature_tests"): - - from selenium import webdriver - from selenium.webdriver.chrome.options import Options - from selenium.webdriver.common.desired_capabilities import \ - DesiredCapabilities - - default_browser = 'chrome' - - # Check default browser provided through command line. If provided - # then use that browser as default browser else check for the setting - # provided in test_config.json file. - if ( - 'default_browser' in arguments and - arguments['default_browser'] is not None - ): - default_browser = arguments['default_browser'].lower() - elif ( - test_setup.config_data and - "default_browser" in test_setup.config_data - ): - default_browser = test_setup.config_data['default_browser'].lower() - - if default_browser == 'firefox': - cap = DesiredCapabilities.FIREFOX - cap['requireWindowFocus'] = True - cap['enablePersistentHover'] = False - profile = webdriver.FirefoxProfile() - profile.set_preference("dom.disable_beforeunload", True) - driver = webdriver.Firefox(capabilities=cap, - firefox_profile=profile) - driver.implicitly_wait(1) - else: - options = Options() - if test_setup.config_data: - if 'headless_chrome' in test_setup.config_data: - if test_setup.config_data['headless_chrome']: - options.add_argument("--headless") - options.add_argument("--no-sandbox") - options.add_argument("--disable-setuid-sandbox") - options.add_argument("--window-size=1280,1024") - options.add_argument("--disable-infobars") - options.add_experimental_option('w3c', False) - driver = webdriver.Chrome(chrome_options=options) - - # maximize browser window - driver.maximize_window() - - app_starter = AppStarter(driver, config) - app_starter.start_app() + (arguments['pkg'] is None or arguments['pkg'] == "all" or + arguments['pkg'] == "feature_tests"): + + if not test_utils.is_parallel_ui_tests(args): + from selenium import webdriver + from selenium.webdriver.chrome.options import Options + from selenium.webdriver.common.desired_capabilities import \ + DesiredCapabilities + + default_browser = 'chrome' + + # Check default browser provided through command line. If provided + # then use that browser as default browser else check for the + # settingn provided in test_config.json file. + if ( + 'default_browser' in arguments and + arguments['default_browser'] is not None + ): + default_browser = arguments['default_browser'].lower() + elif ( + test_setup.config_data and + "default_browser" in test_setup.config_data + ): + default_browser = test_setup.config_data[ + 'default_browser'].lower() + + if default_browser == 'firefox': + cap = DesiredCapabilities.FIREFOX + cap['requireWindowFocus'] = True + cap['enablePersistentHover'] = False + profile = webdriver.FirefoxProfile() + profile.set_preference("dom.disable_beforeunload", True) + driver = webdriver.Firefox(capabilities=cap, + firefox_profile=profile) + driver.implicitly_wait(1) + else: + options = Options() + if test_setup.config_data: + if 'headless_chrome' in test_setup.config_data: + if test_setup.config_data['headless_chrome']: + options.add_argument("--headless") + options.add_argument("--no-sandbox") + options.add_argument("--disable-setuid-sandbox") + options.add_argument("--window-size=1280,1024") + options.add_argument("--disable-infobars") + options.add_experimental_option('w3c', False) + driver = webdriver.Chrome(chrome_options=options) + + # maximize browser window + driver.maximize_window() + + app_starter = AppStarter(driver, config) + app_starter.start_app() handle_cleanup = test_utils.get_cleanup_handler(test_client, app_starter) # Register cleanup function to cleanup on exit @@ -319,6 +322,9 @@ def add_arguments(): '--modules', help='Executes the feature test for specific modules in pkg' ) + parser.add_argument('--parallel', nargs='?', const=True, + type=bool, default=False, + help='Enable parallel Feature Tests') arg = parser.parse_args() return arg @@ -404,6 +410,115 @@ class StreamToLogger(object): pass +driver_info = [] + + +def run_test(test_module_list_passed, server_passed, driver_passed): + try: + print("\n=============Running the test cases for '%s' =============" + % server_passed['name'], file=sys.stderr) + + # Store driver object info & thread id + driver_info.append((threading.current_thread().ident, driver_passed)) + + # Create test server + server_information = \ + test_utils.create_parent_server_node(server_passed) + + # Create test database with random number to avoid conflict in + # parallel execution on different platforms. This database will be + # used across all feature tests. + test_db_name = "acceptance_test_db" + \ + str(random.randint(10000, 65535)) + connection = test_utils.get_db_connection( + server_passed['db'], + server_passed['username'], + server_passed['db_password'], + server_passed['host'], + server_passed['port'], + server_passed['sslmode'] + ) + + # Add the server version in server information + server_information['server_version'] = connection.server_version + server_information['type'] = server_passed['type'] + + # Drop the database if already exists. + test_utils.drop_database(connection, test_db_name) + + # Create database + test_utils.create_database(server_passed, test_db_name) + + # Configure preferences for the test cases + test_utils.configure_preferences( + default_binary_path=server_passed['default_binary_paths']) + + # Get unit test suit + suite = get_suite(test_module_list_passed, + server_passed, + test_client, + server_information, test_db_name, driver_passed) + + # Run unit test suit created + tests = unittest.TextTestRunner(stream=sys.stderr, + descriptions=True, + verbosity=2).run(suite) + + # processing results + ran_tests, failed_cases, skipped_cases, passed_cases = \ + get_tests_result(tests) + + # This is required when some tests are running parallel + # & some sequential in case of parallel ui tests + if thread.getName() == "sequential_tests": + try: + if test_result[server_passed['name']][0] is not None: + ran_tests = test_result[server_passed['name']][0] + \ + ran_tests + failed_cases.update(test_result[server_passed['name']][1]) + skipped_cases.update(test_result[server_passed['name']][2]) + passed_cases.update(test_result[server_passed['name']][3]) + test_result[server_passed['name']] = [ran_tests, failed_cases, + skipped_cases, + passed_cases] + except KeyError: + pass + + # Add final results server wise in test_result dict + test_result[server_passed['name']] = [ran_tests, failed_cases, + skipped_cases, passed_cases] + + # Set empty list for 'passed' parameter for each testRun. + # So that it will not append same test case name + unittest.result.TestResult.passed = [] + + # Drop the testing database created initially + if connection: + test_utils.drop_database(connection, test_db_name) + connection.close() + + # Delete test server + test_utils.delete_test_server(test_client) + except Exception: + traceback.print_exc(file=sys.stderr) + print("Exception in {0}".format(threading.current_thread().ident)) + finally: + # Delete web-driver instance + thread_name = "parallel_tests" + server_passed['name'] + if threading.currentThread().getName() == thread_name: + for d in driver_info: + if d[0] == threading.currentThread().ident: + d[1].quit() + driver_info.remove(d) + # Need time by selenoid to clean previous thread before new thread + time.sleep(10) + + # Print info about completed tests + print( + "\n=============Completed the test cases for '%s'=============" + % server_passed['name'], file=sys.stderr) + + if __name__ == '__main__': # Failure detected? failure = False @@ -423,7 +538,8 @@ if __name__ == '__main__': fh = logging.FileHandler(CURRENT_PATH + '/' + 'regression.log', 'w', 'utf-8') fh.setLevel(logging.DEBUG) - fh.setFormatter(logging.Formatter(config.FILE_LOG_FORMAT)) + fh.setFormatter(logging.Formatter('[%(thread)d] ' + + config.FILE_LOG_FORMAT)) logger = logging.getLogger() logger.addHandler(fh) @@ -451,69 +567,141 @@ if __name__ == '__main__': cov = coverage.Coverage(config_file=COVERAGE_CONFIG_FILE) cov.start() - try: - for server in servers_info: - print("\n=============Running the test cases for '%s'=============" - % server['name'], file=sys.stderr) - # Create test server - server_information = test_utils.create_parent_server_node(server) - - # Create test database with random number to avoid conflict in - # parallel execution on different platforms. This database will be - # used across all feature tests. - test_db_name = "acceptance_test_db" + \ - str(random.randint(10000, 65535)) - connection = test_utils.get_db_connection( - server['db'], - server['username'], - server['db_password'], - server['host'], - server['port'], - server['sslmode'] - ) - - # Add the server version in server information - server_information['server_version'] = connection.server_version - server_information['type'] = server['type'] - - # Drop the database if already exists. - test_utils.drop_database(connection, test_db_name) - # Create database - test_utils.create_database(server, test_db_name) - # Configure preferences for the test cases - test_utils.configure_preferences( - default_binary_path=server['default_binary_paths']) - - suite = get_suite(test_module_list, - server, - test_client, - server_information, test_db_name) - tests = unittest.TextTestRunner(stream=sys.stderr, - descriptions=True, - verbosity=2).run(suite) - - ran_tests, failed_cases, skipped_cases, passed_cases = \ - get_tests_result(tests) - test_result[server['name']] = [ran_tests, failed_cases, - skipped_cases, passed_cases] - - # Set empty list for 'passed' parameter for each testRun. - # So that it will not append same test case name - unittest.result.TestResult.passed = [] - - if len(failed_cases) > 0: - failure = True - - # Drop the testing database created initially - if connection: - test_utils.drop_database(connection, test_db_name) - connection.close() - - # Delete test server - test_utils.delete_test_server(test_client) - except SystemExit: - if handle_cleanup: - handle_cleanup() + # Check if feature tests included & parallel tests switch passed + if test_utils.is_feature_test_included(args) and \ + test_utils.is_parallel_ui_tests(args): + + # Get selenium grid status & list of available browser out passed + selenium_grid_status, list_of_browsers \ + = test_utils.get_selenium_grid_status_and_browser_list( + test_setup.config_data['selenoid_info']['selenoid_url']) + # list of files consisting tests that needs to be + # executed sequentially + sequential_tests_file = [ + 'pgadmin.feature_tests.pg_utilities_backup_restore_test', + 'pgadmin.feature_tests.pg_utilities_maintenance_test', + 'pgadmin.feature_tests.keyboard_shortcut_test', + 'regression.re_sql.tests.test_resql'] + # If selenium grid is up & browsers are available + if selenium_grid_status and len(list_of_browsers) > 0: + + try: + # run across browsers + for browser_info in list_of_browsers: + # browser info + browser_name = browser_info["name"].lower() + browser_version = browser_info["version"] + + # list of tests can be executed in parallel + parallel_tests = list(test_module_list) + for module in test_module_list: + if str(module[0]) in sequential_tests_file: + parallel_tests.remove(module) + + # list of tests can be executed in sequentially + sequential_tests = list( + filter(lambda i: i not in parallel_tests, + test_module_list)) + + print( + "====================================================" + "==================\n", + file=sys.stderr + ) + print( + "Total Tests # {0}\nParallel Tests # {1}, Sequential " + "Tests # {2}".format( + len(test_module_list), len(parallel_tests), + len(sequential_tests)), + file=sys.stderr) + print("Browser: [Name:{0}, Version: {1}]".format( + browser_name.capitalize(), browser_version), + file=sys.stderr) + print( + "====================================================" + "==================\n", + file=sys.stderr + ) + + # Running Parallel tests + if len(parallel_tests) > 0: + # Create app form source code + app_starter_local = AppStarter(None, config) + client_url = app_starter_local.start_app() + + try: + for srv in servers_info: + # Get driver instance at server level + driver_obj = test_utils.get_remote_webdriver( + browser_name, + browser_version, + srv['name']) + test_utils.launch_url_in_browser(driver_obj, + client_url, 0) + + threads = [] + t_name = "parallel_tests" + srv['name'] + thread = threading.Thread(target=run_test, + name=t_name, + args=( + parallel_tests, + srv, driver_obj)) + threads.append(thread) + thread.start() + for thread in threads: + thread.join() + except Exception: + traceback.print_exc(file=sys.stderr) + print("Exception in thread {0}".format( + thread.ident())) + + # Sequential Tests + if len(sequential_tests) > 0: + # Get driver instance + driver_obj2 = test_utils.get_remote_webdriver( + browser_name, + browser_version, + "Sequential_Tests") + test_utils.launch_url_in_browser(driver_obj2, + client_url, 0) + try: + t_name = "sequential_tests" + for srv in servers_info: + thread = threading.Thread(target=run_test, + name=t_name, + args=( + sequential_tests, + srv, + driver_obj2)) + thread.start() + thread.join() + driver_obj2.quit() + except Exception: + traceback.print_exc(file=sys.stderr) + print("Exception in thread {0}".format( + thread.ident())) + + # Clean up environmetn + if app_starter_local: + app_starter_local.stop_app() + except SystemExit: + if app_starter_local: + app_starter_local.stop_app() + if handle_cleanup: + handle_cleanup() + else: + try: + for server in servers_info: + thread = threading.Thread(target=run_test, args=( + test_module_list, server, driver)) + thread.start() + thread.join() + except SystemExit: + if handle_cleanup: + handle_cleanup() + + # Pause before printing result in order not to mix output + time.sleep(3) print( "\n===============================================================" @@ -542,6 +730,8 @@ if __name__ == '__main__': skipped_cases.items()).values()) total_passed_cases = int( test_result[server_res][0]) - total_failed - total_skipped + if len(failed_cases) > 0: + failure = True print( "%s:\n\n\t%s test%s passed\n\t%s test%s failed%s%s" diff --git a/web/regression/test_config.json.in b/web/regression/test_config.json.in index 0a151e633..07bfacd7b 100644 --- a/web/regression/test_config.json.in +++ b/web/regression/test_config.json.in @@ -1,6 +1,8 @@ { "headless_chrome": false, "default_browser": "Chrome", + "selenoid_info":{"cross_Browsers": [{"name": "browser_name","version": "browserr_version"}], + "selenoid_url": "Selenoid Url"}, "pgAdmin4_login_credentials": { "new_password": "NEWPASSWORD", "login_password": "PASSWORD",