diff --git a/web/pgadmin/conftest.py b/web/conftest.py similarity index 88% rename from web/pgadmin/conftest.py rename to web/conftest.py index 4ab174ed..c0c3476d 100644 --- a/web/pgadmin/conftest.py +++ b/web/conftest.py @@ -6,17 +6,39 @@ # This software is released under the PostgreSQL Licence # ########################################################################## + +import atexit +import logging import os +import signal import sys import pytest +from selenium import webdriver +from selenium.webdriver.chrome.options import Options + +logger = logging.getLogger(__name__) +file_name = os.path.basename(__file__) + +if sys.version_info < (2, 7): + pass +else: + pass + +if sys.version_info[0] >= 3: + import builtins +else: + import __builtin__ as builtins + +# Ensure the global server mode is set. +builtins.SERVER_MODE = None logger = logging.getLogger(__name__) file_name = os.path.basename(__file__) CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) -root = os.path.dirname(CURRENT_PATH) +root = os.path.dirname(CURRENT_PATH) + os.path.sep + 'web' if sys.path[0] != root: sys.path.insert(0, root) @@ -36,6 +58,13 @@ from regression import test_setup from regression.feature_utils.app_starter import AppStarter +if config.SERVER_MODE is True: + config.SECURITY_RECOVERABLE = True + config.SECURITY_CHANGEABLE = True + config.SECURITY_POST_CHANGE_VIEW = 'browser.change_password' + +from regression.feature_utils.app_starter import AppStarter + # Delete SQLite db file if exists if os.path.isfile(config.TEST_SQLITE_PATH): os.remove(config.TEST_SQLITE_PATH) @@ -74,6 +103,11 @@ from regression.python_test_utils import test_utils config.SETTINGS_SCHEMA_VERSION = SCHEMA_VERSION +# Override some other defaults +from logging import WARNING + +config.CONSOLE_LOG_LEVEL = WARNING + # Create the app app = create_app() app.config['WTF_CSRF_ENABLED'] = False @@ -87,14 +121,25 @@ handle_cleanup = None server_info = test_utils.get_config_data() +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + # execute all other hooks to obtain the report object + outcome = yield + rep = outcome.get_result() + + # set a report attribute for each phase of a call, which can + # be "setup", "call", "teardown" + + setattr(item, "rep_" + rep.when, rep) + + @pytest.fixture(scope="session", autouse=True) -def database_server(): +def database_server(request): server_information = test_utils.create_parent_server_node(server_info[0]) + request.addfinalizer(lambda: test_utils.delete_test_server(test_client)) yield server_information - test_utils.delete_test_server(test_client) - @pytest.fixture(scope='session') def context_of_tests(database_server): diff --git a/web/package.json b/web/package.json index 0e895968..23ea3d0f 100644 --- a/web/package.json +++ b/web/package.json @@ -102,8 +102,8 @@ "test:karma-once": "yarn run linter && yarn run karma start --single-run", "test:karma": "yarn run linter && yarn run karma start", "test:unit": "yarn run pep8 && python -m pytest -q pgadmin", - "test:feature": "yarn run bundle && python regression/runtests.py --pkg feature_tests", - "test": "yarn run test:karma-once && yarn run bundle && yarn test:unit && python regression/runtests.py --pkg feature_tests", + "test:feature": "yarn run bundle && python -m pytest -q regression/feature_tests", + "test": "yarn run test:karma-once && yarn run bundle && yarn test:unit && yarn test:feature", "pep8": "pycodestyle --config=.pycodestyle ." } } diff --git a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py index 9cc4f6a4..d14b3002 100644 --- a/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py +++ b/web/pgadmin/browser/server_groups/servers/tests/test_add_server_with_ssh_tunnel.py @@ -6,12 +6,11 @@ # This software is released under the PostgreSQL Licence # ########################################################################## - +import copy import json from grappa import should -from pgadmin.utils.base_test_generator import BaseTestGenerator from regression.python_test_utils import test_utils as utils @@ -29,7 +28,7 @@ class TestServersWithSSHTunnelAdd: url = "/browser/server/obj/{0}/".format(utils.SERVER_GROUP) - self.server = context_of_tests['server'] + self.server = copy.deepcopy(context_of_tests['server']) self.tester = context_of_tests['test_client'] self.server['use_ssh_tunnel'] = 1 self.server['tunnel_host'] = '127.0.0.1' @@ -61,7 +60,7 @@ class TestServersWithSSHTunnelAdd: url = "/browser/server/obj/{0}/".format(utils.SERVER_GROUP) - self.server = context_of_tests['server'] + self.server = copy.deepcopy(context_of_tests['server']) self.tester = context_of_tests['test_client'] self.server['use_ssh_tunnel'] = 1 self.server['tunnel_host'] = '127.0.0.1' diff --git a/web/regression/conftest.py b/web/regression/conftest.py deleted file mode 100644 index 1f66b465..00000000 --- a/web/regression/conftest.py +++ /dev/null @@ -1,35 +0,0 @@ -# -# pgAdmin 4 - PostgreSQL Tools -# -# Copyright (C) 2013 - 2018, The pgAdmin Development Team -# This software is released under the PostgreSQL Licence -# - -# def pytest_generate_tests(metafunc): -# print('\n\n\npytest_generate_tests\n\n\n') -# idlist = [] -# argvalues = [] -# for scenario in metafunc.cls.scenarios: -# idlist.append(scenario[0]) -# items = scenario[1].items() -# argnames = [x[0] for x in items] -# argvalues.append(([x[1] for x in items])) -# metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") -import sys - - -def pytest_generate_tests(metafunc): - print('Generation next') - idlist = [] - argvalues = [] - argnames = [] - print('output', file=sys.stderr) - for scenario in metafunc.cls.scenarios: - idlist.append(scenario[0]) - items = scenario[1].items() - argnames = [x[0] for x in items] - argvalues.append(([x[1] for x in items])) - print('bamm', file=sys.stderr) - print('shebang', file=sys.stderr) - metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class", - indirect=False) diff --git a/web/pgadmin/feature_tests/__init__.py b/web/regression/feature_tests/__init__.py similarity index 100% rename from web/pgadmin/feature_tests/__init__.py rename to web/regression/feature_tests/__init__.py diff --git a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py b/web/regression/feature_tests/copy_selected_query_results_feature_test.py similarity index 85% rename from web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py rename to web/regression/feature_tests/copy_selected_query_results_feature_test.py index ab54d289..9dbdb53d 100644 --- a/web/pgadmin/feature_tests/copy_selected_query_results_feature_test.py +++ b/web/regression/feature_tests/copy_selected_query_results_feature_test.py @@ -8,6 +8,7 @@ ########################################################################## import pyperclip +from grappa import should from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys @@ -16,16 +17,15 @@ from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest -class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): - """ - Tests various ways to copy data from the query results grid. - """ +class TestCopySelectedQueryResults(BaseFeatureTest): + def test_copy_selected_query_results(self, driver): + """ + Tests various ways to copy data from the query results grid. + """ + self.driver = driver - scenarios = [ - ("Copy rows, column using button and keyboard shortcut", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -40,7 +40,6 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.server, "acceptance_test_db", "test_table") self.page.add_server(self.server) - def runTest(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') @@ -63,6 +62,19 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self._shift_resizes_column_selection() self._mouseup_outside_grid_still_makes_a_selection() + self.page.close_query_tool() + 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'], + self.server['sslmode'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + def _copies_rows(self): pyperclip.copy("old clipboard contents") self.page.find_by_xpath( @@ -70,8 +82,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertEqual('"Some-Name"\t"6"\t"some info"', - pyperclip.paste()) + pyperclip.paste() | should.equal('"Some-Name"\t"6"\t"some info"') def _copies_columns(self): pyperclip.copy("old clipboard contents") @@ -81,11 +92,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ).click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertEqual( - """\"Some-Name" + pyperclip.paste() | should.equal("""\"Some-Name" "Some-Other-Name" -"Yet-Another-Name\"""", - pyperclip.paste()) +"Yet-Another-Name\"""") def _copies_row_using_keyboard_shortcut(self): pyperclip.copy("old clipboard contents") @@ -95,8 +104,7 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertEqual('"Some-Name"\t"6"\t"some info"', - pyperclip.paste()) + pyperclip.paste() | should.equal('"Some-Name"\t"6"\t"some info"') def _copies_column_using_keyboard_shortcut(self): pyperclip.copy("old clipboard contents") @@ -108,11 +116,9 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertEqual( - """\"Some-Name" + pyperclip.paste() | should.equal("""\"Some-Name" "Some-Other-Name" -"Yet-Another-Name\"""", - pyperclip.paste()) +"Yet-Another-Name\"""") def _copies_rectangular_selection(self): pyperclip.copy("old clipboard contents") @@ -134,8 +140,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): self.page.driver ).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertEqual("""\"Some-Other-Name"\t"22" -"Yet-Another-Name"\t"14\"""", pyperclip.paste()) + pyperclip.paste() | should.equal("""\"Some-Other-Name"\t"22" +"Yet-Another-Name"\t"14\"""") def _shift_resizes_rectangular_selection(self): pyperclip.copy("old clipboard contents") @@ -160,8 +166,8 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): Keys.CONTROL ).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertEqual("""\"Some-Other-Name"\t"22"\t"some other info" -"Yet-Another-Name"\t"14"\t"cool info\"""", pyperclip.paste()) + pyperclip.paste() | should.equal("""\"Some-Other-Name"\t"22"\t"some other info" +"Yet-Another-Name"\t"14"\t"cool info\"""") def _shift_resizes_column_selection(self): pyperclip.copy("old clipboard contents") @@ -177,11 +183,10 @@ class CopySelectedQueryResultsFeatureTest(BaseFeatureTest): ActionChains(self.page.driver).key_down( Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() - self.assertEqual( + pyperclip.paste() | should.equal( """\"Some-Name"\t"6" "Some-Other-Name"\t"22" -"Yet-Another-Name"\t"14\"""", - pyperclip.paste()) +"Yet-Another-Name"\t"14\"""") def _mouseup_outside_grid_still_makes_a_selection(self): pyperclip.copy("old clipboard contents") @@ -200,18 +205,4 @@ 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()) - - def after(self): - self.page.close_query_tool() - 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'], - self.server['sslmode'] - ) - test_utils.drop_database(connection, "acceptance_test_db") + pyperclip.paste() | should.contain('"cool info"') diff --git a/web/pgadmin/feature_tests/datatype_test.json b/web/regression/feature_tests/datatype_test.json similarity index 100% rename from web/pgadmin/feature_tests/datatype_test.json rename to web/regression/feature_tests/datatype_test.json diff --git a/web/pgadmin/feature_tests/keyboard_shortcut_test.py b/web/regression/feature_tests/keyboard_shortcut_test.py similarity index 82% rename from web/pgadmin/feature_tests/keyboard_shortcut_test.py rename to web/regression/feature_tests/keyboard_shortcut_test.py index bbae1940..0a8d9ff0 100644 --- a/web/pgadmin/feature_tests/keyboard_shortcut_test.py +++ b/web/regression/feature_tests/keyboard_shortcut_test.py @@ -8,28 +8,31 @@ ########################################################################## from __future__ import print_function -import time + import sys +import time -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By +from grappa import should from selenium.webdriver import ActionChains -from regression.feature_utils.base_feature_test import BaseFeatureTest +from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait + +from regression.feature_utils.base_feature_test import BaseFeatureTest -class KeyboardShortcutFeatureTest(BaseFeatureTest): - """ +class TestKeyboardShortcut(BaseFeatureTest): + def test_keyboard_shortcut(self, driver): + """ This feature test will test the keyboard short is working properly. - """ + """ - scenarios = [ - ("Test for keyboard shortcut", dict()) - ] + self.driver = driver + + self.setUp() - def before(self): self.new_shortcuts = { 'mnu_file': { 'shortcut': [Keys.ALT, Keys.SHIFT, 'i'], @@ -43,18 +46,19 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest): self.wait = WebDriverWait(self.page.driver, 10) - def runTest(self): self._update_preferences() + # On updating keyboard shortcuts, preference cache is updated. # There is no UI event through which we can identify that the cache # is updated, So, added time.sleep() time.sleep(1) + self._check_shortcuts() def _check_shortcuts(self): action = ActionChains(self.driver) - for s in self.new_shortcuts: - key_combo = self.new_shortcuts[s]['shortcut'] + for shortcut in self.new_shortcuts: + key_combo = self.new_shortcuts[shortcut]['shortcut'] action.key_down( key_combo[0] ).key_down( @@ -67,20 +71,25 @@ class KeyboardShortcutFeatureTest(BaseFeatureTest): key_combo[1] ).perform() - print("Executing shortcut: " + self.new_shortcuts[s]['locator'] + - "...", file=sys.stderr, end="") + print( + "Executing shortcut: " + + self.new_shortcuts[shortcut]['locator'] + + "...", + file=sys.stderr, end="" + ) self.wait.until( EC.presence_of_element_located( (By.XPATH, "//li[contains(@id, " + - s + + shortcut + ") and contains(@class, 'open')]") ) ) - is_open = 'open' in self.page.find_by_id(s).get_attribute('class') - - assert is_open is True, "Keyboard shortcut change is unsuccessful." + self.page.find_by_id(shortcut).get_attribute('class') | \ + should.contain( + 'open', + msg='Keyboard shortcut change is unsuccessful.') print("OK", file=sys.stderr) diff --git a/web/pgadmin/feature_tests/pg_datatype_validation_test.py b/web/regression/feature_tests/pg_datatype_validation_test.py similarity index 85% rename from web/pgadmin/feature_tests/pg_datatype_validation_test.py rename to web/regression/feature_tests/pg_datatype_validation_test.py index 3e34c175..c23d08a6 100644 --- a/web/pgadmin/feature_tests/pg_datatype_validation_test.py +++ b/web/regression/feature_tests/pg_datatype_validation_test.py @@ -7,17 +7,19 @@ # ########################################################################## -import os import json +import os import time + +from grappa import should from selenium.common.exceptions import TimeoutException -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.webdriver.common.by import By from selenium.webdriver import ActionChains -from regression.python_test_utils import test_utils -from regression.feature_utils.base_feature_test import BaseFeatureTest +from selenium.webdriver.common.by import By +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait +from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.python_test_utils import test_utils CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) @@ -31,17 +33,16 @@ except Exception as e: print(str(e)) -class PGDataypeFeatureTest(BaseFeatureTest): - """ - This feature test will test the different Postgres - data-type output. - """ +class TestPGDataype(BaseFeatureTest): + def test_pg_datatype(self, driver): + """ + This feature test will test the different Postgres + data-type output. + """ + self.driver = driver - scenarios = [ - ("Test checks for PG data-types output", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -76,6 +77,25 @@ class PGDataypeFeatureTest(BaseFeatureTest): # to add matching closing bracket by it self. self._update_preferences() + self.page.wait_for_spinner_to_disappear() + self.page.add_server(self.server) + self._schema_node_expandable() + + # Check data types + self._check_datatype() + self.page.close_query_tool() + + 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'], + self.server['sslmode'] + ) + test_utils.drop_database(connection, "acceptance_test_db") + def _update_preferences(self): self.page.find_by_id("mnu_file").click() self.page.find_by_id("mnu_preferences").click() @@ -100,7 +120,7 @@ class PGDataypeFeatureTest(BaseFeatureTest): "contains(.,'Insert bracket pairs?')]" ) - switch_btn = insert_bracket_pairs_control.\ + switch_btn = insert_bracket_pairs_control. \ find_element_by_class_name('bootstrap-switch') # check if switch is on then only toggle. @@ -126,27 +146,6 @@ class PGDataypeFeatureTest(BaseFeatureTest): self.page.find_by_id("btn-flash").click() self._clear_query_tool() - def runTest(self): - self.page.wait_for_spinner_to_disappear() - self.page.add_server(self.server) - self._schema_node_expandable() - - # Check data types - self._check_datatype() - self.page.close_query_tool() - - def after(self): - 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'], - self.server['sslmode'] - ) - test_utils.drop_database(connection, "acceptance_test_db") - def _schema_node_expandable(self): self.page.toggle_open_tree_item(self.server['name']) self.page.toggle_open_tree_item('Databases') @@ -158,7 +157,7 @@ class PGDataypeFeatureTest(BaseFeatureTest): self.page.open_query_tool() self._create_enum_type() for batch in config_data: - query = self.construct_select_query(batch) + query = self._construct_select_query(batch) self.page.fill_codemirror_area_with(query) self.page.find_by_id("btn-flash").click() wait = WebDriverWait(self.page.driver, 5) @@ -179,35 +178,30 @@ class PGDataypeFeatureTest(BaseFeatureTest): cells = canvas.find_elements_by_css_selector('.slick-cell') # remove first element as it is row number. cells.pop(0) - for val, cell, datatype in zip( - batch['output'], cells, batch['datatype']): + for val, cell, datatype in zip(batch['output'], + cells, + batch['datatype']): expected_output = batch['output'][cnt - 2] if not self._is_datatype_available_in_current_database( - datatype): + datatype): cnt += 1 continue if datatype in ('tstzrange', 'tstzrange[]'): expected_output = expected_output.format( **dict([('tz', self.timezone_hh_mm)])) - try: - source_code = cell.text - PGDataypeFeatureTest.check_result( - datatype, - source_code, - expected_output - ) - cnt += 1 - except TimeoutException: - assert False,\ - "for datatype {0}\n{1} does not match with {2}".format( - datatype, val, expected_output - ) + source_code = cell.text + (lambda: TestPGDataype.check_result( + datatype, + source_code, + expected_output + )) | should.does_not.raises(TimeoutException) + self._clear_query_tool() - def construct_select_query(self, batch): + def _construct_select_query(self, batch): query = 'SELECT ' first = True for datatype, inputdata in zip(batch['datatype'], batch['input']): @@ -231,17 +225,18 @@ class PGDataypeFeatureTest(BaseFeatureTest): @staticmethod def check_result(datatype, source_code, string_to_find): - assert source_code == string_to_find,\ - "for datatype {0}\n{1} does not match with {2}".format( - datatype, source_code, string_to_find - ) + source_code | \ + should.equal( + string_to_find, + msg="for datatype {0}\n{1} does not match with {2}".format( + datatype, source_code, string_to_find)) def _clear_query_tool(self): self.page.click_element( self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") ) - ActionChains(self.driver)\ - .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']"))\ + ActionChains(self.driver) \ + .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']")) \ .perform() self.page.click_element( self.page.find_by_xpath("//*[@id='btn-clear']") diff --git a/web/pgadmin/feature_tests/query_tool_journey_test.py b/web/regression/feature_tests/query_tool_journey_test.py similarity index 83% rename from web/pgadmin/feature_tests/query_tool_journey_test.py rename to web/regression/feature_tests/query_tool_journey_test.py index ec120439..26282979 100644 --- a/web/pgadmin/feature_tests/query_tool_journey_test.py +++ b/web/regression/feature_tests/query_tool_journey_test.py @@ -8,24 +8,23 @@ ########################################################################## import pyperclip - +from grappa import should from selenium.webdriver import ActionChains from selenium.webdriver.common.keys import Keys -from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.python_test_utils import test_utils -class QueryToolJourneyTest(BaseFeatureTest): - """ - Tests the path through the query tool - """ +class TestQueryToolJourney(BaseFeatureTest): + def test_table_ddl(self, driver): + """ + Tests the path through the query tool + """ + self.driver = driver - scenarios = [ - ("Tests the path through the query tool", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -39,7 +38,6 @@ class QueryToolJourneyTest(BaseFeatureTest): self.server, "acceptance_test_db", "test_table") self.page.add_server(self.server) - def runTest(self): self._navigate_to_query_tool() self._execute_query( @@ -50,6 +48,16 @@ class QueryToolJourneyTest(BaseFeatureTest): self._test_copies_columns() self._test_history_tab() + self.page.close_query_tool() + 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 _test_copies_rows(self): pyperclip.copy("old clipboard contents") self.page.driver.switch_to.default_content() @@ -59,8 +67,7 @@ class QueryToolJourneyTest(BaseFeatureTest): "//*[contains(@class, 'slick-row')]/*[1]").click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertEqual('"Some-Name"\t"6"\t"some info"', - pyperclip.paste()) + pyperclip.paste() | should.equal('"Some-Name"\t"6"\t"some info"') def _test_copies_columns(self): pyperclip.copy("old clipboard contents") @@ -74,9 +81,9 @@ class QueryToolJourneyTest(BaseFeatureTest): ).click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() - self.assertTrue('"Some-Name"' in pyperclip.paste()) - self.assertTrue('"Some-Other-Name"' in pyperclip.paste()) - self.assertTrue('"Yet-Another-Name"' in pyperclip.paste()) + pyperclip.paste() | should.contain('"Some-Name"') + pyperclip.paste() | should.contain('"Some-Other-Name"') + pyperclip.paste() | should.contain('"Yet-Another-Name"') def _test_history_tab(self): self.__clear_query_tool() @@ -87,30 +94,29 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.click_tab("Query History") selected_history_entry = self.page.find_by_css_selector( "#query_list .selected") - self.assertIn("SELECT * FROM table_that_doesnt_exist", - selected_history_entry.text) + selected_history_entry.text | should.contain( + "SELECT * FROM table_that_doesnt_exist") failed_history_detail_pane = self.page.find_by_id("query_detail") - self.assertIn( + failed_history_detail_pane.text | should.contain( "Error Message relation \"table_that_doesnt_exist\" " - "does not exist", failed_history_detail_pane.text - ) + "does not exist") ActionChains(self.page.driver) \ .send_keys(Keys.ARROW_DOWN) \ .perform() selected_history_entry = self.page.find_by_css_selector( "#query_list .selected") - self.assertIn("SELECT * FROM test_table ORDER BY value", - selected_history_entry.text) + selected_history_entry.text | should.contain( + "SELECT * FROM test_table ORDER BY value") selected_history_detail_pane = self.page.find_by_id("query_detail") - self.assertIn("SELECT * FROM test_table ORDER BY value", - selected_history_detail_pane.text) + selected_history_detail_pane.text | should.contain( + "SELECT * FROM test_table ORDER BY value") newly_selected_history_entry = self.page.find_by_xpath( "//*[@id='query_list']/ul/li[2]") self.page.click_element(newly_selected_history_entry) selected_history_detail_pane = self.page.find_by_id("query_detail") - self.assertIn("SELECT * FROM table_that_doesnt_exist", - selected_history_detail_pane.text) + selected_history_detail_pane.text | should.contain( + "SELECT * FROM table_that_doesnt_exist") self.__clear_query_tool() @@ -156,8 +162,8 @@ class QueryToolJourneyTest(BaseFeatureTest): self.page.click_element( self.page.find_by_xpath("//*[@id='btn-clear-dropdown']") ) - ActionChains(self.driver)\ - .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']"))\ + ActionChains(self.driver) \ + .move_to_element(self.page.find_by_xpath("//*[@id='btn-clear']")) \ .perform() self.page.click_element( self.page.find_by_xpath("//*[@id='btn-clear']") @@ -176,14 +182,3 @@ class QueryToolJourneyTest(BaseFeatureTest): def _assert_clickable(self, element): self.page.click_element(element) - - def after(self): - self.page.close_query_tool() - 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") diff --git a/web/pgadmin/feature_tests/query_tool_tests.py b/web/regression/feature_tests/query_tool_tests.py similarity index 95% rename from web/pgadmin/feature_tests/query_tool_tests.py rename to web/regression/feature_tests/query_tool_tests.py index ac463d35..5b81f91d 100644 --- a/web/pgadmin/feature_tests/query_tool_tests.py +++ b/web/regression/feature_tests/query_tool_tests.py @@ -8,12 +8,12 @@ ########################################################################## from __future__ import print_function -import time import sys from selenium.common.exceptions import StaleElementReferenceException import config +from grappa import should from selenium.webdriver import ActionChains from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -22,16 +22,15 @@ from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest -class QueryToolFeatureTest(BaseFeatureTest): - """ - This feature test will test the different query tool features. - """ +class TestQueryTool(BaseFeatureTest): + def test_query_tool(self, driver): + """ + This feature test will test the different query tool features. + """ + self.driver = driver - scenarios = [ - ("Query tool feature test", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -48,7 +47,6 @@ class QueryToolFeatureTest(BaseFeatureTest): self.page.open_query_tool() self._reset_options() - def runTest(self): # on demand result set on scrolling. print("\nOn demand query result... ", file=sys.stderr, end="") @@ -104,7 +102,6 @@ class QueryToolFeatureTest(BaseFeatureTest): self._query_tool_notify_statements() self._clear_query_tool() - def after(self): self.page.remove_server(self.server) connection = test_utils.get_db_connection( self.server['db'], @@ -379,10 +376,11 @@ SELECT relname FROM pg_class "//div[contains(@class, 'slick-cell') and " "contains(text(), '{}')]".format(table_name)) - assert len(el) == 0, "Table '{}' created with auto commit disabled " \ - "and without any explicit commit.".format( - table_name - ) + len(el) | \ + should.have.length.of(0, + msg="Table '{}' created with auto commit " + "disabled and without any explicit " + "commit.".format(table_name)) def _query_tool_auto_commit_enabled(self): @@ -465,8 +463,10 @@ SELECT relname FROM pg_class "//div[contains(@class, 'slick-cell') and " "contains(text(), '{}')]".format(table_name)) - assert len(el) != 0, "Table '{}' is not created with auto " \ - "commit enabled.".format(table_name) + len(el) | should.not_have.length.of(0, + msg="Table '{}' is not created " + "with auto commit " + "enabled.".format(table_name)) def _query_tool_auto_rollback_enabled(self): table_name = 'query_tool_auto_rollback_enabled_table' @@ -563,8 +563,10 @@ SELECT relname FROM pg_class "//div[contains(@class, 'slick-cell') and " "contains(text(), '{}')]".format(table_name)) - assert len(el) == 0, "Table '{}' created even after ROLLBACK due to " \ - "sql error.".format(table_name) + len(el) | should.have.length.of(0, + msg="Table '{}' created even after " + "ROLLBACK due to sql " + "error.".format(table_name)) def _query_tool_cancel_query(self): query = """-- 1. END any open transaction. diff --git a/web/pgadmin/feature_tests/table_ddl_feature_test.py b/web/regression/feature_tests/table_ddl_feature_test.py similarity index 89% rename from web/pgadmin/feature_tests/table_ddl_feature_test.py rename to web/regression/feature_tests/table_ddl_feature_test.py index 18760019..20d54d7f 100644 --- a/web/pgadmin/feature_tests/table_ddl_feature_test.py +++ b/web/regression/feature_tests/table_ddl_feature_test.py @@ -6,19 +6,16 @@ # This software is released under the PostgreSQL Licence # ########################################################################## - from regression.feature_utils.base_feature_test import BaseFeatureTest from regression.python_test_utils import test_utils -class TableDdlFeatureTest(BaseFeatureTest): - """ This class test acceptance test scenarios """ +class TestTableDdl(BaseFeatureTest): + def test_table_ddl(self, driver): + self.driver = driver - scenarios = [ - ("Test table DDL generation", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -33,7 +30,6 @@ class TableDdlFeatureTest(BaseFeatureTest): self.page.add_server(self.server) - def runTest(self): test_utils.create_table( self.server, "acceptance_test_db", "test_table") @@ -50,7 +46,6 @@ class TableDdlFeatureTest(BaseFeatureTest): "//*[contains(@class,'CodeMirror-lines') and " "contains(.,'CREATE TABLE public.test_table')]") - def after(self): self.page.remove_server(self.server) connection = test_utils.get_db_connection( self.server['db'], diff --git a/web/pgadmin/feature_tests/test_data.json b/web/regression/feature_tests/test_data.json similarity index 100% rename from web/pgadmin/feature_tests/test_data.json rename to web/regression/feature_tests/test_data.json diff --git a/web/pgadmin/feature_tests/view_data_dml_queries.py b/web/regression/feature_tests/view_data_dml_queries.py similarity index 92% rename from web/pgadmin/feature_tests/view_data_dml_queries.py rename to web/regression/feature_tests/view_data_dml_queries.py index f5dc6590..0cd2ba1d 100644 --- a/web/pgadmin/feature_tests/view_data_dml_queries.py +++ b/web/regression/feature_tests/view_data_dml_queries.py @@ -10,6 +10,8 @@ import json import os import time + +from grappa import should from selenium.webdriver import ActionChains from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest @@ -27,7 +29,7 @@ except Exception as e: print(str(e)) -class CheckForViewDataTest(BaseFeatureTest): +class TestCheckForViewData(BaseFeatureTest): """ Test cases to validate insert, update operations in table with input test data @@ -43,12 +45,6 @@ class CheckForViewDataTest(BaseFeatureTest): 4) Copy/Paste row """ - scenarios = [ - ("Validate Insert, Update operations in View/Edit data with " - "given test data", - dict()) - ] - TIMEOUT_STRING = "Timed out waiting for div element to appear" # query for creating 'defaults_text' table @@ -80,7 +76,11 @@ CREATE TABLE public.defaults_{0} ) """ - def before(self): + def test_check_for_view_data(self, driver): + self.driver = driver + + self.setUp() + with test_utils.Database(self.server) as (connection, _): if connection.server_version < 90100: self.skipTest( @@ -103,12 +103,11 @@ CREATE TABLE public.defaults_{0} test_utils.create_table_with_query( self.server, "acceptance_test_db", - CheckForViewDataTest.defaults_query.format(k, v)) + TestCheckForViewData.defaults_query.format(k, v)) # Initialize an instance of WebDriverWait with timeout of 3 seconds self.wait = WebDriverWait(self.driver, 3) - def runTest(self): self.page.wait_for_spinner_to_disappear() self.page.add_server(self.server) self._tables_node_expandable() @@ -127,7 +126,6 @@ CREATE TABLE public.defaults_{0} self._copy_paste_row() self.page.close_data_grid() - def after(self): self.page.remove_server(self.server) connection = test_utils.get_db_connection( self.server['db'], @@ -161,12 +159,12 @@ CREATE TABLE public.defaults_{0} try: wait.until(EC.text_to_be_present_in_element( (By.XPATH, xpath + "//span"), str(value)), - CheckForViewDataTest.TIMEOUT_STRING + TestCheckForViewData.TIMEOUT_STRING ) except Exception: wait.until(EC.text_to_be_present_in_element( (By.XPATH, xpath), str(value)), - CheckForViewDataTest.TIMEOUT_STRING + TestCheckForViewData.TIMEOUT_STRING ) def _update_cell(self, xpath, data): @@ -182,7 +180,7 @@ CREATE TABLE public.defaults_{0} """ self.wait.until(EC.visibility_of_element_located( - (By.XPATH, xpath)), CheckForViewDataTest.TIMEOUT_STRING + (By.XPATH, xpath)), TestCheckForViewData.TIMEOUT_STRING ) cell_el = self.page.find_by_xpath(xpath) self.page.driver.execute_script("arguments[0].scrollIntoView()", @@ -247,16 +245,16 @@ CREATE TABLE public.defaults_{0} self.wait.until( EC.visibility_of_element_located( (By.CSS_SELECTOR, 'iframe') - ), CheckForViewDataTest.TIMEOUT_STRING + ), TestCheckForViewData.TIMEOUT_STRING ) self.page.driver.switch_to.frame( self.page.driver.find_element_by_tag_name('iframe') ) def _copy_paste_row(self): - row0_cell0_xpath = CheckForViewDataTest._get_cell_xpath("r0", 1) - row1_cell1_xpath = CheckForViewDataTest._get_cell_xpath("r1", 2) - row1_cell2_xpath = CheckForViewDataTest._get_cell_xpath("r2", 2) + row0_cell0_xpath = TestCheckForViewData._get_cell_xpath("r0", 1) + row1_cell1_xpath = TestCheckForViewData._get_cell_xpath("r1", 2) + row1_cell2_xpath = TestCheckForViewData._get_cell_xpath("r2", 2) self.page.find_by_xpath(row0_cell0_xpath).click() self.page.find_by_xpath("//*[@id='btn-copy-row']").click() @@ -264,7 +262,7 @@ CREATE TABLE public.defaults_{0} # Update primary key of copied cell self._update_cell(row1_cell1_xpath, [2, "", "int"]) self.page.find_by_xpath( - CheckForViewDataTest._get_cell_xpath("r1", "3") + TestCheckForViewData._get_cell_xpath("r1", "3") ).click() # Check if removing a cell value with default value sets @@ -272,7 +270,7 @@ CREATE TABLE public.defaults_{0} self._update_cell(row1_cell2_xpath, ["clear", "", "int"]) # click outside self.page.find_by_xpath( - CheckForViewDataTest._get_cell_xpath("r1", "3") + TestCheckForViewData._get_cell_xpath("r1", "3") ).click() self._compare_cell_value(row1_cell2_xpath, "[default]") @@ -291,7 +289,7 @@ CREATE TABLE public.defaults_{0} def _add_row(self): for idx in range(1, len(config_data.keys()) + 1): - cell_xpath = CheckForViewDataTest._get_cell_xpath( + cell_xpath = TestCheckForViewData._get_cell_xpath( 'r' + str(idx), 1 ) time.sleep(0.2) @@ -326,7 +324,7 @@ CREATE TABLE public.defaults_{0} element) if (idx != 1 and not is_new_row) or is_new_row: - self.assertEquals(element.text, config_data[str(idx)][1]) + element.text | should.equal(config_data[str(idx)][1]) # scroll browser back to the left # to reset position so other assertions can succeed diff --git a/web/pgadmin/feature_tests/xss_checks_file_manager_test.py b/web/regression/feature_tests/xss_checks_file_manager_test.py similarity index 84% rename from web/pgadmin/feature_tests/xss_checks_file_manager_test.py rename to web/regression/feature_tests/xss_checks_file_manager_test.py index 60d7e91c..ae5aa282 100644 --- a/web/pgadmin/feature_tests/xss_checks_file_manager_test.py +++ b/web/regression/feature_tests/xss_checks_file_manager_test.py @@ -8,24 +8,23 @@ ########################################################################## import os -import time -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support.ui import WebDriverWait + +from grappa import should from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC -from regression.python_test_utils import test_utils +from selenium.webdriver.support.ui import WebDriverWait + from regression.feature_utils.base_feature_test import BaseFeatureTest +from regression.python_test_utils import test_utils -class CheckFileManagerFeatureTest(BaseFeatureTest): - """Tests to check file manager for XSS.""" +class TestCheckFileManager(BaseFeatureTest): + def test_check_file_manager(self, driver): + self.driver = driver - scenarios = [ - ("Tests to check if File manager is vulnerable to XSS", - dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -42,7 +41,11 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): if os.path.isfile(self.XSS_FILE): os.remove(self.XSS_FILE) - def after(self): + self._navigate_to_query_tool() + self.page.fill_codemirror_area_with("SELECT 1;") + self._create_new_file() + self._open_file_manager_and_check_xss_file() + self.page.close_query_tool('sql', False) self.page.remove_server(self.server) connection = test_utils.get_db_connection( @@ -54,12 +57,6 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): ) test_utils.drop_database(connection, "acceptance_test_db") - def runTest(self): - self._navigate_to_query_tool() - self.page.fill_codemirror_area_with("SELECT 1;") - self._create_new_file() - self._open_file_manager_and_check_xss_file() - def _navigate_to_query_tool(self): self.page.toggle_open_tree_item(self.server['name']) self.page.toggle_open_tree_item('Databases') @@ -113,14 +110,9 @@ class CheckFileManagerFeatureTest(BaseFeatureTest): self.page.click_modal('Cancel') self.page.wait_for_query_tool_loading_indicator_to_disappear() - self._check_escaped_characters( - contents, - '<img src=x onmouseover=alert("1")>.sql', - 'File manager' - ) - def _check_escaped_characters(self, source_code, string_to_find, source): - # 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) + escaped_characters = '<img src=x onmouseover=alert("1")>.sql' + contents | should.contain( + escaped_characters, + msg="File Manager might be vulnerable to XSS " + ) diff --git a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py b/web/regression/feature_tests/xss_checks_panels_and_query_tool_test.py similarity index 87% rename from web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py rename to web/regression/feature_tests/xss_checks_panels_and_query_tool_test.py index 4dc082c2..ea507935 100644 --- a/web/pgadmin/feature_tests/xss_checks_panels_and_query_tool_test.py +++ b/web/regression/feature_tests/xss_checks_panels_and_query_tool_test.py @@ -6,31 +6,30 @@ # This software is released under the PostgreSQL Licence # ########################################################################## - +from grappa import should from regression.python_test_utils import test_utils from regression.feature_utils.base_feature_test import BaseFeatureTest -class CheckForXssFeatureTest(BaseFeatureTest): - """ - Tests to check if pgAdmin4 is vulnerable to XSS. +class TestCheckForXss(BaseFeatureTest): + def test_check_for_xss(self, driver): + """ + Tests to check if pgAdmin4 is vulnerable to XSS. - Here we will check html source code for escaped characters if we - found them in the code then we are not vulnerable otherwise we might. + Here we will check html source code for escaped characters if we + found them in the code then we are not vulnerable otherwise we might. - We will cover, - 1) Browser Tree (aciTree) - 2) Properties Tab (BackFrom) - 3) Dependents Tab (BackGrid) - 4) SQL Tab (Code Mirror) - 5) Query Tool (SlickGrid) - """ + We will cover, + 1) Browser Tree (aciTree) + 2) Properties Tab (BackFrom) + 3) Dependents Tab (BackGrid) + 4) SQL Tab (Code Mirror) + 5) Query Tool (SlickGrid) + """ + self.driver = driver - scenarios = [ - ("Test XSS check for panels and query tool", dict()) - ] + self.setUp() - def before(self): connection = test_utils.get_db_connection( self.server['db'], self.server['username'], @@ -52,7 +51,6 @@ class CheckForXssFeatureTest(BaseFeatureTest): "unique", "