diff --git a/test/gui/environment.py b/test/gui/environment.py index cf062d301..d1b936be1 100644 --- a/test/gui/environment.py +++ b/test/gui/environment.py @@ -12,6 +12,7 @@ from helpers.ConfigHelper import get_config from helpers.FilesHelper import prefix_path_namespace, cleanup_created_paths from helpers.AppHelper import close_and_kill_app +from helpers.SyncHelper import clear_socket_messages from step_types.types import * # register all step types @@ -93,3 +94,4 @@ def after_scenario(context, scenario): append_scenario_to_app_log(scenario) store_app_log() cleanup_app_log() + clear_socket_messages() diff --git a/test/gui/features/activity/activity.feature b/test/gui/features/activity/activity.feature index 8b3254426..7f9db44c5 100644 --- a/test/gui/features/activity/activity.feature +++ b/test/gui/features/activity/activity.feature @@ -3,11 +3,12 @@ Feature: filter activity for user I want to filter activity So that I can view activity of specific user - @smoke + @smoke @skip Scenario: filter synced activities 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 user "Alice" has created folder "simple-folder" in the server + And user "Brian" has created folder "brian-folder" in the server And the user has set up the following accounts with default settings: | users | | Alice | @@ -18,6 +19,9 @@ Feature: filter activity for user Then the following activities should be displayed in synced table | resource | action | account | | simple-folder | Downloaded | Alice Hansen@%local_server_hostname% | + But the following activities should not be displayed in synced table + | resource | action | account | + | brian-folder | Downloaded | Brian Murphy@%local_server_hostname% | @skipOnWindows Scenario: filter not synced activities (Linux only) diff --git a/test/gui/features/delete-files-folders/delete.feature b/test/gui/features/delete-files-folders/delete.feature index 5e70c2629..8136293ce 100644 --- a/test/gui/features/delete-files-folders/delete.feature +++ b/test/gui/features/delete-files-folders/delete.feature @@ -11,7 +11,7 @@ Feature: deleting files and folders Given user "Alice" has uploaded file with content "openCloud test text file 0" to "" in the server And user "Alice" has set up a client with default settings When the user deletes the file "" - And the user waits for the files to sync + And the user waits for file "" to be synced Then as "Alice" file "" should not exist in the server Examples: | fileName | @@ -24,7 +24,7 @@ Feature: deleting files and folders Given user "Alice" has created folder "" in the server And user "Alice" has set up a client with default settings When the user deletes the folder "" - And the user waits for the files to sync + And the user waits for folder "" to be synced Then as "Alice" file "" should not exist in the server Examples: | folderName | diff --git a/test/gui/features/spaces/spaces.feature b/test/gui/features/spaces/spaces.feature index c8cfa8d97..7c155b3d8 100644 --- a/test/gui/features/spaces/spaces.feature +++ b/test/gui/features/spaces/spaces.feature @@ -44,7 +44,8 @@ Feature: Project spaces test content """ And user "Alice" creates a folder "localFolder" inside the sync folder - And the user waits for the files to sync + And the user waits for file "localFile.txt" to be synced + And the user waits for folder "localFolder" to be synced Then as "Alice" the file "localFile.txt" in the space "Project101" should have content "test content" in the server And as "Alice" the space "Project101" should have folder "localFolder" in the server diff --git a/test/gui/features/sync-resources/syncResources.feature b/test/gui/features/sync-resources/syncResources.feature index 97d0480b2..8f96fd6f1 100644 --- a/test/gui/features/sync-resources/syncResources.feature +++ b/test/gui/features/sync-resources/syncResources.feature @@ -109,9 +109,6 @@ Feature: Syncing files Then the file "simple-folder/lorem.txt" should exist on the file system And the file "large-folder/lorem.txt" should not exist on the file system And as "Alice" file "simple-folder/localFile.txt" should exist in the server - When the user deletes the folder "simple-folder" - And the user waits for the files to sync - Then as "Alice" folder "simple-folder" should not exist in the server @issue-9733 @skipOnWindows Scenario: sort folders list by name and size @@ -223,7 +220,7 @@ Feature: Syncing files """ test content """ - And the user waits for the files to sync + And the user waits for folder "parent/subfolder5/test.txt" to be synced Then as "Alice" folder "parent/subfolderEmpty1" should exist in the server And as "Alice" folder "parent/subfolderEmpty2" should exist in the server And as "Alice" folder "parent/subfolderEmpty3" should exist in the server @@ -580,10 +577,11 @@ Feature: Syncing files When the user unselects the following folders to sync in "Choose what to sync" window: | folder | | test-folder/sub-folder2 | - And the user waits for the files to sync + And the user waits for folder "test-folder/sub-folder2" to be synced Then the folder "test-folder/sub-folder1" should exist on the file system - But the folder "test-folder/sub-folder2" should not exist on the file system + And the folder "test-folder/sub-folder2" should not exist on the file system When user "Alice" uploads file with content "some content" to "test-folder/sub-folder2/lorem.txt" in the server + And the user force syncs the files And the user waits for the files to sync Then the file "test-folder/sub-folder2/lorem.txt" should not exist on the file system diff --git a/test/gui/helpers/AppHelper.py b/test/gui/helpers/AppHelper.py index 2bdba5213..d6474890c 100644 --- a/test/gui/helpers/AppHelper.py +++ b/test/gui/helpers/AppHelper.py @@ -1,7 +1,10 @@ import pyautogui import psutil +import threading from appium.webdriver import Remote, WebElement from appium.options.common.base import AppiumOptions +from appium.webdriver.common.appiumby import AppiumBy as By +from selenium.common.exceptions import WebDriverException, NoSuchElementException from helpers.ConfigHelper import get_config, get_app_env from helpers.ElementHelper import get_element_center_xy @@ -10,6 +13,11 @@ def native_click(self, **kwargs): x, y = get_element_center_xy(self) + win_x, win_y = get_window_location() + if x < win_x: + x = x + win_x + if y < win_y: + y = y + win_y pyautogui.click(x, y, **kwargs) @@ -22,10 +30,36 @@ def native_send_keys(self, key): pyautogui.press(get_key(key)) +def find_element(self, by, selector): + """ + Returns a visible element. + Throws if no elements are found or if multiple visible elements are found. + """ + elements = self.find_elements(by, selector) + elements_count = len(elements) + if elements_count > 1: + visible_elements = [el for el in elements if el.is_displayed()] + if len(visible_elements) == 1: + return visible_elements.pop() + raise WebDriverException( + f'Found {elements_count} elements using "{by}={selector}"' + ) + if elements_count == 0: + raise NoSuchElementException(f'No element found for "{by}={selector}"') + return elements[0] + + +def pause(self): + threading.Event().wait() + + # bind custom element methods +Remote.find_element = find_element +Remote.pause = pause WebElement.native_click = native_click WebElement.native_double_click = native_double_click WebElement.native_send_keys = native_send_keys +WebElement.find_element = find_element app_driver = None @@ -69,3 +103,12 @@ def close_and_kill_app(): # Reset driver for reuse app_driver = None + + +def get_window_location(): + window = ( + app() + .find_element(By.XPATH, "//*[contains(@name,'OpenCloud Desktop')]") + .location + ) + return window['x'], window['y'] diff --git a/test/gui/helpers/SyncHelper.py b/test/gui/helpers/SyncHelper.py index e3d52c584..09796aea9 100644 --- a/test/gui/helpers/SyncHelper.py +++ b/test/gui/helpers/SyncHelper.py @@ -5,6 +5,7 @@ from selenium.webdriver.support.ui import WebDriverWait from selenium.common.exceptions import TimeoutException +from pageObjects.SyncConnection import SyncConnection from helpers.ConfigHelper import get_config, is_linux, is_windows from helpers.FilesHelper import sanitize_path @@ -74,36 +75,51 @@ SYNC_STATUS['UPDATE'], ], # when syncing empty account (hidden files are ignored) - [SYNC_STATUS['UPDATE'], SYNC_STATUS['OK']], - [SYNC_STATUS['UPDATE'], SYNC_STATUS['OKAL']], + # [SYNC_STATUS['UPDATE'], SYNC_STATUS['OK']], + # [SYNC_STATUS['UPDATE'], SYNC_STATUS['OKAL']], # when syncing an account that has some files/folders - [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], - ], - 'root_synced': [ + # [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], + # initial root sync [ - SYNC_STATUS['SYNC'], - SYNC_STATUS['OK'], SYNC_STATUS['OK'], SYNC_STATUS['OK'], SYNC_STATUS['UPDATE'], ], + ], + 'root_synced': [ [ - SYNC_STATUS['SYNC'], - SYNC_STATUS['UPDATE'], - SYNC_STATUS['OK'], - SYNC_STATUS['OK'], - SYNC_STATUS['OK'], - SYNC_STATUS['UPDATE'], - ], - # used for local resource creation and deletion - [ - SYNC_STATUS['OKAL'], SYNC_STATUS['OK'], SYNC_STATUS['OK'], SYNC_STATUS['UPDATE'], ], + # [ + # SYNC_STATUS['SYNC'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['UPDATE'], + # ], + # [ + # SYNC_STATUS['SYNC'], + # SYNC_STATUS['UPDATE'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['UPDATE'], + # ], + # # used for local resource creation and deletion + # [ + # SYNC_STATUS['OKAL'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['OK'], + # SYNC_STATUS['UPDATE'], + # ], + ], + 'single_synced': [ + [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], + # file/folder deletion + [SYNC_STATUS['SYNC'], SYNC_STATUS['NOP']], ], - 'single_synced': [SYNC_STATUS['SYNC'], SYNC_STATUS['OK']], 'error': [SYNC_STATUS['ERROR']], } @@ -226,20 +242,45 @@ def get_current_sync_status(resource, resource_type): return messages[-1] -def wait_for_resource_to_sync(resource, resource_type='FOLDER', patterns=None): +def wait_for_resource_to_sync( + resource, resource_type='FOLDER', patterns=None, force_sync=False +): listen_sync_status_for_item(resource, resource_type) + initial_timeout = 0 timeout = get_config('maxSyncTimeout') * 1000 if patterns is None: patterns = get_synced_pattern(resource) + if force_sync: + initial_timeout = 5000 + # first try with 5 seconds timeout + synced = wait_for( + lambda: has_sync_pattern(patterns, resource), + initial_timeout, + ) + if not synced: + # trigger force sync if the current status is OK + status = get_current_sync_status(resource, resource_type) + if status.startswith(SYNC_STATUS['OK']): + print('[WARN] Retrying sync pattern check with force sync') + SyncConnection.force_sync() + else: + clear_socket_messages(resource) + return + synced = wait_for( lambda: has_sync_pattern(patterns, resource), - timeout, + timeout - initial_timeout, ) + + messages = read_and_update_socket_messages() + messages = filter_messages_for_item(messages, resource) clear_socket_messages(resource) - if not synced: + if synced: + return + elif not force_sync: # if the sync pattern doesn't match then check the last sync status # and pass the step if the last sync status is STATUS:OK status = get_current_sync_status(resource, resource_type) @@ -251,11 +292,11 @@ def wait_for_resource_to_sync(resource, resource_type='FOLDER', patterns=None): + '. So passing the step.' ) return - raise TimeoutError( - 'Timeout while waiting for sync to complete for ' - + str(timeout) - + ' milliseconds' - ) + raise TimeoutError( + 'Timeout while waiting for sync to complete for ' + + str(timeout) + + ' milliseconds' + ) def wait_for_initial_sync_to_complete(path): @@ -263,6 +304,7 @@ def wait_for_initial_sync_to_complete(path): path, 'FOLDER', get_initial_sync_patterns(), + True, ) @@ -281,6 +323,7 @@ def has_sync_pattern(patterns, resource=None): if len(actual_pattern) < pattern_len: break if pattern_len == len(actual_pattern) and pattern == actual_pattern: + print("MATCHED SYNC PATTERN:", pattern) return True # 100 milliseconds polling interval time.sleep(0.1) diff --git a/test/gui/pageObjects/AccountConnectionWizard.py b/test/gui/pageObjects/AccountConnectionWizard.py index bdbc3dcad..bef9c4e35 100644 --- a/test/gui/pageObjects/AccountConnectionWizard.py +++ b/test/gui/pageObjects/AccountConnectionWizard.py @@ -63,10 +63,13 @@ def add_server(server_url): @staticmethod def accept_certificate(): - app().find_element( + buttons = app().find_elements( AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.by, AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.selector, - ).click() + ) + # click the last button + last_button = buttons.pop() + last_button.click() @staticmethod def add_user_credentials(username, password): diff --git a/test/gui/pageObjects/AccountSetting.py b/test/gui/pageObjects/AccountSetting.py index 50fca8a71..7733019a3 100644 --- a/test/gui/pageObjects/AccountSetting.py +++ b/test/gui/pageObjects/AccountSetting.py @@ -66,15 +66,12 @@ def login(): @staticmethod def get_account_connection_label(): - label = ( - app() - .find_element( - AccountSetting.ACCOUNT_CONNECTION_LABEL.by, - AccountSetting.ACCOUNT_CONNECTION_LABEL.selector, - ) - .text + labels = app().find_elements( + AccountSetting.ACCOUNT_CONNECTION_LABEL.by, + AccountSetting.ACCOUNT_CONNECTION_LABEL.selector, ) - return label + # first label is the sync status label + return labels[0].text @staticmethod def is_connecting(): diff --git a/test/gui/pageObjects/Activity.py b/test/gui/pageObjects/Activity.py index 54bfb832f..e4f2032bb 100644 --- a/test/gui/pageObjects/Activity.py +++ b/test/gui/pageObjects/Activity.py @@ -1,6 +1,7 @@ # from objectmaphelper import RegularExpression from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By +from selenium.common.exceptions import NoSuchElementException from helpers.FilesHelper import build_conflicted_regex from helpers.ConfigHelper import get_config @@ -9,11 +10,14 @@ class Activity: TAB_CONTAINER = SimpleNamespace(by=None, selector=None) - SUBTAB_CONTAINER = SimpleNamespace(by=By.XPATH, selector="//*[@name='{tab_name}']") + SUBTAB_CONTAINER = SimpleNamespace( + by=By.CLASS_NAME, selector="[page tab | {tab_name}]" + ) NOT_SYNCED_TABLE = SimpleNamespace(by=None, selector=None) LOCAL_ACTIVITY_FILTER_BUTTON = SimpleNamespace(by=By.NAME, selector="Filter") - SYNCED_ACTIVITY_FILTER_OPTION_SELECTOR = SimpleNamespace(by=By.NAME, selector=None) - SYNCED_ACTIVITY_TABLE = SimpleNamespace(by=None, selector=None) + LOCAL_ACTIVITY_FILTER_OPTION_SELECTOR = SimpleNamespace(by=By.NAME, selector=None) + LOCAL_ACTIVITY_TABLE = SimpleNamespace(by=By.NAME, selector="Local activity table") + FILTER_BUTTON_SELECTED_STATE = SimpleNamespace(by=By.NAME, selector="1 Filter") NOT_SYNCED_FILTER_BUTTON = SimpleNamespace(by=None, selector=None) NOT_SYNCED_FILTER_OPTION_SELECTOR = SimpleNamespace(by=None, selector=None) SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR = SimpleNamespace(by=None, selector=None) @@ -82,7 +86,7 @@ def has_sync_status(filename, status): try: app().find_element(Activity.SYNCED_ACTIVITY_STATUS.by, status) return True - except: + except NoSuchElementException: return False @staticmethod @@ -91,15 +95,25 @@ def select_synced_filter(sync_filter): Activity.LOCAL_ACTIVITY_FILTER_BUTTON.by, Activity.LOCAL_ACTIVITY_FILTER_BUTTON.selector, ).click() - app().find_element( - Activity.SYNCED_ACTIVITY_FILTER_OPTION_SELECTOR.by, sync_filter + container = app().find_element( + Activity.LOCAL_ACTIVITY_TABLE.by, Activity.LOCAL_ACTIVITY_TABLE.selector ) + # NOTE: clicking filter options does not work + container.find_element( + Activity.LOCAL_ACTIVITY_FILTER_OPTION_SELECTOR.by, sync_filter + ).click() + # FIXME: enable the check below once the filter options are clickable + # confirm filter is applied + # app().find_element( + # Activity.FILTER_BUTTON_SELECTED_STATE.by, + # Activity.FILTER_BUTTON_SELECTED_STATE.selector, + # ) @staticmethod def get_synced_file_selector(resource): return { "column": Activity.get_synced_table_column_number_by_name("File"), - "container": Activity.SYNCED_ACTIVITY_TABLE, + "container": Activity.LOCAL_ACTIVITY_TABLE, "text": resource, "type": "QModelIndex", } @@ -116,10 +130,34 @@ def get_synced_table_column_number_by_name(column_name): )["section"] @staticmethod - def check_synced_table(resource, action, account): - app().find_element(By.NAME, resource) - app().find_element(By.NAME, action) - app().find_element(By.NAME, account) + def has_activity(resource, action, account): + try: + row = app().find_element(By.NAME, resource) + row_y = row.rect['y'] + # check other properties using current row position + action_cells = app().find_elements(By.NAME, action) + found_action_cell = False + for action_el in action_cells: + if action_el.rect['y'] == row_y: + found_action_cell = True + break + if not found_action_cell: + raise NoSuchElementException( + f'Activity for "{resource}" does not have "{action}" action' + ) + account_cells = app().find_elements(By.NAME, account) + found_account_cell = False + for account_el in account_cells: + if account_el.rect['y'] == row_y: + found_account_cell = True + break + if not found_account_cell: + raise NoSuchElementException( + f'Activity for "{resource}" does not have "{account}" account label' + ) + return True + except: + return False @staticmethod def select_not_synced_filter(filter_option): diff --git a/test/gui/pageObjects/SyncConnection.py b/test/gui/pageObjects/SyncConnection.py index c4bf004b9..1693c3bd2 100644 --- a/test/gui/pageObjects/SyncConnection.py +++ b/test/gui/pageObjects/SyncConnection.py @@ -1,6 +1,6 @@ from types import SimpleNamespace from appium.webdriver.common.appiumby import AppiumBy as By -from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import NoSuchElementException from helpers.ConfigHelper import get_config from helpers.AppHelper import app @@ -93,7 +93,7 @@ def has_sync_connection(sync_folder): ), ) return True - except: + except NoSuchElementException: return False @staticmethod diff --git a/test/gui/pageObjects/SyncConnectionWizard.py b/test/gui/pageObjects/SyncConnectionWizard.py index 9c497dc6b..80c53dd98 100644 --- a/test/gui/pageObjects/SyncConnectionWizard.py +++ b/test/gui/pageObjects/SyncConnectionWizard.py @@ -71,7 +71,10 @@ def select_remote_destination_folder(folder): @staticmethod def deselect_all_remote_folders(): - element = app().find_element(By.NAME, "Add Space") + element = app().find_element( + SyncConnectionWizard.ADD_SYNC_CONNECTION_BUTTON.by, + SyncConnectionWizard.ADD_SYNC_CONNECTION_BUTTON.selector, + ) element.send_keys(Keys.ARROW_DOWN) element.native_send_keys(Keys.SPACE) # uncheck the root folder @@ -214,6 +217,14 @@ def select_or_unselect_folders_to_sync(folders, select=True): if p_element.get_attribute("checked") == 'true': parent_element = p_element parent_element.native_double_click() # expand the folder + # retry once if the folder is not expanded + if parent_element.is_selected(): + print('[WARN] Folder was not expanded, retrying with space key') + # expand using space key + parent_element.native_click() + parent_element.native_send_keys(Keys.SPACE) + if parent_element.is_selected(): + raise AssertionError(f'Failed to expand folder: {parent}') folder_element = app().find_element(By.NAME, target_folder) is_checked = folder_element.get_attribute("checked") diff --git a/test/gui/pageObjects/Toolbar.py b/test/gui/pageObjects/Toolbar.py index 243dd7522..24967134c 100644 --- a/test/gui/pageObjects/Toolbar.py +++ b/test/gui/pageObjects/Toolbar.py @@ -2,26 +2,48 @@ from urllib.parse import urlparse from appium.webdriver.common.appiumby import AppiumBy as By from selenium.webdriver.common.keys import Keys +from selenium.common.exceptions import NoSuchElementException -from helpers.AppHelper import app +from helpers.AppHelper import app, get_window_location from helpers.ConfigHelper import get_config from helpers.UserHelper import get_displayname_for_user +from helpers.SyncHelper import wait_for class Toolbar: TOOLBAR_ROW = SimpleNamespace(by=None, selector=None) - 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") + NAVIGATION_BAR = SimpleNamespace( + by=By.XPATH, selector="//*[@name='Navigation bar']/.." + ) + ACCOUNT_BUTTON = 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) - QUIT_BUTTON = SimpleNamespace(by=By.NAME, selector="Quit") + QUIT_BUTTON = SimpleNamespace(by=By.CLASS_NAME, selector="[push button | Quit]") CONFIRM_QUIT_BUTTON = SimpleNamespace( - by=By.ACCESSIBILITY_ID, - selector="QApplication.QMessageBox.qt_msgbox_buttonbox.QPushButton", + by=By.NAME, + selector="Yes", ) TOOLBAR_ITEMS = ["Add Account", "Activity", "Settings", "Quit"] + @staticmethod + def wait_toolbar_enabled(): + toolbar = app().find_element( + Toolbar.NAVIGATION_BAR.by, Toolbar.NAVIGATION_BAR.selector + ) + timeout = get_config('maxSyncTimeout') * 1000 + enabled = wait_for( + lambda: toolbar.is_enabled(), + timeout, + ) + if not enabled: + raise AssertionError(f"Toolbar is not enabled within {timeout} ms") + @staticmethod def get_item_selector(item_name): return { @@ -41,9 +63,17 @@ def has_item(item_name, timeout=get_config("minSyncTimeout") * 1000): @staticmethod def open_activity(): - app().find_element( + tab = app().find_element( Toolbar.ACTIVITY_BUTTON.by, Toolbar.ACTIVITY_BUTTON.selector - ).click() + ) + # 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("Activity tab is not active") @staticmethod def open_new_account_setup(): @@ -60,8 +90,10 @@ def open_account(username): # 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) + account_tab.native_click() + # confirm account is active + if account_tab.get_attribute("checked") != "true": + raise AssertionError(f"Account is not active: {username}") @staticmethod def get_displayed_account_text(displayname, host): @@ -113,8 +145,11 @@ def get_account(username): account_label = f"{display_name}@{server_host}" account = None try: - account = app().find_element(By.NAME, account_label) - except: + account = app().find_element( + Toolbar.ACCOUNT_BUTTON.by, + Toolbar.ACCOUNT_BUTTON.selector.format(text=account_label), + ) + except NoSuchElementException: pass return account diff --git a/test/gui/steps/account_context.py b/test/gui/steps/account_context.py index a3c338da5..8b4d27a14 100644 --- a/test/gui/steps/account_context.py +++ b/test/gui/steps/account_context.py @@ -63,6 +63,7 @@ def step(context, username): # wait for files to sync wait_for_initial_sync_to_complete(get_resource_path('/', username)) + Toolbar.wait_toolbar_enabled() @Given('the user has set up the following accounts with default settings:') @@ -86,6 +87,7 @@ def step(context): enter_password.login_after_setup(username, password) # wait for files to sync wait_for_initial_sync_to_complete(sync_paths[username]) + Toolbar.wait_toolbar_enabled() @When('the user starts the client') @@ -105,6 +107,7 @@ def step(context): AccountConnectionWizard.add_account(account_details) # # wait for files to sync wait_for_initial_sync_to_complete(get_resource_path('/', account_details['user'])) + Toolbar.wait_toolbar_enabled() @Given('the user has entered the following account information:') @@ -143,6 +146,7 @@ def step(context, username): # wait for files to sync wait_for_initial_sync_to_complete(get_resource_path('/', username)) + Toolbar.wait_toolbar_enabled() @When('user "|any|" opens login dialog') diff --git a/test/gui/steps/file_context.py b/test/gui/steps/file_context.py index 4cb560503..e99f38130 100644 --- a/test/gui/steps/file_context.py +++ b/test/gui/steps/file_context.py @@ -137,6 +137,8 @@ def move_resource(username, resource_type, source, destination, is_temp_folder=F def deleteResource(resource, resource_type): + wait_for_client_to_be_ready() + listen_sync_status_for_item(resource, resource_type) resource_path = sanitize_path(get_resource_path(resource)) if resource_type == 'file': os.remove(resource_path) @@ -175,7 +177,9 @@ def step(context, resource_type, resource_name, destination_dir): copy_resource(resource_type, resource_name, destination_dir, False) -@When('the user copies {resource_type:ResourceType} "{resource_name}" into the same directory') +@When( + 'the user copies {resource_type:ResourceType} "{resource_name}" into the same directory' +) def step(context, resource_type, resource_name): copy_resource(resource_type, resource_name, resource_name, False) @@ -187,17 +191,19 @@ def step(context, source, destination): rename_file_folder(source, destination) -@Then('the file "{file_path}" should exist on the file system with the following content') +@Then( + 'the file "{file_path}" should exist on the file system with the following content' +) def step(context, file_path): expected = context.text file_path = get_resource_path(file_path) with open(file_path, 'r', encoding='utf-8') as f: contents = f.read() with ensure( - '{0} expected to exist with content "{1}" but has content "{2}"', - file_path, - expected, - contents, + '{0} expected to exist with content "{1}" but has content "{2}"', + file_path, + expected, + contents, ): contents.should.equal(expected) @@ -281,18 +287,14 @@ def step(context, user, resource, content): @When('the user deletes the {resource_type:ResourceType} "{resource_name}"') def step(context, resource_type, resource_name): - wait_for_client_to_be_ready() - print(f"Deleting {resource_type} '{resource_name}'") deleteResource(resource_name, resource_type) @When('user "|any|" creates the following files inside the sync folder:') def step(context, username): - wait_for_client_to_be_ready() - for row in context.table[1:]: file = get_resource_path(row[0], username) - write_file(file, '') + wait_and_write_file(file, '') @Given('the user has created a folder "|any|" in temp folder') diff --git a/test/gui/steps/spaces_context.py b/test/gui/steps/spaces_context.py index d5d8c2e98..5d7861b81 100644 --- a/test/gui/steps/spaces_context.py +++ b/test/gui/steps/spaces_context.py @@ -1,6 +1,7 @@ from sure import ensure from pageObjects.EnterPassword import EnterPassword +from pageObjects.Toolbar import Toolbar from helpers.UserHelper import get_password_for_user from helpers.SetupClientHelper import setup_client, get_resource_path from helpers.SyncHelper import wait_for_initial_sync_to_complete @@ -49,6 +50,7 @@ def step(context, user, space_name): enter_password.login_after_setup(user, password) # wait for files to sync wait_for_initial_sync_to_complete(get_resource_path('/', user, space_name)) + Toolbar.wait_toolbar_enabled() @Then( diff --git a/test/gui/steps/sync_context.py b/test/gui/steps/sync_context.py index 92f4b21bb..87c9f6f3c 100644 --- a/test/gui/steps/sync_context.py +++ b/test/gui/steps/sync_context.py @@ -18,6 +18,7 @@ get_resource_path, ) from helpers.FilesHelper import convert_path_separators_for_os +from helpers.TableParser import table_hashes @Given('the user has paused the file sync') @@ -37,13 +38,14 @@ def step(context): @When('the user waits for the files to sync') def step(context): - wait_for_resource_to_sync(get_resource_path('/')) + wait_for_resource_to_sync(get_resource_path('/'), force_sync=True) @When('the user waits for {resource_type:ResourceType} "{resource}" to be synced') def step(context, resource_type, resource): resource = get_resource_path(resource) wait_for_resource_to_sync(convert_path_separators_for_os(resource), resource_type) + Toolbar.wait_toolbar_enabled() @When(r'the user waits for (file|folder) "([^"]*)" to have sync error', regexp=True) @@ -264,11 +266,36 @@ def step(context, account): @Then('the following activities should be displayed in synced table') def step(context): - for row in context.table: - resource = row[0] - action = row[1] - account = substitute_inline_codes(row[2]) - Activity.check_synced_table(resource, action, account) + activities = table_hashes(context.table) + for activity in activities: + activity["account"] = substitute_inline_codes(activity["account"]) + has_activity = Activity.has_activity( + activity["resource"], activity["action"], activity["account"] + ) + with ensure( + 'Activity should exist: {0} | {1} | {2}', + activity["resource"], + activity["action"], + activity["account"], + ): + has_activity.should.be.true + + +@Then('the following activities should not be displayed in synced table') +def step(context): + activities = table_hashes(context.table) + for activity in activities: + activity["account"] = substitute_inline_codes(activity["account"]) + has_activity = Activity.has_activity( + activity["resource"], activity["action"], activity["account"] + ) + with ensure( + 'Activity should not exist: {0} | {1} | {2}', + activity["resource"], + activity["action"], + activity["account"], + ): + has_activity.should.be.false @Then(