diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index f7dcce276f..bf969a6e65 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -34,6 +34,9 @@ when: path: <<: *trigger_path - event: pull_request + branch: + - main + - stable-* path: <<: *trigger_path evaluate: | diff --git a/.woodpecker/cache-pnpm.yaml b/.woodpecker/cache-pnpm.yaml index 356ec577ac..8f33e5f55b 100644 --- a/.woodpecker/cache-pnpm.yaml +++ b/.woodpecker/cache-pnpm.yaml @@ -34,6 +34,9 @@ when: <<: *trigger_path - event: tag - event: pull_request + branch: + - main + - stable-* path: <<: *trigger_path evaluate: | diff --git a/.woodpecker/cache-python.yaml b/.woodpecker/cache-python.yaml index 640d08e3cd..7178d3da41 100644 --- a/.woodpecker/cache-python.yaml +++ b/.woodpecker/cache-python.yaml @@ -35,6 +35,9 @@ when: <<: *trigger_path - event: tag - event: pull_request + branch: + - main + - stable-* path: <<: *trigger_path evaluate: | diff --git a/.woodpecker/ui-tests.yaml b/.woodpecker/ui-tests.yaml index 97fb42dc14..1e2b287e68 100644 --- a/.woodpecker/ui-tests.yaml +++ b/.woodpecker/ui-tests.yaml @@ -20,6 +20,9 @@ when: - push - manual - event: pull_request + branch: + - main + - stable-* - event: tag - event: cron cron: nightly* diff --git a/test/gui/.gitignore b/test/gui/.gitignore index 7648ceb913..e10e7e0638 100644 --- a/test/gui/.gitignore +++ b/test/gui/.gitignore @@ -1,2 +1,3 @@ -shared/scripts/custom_lib +custom_lib/ reports +venv/ \ No newline at end of file diff --git a/test/gui/behave.ini b/test/gui/behave.ini new file mode 100644 index 0000000000..867a69bf4d --- /dev/null +++ b/test/gui/behave.ini @@ -0,0 +1,8 @@ +[behave] +paths=features +default_format = plain +default_tags = not (@skip) +logging_level = WARNING +capture_stderr = false +capture_stdout = false +capture_log = false \ No newline at end of file diff --git a/test/gui/environment.py b/test/gui/environment.py new file mode 100644 index 0000000000..73f9268845 --- /dev/null +++ b/test/gui/environment.py @@ -0,0 +1,43 @@ +import psutil +import shutil +import os + +from helpers.ConfigHelper import init_config +from helpers.api.provisioning import delete_created_users +from helpers.ConfigHelper import set_config, get_config +from helpers.FilesHelper import prefix_path_namespace, cleanup_created_paths +from helpers.SetupClientHelper import app + + +def before_feature(context, feature): + init_config() + + +def before_scenario(context, feature): + set_config("currentUserSyncPath", "") + + +def after_scenario(context, scenario): + # clean up config dir + shutil.rmtree(get_config("clientConfigDir")) + # clean up sync dir + for entry in os.scandir(get_config("clientRootSyncPath")): + try: + if entry.is_file() or entry.is_symlink(): + print("Deleting file: " + entry.name) + os.unlink(prefix_path_namespace(entry.path)) + elif entry.is_dir(): + print("Deleting folder: " + entry.name) + shutil.rmtree(prefix_path_namespace(entry.path)) + except OSError as e: + print(f"Failed to delete '{entry.name}'.\nReason: {e}.") + # cleanup paths created outside of the temporary directory during the test + cleanup_created_paths() + delete_created_users() + # quit the application + 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 diff --git a/test/gui/tst_addAccount/test.feature b/test/gui/features/add-account/account.feature similarity index 99% rename from test/gui/tst_addAccount/test.feature rename to test/gui/features/add-account/account.feature index 6d05242a6d..9e70f4d2ef 100644 --- a/test/gui/tst_addAccount/test.feature +++ b/test/gui/features/add-account/account.feature @@ -17,7 +17,7 @@ Feature: adding accounts Then the download everything option should be selected by default for Linux And the user should be able to choose the local download directory - + @smoke Scenario: Adding normal Account Given the user has started the client When the user adds the following account: diff --git a/test/gui/shared/scripts/helpers/ConfigHelper.py b/test/gui/helpers/ConfigHelper.py similarity index 90% rename from test/gui/shared/scripts/helpers/ConfigHelper.py rename to test/gui/helpers/ConfigHelper.py index af602387ce..908030ff84 100644 --- a/test/gui/shared/scripts/helpers/ConfigHelper.py +++ b/test/gui/helpers/ConfigHelper.py @@ -11,7 +11,7 @@ def read_env_file(): envs = {} script_path = os.path.dirname(os.path.realpath(__file__)) - env_path = os.path.abspath(os.path.join(script_path, '..', '..', '..', 'envs.txt')) + env_path = os.path.abspath(os.path.join(script_path, '..', 'envs.txt')) with open(env_path, 'rt', encoding='UTF-8') as f: for line in f: if not line.strip(): @@ -64,6 +64,7 @@ def get_default_home_dir(): # map environment variables to config keys CONFIG_ENV_MAP = { + 'app_path': 'APP_PATH', 'localBackendUrl': 'BACKEND_HOST', 'maxSyncTimeout': 'MAX_SYNC_TIMEOUT', 'minSyncTimeout': 'MIN_SYNC_TIMEOUT', @@ -73,14 +74,17 @@ def get_default_home_dir(): 'clientRootSyncPath': 'CLIENT_ROOT_SYNC_PATH', 'tempFolderPath': 'TEMP_FOLDER_PATH', 'guiTestReportDir': 'GUI_TEST_REPORT_DIR', - 'record_video_on_failure': 'RECORD_VIDEO_ON_FAILURE' + 'record_video_on_failure': 'RECORD_VIDEO_ON_FAILURE', } DEFAULT_PATH_CONFIG = { - 'custom_lib': os.path.abspath('../shared/scripts/custom_lib'), + 'custom_lib': os.path.abspath( + os.path.join(os.path.dirname(__file__), 'custom_lib') + ), 'home_dir': get_default_home_dir(), # allow to record first 5 videos 'video_record_limit': 5, + 'app_path': None, } # default config values @@ -98,13 +102,13 @@ def get_default_home_dir(): 'guiTestReportDir': os.path.abspath('../reports'), 'record_video_on_failure': False, 'files_for_upload': os.path.join(CURRENT_DIR.parent.parent, 'files-for-upload'), - 'syncConnectionName': 'Personal' + 'syncConnectionName': 'Personal', } # Permission roles mapping PERMISSION_ROLES = { 'Viewer': 'b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5', - 'Editor': 'fb6c3e19-e378-47e5-b277-9732f9de6e21' + 'Editor': 'fb6c3e19-e378-47e5-b277-9732f9de6e21', } CONFIG.update(DEFAULT_PATH_CONFIG) @@ -130,9 +134,7 @@ def init_config(): # try reading configs from config.ini try: script_path = os.path.dirname(os.path.realpath(__file__)) - cfg_path = os.path.abspath( - os.path.join(script_path, '..', '..', '..', 'config.ini') - ) + cfg_path = os.path.abspath(os.path.join(script_path, '..', 'config.ini')) read_cfg_file(cfg_path) except: pass @@ -165,6 +167,9 @@ def init_config(): else: CONFIG[key] = value.rstrip('/') + '/' + if 'app_path' not in CONFIG or not CONFIG['app_path']: + raise KeyError('APP_PATH must be set in config.ini or environment variables') + def get_config(key): return CONFIG[key] diff --git a/test/gui/shared/scripts/helpers/FilesHelper.py b/test/gui/helpers/FilesHelper.py similarity index 96% rename from test/gui/shared/scripts/helpers/FilesHelper.py rename to test/gui/helpers/FilesHelper.py index a3ad0cdbf2..1e246fda69 100644 --- a/test/gui/shared/scripts/helpers/FilesHelper.py +++ b/test/gui/helpers/FilesHelper.py @@ -2,11 +2,11 @@ import re import shutil from pathlib import Path - from pypdf import PdfReader from docx import Document from pptx import Presentation from openpyxl import load_workbook + from helpers.ConfigHelper import is_windows, get_config @@ -16,12 +16,9 @@ def build_conflicted_regex(filename): namepart = filename.split(".")[0] extpart = filename.split(".")[1] # pylint: disable=anomalous-backslash-in-string - return "%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)\.%s" % ( - namepart, - extpart, - ) + return rf"{namepart} \(conflicted copy \d{{4}}-\d{{2}}-\d{{2}} \d{{6}}\)\.{extpart}" # pylint: disable=anomalous-backslash-in-string - return "%s \(conflicted copy \d{4}-\d{2}-\d{2} \d{6}\)" % filename + return rf"{filename} \(conflicted copy \d{{4}}-\d{{2}}-\d{{2}} \d{{6}}\)" def sanitize_path(path): diff --git a/test/gui/shared/scripts/helpers/ReportHelper.py b/test/gui/helpers/ReportHelper.py similarity index 98% rename from test/gui/shared/scripts/helpers/ReportHelper.py rename to test/gui/helpers/ReportHelper.py index c7eff43a0b..f29e15c6f6 100644 --- a/test/gui/shared/scripts/helpers/ReportHelper.py +++ b/test/gui/helpers/ReportHelper.py @@ -2,8 +2,6 @@ import glob import shutil import test -import squish -import squishinfo from helpers.ConfigHelper import get_config from helpers.FilesHelper import prefix_path_namespace diff --git a/test/gui/shared/scripts/helpers/SetupClientHelper.py b/test/gui/helpers/SetupClientHelper.py similarity index 85% rename from test/gui/shared/scripts/helpers/SetupClientHelper.py rename to test/gui/helpers/SetupClientHelper.py index 4c66aec862..ca663d8be7 100644 --- a/test/gui/shared/scripts/helpers/SetupClientHelper.py +++ b/test/gui/helpers/SetupClientHelper.py @@ -1,24 +1,29 @@ import uuid import os import subprocess +import test +import psutil from urllib.parse import urlparse from os import makedirs from os.path import exists, join -import test -import psutil -import squish -import squishinfo from PySide6.QtCore import QSettings, QUuid, QUrl, QJsonValue +from appium import webdriver +from appium.options.common.base import AppiumOptions from helpers.SpaceHelper import get_space_id, get_personal_space_id from helpers.ConfigHelper import get_config, set_config, is_windows from helpers.SyncHelper import listen_sync_status_for_item from helpers.api.utils import url_join from helpers.UserHelper import get_displayname_for_user, get_password_for_user -from helpers.ReportHelper import is_video_enabled from helpers.api import provisioning +app_driver = None + + +def app(): + return app_driver + def substitute_inline_codes(value): value = value.replace('%local_server%', get_config('localBackendUrl')) @@ -32,7 +37,7 @@ def substitute_inline_codes(value): return value -def get_client_details(context): +def get_client_details(table): client_details = { 'server': '', 'user': '', @@ -40,16 +45,16 @@ def get_client_details(context): 'sync_folder': '', 'oauth': False, } - for row in context.table[0:]: - row[1] = substitute_inline_codes(row[1]) - if row[0] == 'server': - client_details.update({'server': row[1]}) - elif row[0] == 'user': - client_details.update({'user': row[1]}) - elif row[0] == 'password': - client_details.update({'password': row[1]}) - elif row[0] == 'sync_folder': - client_details.update({'sync_folder': row[1]}) + for key, value in table.items(): + value = substitute_inline_codes(value) + if key == 'server': + client_details.update({'server': value}) + elif key == 'user': + client_details.update({'user': value}) + elif key == 'password': + client_details.update({'password': value}) + elif key == 'sync_folder': + client_details.update({'sync_folder': value}) return client_details @@ -103,26 +108,30 @@ def get_current_user_sync_path(): def start_client(): + global app_driver log_command_suffix = "" logfile = get_config("clientLogFile") - logdir = get_config("clientLogDir") + "/" + squishinfo.testCaseName + logdir = get_config("clientLogDir") if logfile != "": log_command_suffix = f' --logfile {logfile}' elif logdir != "": log_command_suffix = f' --logdir {logdir}' - squish.startApplication( - 'opencloud -s' - + f' {log_command_suffix}' - + ' --logdebug' + options = AppiumOptions() + options.set_capability( + 'app', + f'{get_config("app_path")} -s {log_command_suffix} --logdebug', + ) + options.set_capability( + 'appium:environ', + { + 'XDG_CONFIG_HOME': '/tmp/opencloudtest/.config', + }, + ) + app_driver = webdriver.Remote( + command_executor='http://127.0.0.1:4723', options=options ) - if is_video_enabled(): - test.startVideoCapture() - else: - test.log( - f'Video recordings reached the maximum limit of {get_config("video_record_limit")}.' - + 'Skipping video recording...' - ) + app_driver.implicitly_wait = 10 def get_polling_interval(): @@ -134,6 +143,7 @@ def get_polling_interval(): polling_interval = polling_interval.format(**args) return polling_interval + def generate_account_config(users, space='Personal'): sync_paths = {} settings = QSettings(get_config('clientConfigFile'), QSettings.Format.IniFormat) @@ -145,7 +155,7 @@ def generate_account_config(users, space='Personal'): for idx, username in enumerate(users): users_uuids[username] = QUuid.createUuid() settings.beginGroup("Accounts") - settings.beginWriteArray(str(idx+1),len(users)) + settings.beginWriteArray(str(idx + 1), len(users)) settings.setValue("capabilities", capabilities_variant) settings.setValue("default_sync_root", create_user_sync_path(username)) @@ -161,7 +171,7 @@ def generate_account_config(users, space='Personal'): settings.beginGroup("Folders") for idx, username in enumerate(users): sync_path = create_space_path(username, space) - settings.beginWriteArray(str(idx+1),len(users)) + settings.beginWriteArray(str(idx + 1), len(users)) if space == 'Personal': space_id = get_personal_space_id(username) @@ -181,17 +191,17 @@ def generate_account_config(users, space='Personal'): settings.setValue("virtualFilesMode", 'cfapi') else: settings.setValue("virtualFilesMode", 'off') - settings.setValue("journalPath",".sync_journal.db") + settings.setValue("journalPath", ".sync_journal.db") settings.endArray() settings.setValue("size", len(users)) sync_paths.update({username: sync_path}) settings.endGroup() - settings.sync() return sync_paths + def setup_client(username, space='Personal'): set_config('syncConnectionName', space) sync_paths = generate_account_config([username], space) diff --git a/test/gui/shared/scripts/helpers/SpaceHelper.py b/test/gui/helpers/SpaceHelper.py similarity index 100% rename from test/gui/shared/scripts/helpers/SpaceHelper.py rename to test/gui/helpers/SpaceHelper.py diff --git a/test/gui/shared/scripts/helpers/StacktraceHelper.py b/test/gui/helpers/StacktraceHelper.py similarity index 100% rename from test/gui/shared/scripts/helpers/StacktraceHelper.py rename to test/gui/helpers/StacktraceHelper.py diff --git a/test/gui/shared/scripts/helpers/SyncHelper.py b/test/gui/helpers/SyncHelper.py similarity index 94% rename from test/gui/shared/scripts/helpers/SyncHelper.py rename to test/gui/helpers/SyncHelper.py index 98c751a10f..34b4e7bb46 100644 --- a/test/gui/shared/scripts/helpers/SyncHelper.py +++ b/test/gui/helpers/SyncHelper.py @@ -1,13 +1,12 @@ import os import re -import sys -import test +import time import urllib.request -import squish from helpers.ConfigHelper import get_config, is_linux, is_windows from helpers.FilesHelper import sanitize_path + if is_windows(): from helpers.WinPipeHelper import WinPipeConnect as SocketConnect else: @@ -29,8 +28,7 @@ # see https://kb.froglogic.com/squish/howto/using-external-python-interpreter-squish-6-6/ # if the IDE fails to reference the script, # add the folder in Edit->Preferences->PyDev->Interpreters->Libraries - sys.path.append(custom_lib) - from custom_lib.syncstate import SocketConnect + from helpers.custom_lib.syncstate import SocketConnect # socket messages socket_messages = [] @@ -170,10 +168,12 @@ def generate_sync_pattern_from_messages(messages): sync_messages = filter_sync_messages(messages) for message in sync_messages: - # E.g; from "STATUS:OK:/tmp/client-bdd/Alice/" + # E.g; from; + # Linux: "STATUS:OK:/tmp/client-bdd/Alice/" + # Win: "STATUS:OK:C:\tmp\client-bdd\Alice\" # excludes ":/tmp/client-bdd/Alice/" # adds only "STATUS:OK" to the pattern list - if match := re.search(':(/|[A-Z]{1}:\\\\|[A-Z]{1}:\/).*', message): + if match := re.search(r':(/|[A-Za-z]:[\\/]).*', message): (end, _) = match.span() # shared resources will have status like "STATUS:OK+SWM" status = message[:end].replace('+SWM', '') @@ -222,7 +222,7 @@ def wait_for_resource_to_sync(resource, resource_type='FOLDER', patterns=None): if patterns is None: patterns = get_synced_pattern(resource) - synced = squish.waitFor( + synced = wait_for( lambda: has_sync_pattern(patterns, resource), timeout, ) @@ -232,7 +232,7 @@ def wait_for_resource_to_sync(resource, resource_type='FOLDER', patterns=None): # and pass the step if the last sync status is STATUS:OK status = get_current_sync_status(resource, resource_type) if status.startswith(SYNC_STATUS['OK']): - test.log( + print( '[WARN] Failed to match sync pattern for resource: ' + resource + f'\nBut its last status is "{SYNC_STATUS["OK"]}"' @@ -271,7 +271,7 @@ def has_sync_pattern(patterns, resource=None): if pattern_len == len(actual_pattern) and pattern == actual_pattern: return True # 100 milliseconds polling interval - squish.snooze(0.1) + time.sleep(0.1) return False @@ -299,7 +299,7 @@ def wait_for_resource_to_have_sync_status( if not timeout: timeout = get_config('maxSyncTimeout') * 1000 - result = squish.waitFor( + result = wait_for( lambda: has_sync_status(resource, status), timeout, ) @@ -341,9 +341,7 @@ def perform_file_explorer_vfs_action(resource_path, action): elif action == 'Always keep on this device': make_available_locally(resource_path) else: - raise ValueError( - f'Invalid file explorer action: {action}' - ) + raise ValueError(f'Invalid file explorer action: {action}') def make_online_only(resource_path): @@ -356,3 +354,13 @@ def make_available_locally(resource_path): socket_connect = get_socket_connection() resource_path = resource_path.rstrip('\\').rstrip('/') socket_connect.sendCommand(f'MAKE_AVAILABLE_LOCALLY:{resource_path}\n') + + +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) diff --git a/test/gui/shared/scripts/helpers/UserHelper.py b/test/gui/helpers/UserHelper.py similarity index 100% rename from test/gui/shared/scripts/helpers/UserHelper.py rename to test/gui/helpers/UserHelper.py diff --git a/test/gui/shared/scripts/helpers/VFSFileHelper.py b/test/gui/helpers/VFSFileHelper.py similarity index 100% rename from test/gui/shared/scripts/helpers/VFSFileHelper.py rename to test/gui/helpers/VFSFileHelper.py diff --git a/test/gui/helpers/WebUIHelper.py b/test/gui/helpers/WebUIHelper.py new file mode 100644 index 0000000000..eafbe25383 --- /dev/null +++ b/test/gui/helpers/WebUIHelper.py @@ -0,0 +1,20 @@ +import pyperclip +from playwright.sync_api import sync_playwright + + +def authorize_via_webui(username, password): + url = pyperclip.paste() + with sync_playwright() as pw: + browser = pw.chromium.launch(headless=True) + context = browser.new_context(ignore_https_errors=True) + page = context.new_page() + + page.goto(url) + page.fill('#oc-login-username', username) + page.fill('#oc-login-password', password) + page.click('button :text("Log in")') + page.click('button :text("Allow")') + page.wait_for_selector(':text("Login successful")') + + context.close() + browser.close() diff --git a/test/gui/shared/scripts/helpers/WinPipeHelper.py b/test/gui/helpers/WinPipeHelper.py similarity index 100% rename from test/gui/shared/scripts/helpers/WinPipeHelper.py rename to test/gui/helpers/WinPipeHelper.py diff --git a/test/gui/shared/scripts/helpers/api/http_helper.py b/test/gui/helpers/api/http_helper.py similarity index 100% rename from test/gui/shared/scripts/helpers/api/http_helper.py rename to test/gui/helpers/api/http_helper.py diff --git a/test/gui/shared/scripts/helpers/api/provisioning.py b/test/gui/helpers/api/provisioning.py similarity index 99% rename from test/gui/shared/scripts/helpers/api/provisioning.py rename to test/gui/helpers/api/provisioning.py index 2f78d2df23..133d9db39d 100644 --- a/test/gui/shared/scripts/helpers/api/provisioning.py +++ b/test/gui/helpers/api/provisioning.py @@ -1,9 +1,11 @@ +import json +from PySide6.QtCore import QJsonDocument + +import helpers.api.http_helper as request from helpers.ConfigHelper import get_config from helpers import UserHelper -import helpers.api.http_helper as request from helpers.api.utils import url_join -import json -from PySide6.QtCore import QJsonDocument + created_groups = {} created_users = {} diff --git a/test/gui/shared/scripts/helpers/api/utils.py b/test/gui/helpers/api/utils.py similarity index 100% rename from test/gui/shared/scripts/helpers/api/utils.py rename to test/gui/helpers/api/utils.py diff --git a/test/gui/shared/scripts/helpers/api/webdav_helper.py b/test/gui/helpers/api/webdav_helper.py similarity index 100% rename from test/gui/shared/scripts/helpers/api/webdav_helper.py rename to test/gui/helpers/api/webdav_helper.py diff --git a/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py b/test/gui/pageObjects/AccountConnectionWizard.py similarity index 54% rename from test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py rename to test/gui/pageObjects/AccountConnectionWizard.py index 3395969c9f..fefcc384f7 100644 --- a/test/gui/shared/scripts/pageObjects/AccountConnectionWizard.py +++ b/test/gui/pageObjects/AccountConnectionWizard.py @@ -1,9 +1,6 @@ -import test -import names -import squish import os - -from pageObjects.EnterPassword import EnterPassword +from types import SimpleNamespace +from appium.webdriver.common.appiumby import AppiumBy as By from helpers.WebUIHelper import authorize_via_webui from helpers.ConfigHelper import get_config @@ -13,103 +10,61 @@ set_current_user_sync_path, ) from helpers.SyncHelper import listen_sync_status_for_item +from helpers.SetupClientHelper import app class AccountConnectionWizard: - SERVER_ADDRESS_BOX = { - "container": names.setupWizardWindow_contentWidget_QStackedWidget, - "name": "urlLineEdit", - "type": "QLineEdit", - "visible": 1, - } - NEXT_BUTTON = { - "container": names.settings_dialogStack_QStackedWidget, - "name": "nextButton", - "type": "QPushButton", - "visible": 1, - } - CONFIRM_INSECURE_CONNECTION_BUTTON = { - "text": "Confirm", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - "window": names.insecure_connection_QMessageBox, - } - USERNAME_BOX = { - "container": names.contentWidget_OCC_QmlUtils_OCQuickWidget, - "id": "userNameField", - "type": "TextField", - "visible": True, - } - SELECT_LOCAL_FOLDER = { - "container": names.advancedConfigGroupBox_localDirectoryGroupBox_QGroupBox, - "name": "localDirectoryLineEdit", - "type": "QLineEdit", - "visible": 1, - } - DIRECTORY_NAME_BOX = { - "container": names.advancedConfigGroupBox_localDirectoryGroupBox_QGroupBox, - "name": "chooseLocalDirectoryButton", - "type": "QToolButton", - "visible": 1, - } - CHOOSE_BUTTON = { - "text": "Choose", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - "window": names.qFileDialog_QFileDialog, - } - OAUTH_CREDENTIAL_PAGE = { - "container": names.contentWidget_contentWidget_QStackedWidget, - "type": "OCC::Wizard::OAuthCredentialsSetupWizardPage", - "visible": 1, - } - COPY_URL_TO_CLIPBOARD_BUTTON = { - "container": names.contentWidget_OCC_QmlUtils_OCQuickWidget, - "id": "copyToClipboardButton", - "type": "Button", - "visible": True, - } - CONF_SYNC_MANUALLY_RADIO_BUTTON = { - "container": names.advancedConfigGroupBox_syncModeGroupBox_QGroupBox, - "name": "configureSyncManuallyRadioButton", - "type": "QRadioButton", - "visible": 1, - } - ADVANCED_CONFIGURATION_CHECKBOX = { - "container": names.setupWizardWindow_contentWidget_QStackedWidget, - "name": "advancedConfigGroupBox", - "type": "QGroupBox", - "visible": 1, - } - DIRECTORY_NAME_EDIT_BOX = { - "buddy": names.qFileDialog_fileNameLabel_QLabel, - "name": "fileNameEdit", - "type": "QLineEdit", - "visible": 1, - } - SYNC_EVERYTHING_RADIO_BUTTON = { - "container": names.advancedConfigGroupBox_syncModeGroupBox_QGroupBox, - "name": "syncEverythingRadioButton", - "type": "QRadioButton", - "visible": 1, - } + SERVER_ADDRESS_BOX = SimpleNamespace( + by=By.ACCESSIBILITY_ID, + selector="QApplication.Settings.centralwidget.dialogStack.SetupWizardWidget.contentWidget.ServerUrlSetupWizardPage.urlLineEdit", + ) + NEXT_BUTTON = SimpleNamespace( + by=By.ACCESSIBILITY_ID, + selector="QApplication.Settings.centralwidget.dialogStack.SetupWizardWidget.nextButton", + ) + ACCEPT_CERTIFICATE_YES = SimpleNamespace( + by=By.NAME, + selector="Yes", + ) + SELECT_LOCAL_FOLDER = SimpleNamespace(by=None, selector=None) + DIRECTORY_NAME_BOX = SimpleNamespace( + by=By.ACCESSIBILITY_ID, + selector="QApplication.Settings.centralwidget.dialogStack.SetupWizardWidget.contentWidget.AccountConfiguredWizardPage.advancedConfigGroupBox.advancedConfigGroupBoxContentWidget.localDirectoryGroupBox.chooseLocalDirectoryButton", + ) + CHOOSE_FOLDER_BUTTON = SimpleNamespace(by=By.NAME, selector="Choose") + OAUTH_CREDENTIAL_PAGE = SimpleNamespace(by=None, selector=None) + COPY_URL_TO_CLIPBOARD_BUTTON = SimpleNamespace( + by=By.NAME, + selector="Copy URL", + ) + CONF_SYNC_MANUALLY_RADIO_BUTTON = SimpleNamespace(by=None, selector=None) + ADVANCED_CONFIGURATION_CHECKBOX = SimpleNamespace( + by=By.NAME, + selector="Advanced configuration", + ) + DIRECTORY_NAME_EDIT_BOX = SimpleNamespace( + by=By.ACCESSIBILITY_ID, + selector="QApplication.QFileDialog.fileNameEdit", + ) + SYNC_EVERYTHING_RADIO_BUTTON = SimpleNamespace(by=None, selector=None) @staticmethod def add_server(server_url): - squish.mouseClick( - squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX) - ) - squish.type( - squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX), - server_url, + url_input = app().find_element( + AccountConnectionWizard.SERVER_ADDRESS_BOX.by, + AccountConnectionWizard.SERVER_ADDRESS_BOX.selector, ) + url_input.clear() + url_input.send_keys(get_config("localBackendUrl")) + AccountConnectionWizard.next_step() @staticmethod def accept_certificate(): - squish.clickButton(squish.waitForObject(EnterPassword.ACCEPT_CERTIFICATE_YES)) + app().find_element( + AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.by, + AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.selector, + ).click() @staticmethod def add_user_credentials(username, password): @@ -117,22 +72,22 @@ def add_user_credentials(username, password): @staticmethod def oidc_login(username, password): - AccountConnectionWizard.browser_login(username, password, "oidc") + AccountConnectionWizard.browser_login(username, password) @staticmethod - def browser_login(username, password, login_type=None): - # wait 500ms for copy button to fully load - squish.snooze(1 / 2) - squish.mouseClick( - squish.waitForObject(AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON) - ) - authorize_via_webui(username, password, login_type) + def browser_login(username, password): + app().find_element( + AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON.by, + AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON.selector, + ).click() + authorize_via_webui(username, password) @staticmethod def next_step(): - squish.clickButton( - squish.waitForObjectExists(AccountConnectionWizard.NEXT_BUTTON) - ) + app().find_element( + AccountConnectionWizard.NEXT_BUTTON.by, + AccountConnectionWizard.NEXT_BUTTON.selector, + ).click() @staticmethod def select_sync_folder(user): @@ -140,14 +95,20 @@ def select_sync_folder(user): sync_path = create_user_sync_path(user) AccountConnectionWizard.select_advanced_config() - squish.mouseClick( - squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_BOX) - ) - squish.type( - squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_EDIT_BOX), - sync_path, + app().find_element( + AccountConnectionWizard.DIRECTORY_NAME_BOX.by, + AccountConnectionWizard.DIRECTORY_NAME_BOX.selector, + ).click() + dir_location_input = app().find_element( + AccountConnectionWizard.DIRECTORY_NAME_EDIT_BOX.by, + AccountConnectionWizard.DIRECTORY_NAME_EDIT_BOX.selector, ) - squish.clickButton(squish.waitForObject(AccountConnectionWizard.CHOOSE_BUTTON)) + dir_location_input.clear() + dir_location_input.send_keys(sync_path) + app().find_element( + AccountConnectionWizard.CHOOSE_FOLDER_BUTTON.by, + AccountConnectionWizard.CHOOSE_FOLDER_BUTTON.selector, + ).click() return os.path.join(sync_path, get_config('syncConnectionName')) @staticmethod @@ -205,14 +166,12 @@ def select_manual_sync_folder_option(): ) ) - @staticmethod def select_download_everything_option(): squish.clickButton( squish.waitForObject(AccountConnectionWizard.SYNC_EVERYTHING_RADIO_BUTTON) ) - @staticmethod def is_new_connection_window_visible(): visible = False @@ -235,9 +194,10 @@ def is_credential_window_visible(): @staticmethod def select_advanced_config(): - squish.waitForObject( - AccountConnectionWizard.ADVANCED_CONFIGURATION_CHECKBOX - ).setChecked(True) + app().find_element( + AccountConnectionWizard.ADVANCED_CONFIGURATION_CHECKBOX.by, + AccountConnectionWizard.ADVANCED_CONFIGURATION_CHECKBOX.selector, + ).click() @staticmethod def can_change_local_sync_dir(): @@ -247,7 +207,7 @@ def can_change_local_sync_dir(): squish.clickButton( squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_BOX) ) - squish.waitForObjectExists(AccountConnectionWizard.CHOOSE_BUTTON) + squish.waitForObjectExists(AccountConnectionWizard.CHOOSE_FOLDER_BUTTON) can_change = True except: pass diff --git a/test/gui/shared/scripts/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/AccountSetting.py rename to test/gui/pageObjects/AccountSetting.py diff --git a/test/gui/shared/scripts/pageObjects/Activity.py b/test/gui/pageObjects/Activity.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/Activity.py rename to test/gui/pageObjects/Activity.py diff --git a/test/gui/shared/scripts/pageObjects/EnterPassword.py b/test/gui/pageObjects/EnterPassword.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/EnterPassword.py rename to test/gui/pageObjects/EnterPassword.py diff --git a/test/gui/shared/scripts/pageObjects/PublicLinkDialog.py b/test/gui/pageObjects/PublicLinkDialog.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/PublicLinkDialog.py rename to test/gui/pageObjects/PublicLinkDialog.py diff --git a/test/gui/shared/scripts/pageObjects/Settings.py b/test/gui/pageObjects/Settings.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/Settings.py rename to test/gui/pageObjects/Settings.py diff --git a/test/gui/shared/scripts/pageObjects/SyncConnection.py b/test/gui/pageObjects/SyncConnection.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/SyncConnection.py rename to test/gui/pageObjects/SyncConnection.py diff --git a/test/gui/shared/scripts/pageObjects/SyncConnectionWizard.py b/test/gui/pageObjects/SyncConnectionWizard.py similarity index 100% rename from test/gui/shared/scripts/pageObjects/SyncConnectionWizard.py rename to test/gui/pageObjects/SyncConnectionWizard.py diff --git a/test/gui/shared/scripts/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py similarity index 67% rename from test/gui/shared/scripts/pageObjects/Toolbar.py rename to test/gui/pageObjects/Toolbar.py index be66fa984e..2e495a3da9 100644 --- a/test/gui/shared/scripts/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -1,54 +1,20 @@ -import names -import squish -import object # pylint: disable=redefined-builtin +from types import SimpleNamespace +from urllib.parse import urlparse +from appium.webdriver.common.appiumby import AppiumBy as By from helpers.SetupClientHelper import wait_until_app_killed from helpers.ConfigHelper import get_config +from helpers.SetupClientHelper import app class Toolbar: - TOOLBAR_ROW = { - "container": names.dialogStack_quickWidget_OCC_QmlUtils_OCQuickWidget, - "type": "RowLayout", - "visible": True, - } - ACCOUNT_BUTTON = { - "checkable": False, - "container": names.dialogStack_quickWidget_OCC_QmlUtils_OCQuickWidget, - "type": "AccountButton", - "visible": True, - } - ADD_ACCOUNT_BUTTON = { - "container": names.dialogStack_quickWidget_QQuickWidget, - "id": "addAccountButton", - "type": "AccountButton", - "visible": True, - } - ACTIVITY_BUTTON = { - "container": names.dialogStack_quickWidget_QQuickWidget, - "id": "logButton", - "type": "AccountButton", - "visible": True, - } - SETTINGS_BUTTON = { - "container": names.dialogStack_quickWidget_QQuickWidget, - "id": "settingsButton", - "type": "AccountButton", - "visible": True, - } - QUIT_BUTTON = { - "container": names.dialogStack_quickWidget_QQuickWidget, - "id": "quitButton", - "type": "AccountButton", - "visible": True, - } - CONFIRM_QUIT_BUTTON = { - "text": "Yes", - "type": "QPushButton", - "unnamed": 1, - "visible": 1, - "window": names.quit_OpenCloud_Desktop_QMessageBox, - } + TOOLBAR_ROW = SimpleNamespace(by=None, selector=None) + ACCOUNT_BUTTON = SimpleNamespace(by=None, selector=None) + ADD_ACCOUNT_BUTTON = SimpleNamespace(by=None, selector=None) + ACTIVITY_BUTTON = SimpleNamespace(by=None, selector=None) + SETTINGS_BUTTON = SimpleNamespace(by=None, selector=None) + QUIT_BUTTON = SimpleNamespace(by=None, selector=None) + CONFIRM_QUIT_BUTTON = SimpleNamespace(by=None, selector=None) TOOLBAR_ITEMS = ["Add Account", "Activity", "Settings", "Quit"] @@ -147,11 +113,6 @@ def account_has_focus(display_name): @staticmethod def account_exists(display_name): - account, selector = Toolbar.get_account(display_name) - if ( - account is None - or selector is None - and account["displayname"] != display_name - ): - raise LookupError(f'Account "{display_name}" does not exist') - squish.waitForObject(selector) + server_host = urlparse(get_config('localBackendUrl')).netloc + account_label = f"{display_name}@{server_host}" + app().find_element(By.NAME, account_label) diff --git a/test/gui/requirements.txt b/test/gui/requirements.txt index 7aca78c907..2a440f8e2e 100644 --- a/test/gui/requirements.txt +++ b/test/gui/requirements.txt @@ -1,11 +1,17 @@ requests==2.32.*; python_version >= "3.10" -PyGObject==3.42.*; sys_platform != 'win32' and python_version >= "3.10" +PyGObject==3.42.*; python_version >= "3.10" and sys_platform == 'linux' psutil==5.9.*; python_version >= "3.10" black==24.3.*; python_version >= "3.10" pylint==3.2.*; python_version >= "3.10" -pywin32==305; sys_platform == 'win32' and python_version >= "3.10" +pywin32==305; python_version >= "3.10" and sys_platform == 'win32' pyside6==6.9.*; python_version >= "3.10" pypdf==6.5.*; python_version >= "3.10" python-docx==1.2.*; python_version >= "3.10" python-pptx==1.0.*; python_version >= "3.10" -openpyxl==3.1.*; python_version >= "3.10" \ No newline at end of file +openpyxl==3.1.*; python_version >= "3.10" +pyperclip==1.11.*; python_version >= "3.10" +playwright==1.58.*; python_version >= "3.10" +behave==1.3.*; python_version >= "3.10" +Appium-Python-Client==5.3.*; python_version >= "3.10" +Flask==3.0.*; python_version >= "3.10" and sys_platform == 'linux' +numpy==1.26.*; python_version >= "3.10" and sys_platform == 'linux' \ No newline at end of file diff --git a/test/gui/shared/scripts/helpers/WebUIHelper.py b/test/gui/shared/scripts/helpers/WebUIHelper.py deleted file mode 100644 index 52e2f30105..0000000000 --- a/test/gui/shared/scripts/helpers/WebUIHelper.py +++ /dev/null @@ -1,37 +0,0 @@ -import os -import subprocess -import squish - - -def get_clipboard_text(): - try: - return squish.getClipboardText() - except: - # Retry after 2 seconds - squish.snooze(2) - return squish.getClipboardText() - - -def authorize_via_webui(username, password, login_type='oidc'): - script_path = os.path.dirname(os.path.realpath(__file__)) - - webui_path = os.path.join(script_path, '..', '..', '..', 'webUI') - os.chdir(webui_path) - - envs = { - 'OC_USERNAME': username.strip('"'), - 'OC_PASSWORD': password.strip('"'), - 'OC_AUTH_URL': get_clipboard_text(), - } - proc = subprocess.run( - f"pnpm run {login_type}-login", - capture_output=True, - shell=True, - env={**os.environ, **envs}, - check=False, - ) - if proc.returncode: - if proc.stderr.decode('utf-8'): - raise OSError(proc.stderr.decode('utf-8')) - raise OSError(proc.stdout.decode('utf-8')) - os.chdir(script_path) diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py new file mode 100644 index 0000000000..d424a975ff --- /dev/null +++ b/test/gui/steps/account_context.py @@ -0,0 +1,286 @@ +from behave import given as Given, when as When, then as Then + +from pageObjects.AccountConnectionWizard import AccountConnectionWizard + +from pageObjects.Toolbar import Toolbar + +from helpers.SetupClientHelper import ( + start_client, + substitute_inline_codes, + get_client_details, + get_resource_path, +) + +from helpers.SyncHelper import wait_for_initial_sync_to_complete + + +@Given('the user has started the client') +def step(context): + start_client() + + +@When('the user adds the following user credentials:') +def step(context): + account_details = get_client_details(context) + set_config('syncConnectionName', get_displayname_for_user(account_details['user'])) + AccountConnectionWizard.add_user_credentials( + account_details['user'], account_details['password'] + ) + + +@Then('the account with displayname "{displayname}" should be displayed') +def step(context, displayname): + displayname = substitute_inline_codes(displayname) + Toolbar.account_exists(displayname) + + +@Then('the account with displayname "|any|" 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", + ) + + +@Given('user "|any|" has set up a client with default settings') +def step(context, username): + password = get_password_for_user(username) + setup_client(username) + enter_password = EnterPassword() + enter_password.accept_certificate() + + enter_password.login_after_setup(username, password) + + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', username)) + + +@Given('the user has set up the following accounts with default settings:') +def step(context): + users = [] + for row in context.table: + users.append(row[0]) + sync_paths = generate_account_config(users) + start_client() + # accept certificate for each user + for idx, _ in enumerate(users): + enter_password = EnterPassword(len(users) - idx) + enter_password.accept_certificate() + + for idx, _ in enumerate(sync_paths.values()): + # login from last dialog + account_idx = len(sync_paths) - idx + enter_password = EnterPassword(account_idx) + username = enter_password.get_username() + password = get_password_for_user(username) + listen_sync_status_for_item(sync_paths[username]) + enter_password.login_after_setup(username, password) + # wait for files to sync + wait_for_initial_sync_to_complete(sync_paths[username]) + + +@When('the user starts the client') +def step(context): + start_client() + + +@When('the user opens the add-account dialog') +def step(context): + Toolbar.open_new_account_setup() + + +@When('the user adds the following account:') +def step(context): + data_table = {} + for row in context.table: + if row.headings[0] not in data_table: + data_table[row.headings[0]] = row.headings[1] + data_table[row[0]] = row[1] + account_details = get_client_details(data_table) + AccountConnectionWizard.add_account(account_details) + # # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', account_details['user'])) + + +@Given('the user has entered the following account information:') +def step(context): + account_details = get_client_details(context) + AccountConnectionWizard.add_account_information(account_details) + + +@When('the user "|any|" logs out using the client-UI') +def step(context, _): + AccountSetting.logout() + + +@Then('user "|any|" should be signed out') +def step(context, username): + test.compare( + AccountSetting.is_user_signed_out(), + True, + f'User "{username}" is signed out', + ) + + +@Given('user "|any|" 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') +def step(context, username): + AccountSetting.login() + password = get_password_for_user(username) + enter_password = EnterPassword() + enter_password.relogin(username, password) + + # wait for files to sync + wait_for_initial_sync_to_complete(get_resource_path('/', username)) + + +@When('user "|any|" opens login dialog') +def step(context, _): + AccountSetting.login() + + +@Then('user "|any|" should be connected to the server') +def step(context, _): + AccountSetting.wait_until_account_is_connected() + AccountSetting.wait_until_sync_folder_is_configured() + + +@When('the user removes the connection for user "|any|"') +def step(context, username): + AccountSetting.remove_connection_for_user(username) + + +@Then('connection wizard should be visible') +def step(context): + test.compare( + AccountConnectionWizard.is_new_connection_window_visible(), + True, + 'Connection window is visible', + ) + + +@When('the user accepts the certificate') +def step(context): + AccountConnectionWizard.accept_certificate() + + +@When('the user adds the server "|any|"') +def step(context, server): + server_url = substitute_inline_codes(server) + AccountConnectionWizard.add_server(server_url) + + +@When('the user selects manual sync folder option in advanced section') +def step(context): + AccountConnectionWizard.select_manual_sync_folder_option() + AccountConnectionWizard.next_step() + + +@Then('credentials wizard should be visible') +def step(context): + test.compare( + AccountConnectionWizard.is_credential_window_visible(), + True, + 'Credentials wizard is visible', + ) + + +@When('the user selects download everything option in advanced section') +def step(context): + AccountConnectionWizard.select_download_everything_option() + AccountConnectionWizard.next_step() + + +@When('the user opens the advanced configuration') +def step(context): + AccountConnectionWizard.select_advanced_config() + + +@Then('the user should be able to choose the local download directory') +def step(context): + test.compare(True, AccountConnectionWizard.can_change_local_sync_dir()) + + +@Then('the download everything option should be selected by default for Linux') +def step(context): + if is_linux(): + test.compare( + True, + AccountConnectionWizard.is_sync_everything_option_checked(), + 'Sync everything option is checked', + ) + + +@When(r'^the user presses the "([^"]*)" key(?:s)?', regexp=True) +def step(context, key): + AccountSetting.press_key(key) + + +@Then('the log dialog should be opened') +def step(context): + test.compare(True, AccountSetting.is_log_dialog_visible(), 'Log dialog is opened') + + +@Step('the user cancels the sync connection wizard') +def step(context): + SyncConnectionWizard.cancel_folder_sync_connection_wizard() + + +@When('the user quits the client') +def step(context): + Toolbar.quit_opencloud() + + +@Then('"|any|" account should be opened') +def step(context, displayname): + displayname = substitute_inline_codes(displayname) + if not Toolbar.account_has_focus(displayname): + raise LookupError(f"Account '{displayname}' should be opened, but it is not") + + +@Then( + r'the default local sync path should contain "([^"]*)" in the (configuration|sync connection) wizard', + regexp=True, +) +def step(context, sync_path, wizard): + sync_path = substitute_inline_codes(sync_path) + + actual_sync_path = '' + + if wizard == 'configuration': + actual_sync_path = AccountConnectionWizard.get_local_sync_path() + else: + actual_sync_path = SyncConnectionWizard.get_local_sync_path() + + test.compare( + actual_sync_path, + convert_path_separators_for_os(sync_path), + 'Compare sync path contains the expected path', + ) + + +@Then('the warning "|any|" should appear in the sync connection wizard') +def step(context, warn_message): + actual_message = SyncConnectionWizard.get_warn_label() + test.compare( + True, + warn_message in actual_message, + 'Contains warning message', + ) + + +@Given('the user has removed the connection for user "|any|"') +def step(context, 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/steps/server_context.py b/test/gui/steps/server_context.py new file mode 100644 index 0000000000..f7d85312ac --- /dev/null +++ b/test/gui/steps/server_context.py @@ -0,0 +1,135 @@ +from behave import given as Given + +from helpers.api import provisioning + + +@Given('user "{user}" has been created in the server with default attributes') +def step(context, user): + provisioning.create_user(user) + + +# @Then( +# r'^as "([^"].*)" (?:file|folder) "([^"].*)" should not exist in the server', +# regexp=True, +# ) +# def step(context, user_name, resource_name): +# test.compare( +# webdav.resource_exists(user_name, resource_name), +# False, +# f"Resource '{resource_name}' should not exist, but does", +# ) + + +# @Then( +# r'^as "([^"].*)" (?:file|folder) "([^"].*)" should exist in the server', regexp=True +# ) +# def step(context, user_name, resource_name): +# test.compare( +# webdav.resource_exists(user_name, resource_name), +# True, +# f"Resource '{resource_name}' should exist, but does not", +# ) + + +# @Then('as "|any|" the file "|any|" should have the content "|any|" in the server') +# def step(context, user_name, file_name, content): +# text_content = webdav.get_file_content(user_name, file_name) +# test.compare( +# text_content, +# content, +# f"File '{file_name}' should have content '{content}' but found '{text_content}'", +# ) + + +# @Then( +# r'as user "([^"].*)" folder "([^"].*)" should contain "([^"].*)" items in the server', +# regexp=True, +# ) +# def step(context, user_name, folder_name, items_number): +# total_items = webdav.get_folder_items_count(user_name, folder_name) +# test.compare( +# total_items, items_number, f'Folder should contain {items_number} items' +# ) + + +# @Given('user "|any|" has created folder "|any|" in the server') +# def step(context, user, folder_name): +# webdav.create_folder(user, folder_name) + + +# @Given('user "|any|" has uploaded file with content "|any|" to "|any|" in the server') +# def step(context, user, file_content, file_name): +# webdav.create_file(user, file_name, file_content) + + +# @When('the user clicks on the settings tab') +# def step(context): +# Toolbar.open_settings_tab() + + +# @When('user "|any|" uploads file with content "|any|" to "|any|" in the server') +# def step(context, user, file_content, file_name): +# webdav.create_file(user, file_name, file_content) + + +# @When('user "|any|" deletes the folder "|any|" in the server') +# def step(context, user, folder_name): +# webdav.delete_resource(user, folder_name) + + +# @Given('user "|any|" has uploaded file "|any|" to "|any|" in the server') +# def step(context, user, file_name, destination): +# webdav.upload_file(user, file_name, destination) + + +# @Then( +# 'as "|any|" the content of file "|any|" in the server should match the content of local file "|any|"' +# ) +# def step(context, user_name, server_file_name, local_file_name): +# raw_server_content = webdav.get_file_content(user_name, server_file_name) +# with tempfile.NamedTemporaryFile(suffix=Path(server_file_name).suffix) as tmp_file: +# if isinstance(raw_server_content, str): +# tmp_file.write(raw_server_content.encode('utf-8')) +# else: +# tmp_file.write(raw_server_content) +# server_content = get_document_content(tmp_file.name) +# local_content = get_document_content(get_file_for_upload(local_file_name)) + +# test.compare( +# server_content, +# local_content, +# f"Server file '{server_file_name}' differs from local file '{local_file_name}'", +# ) + + +# @Then( +# r'as "([^"].*)" following files should not exist in the server', +# regexp=True, +# ) +# def step(context, user_name): +# for row in context.table[1:]: +# resource_name = row[0] +# test.compare( +# webdav.resource_exists(user_name, resource_name), +# False, +# f"Resource '{resource_name}' should not exist, but does", +# ) + + +# @Given('user "|any|" has uploaded the following files to the server') +# def step(context, user): +# for row in context.table[1:]: +# file_name = row[0] +# file_content = row[1] +# webdav.create_file(user, file_name, file_content) + + +# @Given('user "|any|" has sent the following resource share invitation:') +# def step(context, user): +# resource_details = {row[0]: row[1] for row in context.table} +# webdav.send_resource_share_invitation( +# user, +# resource_details['resource'], +# resource_details['sharee'], +# resource_details['permissionsRole'], +# ) diff --git a/test/gui/tst_addAccount/test.py b/test/gui/tst_addAccount/test.py deleted file mode 100644 index 83b0a5275a..0000000000 --- a/test/gui/tst_addAccount/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')