From e3ef3aca925a7e7fe78e0018dd384cbcf48e5a31 Mon Sep 17 00:00:00 2001 From: pradip Date: Wed, 22 Apr 2026 11:53:40 +0545 Subject: [PATCH 1/5] test: port tst_loginLogout --- .../login-logout/login-logout.feature} | 18 ++++++------ .../login-logout}/test.py | 0 test/gui/pageObjects/AccountSetting.py | 28 +++++++++--------- test/gui/steps/account_context.py | 29 +++++++++---------- 4 files changed, 38 insertions(+), 37 deletions(-) rename test/gui/{tst_loginLogout/test.feature => features/login-logout/login-logout.feature} (58%) rename test/gui/{tst_loginLogout => features/login-logout}/test.py (100%) diff --git a/test/gui/tst_loginLogout/test.feature b/test/gui/features/login-logout/login-logout.feature similarity index 58% rename from test/gui/tst_loginLogout/test.feature rename to test/gui/features/login-logout/login-logout.feature index aedd31706f..84a6f33886 100644 --- a/test/gui/tst_loginLogout/test.feature +++ b/test/gui/features/login-logout/login-logout.feature @@ -6,18 +6,18 @@ Feature: Logout users Background: Given user "Alice" has been created in the server with default attributes - @smoke @skip - Scenario: logging out - Given user "Alice" has set up a client with default settings - When the user "Alice" logs out using the client-UI - Then user "Alice" should be signed out + # @smoke + # Scenario: logging out + # Given user "Alice" has set up a client with default settings + # When the user "Alice" logs out using the client-UI + # Then user "Alice" should be signed out - @smoke @skip + @smoke Scenario: login after logging out Given user "Alice" has set up a client with default settings And user "Alice" has logged out from the client-UI When user "Alice" logs in using the client-UI Then user "Alice" should be connected to the server - When the user quits the client - And the user starts the client - Then user "Alice" should be connected to the server + # When the user quits the client + # And the user starts the client + # Then user "Alice" should be connected to the server \ No newline at end of file diff --git a/test/gui/tst_loginLogout/test.py b/test/gui/features/login-logout/test.py similarity index 100% rename from test/gui/tst_loginLogout/test.py rename to test/gui/features/login-logout/test.py diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index 2418eb406a..bb71c85635 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -1,5 +1,11 @@ +# pyright: reportUndefinedVariable=false + from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import TimeoutException, NoSuchElementException +from helpers.UserHelper import get_displayname_for_user +from helpers.SetupClientHelper import substitute_inline_codes, app from pageObjects.Toolbar import Toolbar from helpers.UserHelper import get_displayname_for_user @@ -61,9 +67,8 @@ def login(): @staticmethod def get_account_connection_label(): - return str( - squish.waitForObjectExists(AccountSetting.ACCOUNT_CONNECTION_LABEL).text - ) + label = app().find_element(AccountSetting.ACCOUNT_CONNECTION_LABEL.by, AccountSetting.ACCOUNT_CONNECTION_LABEL.selector).text + return str(label) @staticmethod def is_connecting(): @@ -93,18 +98,15 @@ def wait_until_connection_is_configured(timeout=5000): @staticmethod def wait_until_account_is_connected(timeout=5000): - result = squish.waitFor( - AccountSetting.is_user_signed_in, - timeout, - ) - - if not result: + wait = WebDriverWait(app(), timeout / 1000) # Convert to seconds + try: + wait.until(lambda _: AccountSetting.is_user_signed_in()) + return True + except TimeoutException: raise TimeoutError( - "Timeout waiting for the account to be connected for " - + str(timeout) - + " milliseconds" + f"Timeout waiting for the account to be connected for {timeout} milliseconds" ) - return result + @staticmethod def wait_until_sync_folder_is_configured(timeout=5000): diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py index f3bd9b90b0..7f19bb3c78 100644 --- a/test/gui/steps/account_context.py +++ b/test/gui/steps/account_context.py @@ -1,13 +1,14 @@ import shutil import os from behave import given as Given, when as When, then as Then -from sure import expect +from sure import expect, ensure from pageObjects.AccountConnectionWizard import AccountConnectionWizard from pageObjects.SyncConnectionWizard import SyncConnectionWizard from pageObjects.AccountSetting import AccountSetting from pageObjects.Toolbar import Toolbar from pageObjects.EnterPassword import EnterPassword +from pageObjects.AccountSetting import AccountSetting from helpers.SetupClientHelper import ( start_client, setup_client, @@ -113,28 +114,27 @@ def step(context): AccountConnectionWizard.add_account_information(account_details) -@When('the user "|any|" logs out using the client-UI') -def step(context, _): +@When('the user "{username}" logs out using the client-UI') +def step(context, username): AccountSetting.logout() -@Then('user "|any|" should be signed out') +@Then('user "{username}" should be signed out') def step(context, username): - test.compare( - AccountSetting.is_user_signed_out(), - True, - f'User "{username}" is signed out', - ) + user_signed_out = AccountSetting.is_user_signed_out() + + with ensure('User "{0}" should be signed out, but is still signed in', username): + user_signed_out.should.be.true -@Given('user "|any|" has logged out from the client-UI') +@Given('user "{username}" has logged out from the client-UI') def step(context, username): AccountSetting.logout() if not AccountSetting.is_user_signed_out(): raise LookupError(f'Failed to logout user {username}') -@When('user "|any|" logs in using the client-UI') +@When('user "{username}" logs in using the client-UI') def step(context, username): AccountSetting.login() password = get_password_for_user(username) @@ -150,10 +150,9 @@ def step(context, _): AccountSetting.login() -@Then('user "|any|" should be connected to the server') -def step(context, _): +@Then('user "{username}" should be connected to the server') +def step(context, username): AccountSetting.wait_until_account_is_connected() - AccountSetting.wait_until_sync_folder_is_configured() @When('the user removes the connection for user "{username}"') @@ -233,7 +232,7 @@ def step(context): test.compare(True, AccountSetting.is_log_dialog_visible(), 'Log dialog is opened') -@Step('the user cancels the sync connection wizard') +@When('the user cancels the sync connection wizard') def step(context): SyncConnectionWizard.cancel_folder_sync_connection_wizard() From 2b889c40b811c6178595ccd8c396d5a3d2db99af Mon Sep 17 00:00:00 2001 From: pradip Date: Mon, 27 Apr 2026 16:17:19 +0545 Subject: [PATCH 2/5] test: fix closing of client --- .../login-logout/login-logout.feature | 16 +++++----- test/gui/pageObjects/AccountSetting.py | 7 +++-- test/gui/pageObjects/Toolbar.py | 29 ++++++++++++------- test/gui/steps/account_context.py | 1 + 4 files changed, 33 insertions(+), 20 deletions(-) diff --git a/test/gui/features/login-logout/login-logout.feature b/test/gui/features/login-logout/login-logout.feature index 84a6f33886..1b08bd854f 100644 --- a/test/gui/features/login-logout/login-logout.feature +++ b/test/gui/features/login-logout/login-logout.feature @@ -6,11 +6,11 @@ Feature: Logout users Background: Given user "Alice" has been created in the server with default attributes - # @smoke - # Scenario: logging out - # Given user "Alice" has set up a client with default settings - # When the user "Alice" logs out using the client-UI - # Then user "Alice" should be signed out + @smoke + Scenario: logging out + Given user "Alice" has set up a client with default settings + When the user "Alice" logs out using the client-UI + Then user "Alice" should be signed out @smoke Scenario: login after logging out @@ -18,6 +18,6 @@ Feature: Logout users And user "Alice" has logged out from the client-UI When user "Alice" logs in using the client-UI Then user "Alice" should be connected to the server - # When the user quits the client - # And the user starts the client - # Then user "Alice" should be connected to the server \ No newline at end of file + When the user quits the client + And the user starts the client + Then user "Alice" should be connected to the server diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index bb71c85635..dcd80d2c1a 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import TimeoutException, NoSuchElementException +from selenium.common.exceptions import TimeoutException from helpers.UserHelper import get_displayname_for_user from helpers.SetupClientHelper import substitute_inline_codes, app @@ -22,7 +22,10 @@ class AccountSetting: CONFIRM_REMOVE_CONNECTION_BUTTON = SimpleNamespace( by=By.NAME, selector="Remove connection" ) - ACCOUNT_CONNECTION_LABEL = SimpleNamespace(by=None, selector=None) + ACCOUNT_CONNECTION_LABEL = SimpleNamespace( + by=By.XPATH, + selector="//list[@name='Folder Sync']//label", + ) LOG_BROWSER_WINDOW = SimpleNamespace(by=None, selector=None) ACCOUNT_LOADING = SimpleNamespace(by=None, selector=None) DIALOG_STACK = SimpleNamespace(by=None, selector=None) diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index d36410d8bd..c32f3bf329 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -3,9 +3,8 @@ 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 +from helpers.ConfigHelper import get_config from helpers.UserHelper import get_displayname_for_user @@ -15,8 +14,14 @@ class Toolbar: 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) - QUIT_BUTTON = SimpleNamespace(by=None, selector=None) - CONFIRM_QUIT_BUTTON = SimpleNamespace(by=None, selector=None) + QUIT_BUTTON = SimpleNamespace( + by=By.NAME, + selector="Quit" + ) + CONFIRM_QUIT_BUTTON = SimpleNamespace( + by=By.ACCESSIBILITY_ID, + selector="QApplication.QMessageBox.qt_msgbox_buttonbox.QPushButton" + ) TOOLBAR_ITEMS = ["Add Account", "Activity", "Settings", "Quit"] @@ -75,12 +80,16 @@ def open_settings_tab(): @staticmethod def quit_opencloud(): - squish.mouseClick(squish.waitForObject(Toolbar.QUIT_BUTTON)) - squish.clickButton(squish.waitForObject(Toolbar.CONFIRM_QUIT_BUTTON)) - for ctx in squish.applicationContextList(): - pid = ctx.pid - ctx.detach() - wait_until_app_killed(pid) + app().find_element( + Toolbar.QUIT_BUTTON.by, + Toolbar.QUIT_BUTTON.selector + ).click() + app().find_element( + Toolbar.CONFIRM_QUIT_BUTTON.by, + Toolbar.CONFIRM_QUIT_BUTTON.selector + ).click() + app().quit() + @staticmethod def get_accounts(): diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py index 7f19bb3c78..bd92e94bdb 100644 --- a/test/gui/steps/account_context.py +++ b/test/gui/steps/account_context.py @@ -16,6 +16,7 @@ get_client_details, generate_account_config, get_resource_path, + app, ) from helpers.SyncHelper import ( wait_for_initial_sync_to_complete, From 25651a8c3c307e988a6e4f078fce7bff00674c7c Mon Sep 17 00:00:00 2001 From: pradip Date: Tue, 28 Apr 2026 10:34:00 +0545 Subject: [PATCH 3/5] test: Add reusable close_and_kill_app() for Appium session and process cleanup --- test/gui/environment.py | 11 ++--------- test/gui/helpers/SetupClientHelper.py | 28 +++++++++++++++++++++++++++ test/gui/pageObjects/Toolbar.py | 4 ++-- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/test/gui/environment.py b/test/gui/environment.py index 26764d37ac..3f9b9bc64a 100644 --- a/test/gui/environment.py +++ b/test/gui/environment.py @@ -1,4 +1,3 @@ -import psutil import shutil import os @@ -7,7 +6,7 @@ from helpers.SpaceHelper import delete_project_spaces from helpers.ConfigHelper import set_config, get_config from helpers.FilesHelper import prefix_path_namespace, cleanup_created_paths -from helpers.SetupClientHelper import app +from helpers.SetupClientHelper import close_and_kill_app from step_types.types import * # register all step types @@ -37,10 +36,4 @@ def after_scenario(context, scenario): delete_project_spaces() delete_created_users() # quit the application - if app() is not None: - app().quit() - for process in psutil.process_iter(['pid', 'exe']): - if process.info['exe'] == get_config("app_path"): - print("Closing desktop client...") - psutil.Process(process.info['pid']).kill() - break + close_and_kill_app() diff --git a/test/gui/helpers/SetupClientHelper.py b/test/gui/helpers/SetupClientHelper.py index d8e935969d..5718dc5448 100644 --- a/test/gui/helpers/SetupClientHelper.py +++ b/test/gui/helpers/SetupClientHelper.py @@ -270,3 +270,31 @@ def run_sys_command(command=None, shell=False): check=False, ) return cmd.stdout, cmd.stderr, cmd.returncode + + +def close_and_kill_app(): + """ + Close Appium session and kill the desktop client process. + Use this for both mid-scenario and end-of-scenario cleanup. + """ + global app_driver + # Quit Appium session + if app_driver is not None: + try: + app_driver.quit() + except Exception: + pass + + # Kill remaining process by exe path + app_path = get_config("app_path") + for process in psutil.process_iter(['pid', 'exe']): + try: + if process.info['exe'] == app_path: + print("Closing desktop client...") + psutil.Process(process.info['pid']).kill() + break + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + + # Reset driver for reuse + app_driver = None diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index c32f3bf329..6a88acb065 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -3,7 +3,7 @@ from appium.webdriver.common.appiumby import AppiumBy as By from selenium.webdriver.common.keys import Keys -from helpers.SetupClientHelper import app +from helpers.SetupClientHelper import app, close_and_kill_app from helpers.ConfigHelper import get_config from helpers.UserHelper import get_displayname_for_user @@ -88,7 +88,7 @@ def quit_opencloud(): Toolbar.CONFIRM_QUIT_BUTTON.by, Toolbar.CONFIRM_QUIT_BUTTON.selector ).click() - app().quit() + close_and_kill_app() @staticmethod From 95ccf28bc91affb8b4608ff1b505d13f125d2785 Mon Sep 17 00:00:00 2001 From: pradip Date: Tue, 28 Apr 2026 10:41:59 +0545 Subject: [PATCH 4/5] test: remove test.py --- test/gui/features/login-logout/test.py | 8 -------- test/gui/helpers/SetupClientHelper.py | 16 +++++----------- test/gui/pageObjects/AccountSetting.py | 11 +++-------- 3 files changed, 8 insertions(+), 27 deletions(-) delete mode 100644 test/gui/features/login-logout/test.py diff --git a/test/gui/features/login-logout/test.py b/test/gui/features/login-logout/test.py deleted file mode 100644 index 83b0a5275a..0000000000 --- a/test/gui/features/login-logout/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') diff --git a/test/gui/helpers/SetupClientHelper.py b/test/gui/helpers/SetupClientHelper.py index 5718dc5448..45392fb8b8 100644 --- a/test/gui/helpers/SetupClientHelper.py +++ b/test/gui/helpers/SetupClientHelper.py @@ -280,21 +280,15 @@ def close_and_kill_app(): global app_driver # Quit Appium session if app_driver is not None: - try: - app_driver.quit() - except Exception: - pass + app_driver.quit() # Kill remaining process by exe path app_path = get_config("app_path") for process in psutil.process_iter(['pid', 'exe']): - try: - if process.info['exe'] == app_path: - print("Closing desktop client...") - psutil.Process(process.info['pid']).kill() - break - except (psutil.NoSuchProcess, psutil.AccessDenied): - pass + if process.info['exe'] == app_path: + print("Closing desktop client...") + psutil.Process(process.info['pid']).kill() + break # Reset driver for reuse app_driver = None diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index dcd80d2c1a..06cb17289a 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -1,9 +1,5 @@ -# pyright: reportUndefinedVariable=false - from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import TimeoutException from helpers.UserHelper import get_displayname_for_user from helpers.SetupClientHelper import substitute_inline_codes, app @@ -71,7 +67,7 @@ def login(): @staticmethod def get_account_connection_label(): label = app().find_element(AccountSetting.ACCOUNT_CONNECTION_LABEL.by, AccountSetting.ACCOUNT_CONNECTION_LABEL.selector).text - return str(label) + return label @staticmethod def is_connecting(): @@ -101,11 +97,10 @@ def wait_until_connection_is_configured(timeout=5000): @staticmethod def wait_until_account_is_connected(timeout=5000): - wait = WebDriverWait(app(), timeout / 1000) # Convert to seconds try: - wait.until(lambda _: AccountSetting.is_user_signed_in()) + wait_for(AccountSetting.is_user_signed_in, timeout) return True - except TimeoutException: + except TimeoutError: raise TimeoutError( f"Timeout waiting for the account to be connected for {timeout} milliseconds" ) From 1c437d46be8db8230986f26ade34ebe9110c2570 Mon Sep 17 00:00:00 2001 From: pradip Date: Tue, 28 Apr 2026 15:14:57 +0545 Subject: [PATCH 5/5] test: use exception handlers in sync helper --- test/gui/helpers/SyncHelper.py | 16 +++++++++------- test/gui/pageObjects/AccountSetting.py | 15 ++++++++++----- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/test/gui/helpers/SyncHelper.py b/test/gui/helpers/SyncHelper.py index 3504bdee46..d1c1460561 100644 --- a/test/gui/helpers/SyncHelper.py +++ b/test/gui/helpers/SyncHelper.py @@ -2,6 +2,8 @@ import re import time import urllib.request +from selenium.webdriver.support.ui import WebDriverWait +from selenium.common.exceptions import TimeoutException from helpers.ConfigHelper import get_config, is_linux, is_windows from helpers.FilesHelper import sanitize_path @@ -357,10 +359,10 @@ def make_available_locally(resource_path): def wait_for(condition, timeout, interval=0.5): - start = time.time() * 1000 - while True: - if condition(): - return True - if time.time() * 1000 - start > timeout: - return False - time.sleep(interval) + from helpers.SetupClientHelper import app + wait = WebDriverWait(app(), timeout / 1000, poll_frequency=interval) + try: + wait.until(lambda _: condition()) + return True + except TimeoutException: + return False diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index 06cb17289a..3d8acc919a 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -97,13 +97,18 @@ def wait_until_connection_is_configured(timeout=5000): @staticmethod def wait_until_account_is_connected(timeout=5000): - try: - wait_for(AccountSetting.is_user_signed_in, timeout) - return True - except TimeoutError: + result = wait_for( + AccountSetting.is_user_signed_in, + timeout, + ) + + if not result: raise TimeoutError( - f"Timeout waiting for the account to be connected for {timeout} milliseconds" + "Timeout waiting for the account to be connected for " + + str(timeout) + + " milliseconds" ) + return result @staticmethod