diff --git a/.woodpecker/cache-python.yaml b/.woodpecker/cache-python.yaml index d1d90924d2..9073e0b5aa 100644 --- a/.woodpecker/cache-python.yaml +++ b/.woodpecker/cache-python.yaml @@ -75,7 +75,9 @@ steps: - . ./.woodpecker.env - if $BROWSER_CACHE_FOUND; then exit 0; fi - cd test/gui/ + - python3 -m venv .venv --system-site-packages - . .venv/bin/activate + - grep "^playwright" requirements.txt | pip install -r /dev/stdin - make install-chromium - tar -czf playwright-browsers.tar.gz .playwright diff --git a/test/gui/features/activity/activity.feature b/test/gui/features/activity/activity.feature index f8c550584b..2b86e6ed7d 100644 --- a/test/gui/features/activity/activity.feature +++ b/test/gui/features/activity/activity.feature @@ -13,7 +13,7 @@ Feature: filter activity for user | users | | Alice | | Brian | - When the user clicks on the activity tab + When the user opens the activity tab And the user selects "Local Activity" tab in the activity And the user checks the activities of account "Alice Hansen@%local_server_hostname%" Then the following activities should be displayed in synced table @@ -32,7 +32,7 @@ Feature: filter activity for user | files | | /.htaccess | | /Folder1/a\\a.txt | - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the file "Folder1/a\\a.txt" should be blacklisted And the file ".htaccess" should be excluded @@ -48,7 +48,7 @@ Feature: filter activity for user When user "Alice" creates the following files inside the sync folder: | files | | /.htaccess | - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the file ".htaccess" should be excluded When the user unchecks the "Excluded" filter diff --git a/test/gui/features/spaces/spaces.feature b/test/gui/features/spaces/spaces.feature index 7c155b3d87..4f2bb3267e 100644 --- a/test/gui/features/spaces/spaces.feature +++ b/test/gui/features/spaces/spaces.feature @@ -77,7 +77,7 @@ Feature: Project spaces """ simple-folder: Not allowed because you don't have permission to add subfolders to that folder """ - When the user clicks on the activity tab + When the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the following activities should be displayed in not synced table | resource | status | account | diff --git a/test/gui/features/sync-resources/syncResources.feature b/test/gui/features/sync-resources/syncResources.feature index bd969dffcd..9390ce0efe 100644 --- a/test/gui/features/sync-resources/syncResources.feature +++ b/test/gui/features/sync-resources/syncResources.feature @@ -14,7 +14,7 @@ Feature: Syncing files test content """ And the user waits for file "lorem-for-upload.txt" to be synced - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Local Activity" tab in the activity Then the file "lorem-for-upload.txt" should have status "Uploaded" in the activity tab And as "Alice" the file "lorem-for-upload.txt" should have the content "test content" in the server @@ -45,7 +45,7 @@ Feature: Syncing files And user "Alice" has uploaded file with content "changed server content" to "/conflict.txt" in the server And the user has waited for "5" seconds When the user resumes the file sync on the client - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the table of conflict warnings should include file "conflict.txt" And the file "conflict.txt" should exist on the file system with the following content @@ -181,7 +181,7 @@ Feature: Syncing files And user "Alice" has set up a client with default settings When user "Alice" creates a folder "folder with space at end " inside the sync folder And the user force syncs the files - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the file "trailing-space.txt " should be ignored And the file "folder with space at end " should be ignored @@ -274,7 +274,7 @@ Feature: Syncing files """ test content """ - And the user clicks on the activity tab + And the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the file "Folder1/a\\a.txt" should exist on the file system And the file "Folder1/a\\a.txt" should be blacklisted @@ -527,7 +527,7 @@ Feature: Syncing files """ And as "Brian" folder "simple-folder/sub-folder" should not exist in the server And as "Brian" file "simple-folder/simple.pdf" should not exist in the server - When the user clicks on the activity tab + When the user opens the activity tab And the user selects "Not Synced" tab in the activity Then the following activities should be displayed in not synced table | resource | status | account | diff --git a/test/gui/features/tabs-settings/test.feature b/test/gui/features/tabs-settings/tabsSettings.feature similarity index 93% rename from test/gui/features/tabs-settings/test.feature rename to test/gui/features/tabs-settings/tabsSettings.feature index e5c6e5f6a7..378ec8947c 100644 --- a/test/gui/features/tabs-settings/test.feature +++ b/test/gui/features/tabs-settings/tabsSettings.feature @@ -3,7 +3,7 @@ Feature: Visually check all tabs I want to visually check all tabs in client So that I can perform all the actions related to client - + @smoke Scenario: Tabs in toolbar looks correct Given user "Alice" has been created in the server with default attributes And user "Alice" has set up a client with default settings @@ -13,11 +13,11 @@ Feature: Visually check all tabs | Settings | | Quit | - + @smoke Scenario: Verify various setting options in Settings tab Given user "Alice" has been created in the server with default attributes And user "Alice" has set up a client with default settings - When the user clicks on the settings tab + When the user opens the settings tab Then the settings tab should have the following options in the general section: | Start on Login | And the settings tab should have the following options in the advanced section: @@ -25,7 +25,6 @@ Feature: Visually check all tabs | Edit ignored files | | Log settings | And the settings tab should have the following options in the network section: - | Proxy Settings | | Download Bandwidth | | Upload Bandwidth | When the user opens the about dialog diff --git a/test/gui/features/vfs/test.feature b/test/gui/features/vfs/vfs.feature similarity index 100% rename from test/gui/features/vfs/test.feature rename to test/gui/features/vfs/vfs.feature diff --git a/test/gui/pageObjects/Settings.py b/test/gui/pageObjects/Settings.py index 5a1fb79c3f..10141648f3 100644 --- a/test/gui/pageObjects/Settings.py +++ b/test/gui/pageObjects/Settings.py @@ -1,16 +1,36 @@ from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By +from helpers.AppHelper import app + class Settings: CHECKBOX_OPTION_ITEM = SimpleNamespace(by=None, selector=None) NETWORK_OPTION_ITEM = SimpleNamespace(by=None, selector=None) - ABOUT_BUTTON = SimpleNamespace(by=None, selector=None) - ABOUT_DIALOG = SimpleNamespace(by=None, selector=None) - ABOUT_DIALOG_OK_BUTTON = SimpleNamespace(by=None, selector=None) - GENERAL_OPTIONS_MAP = SimpleNamespace(by=None, selector=None) - ADVANCED_OPTION_MAP = SimpleNamespace(by=None, selector=None) - NETWORK_OPTION_MAP = SimpleNamespace(by=None, selector=None) + ABOUT_BUTTON = SimpleNamespace(by=By.NAME, selector="About") + ABOUT_DIALOG = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | About]") + ABOUT_DIALOG_OK_BUTTON = SimpleNamespace(by=By.NAME, selector="OK") + GENERAL_SETTING_START_ON_LOGIN = SimpleNamespace( + by=By.XPATH, selector="//panel/*[@name='Start on Login']" + ) + GENERAL_SETTING_LANGUAGE = SimpleNamespace( + by=By.XPATH, selector="//panel/label[@name='Language']" + ) + ADVANCED_SETTING_SYNC_HIDDEN_FILES = SimpleNamespace( + by=By.XPATH, selector="//panel/*[@name='Sync hidden files']" + ) + ADVANCED_SETTING_EDIT_IGNORED_FILES = SimpleNamespace( + by=By.XPATH, selector="//panel/*[@name='Edit Ignored Files']" + ) + ADVANCED_SETTING_LOG_SETTINGS = SimpleNamespace( + by=By.XPATH, selector="//panel/*[@name='Log Settings']" + ) + NETWORK_SETTING_DOWNLOAD_BANDWIDTH = SimpleNamespace( + by=By.XPATH, selector="//panel[@name='Download Bandwidth']" + ) + NETWORK_SETTING_UPLOAD_BANDWIDTH = SimpleNamespace( + by=By.XPATH, selector="//panel[@name='Upload Bandwidth']" + ) @staticmethod def get_checkbox_option_selector(name): @@ -29,28 +49,53 @@ def get_network_option_selector(name): return selector @staticmethod - def check_general_option(option): - selector = Settings.GENERAL_OPTIONS_MAP[option] - squish.waitForObjectExists(Settings.get_checkbox_option_selector(selector)) + def has_general_setting(setting): + if setting.lower() == "start on login": + locator = Settings.GENERAL_SETTING_START_ON_LOGIN + elif setting.lower() == "language": + locator = Settings.GENERAL_SETTING_LANGUAGE + else: + raise ValueError(f"Unknown general setting: {setting}") + return app().find_element(locator.by, locator.selector).is_displayed() @staticmethod - def check_advanced_option(option): - selector = Settings.ADVANCED_OPTION_MAP[option] - squish.waitForObjectExists(Settings.get_checkbox_option_selector(selector)) + def has_advanced_setting(setting): + if setting.lower() == "sync hidden files": + locator = Settings.ADVANCED_SETTING_SYNC_HIDDEN_FILES + elif setting.lower() == "edit ignored files": + locator = Settings.ADVANCED_SETTING_EDIT_IGNORED_FILES + elif setting.lower() == "log settings": + locator = Settings.ADVANCED_SETTING_LOG_SETTINGS + else: + raise ValueError(f"Unknown advanced setting: {setting}") + return app().find_element(locator.by, locator.selector).is_displayed() @staticmethod - def check_network_option(option): - selector = Settings.NETWORK_OPTION_MAP[option] - squish.waitForObjectExists(Settings.get_network_option_selector(selector)) + def has_network_setting(setting): + if setting.lower() == "download bandwidth": + locator = Settings.NETWORK_SETTING_DOWNLOAD_BANDWIDTH + elif setting.lower() == "upload bandwidth": + locator = Settings.NETWORK_SETTING_UPLOAD_BANDWIDTH + else: + raise ValueError(f"Unknown network setting: {setting}") + return app().find_element(locator.by, locator.selector).is_displayed() @staticmethod - def open_about_button(): - squish.clickButton(squish.waitForObject(Settings.ABOUT_BUTTON)) + def open_about_dialog(): + app().find_element( + Settings.ABOUT_BUTTON.by, Settings.ABOUT_BUTTON.selector + ).click() @staticmethod - def wait_for_about_dialog_to_be_visible(): - squish.waitForObjectExists(Settings.ABOUT_DIALOG) + def has_about_dialog(): + return ( + app() + .find_element(Settings.ABOUT_DIALOG.by, Settings.ABOUT_DIALOG.selector) + .is_displayed() + ) @staticmethod def close_about_dialog(): - squish.clickButton(squish.waitForObjectExists(Settings.ABOUT_DIALOG_OK_BUTTON)) + app().find_element( + Settings.ABOUT_DIALOG_OK_BUTTON.by, Settings.ABOUT_DIALOG_OK_BUTTON.selector + ).click() diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index 23ba33bd02..515dfae8ac 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -14,14 +14,12 @@ class Toolbar: NAVIGATION_BAR = SimpleNamespace( by=By.XPATH, selector="//*[@name='Navigation bar']/.." ) - ACCOUNT_BUTTON = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | {text}]") + ACCOUNT_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | {text}]") ADD_ACCOUNT_BUTTON = SimpleNamespace( by=By.CLASS_NAME, selector="[push button | Add Account]" ) - ACTIVITY_BUTTON = SimpleNamespace( - by=By.CLASS_NAME, selector="[page tab | Activity]" - ) - SETTINGS_BUTTON = SimpleNamespace(by=None, selector=None) + ACTIVITY_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | Activity]") + SETTINGS_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | Settings]") QUIT_BUTTON = SimpleNamespace(by=By.CLASS_NAME, selector="[push button | Quit]") CONFIRM_QUIT_BUTTON = SimpleNamespace( by=By.NAME, @@ -53,18 +51,22 @@ def get_item_selector(item_name): } @staticmethod - def has_item(item_name, timeout=get_config("minSyncTimeout") * 1000): - try: - squish.waitForObject(Toolbar.get_item_selector(item_name), timeout) - return True - except: - return False + def has_tab(tab_name): + if tab_name.lower() == "add account": + tab = Toolbar.ADD_ACCOUNT_BUTTON + elif tab_name.lower() == "activity": + tab = Toolbar.ACTIVITY_TAB + elif tab_name.lower() == "settings": + tab = Toolbar.SETTINGS_TAB + elif tab_name.lower() == "quit": + tab = Toolbar.QUIT_BUTTON + else: + raise ValueError(f"Unknown tab: {tab_name}") + return app().find_element(tab.by, tab.selector).is_displayed() @staticmethod def open_activity(): - tab = app().find_element( - Toolbar.ACTIVITY_BUTTON.by, Toolbar.ACTIVITY_BUTTON.selector - ) + tab = app().find_element(Toolbar.ACTIVITY_TAB.by, Toolbar.ACTIVITY_TAB.selector) # ISSUE: https://github.com/opencloud-eu/desktop/pull/879 # Cannot select navigation tab by click event # Select the navigation tab using keyboard events as a workaround @@ -104,7 +106,15 @@ def get_displayed_account_text(displayname, host): @staticmethod def open_settings_tab(): - squish.mouseClick(squish.waitForObject(Toolbar.SETTINGS_BUTTON)) + tab = app().find_element(Toolbar.SETTINGS_TAB.by, Toolbar.SETTINGS_TAB.selector) + # ISSUE: https://github.com/opencloud-eu/desktop/pull/879 + # Cannot select navigation tab by click event + # Select the navigation tab using keyboard events as a workaround + # TODO: Remove the workaround and uncomment 'click' action + # tab.click() + tab.native_click() + if tab.get_attribute("checked") != "true": + raise AssertionError("Settings tab is not active") @staticmethod def quit_opencloud(): @@ -127,7 +137,7 @@ def get_accounts(): "initials": str(obj.accountState.account.initials), "current": obj.checked, } - account_locator = Toolbar.ACCOUNT_BUTTON.copy() + account_locator = Toolbar.ACCOUNT_TAB.copy() if account_idx > 1: account_locator.update({"occurrence": account_idx}) account_locator.update({"text": account_info["hostname"]}) @@ -145,8 +155,8 @@ def get_account(username): account = None try: account = app().find_element( - Toolbar.ACCOUNT_BUTTON.by, - Toolbar.ACCOUNT_BUTTON.selector.format(text=account_label), + Toolbar.ACCOUNT_TAB.by, + Toolbar.ACCOUNT_TAB.selector.format(text=account_label), ) except NoSuchElementException: pass diff --git a/test/gui/steps/sync_context.py b/test/gui/steps/sync_context.py index 87c9f6f3c2..9c4b5ebc81 100644 --- a/test/gui/steps/sync_context.py +++ b/test/gui/steps/sync_context.py @@ -18,7 +18,7 @@ get_resource_path, ) from helpers.FilesHelper import convert_path_separators_for_os -from helpers.TableParser import table_hashes +from helpers.TableParser import table_hashes, table_raw @Given('the user has paused the file sync') @@ -78,11 +78,16 @@ def step(context, item): ) -@When('the user clicks on the activity tab') +@When('the user opens the activity tab') def step(context): Toolbar.open_activity() +@When('the user opens the settings tab') +def step(context): + Toolbar.open_settings_tab() + + @Then('the table of conflict warnings should include file "|any|"') def step(context, filename): Activity.check_file_exist(filename) @@ -112,8 +117,11 @@ def step(context, tab_name): @Then('the toolbar should have the following tabs:') def step(context): - for tab_name in context.table: - Toolbar.has_item(tab_name[0]) + tabs = table_raw(context.table) + for tab_name in tabs: + tab_name = tab_name[0] + with ensure('Tab not found: {0}', tab_name): + Toolbar.has_tab(tab_name).should.be.true @When('the user selects the following folders to sync:') @@ -182,30 +190,45 @@ def step(context, space_name): @Then('the settings tab should have the following options in the general section:') def step(context): - for item in context.table: - Settings.check_general_option(item[0]) + settings = table_raw(context.table) + for setting in settings: + setting = setting[0] + with ensure('General setting not found: {0}', setting): + Settings.has_general_setting(setting).should.be.true @Then('the settings tab should have the following options in the advanced section:') def step(context): - for item in context.table: - Settings.check_advanced_option(item[0]) + settings = table_raw(context.table) + for setting in settings: + setting = setting[0] + with ensure('Advanced setting not found: {0}', setting): + Settings.has_advanced_setting(setting).should.be.true @Then('the settings tab should have the following options in the network section:') def step(context): - for item in context.table: - Settings.check_network_option(item[0]) + settings = table_raw(context.table) + for setting in settings: + setting = setting[0] + with ensure('Network setting not found: {0}', setting): + Settings.has_network_setting(setting).should.be.true @When('the user opens the about dialog') def step(context): - Settings.open_about_button() + Settings.open_about_dialog() @Then('the about dialog should be opened') def step(context): - Settings.wait_for_about_dialog_to_be_visible() + with ensure('About dialog is not opened.'): + Settings.has_about_dialog().should.be.true + + +@When('the user closes the about dialog') +def step(context): + Settings.close_about_dialog() @When('the user adds the folder sync connection')