From 394175d267516a6647ca2fe28d65a182278e165b Mon Sep 17 00:00:00 2001 From: prashant-gurung899 Date: Wed, 22 Apr 2026 11:42:17 +0545 Subject: [PATCH 1/2] port removeAccountConnection test suite to appium Signed-off-by: prashant-gurung899 --- .../removeAccountConnection.feature} | 3 +- test/gui/pageObjects/Toolbar.py | 41 ++++++++++++++++++- test/gui/steps/account_context.py | 12 ++---- test/gui/tst_removeAccountConnection/test.py | 8 ---- 4 files changed, 45 insertions(+), 19 deletions(-) rename test/gui/{tst_removeAccountConnection/test.feature => features/remove-account-connection/removeAccountConnection.feature} (97%) delete mode 100644 test/gui/tst_removeAccountConnection/test.py diff --git a/test/gui/tst_removeAccountConnection/test.feature b/test/gui/features/remove-account-connection/removeAccountConnection.feature similarity index 97% rename from test/gui/tst_removeAccountConnection/test.feature rename to test/gui/features/remove-account-connection/removeAccountConnection.feature index b11a7ed392..b28d1a9998 100644 --- a/test/gui/tst_removeAccountConnection/test.feature +++ b/test/gui/features/remove-account-connection/removeAccountConnection.feature @@ -3,11 +3,12 @@ Feature: remove account connection I want to remove my account So that I won't be using any client-UI services - @smoke @skip + @smoke Scenario: remove an account connection Given user "Alice" has been created in the server with default attributes And user "Brian" has been created in the server with default attributes And the user has set up the following accounts with default settings: + | users | | Alice | | Brian | When the user removes the connection for user "Brian" diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index be6521e435..66d54ae253 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -6,10 +6,13 @@ from helpers.ConfigHelper import get_config from helpers.SetupClientHelper import app +import time + class Toolbar: TOOLBAR_ROW = SimpleNamespace(by=None, selector=None) - ACCOUNT_BUTTON = SimpleNamespace(by=None, selector=None) + ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector=None) + # ACCOUNT_BUTTON_LABEL = SimpleNamespace(by=By.XPATH, selector="//label[@name='BM']") ADD_ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector="Add Account") ACTIVITY_BUTTON = SimpleNamespace(by=By.NAME, selector="Activity") SETTINGS_BUTTON = SimpleNamespace(by=None, selector=None) @@ -51,7 +54,12 @@ def open_new_account_setup(): @staticmethod def open_account(displayname): account_tab = Toolbar.get_account(displayname) + # print(f"DEBUG: Before click - checked='{account_tab.get_attribute('checked')}'") + # print(f"DEBUG: Before click - focused='{account_tab.get_attribute('focused')}'") account_tab.click() + # print(f"DEBUG: After click - checked='{account_tab.get_attribute('checked')}'") + # print(f"DEBUG: After click - focused='{account_tab.get_attribute('focused')}'") + # breakpoint() @staticmethod def get_displayed_account_text(displayname, host): @@ -98,6 +106,36 @@ def get_accounts(): account_idx += 1 return accounts, selectors +# account = app().find_element( +# By.XPATH, +# f"//*[contains(@name, '{display_name}')]" +# ) + + # @staticmethod + # def get_account(display_name): + # server_host = urlparse(get_config('localBackendUrl')).netloc + # account_label = f"{display_name}@{server_host}" + # # account = None + # try: + # # nav_bar = app().find_element(By.NAME, "Navigation bar") + # # account = nav_bar.find_element(By.NAME, account_label) + # # account = app().find_element( + # # By.XPATH, + # # f'//panel[@name="Navigation bar"]//pagetab[@name="{account_label}"]' + # # ) + # all_matches = app().find_elements(By.NAME, account_label) + # print(f"DEBUG: Looking for '{account_label}'") + # print(f"DEBUG: Found {len(all_matches)} elements") + # for i, el in enumerate(all_matches): + # print(f"DEBUG: [{i}] name='{el.get_attribute('name')}' " + # f"role='{el.get_attribute('role')}' " + # f"displayed='{el.is_displayed()}'") + # return all_matches[0] if all_matches else None + # # return account + # except: + # return None + # # return account + @staticmethod def get_account(display_name): server_host = urlparse(get_config('localBackendUrl')).netloc @@ -124,5 +162,6 @@ def account_has_focus(display_name): @staticmethod def account_exists(display_name): + time.sleep(5) account = Toolbar.get_account(display_name) return account is not None diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py index 48d4349f53..bb124bfdc8 100644 --- a/test/gui/steps/account_context.py +++ b/test/gui/steps/account_context.py @@ -44,16 +44,10 @@ def step(context, displayname): expect(Toolbar.account_exists(displayname)).to.be.true -@Then('the account with displayname "|any|" should not be displayed') +@Then('the account with displayname "{displayname}" should not be displayed') def step(context, displayname): displayname = substitute_inline_codes(displayname) - timeout = get_config('lowestSyncTimeout') * 1000 - - test.compare( - False, - Toolbar.has_item(displayname, timeout), - f"Expected account '{displayname}' to be removed", - ) + expect(Toolbar.account_exists(displayname)).to.be.false @Given('user "{username}" has set up a client with default settings') @@ -161,7 +155,7 @@ def step(context, _): AccountSetting.wait_until_sync_folder_is_configured() -@When('the user removes the connection for user "|any|"') +@When('the user removes the connection for user "{username}"') def step(context, username): AccountSetting.remove_connection_for_user(username) diff --git a/test/gui/tst_removeAccountConnection/test.py b/test/gui/tst_removeAccountConnection/test.py deleted file mode 100644 index 83b0a5275a..0000000000 --- a/test/gui/tst_removeAccountConnection/test.py +++ /dev/null @@ -1,8 +0,0 @@ -source(findFile('scripts', 'python/bdd.py')) - -setupHooks('../shared/scripts/bdd_hooks.py') -collectStepDefinitions('./steps', '../shared/steps') - - -def main(): - runFeatureFile('test.feature') From d077316ab95e42803a61fc95dd18c724008194b6 Mon Sep 17 00:00:00 2001 From: Saw-jan Date: Fri, 24 Apr 2026 13:30:43 +0545 Subject: [PATCH 2/2] test: fix account selection Signed-off-by: Saw-jan --- test/gui/features/add-account/account.feature | 11 ++-- .../removeAccountConnection.feature | 4 +- test/gui/pageObjects/AccountSetting.py | 27 +++++--- test/gui/pageObjects/SyncConnectionWizard.py | 3 +- test/gui/pageObjects/Toolbar.py | 66 +++++-------------- test/gui/steps/account_context.py | 26 ++++---- test/gui/tst_syncing/test.feature | 2 +- 7 files changed, 61 insertions(+), 78 deletions(-) diff --git a/test/gui/features/add-account/account.feature b/test/gui/features/add-account/account.feature index 93ae5b5365..202ed6eeef 100644 --- a/test/gui/features/add-account/account.feature +++ b/test/gui/features/add-account/account.feature @@ -24,7 +24,7 @@ Feature: adding accounts | server | %local_server% | | user | Alice | | password | 1234 | - Then the account with displayname "Alice Hansen" should be displayed + Then "Alice" account should be added @smoke Scenario: Adding multiple accounts @@ -35,9 +35,8 @@ Feature: adding accounts | server | %local_server% | | user | Brian | | password | AaBb2Cc3Dd4 | - Then "Brian Murphy" account should be opened - And the account with displayname "Alice Hansen" should be displayed - And the account with displayname "Brian Murphy" should be displayed + Then "Brian" account should be opened + And "Alice" account should be added Scenario: Adding account with self signed certificate for the first time @@ -48,7 +47,7 @@ Feature: adding accounts When the user adds the following account: | user | Alice | | password | 1234 | - Then "Alice Hansen" account should be opened + Then "Alice" account should be opened @smoke Scenario: Add space manually from sync connection window @@ -87,6 +86,6 @@ Feature: adding accounts | server | %local_server% | | user | Alice | | password | 1234 | - Then the account with displayname "Alice Hansen" should be displayed + Then "Alice" account should be added And the folder "large-folder" should exist on the file system And the file "testFile.txt" should exist on the file system diff --git a/test/gui/features/remove-account-connection/removeAccountConnection.feature b/test/gui/features/remove-account-connection/removeAccountConnection.feature index b28d1a9998..3003bf68f3 100644 --- a/test/gui/features/remove-account-connection/removeAccountConnection.feature +++ b/test/gui/features/remove-account-connection/removeAccountConnection.feature @@ -12,8 +12,8 @@ Feature: remove account connection | Alice | | Brian | When the user removes the connection for user "Brian" - Then the account with displayname "Brian Murphy" should not be displayed - But the account with displayname "Alice Hansen" should be displayed + Then "Brian" account should not be displayed + But "Alice" account should be added Scenario: remove the only account connection diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index 3c91e699d6..2418eb406a 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -8,6 +8,9 @@ class AccountSetting: + ACCOUNT_CONNECTION_CONTAINER = SimpleNamespace( + by=By.NAME, selector="Sync connections" + ) MANAGE_ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector="Manage Account") ACCOUNT_MENU = SimpleNamespace(by=By.NAME, selector="{menu_item}") CONFIRM_REMOVE_CONNECTION_BUTTON = SimpleNamespace( @@ -21,10 +24,20 @@ class AccountSetting: @staticmethod def account_action(action): - app().find_element( - AccountSetting.MANAGE_ACCOUNT_BUTTON.by, - AccountSetting.MANAGE_ACCOUNT_BUTTON.selector, - ).click() + connections = app().find_elements( + AccountSetting.ACCOUNT_CONNECTION_CONTAINER.by, + AccountSetting.ACCOUNT_CONNECTION_CONTAINER.selector, + ) + manage_button = None + for connection in connections: + # use the active connection + if connection.get_attribute("showing") == "true": + manage_button = connection.find_element( + AccountSetting.MANAGE_ACCOUNT_BUTTON.by, + AccountSetting.MANAGE_ACCOUNT_BUTTON.selector, + ) + break + manage_button.click() app().find_element( AccountSetting.ACCOUNT_MENU.by, AccountSetting.ACCOUNT_MENU.selector.format(menu_item=action), @@ -129,9 +142,7 @@ def is_log_dialog_visible(): @staticmethod def remove_connection_for_user(username): - displayname = get_displayname_for_user(username) - displayname = substitute_inline_codes(displayname) - Toolbar.open_account(displayname) + Toolbar.open_account(username) AccountSetting.remove_account_connection() @staticmethod @@ -140,7 +151,7 @@ def wait_until_account_is_removed(username, timeout=10000): displayname = substitute_inline_codes(displayname) def account_removed(): - account = Toolbar.get_account(displayname) + account = Toolbar.get_account(username) return account is None result = wait_for(account_removed, timeout) diff --git a/test/gui/pageObjects/SyncConnectionWizard.py b/test/gui/pageObjects/SyncConnectionWizard.py index 7268aa8739..3ed99b67c6 100644 --- a/test/gui/pageObjects/SyncConnectionWizard.py +++ b/test/gui/pageObjects/SyncConnectionWizard.py @@ -138,7 +138,8 @@ def select_space(space_name): ), ) # ISSUE: https://github.com/opencloud-eu/desktop/pull/879 - # Workaround until above fix is merged + # Cannot select space by click event + # Select space using keyboard events as a workaround # TODO: Remove 'send_keys' and uncomment 'click' action space_item.send_keys(Keys.ARROW_DOWN) # space_item.click() diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index 66d54ae253..d36410d8bd 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -1,18 +1,17 @@ from types import SimpleNamespace from urllib.parse import urlparse from appium.webdriver.common.appiumby import AppiumBy as By +from selenium.webdriver.common.keys import Keys from helpers.SetupClientHelper import wait_until_app_killed from helpers.ConfigHelper import get_config from helpers.SetupClientHelper import app - -import time +from helpers.UserHelper import get_displayname_for_user class Toolbar: TOOLBAR_ROW = SimpleNamespace(by=None, selector=None) - ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector=None) - # ACCOUNT_BUTTON_LABEL = SimpleNamespace(by=By.XPATH, selector="//label[@name='BM']") + ACCOUNT_BUTTON = SimpleNamespace(by=None, selector=None) ADD_ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector="Add Account") ACTIVITY_BUTTON = SimpleNamespace(by=By.NAME, selector="Activity") SETTINGS_BUTTON = SimpleNamespace(by=None, selector=None) @@ -52,14 +51,15 @@ def open_new_account_setup(): ).click() @staticmethod - def open_account(displayname): - account_tab = Toolbar.get_account(displayname) - # print(f"DEBUG: Before click - checked='{account_tab.get_attribute('checked')}'") - # print(f"DEBUG: Before click - focused='{account_tab.get_attribute('focused')}'") - account_tab.click() - # print(f"DEBUG: After click - checked='{account_tab.get_attribute('checked')}'") - # print(f"DEBUG: After click - focused='{account_tab.get_attribute('focused')}'") - # breakpoint() + def open_account(username): + account_tab = Toolbar.get_account(username) + # ISSUE: https://github.com/opencloud-eu/desktop/pull/879 + # Cannot activate account tab by click event + # Select the account tab using keyboard events as a workaround + # TODO: Remove the workaround and uncomment 'click' action + # account_tab.click() + account_tab.send_keys(Keys.TAB) + account_tab.send_keys(Keys.ENTER) @staticmethod def get_displayed_account_text(displayname, host): @@ -106,38 +106,9 @@ def get_accounts(): account_idx += 1 return accounts, selectors -# account = app().find_element( -# By.XPATH, -# f"//*[contains(@name, '{display_name}')]" -# ) - - # @staticmethod - # def get_account(display_name): - # server_host = urlparse(get_config('localBackendUrl')).netloc - # account_label = f"{display_name}@{server_host}" - # # account = None - # try: - # # nav_bar = app().find_element(By.NAME, "Navigation bar") - # # account = nav_bar.find_element(By.NAME, account_label) - # # account = app().find_element( - # # By.XPATH, - # # f'//panel[@name="Navigation bar"]//pagetab[@name="{account_label}"]' - # # ) - # all_matches = app().find_elements(By.NAME, account_label) - # print(f"DEBUG: Looking for '{account_label}'") - # print(f"DEBUG: Found {len(all_matches)} elements") - # for i, el in enumerate(all_matches): - # print(f"DEBUG: [{i}] name='{el.get_attribute('name')}' " - # f"role='{el.get_attribute('role')}' " - # f"displayed='{el.is_displayed()}'") - # return all_matches[0] if all_matches else None - # # return account - # except: - # return None - # # return account - @staticmethod - def get_account(display_name): + def get_account(username): + display_name = get_displayname_for_user(username) server_host = urlparse(get_config('localBackendUrl')).netloc account_label = f"{display_name}@{server_host}" account = None @@ -156,12 +127,11 @@ def get_active_account(): return None, None @staticmethod - def account_has_focus(display_name): - account = Toolbar.get_account(display_name) + def account_has_focus(username): + account = Toolbar.get_account(username) return account.get_attribute("checked") == "true" @staticmethod - def account_exists(display_name): - time.sleep(5) - account = Toolbar.get_account(display_name) + def account_exists(username): + account = Toolbar.get_account(username) return account is not None diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py index bb124bfdc8..c10294245d 100644 --- a/test/gui/steps/account_context.py +++ b/test/gui/steps/account_context.py @@ -38,16 +38,16 @@ def step(context): ) -@Then('the account with displayname "{displayname}" should be displayed') -def step(context, displayname): - displayname = substitute_inline_codes(displayname) - expect(Toolbar.account_exists(displayname)).to.be.true +@Then('"{username}" account should be added') +def step(context, username): + username = substitute_inline_codes(username) + expect(Toolbar.account_exists(username)).to.be.true -@Then('the account with displayname "{displayname}" should not be displayed') -def step(context, displayname): - displayname = substitute_inline_codes(displayname) - expect(Toolbar.account_exists(displayname)).to.be.false +@Then('"{username}" account should not be displayed') +def step(context, username): + username = substitute_inline_codes(username) + expect(Toolbar.account_exists(username)).to.be.false @Given('user "{username}" has set up a client with default settings') @@ -157,6 +157,7 @@ def step(context, _): @When('the user removes the connection for user "{username}"') def step(context, username): + username = substitute_inline_codes(username) AccountSetting.remove_connection_for_user(username) @@ -241,10 +242,10 @@ def step(context): Toolbar.quit_opencloud() -@Then('"{displayname}" account should be opened') -def step(context, displayname): - displayname = substitute_inline_codes(displayname) - expect(Toolbar.account_has_focus(displayname)).to.be.true +@Then('"{username}" account should be opened') +def step(context, username): + username = substitute_inline_codes(username) + expect(Toolbar.account_has_focus(username)).to.be.true @Then( @@ -280,6 +281,7 @@ def step(context, warn_message): @Given('the user has removed the connection for user "{username}"') def step(context, username): + username = substitute_inline_codes(username) AccountSetting.remove_connection_for_user(username) AccountSetting.wait_until_account_is_removed(username) shutil.rmtree(os.path.join(get_config("clientRootSyncPath"), username)) diff --git a/test/gui/tst_syncing/test.feature b/test/gui/tst_syncing/test.feature index f4e1e8501c..ea1d21a211 100644 --- a/test/gui/tst_syncing/test.feature +++ b/test/gui/tst_syncing/test.feature @@ -437,7 +437,7 @@ Feature: Syncing files | password | 1234 | When the user selects manual sync folder option in advanced section And the user cancels the sync connection wizard - Then the account with displayname "Alice Hansen" should be displayed + Then "Alice" account should be added And the sync folder list should be empty