diff --git a/src/run_tribler.py b/src/run_tribler.py index 7663654ccb8..771b5275b6b 100644 --- a/src/run_tribler.py +++ b/src/run_tribler.py @@ -29,6 +29,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.add_argument('--core', action="store_true") self.add_argument('--gui-test-mode', action="store_true") + self.add_argument('--gui-tests', action="store_true") def init_sentry_reporter(): @@ -159,7 +160,8 @@ def init_boot_logger(): root_state_dir, api_port=api_port, api_key=api_key, - run_core=True) + run_core=True, + run_gui_tests=parsed_args.gui_tests) window.setWindowTitle("Tribler") app.set_activation_window(window) app.parse_sys_args(sys.argv) diff --git a/src/tribler-gui/tribler_gui/core_manager.py b/src/tribler-gui/tribler_gui/core_manager.py index d6603b86ea1..2b7967337ba 100644 --- a/src/tribler-gui/tribler_gui/core_manager.py +++ b/src/tribler-gui/tribler_gui/core_manager.py @@ -28,6 +28,7 @@ def __init__(self, root_state_dir, api_port, api_key, error_handler): self.core_process = None self.api_port = api_port self.api_key = api_key + self.exit_code = 0 self.events_manager = EventRequestManager(self.api_port, self.api_key, error_handler) self.shutting_down = False @@ -131,4 +132,4 @@ def stop(self, stop_app_on_shutdown=True): def on_finished(self): self.tribler_stopped.emit() if self.shutting_down: - QApplication.quit() + QApplication.exit(returnCode=self.exit_code) diff --git a/src/tribler-gui/tribler_gui/tests/gui_test_runner.py b/src/tribler-gui/tribler_gui/tests/gui_test_runner.py new file mode 100644 index 00000000000..33443c9abb1 --- /dev/null +++ b/src/tribler-gui/tribler_gui/tests/gui_test_runner.py @@ -0,0 +1,28 @@ +import logging + +from tribler_gui.tribler_window import TriblerWindow + + +class GUITestRunner: + """ + This class manages the execution of the GUI tests. + """ + logger = logging.getLogger("GUITestRunner") + + @staticmethod + def run(window: TriblerWindow): + from tribler_gui.tests import test_gui + + success = True + for potential_test_method in dir(test_gui): + if potential_test_method.startswith("test_"): + test_method = getattr(test_gui, potential_test_method) + try: + test_method(window) + print(f"[{potential_test_method}] PASSED") + except Exception as exc: + logging.exception(exc) + print(f"[{potential_test_method}] FAILED") + success = False + + return success diff --git a/src/tribler-gui/tribler_gui/tests/test_gui.py b/src/tribler-gui/tribler_gui/tests/test_gui.py index 064599e2334..2958a4599ec 100644 --- a/src/tribler-gui/tribler_gui/tests/test_gui.py +++ b/src/tribler-gui/tribler_gui/tests/test_gui.py @@ -3,24 +3,18 @@ import sys from pathlib import Path -from PyQt5.QtCore import QMetaObject, QPoint, QSettings, QTimer, Q_ARG, Qt +from PyQt5.QtCore import QMetaObject, QPoint, QTimer, Q_ARG, Qt from PyQt5.QtGui import QKeySequence, QPixmap, QRegion from PyQt5.QtTest import QTest -from PyQt5.QtWidgets import QApplication, QListWidget, QTableView, QTextEdit, QTreeWidget, QTreeWidgetItem - -import pytest +from PyQt5.QtWidgets import QListWidget, QTableView, QTextEdit, QTreeWidget, QTreeWidgetItem import tribler_common from tribler_common.reported_error import ReportedError from tribler_common.tag_constants import MIN_TAG_LENGTH -from tribler_core.utilities.unicode import hexlify - import tribler_gui from tribler_gui.dialogs.feedbackdialog import FeedbackDialog from tribler_gui.dialogs.new_channel_dialog import NewChannelDialog -from tribler_gui.tribler_app import TriblerApplication -from tribler_gui.tribler_window import TriblerWindow from tribler_gui.utilities import connect from tribler_gui.widgets.loading_list_item import LoadingListItem from tribler_gui.widgets.tablecontentmodel import Column @@ -32,38 +26,6 @@ TORRENT_WITH_DIRS = COMMON_DATA_DIR / "multi_entries.torrent" -@pytest.fixture(scope="module") -def window(tmpdir_factory): - api_key = hexlify(os.urandom(16)) - root_state_dir = str(tmpdir_factory.mktemp('tribler_state_dir')) - - app = TriblerApplication("triblerapp-guitest", sys.argv) - # We must create a separate instance of QSettings and clear it. - # Otherwise, previous runs of the same app will affect this run. - settings = QSettings("tribler-guitest") - settings.clear() - window = TriblerWindow( # pylint: disable=W0621 - settings, - root_state_dir, - api_key=api_key, - core_args=[str(RUN_TRIBLER_PY.absolute()), '--core', '--gui-test-mode'], - ) # pylint: disable=W0621 - app.set_activation_window(window) - QTest.qWaitForWindowExposed(window) - - screenshot(window, name="tribler_loading") - wait_for_signal( - window.core_manager.events_manager.tribler_started, - flag=window.core_manager.events_manager.tribler_started_flag, - ) - window.downloads_page.can_update_items = True - yield window - - window.close_tribler() - screenshot(window, name="tribler_closing") - QApplication.quit() - - def no_abort(*args, **kwargs): sys.__excepthook__(*args, **kwargs) @@ -238,13 +200,11 @@ def tst_channels_widget(window, widget, widget_name, sort_column=1, test_filter= screenshot(window, name=f"{widget_name}-torrent_details") -@pytest.mark.guitest def test_discovered_page(window): QTest.mouseClick(window.left_menu_button_discovered, Qt.LeftButton) tst_channels_widget(window, window.discovered_page, "discovered_page", sort_column=2) -@pytest.mark.guitest def test_popular_page(window): QTest.mouseClick(window.left_menu_button_popular, Qt.LeftButton) widget = window.popular_page @@ -262,7 +222,6 @@ def wait_for_thumbnail(chan_widget): raise TimeoutException("The thumbnail was not shown within 10 seconds") -@pytest.mark.guitest def test_edit_channel_torrents(window): wait_for_list_populated(window.channels_menu_list) @@ -280,7 +239,6 @@ def test_edit_channel_torrents(window): screenshot(window, name="edit_channel_thumbnail_description") -@pytest.mark.guitest def test_settings(window): QTest.mouseClick(window.settings_button, Qt.LeftButton) QTest.mouseClick(window.settings_general_button, Qt.LeftButton) @@ -301,7 +259,6 @@ def test_settings(window): wait_for_signal(window.settings_page.settings_edited) -@pytest.mark.guitest def test_downloads(window): go_to_and_wait_for_downloads(window) screenshot(window, name="downloads_all") @@ -317,7 +274,6 @@ def test_downloads(window): screenshot(window, name="downloads_channels") -@pytest.mark.guitest def test_download_start_stop_remove_recheck(window): go_to_and_wait_for_downloads(window) QTest.mouseClick(window.downloads_list.topLevelItem(0).progress_slider, Qt.LeftButton) @@ -328,7 +284,6 @@ def test_download_start_stop_remove_recheck(window): QTest.mouseClick(window.downloads_page.dialog.buttons[2], Qt.LeftButton) -@pytest.mark.guitest def test_download_details(window): go_to_and_wait_for_downloads(window) QTest.mouseClick(window.downloads_list.topLevelItem(0).progress_slider, Qt.LeftButton) @@ -357,7 +312,6 @@ def test_download_details(window): screenshot(window, name="download_trackers") -@pytest.mark.guitest def test_search_suggestions(window): QTest.keyClick(window.top_search_bar, 't') QTest.keyClick(window.top_search_bar, 'r') @@ -365,7 +319,6 @@ def test_search_suggestions(window): screenshot(window, name="search_suggestions") -@pytest.mark.guitest def test_search(window): window.top_search_bar.setText("a") # This is likely to trigger some search results QTest.keyClick(window.top_search_bar, Qt.Key_Enter) @@ -382,7 +335,6 @@ def test_search(window): ) -@pytest.mark.guitest def test_add_download_url(window): go_to_and_wait_for_downloads(window) window.on_add_torrent_from_url() @@ -411,7 +363,6 @@ def test_add_download_url(window): wait_for_signal(window.downloads_page.received_downloads) -@pytest.mark.guitest def test_feedback_dialog(window): def screenshot_dialog(): screenshot(dialog, name="feedback_dialog") @@ -424,7 +375,6 @@ def screenshot_dialog(): dialog.exec_() -@pytest.mark.guitest def test_feedback_dialog_report_sent(window): def screenshot_dialog(): screenshot(dialog, name="feedback_dialog") @@ -444,7 +394,6 @@ def on_report_sent(): assert on_report_sent.did_send_report -@pytest.mark.guitest def test_debug_pane(window): wait_for_variable(window, "tribler_settings") QTest.mouseClick(window.settings_button, Qt.LeftButton) @@ -553,14 +502,12 @@ def test_debug_pane(window): window.debug_window.close() -@pytest.mark.guitest def test_trust_page(window): QTest.mouseClick(window.token_balance_widget, Qt.LeftButton) wait_for_variable(window, "trust_page.history") screenshot(window, name="trust_page_values") -@pytest.mark.guitest def test_close_dialog_with_esc_button(window): QTest.mouseClick(window.left_menu_button_new_channel, Qt.LeftButton) screenshot(window, name="create_new_channel_dialog") @@ -569,7 +516,6 @@ def test_close_dialog_with_esc_button(window): assert not window.findChildren(NewChannelDialog) -@pytest.mark.guitest def test_tags_dialog(window): """ Test the behaviour of the dialog where a user can edit tags. @@ -671,7 +617,6 @@ def test_tags_dialog(window): QTest.qWait(200) # It can take a bit of time to hide the dialog -@pytest.mark.guitest def test_no_tags(window): """ Test removing all tags from a content item. diff --git a/src/tribler-gui/tribler_gui/tribler_window.py b/src/tribler-gui/tribler_gui/tribler_window.py index 870446f298a..776659957cd 100644 --- a/src/tribler-gui/tribler_gui/tribler_window.py +++ b/src/tribler-gui/tribler_gui/tribler_window.py @@ -134,6 +134,7 @@ def __init__( api_port=None, api_key=None, run_core=True, + run_gui_tests=False ): QMainWindow.__init__(self) self._logger = logging.getLogger(self.__class__.__name__) @@ -146,6 +147,7 @@ def __init__( self.root_state_dir = Path(root_state_dir) self.gui_settings = settings + self.run_gui_tests = run_gui_tests api_port = api_port or int(get_gui_setting(self.gui_settings, "api_port", DEFAULT_API_PORT)) api_key = api_key or get_gui_setting(self.gui_settings, "api_key", hexlify(os.urandom(16))) if isinstance(api_key, bytes): @@ -528,6 +530,12 @@ def on_tribler_started(self, version): QApplication.setStyle(InstantTooltipStyle(QApplication.style())) + if self.run_gui_tests: + from tribler_gui.tests.gui_test_runner import GUITestRunner + result = GUITestRunner.run(self) + self.core_manager.exit_code = 0 if result else 1 + self.close_tribler() + @property def hide_xxx(self): return get_gui_setting(self.gui_settings, "family_filter", True, is_bool=True)