From e45d65467426bc26fe96d9c995a7a008496979f9 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Mon, 18 May 2026 17:46:08 +0545 Subject: [PATCH 1/5] test: fix settings tab scenarios Signed-off-by: Saw-jan --- test/gui/features/tabs-settings/test.feature | 4 +-- test/gui/pageObjects/Toolbar.py | 38 ++++++++++---------- test/gui/steps/sync_context.py | 9 +++-- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/test/gui/features/tabs-settings/test.feature b/test/gui/features/tabs-settings/test.feature index e5c6e5f6a7..c702277ad0 100644 --- a/test/gui/features/tabs-settings/test.feature +++ b/test/gui/features/tabs-settings/test.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,7 +13,7 @@ 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 diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index 23ba33bd02..39ffa76953 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,7 @@ def get_displayed_account_text(displayname, host): @staticmethod def open_settings_tab(): - squish.mouseClick(squish.waitForObject(Toolbar.SETTINGS_BUTTON)) + squish.mouseClick(squish.waitForObject(Toolbar.SETTINGS_TAB)) @staticmethod def quit_opencloud(): @@ -127,7 +129,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 +147,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..9e735d784a 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') @@ -112,8 +112,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:') From 23f44af80302cb92d0a534fb0896f6e302c4c41e Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Mon, 18 May 2026 18:21:37 +0545 Subject: [PATCH 2/5] test: fix settings tab scenarios Signed-off-by: Saw-jan --- test/gui/features/activity/activity.feature | 6 +- test/gui/features/spaces/spaces.feature | 2 +- .../sync-resources/syncResources.feature | 10 +-- test/gui/features/tabs-settings/test.feature | 5 +- test/gui/pageObjects/Settings.py | 80 ++++++++++++++----- test/gui/pageObjects/Toolbar.py | 10 ++- test/gui/steps/sync_context.py | 34 +++++--- 7 files changed, 102 insertions(+), 45 deletions(-) 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/test.feature index c702277ad0..8ca2e4168b 100644 --- a/test/gui/features/tabs-settings/test.feature +++ b/test/gui/features/tabs-settings/test.feature @@ -17,7 +17,7 @@ Feature: Visually check all tabs 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,8 +25,7 @@ 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 - Then the about dialog should be opened + And the user closes the about dialog diff --git a/test/gui/pageObjects/Settings.py b/test/gui/pageObjects/Settings.py index 5a1fb79c3f..9497fa5a4b 100644 --- a/test/gui/pageObjects/Settings.py +++ b/test/gui/pageObjects/Settings.py @@ -1,16 +1,35 @@ 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_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 +48,45 @@ 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)) - - @staticmethod - def check_advanced_option(option): - selector = Settings.ADVANCED_OPTION_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_network_option(option): - selector = Settings.NETWORK_OPTION_MAP[option] - squish.waitForObjectExists(Settings.get_network_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 open_about_button(): - squish.clickButton(squish.waitForObject(Settings.ABOUT_BUTTON)) + 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 wait_for_about_dialog_to_be_visible(): - squish.waitForObjectExists(Settings.ABOUT_DIALOG) + def open_about_dialog(): + app().find_element( + Settings.ABOUT_BUTTON.by, Settings.ABOUT_BUTTON.selector + ).click() @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 39ffa76953..515dfae8ac 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -106,7 +106,15 @@ def get_displayed_account_text(displayname, host): @staticmethod def open_settings_tab(): - squish.mouseClick(squish.waitForObject(Toolbar.SETTINGS_TAB)) + 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(): diff --git a/test/gui/steps/sync_context.py b/test/gui/steps/sync_context.py index 9e735d784a..e9cf0ac57b 100644 --- a/test/gui/steps/sync_context.py +++ b/test/gui/steps/sync_context.py @@ -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) @@ -185,30 +190,39 @@ 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') +@When('the user closes the about dialog') def step(context): - Settings.wait_for_about_dialog_to_be_visible() + Settings.close_about_dialog() @When('the user adds the folder sync connection') From 287a554154311721f8bd22d68b6ccced3807da9b Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Tue, 19 May 2026 11:37:23 +0545 Subject: [PATCH 3/5] test: rename feature files Signed-off-by: Saw-jan --- .../features/tabs-settings/{test.feature => tabsSettings.feature} | 0 test/gui/features/vfs/{test.feature => vfs.feature} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/gui/features/tabs-settings/{test.feature => tabsSettings.feature} (100%) rename test/gui/features/vfs/{test.feature => vfs.feature} (100%) diff --git a/test/gui/features/tabs-settings/test.feature b/test/gui/features/tabs-settings/tabsSettings.feature similarity index 100% rename from test/gui/features/tabs-settings/test.feature rename to test/gui/features/tabs-settings/tabsSettings.feature 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 From f5127708ebd3f41522e9280a256c53da5b919690 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Wed, 20 May 2026 12:40:00 +0545 Subject: [PATCH 4/5] test: check about dialog Signed-off-by: Saw-jan --- test/gui/features/tabs-settings/tabsSettings.feature | 2 +- test/gui/pageObjects/Settings.py | 9 +++++++++ test/gui/steps/sync_context.py | 6 ++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/test/gui/features/tabs-settings/tabsSettings.feature b/test/gui/features/tabs-settings/tabsSettings.feature index 8ca2e4168b..378ec8947c 100644 --- a/test/gui/features/tabs-settings/tabsSettings.feature +++ b/test/gui/features/tabs-settings/tabsSettings.feature @@ -28,4 +28,4 @@ Feature: Visually check all tabs | Download Bandwidth | | Upload Bandwidth | When the user opens the about dialog - And the user closes the about dialog + Then the about dialog should be opened diff --git a/test/gui/pageObjects/Settings.py b/test/gui/pageObjects/Settings.py index 9497fa5a4b..10141648f3 100644 --- a/test/gui/pageObjects/Settings.py +++ b/test/gui/pageObjects/Settings.py @@ -8,6 +8,7 @@ class Settings: CHECKBOX_OPTION_ITEM = SimpleNamespace(by=None, selector=None) NETWORK_OPTION_ITEM = 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']" @@ -85,6 +86,14 @@ def open_about_dialog(): Settings.ABOUT_BUTTON.by, Settings.ABOUT_BUTTON.selector ).click() + @staticmethod + def has_about_dialog(): + return ( + app() + .find_element(Settings.ABOUT_DIALOG.by, Settings.ABOUT_DIALOG.selector) + .is_displayed() + ) + @staticmethod def close_about_dialog(): app().find_element( diff --git a/test/gui/steps/sync_context.py b/test/gui/steps/sync_context.py index e9cf0ac57b..9c4b5ebc81 100644 --- a/test/gui/steps/sync_context.py +++ b/test/gui/steps/sync_context.py @@ -220,6 +220,12 @@ def step(context): Settings.open_about_dialog() +@Then('the about dialog should be opened') +def step(context): + 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() From 67889e487e3da34197f7cc4852f64045d4cd7789 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Wed, 20 May 2026 13:00:26 +0545 Subject: [PATCH 5/5] ci: fix browser install on browser cache miss Signed-off-by: Saw-jan --- .woodpecker/cache-python.yaml | 2 ++ 1 file changed, 2 insertions(+) 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