Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,236 @@
import os
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
from helpers.SetupClientHelper import (
create_user_sync_path,
get_temp_resource_path,
set_current_user_sync_path,
)
from helpers.SyncHelper import listen_sync_status_for_item
from helpers.AppHelper import app
class AccountConnectionWizard:
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=By.NAME, selector="Configure synchronization manually"
)
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):
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():
buttons = app().find_elements(
AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.by,
AccountConnectionWizard.ACCEPT_CERTIFICATE_YES.selector,
)
# click the last button
last_button = buttons.pop()
last_button.click()
@staticmethod
def add_user_credentials(username, password):
AccountConnectionWizard.oidc_login(username, password)
@staticmethod
def oidc_login(username, password):
AccountConnectionWizard.browser_login(username, password)
@staticmethod
def copy_login_url():
app().find_element(
AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON.by,
AccountConnectionWizard.COPY_URL_TO_CLIPBOARD_BUTTON.selector,
).click()
@staticmethod
def browser_login(username, password):
AccountConnectionWizard.copy_login_url()
authorize_via_webui(username, password)
@staticmethod
def next_step():
app().find_element(
AccountConnectionWizard.NEXT_BUTTON.by,
AccountConnectionWizard.NEXT_BUTTON.selector,
).click()
@staticmethod
def select_sync_folder(user):
# create sync folder for user
sync_path = create_user_sync_path(user)
AccountConnectionWizard.select_advanced_config()
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,
)
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
def set_temp_folder_as_sync_folder(folder_name):
sync_path = get_temp_resource_path(folder_name)
# clear the current path
squish.mouseClick(
squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER)
)
squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER).setText("")
squish.type(
squish.waitForObject(AccountConnectionWizard.SELECT_LOCAL_FOLDER),
sync_path,
)
set_current_user_sync_path(sync_path)
return sync_path
@staticmethod
def add_account(account_details):
AccountConnectionWizard.add_account_information(account_details)
AccountConnectionWizard.next_step()
@staticmethod
def add_account_information(account_details):
if account_details["server"]:
AccountConnectionWizard.add_server(account_details["server"])
AccountConnectionWizard.accept_certificate()
if account_details["user"]:
AccountConnectionWizard.add_user_credentials(
account_details["user"],
account_details["password"],
)
sync_path = ""
if account_details["sync_folder"]:
AccountConnectionWizard.select_advanced_config()
sync_path = AccountConnectionWizard.set_temp_folder_as_sync_folder(
account_details["sync_folder"]
)
elif account_details["user"]:
sync_path = AccountConnectionWizard.select_sync_folder(
account_details["user"]
)
if sync_path:
# listen for sync status
listen_sync_status_for_item(sync_path)
@staticmethod
def select_manual_sync_folder_option():
app().find_element(
AccountConnectionWizard.CONF_SYNC_MANUALLY_RADIO_BUTTON.by,
AccountConnectionWizard.CONF_SYNC_MANUALLY_RADIO_BUTTON.selector,
).click()
@staticmethod
def select_download_everything_option():
squish.clickButton(
squish.waitForObject(AccountConnectionWizard.SYNC_EVERYTHING_RADIO_BUTTON)
)
@staticmethod
def is_new_connection_window_visible():
visible = False
try:
squish.waitForObject(AccountConnectionWizard.SERVER_ADDRESS_BOX)
visible = True
except:
pass
return visible
@staticmethod
def is_credential_window_visible():
visible = False
try:
squish.waitForObject(AccountConnectionWizard.OAUTH_CREDENTIAL_PAGE)
visible = True
except:
pass
return visible
@staticmethod
def select_advanced_config():
app().find_element(
AccountConnectionWizard.ADVANCED_CONFIGURATION_CHECKBOX.by,
AccountConnectionWizard.ADVANCED_CONFIGURATION_CHECKBOX.selector,
).click()
@staticmethod
def can_change_local_sync_dir():
can_change = False
try:
squish.waitForObjectExists(AccountConnectionWizard.SELECT_LOCAL_FOLDER)
squish.clickButton(
squish.waitForObject(AccountConnectionWizard.DIRECTORY_NAME_BOX)
)
squish.waitForObjectExists(AccountConnectionWizard.CHOOSE_FOLDER_BUTTON)
can_change = True
except:
pass
return can_change
@staticmethod
def is_sync_everything_option_checked():
return squish.waitForObjectExists(
AccountConnectionWizard.SYNC_EVERYTHING_RADIO_BUTTON
).checked
@staticmethod
def get_local_sync_path():
return str(
squish.waitForObjectExists(
AccountConnectionWizard.SELECT_LOCAL_FOLDER
).displayText
)
@@ -0,0 +1,172 @@
from types import SimpleNamespace
from appium.webdriver.common.appiumby import AppiumBy as By
from pageObjects.Toolbar import Toolbar
from helpers.UserHelper import get_displayname_for_user
from helpers.SetupClientHelper import substitute_inline_codes
from helpers.UserHelper import get_displayname_for_user
from helpers.AppHelper import app
from helpers.SyncHelper import wait_for
class AccountSetting:
ACCOUNT_CONNECTION_CONTAINER = SimpleNamespace(
by=By.NAME, selector="Sync connections"
)
MANAGE_ACCOUNT_BUTTON = SimpleNamespace(by=By.NAME, selector="Manage Account")
ACCOUNT_MENU = SimpleNamespace(by=By.NAME, selector="{menu_item}")
CONFIRM_REMOVE_CONNECTION_BUTTON = SimpleNamespace(
by=By.NAME, selector="Remove connection"
)
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)
CONFIRMATION_YES_BUTTON = SimpleNamespace(by=None, selector=None)
@staticmethod
def account_action(action):
connections = app().find_elements(
AccountSetting.ACCOUNT_CONNECTION_CONTAINER.by,
AccountSetting.ACCOUNT_CONNECTION_CONTAINER.selector,
)
manage_button = None
for connection in connections:
# use the active connection
if connection.get_attribute("showing") == "true":
manage_button = connection.find_element(
AccountSetting.MANAGE_ACCOUNT_BUTTON.by,
AccountSetting.MANAGE_ACCOUNT_BUTTON.selector,
)
break
manage_button.click()
app().find_element(
AccountSetting.ACCOUNT_MENU.by,
AccountSetting.ACCOUNT_MENU.selector.format(menu_item=action),
).click()
@staticmethod
def remove_account_connection():
AccountSetting.account_action("Remove")
app().find_element(
AccountSetting.CONFIRM_REMOVE_CONNECTION_BUTTON.by,
AccountSetting.CONFIRM_REMOVE_CONNECTION_BUTTON.selector,
).click()
@staticmethod
def logout():
AccountSetting.account_action("Log out")
@staticmethod
def login():
AccountSetting.account_action("Log in")
@staticmethod
def get_account_connection_label():
labels = app().find_elements(
AccountSetting.ACCOUNT_CONNECTION_LABEL.by,
AccountSetting.ACCOUNT_CONNECTION_LABEL.selector,
)
# first label is the sync status label
return labels[0].text
@staticmethod
def is_connecting():
return "Connecting to" in AccountSetting.get_account_connection_label()
@staticmethod
def is_user_signed_out():
return "Signed out" in AccountSetting.get_account_connection_label()
@staticmethod
def is_user_signed_in():
return "Connected" in AccountSetting.get_account_connection_label()
@staticmethod
def wait_until_connection_is_configured(timeout=5000):
result = squish.waitFor(
AccountSetting.is_connecting,
timeout,
)
if not result:
raise TimeoutError(
"Timeout waiting for connection to be configured for "
+ str(timeout)
+ " milliseconds"
)
@staticmethod
def wait_until_account_is_connected(timeout=5000):
result = wait_for(
AccountSetting.is_user_signed_in,
timeout,
)
if not result:
raise TimeoutError(
"Timeout waiting for the account to be connected for "
+ str(timeout)
+ " milliseconds"
)
return result
@staticmethod
def wait_until_sync_folder_is_configured(timeout=5000):
result = squish.waitFor(
lambda: not squish.waitForObjectExists(
AccountSetting.ACCOUNT_LOADING
).visible,
timeout,
)
if not result:
raise TimeoutError(
"Timeout waiting for sync folder to be connected for "
+ str(timeout)
+ " milliseconds"
)
return result
@staticmethod
def press_key(key):
key = key.replace('"', "")
key = f"<{key}>"
squish.nativeType(key)
@staticmethod
def is_log_dialog_visible():
visible = False
try:
visible = squish.waitForObjectExists(
AccountSetting.LOG_BROWSER_WINDOW
).visible
except:
pass
return visible
@staticmethod
def remove_connection_for_user(username):
Toolbar.open_account(username)
AccountSetting.remove_account_connection()
@staticmethod
def wait_until_account_is_removed(username, timeout=10000):
displayname = get_displayname_for_user(username)
displayname = substitute_inline_codes(displayname)
def account_removed():
account = Toolbar.get_account(username)
return account is None
result = wait_for(account_removed, timeout)
if not result:
raise TimeoutError(
"Timeout waiting for account to be removed for "
+ str(timeout)
+ " milliseconds"
)
+216
View File
@@ -0,0 +1,216 @@
# from objectmaphelper import RegularExpression
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.FilesHelper import build_conflicted_regex
from helpers.ConfigHelper import get_config
from helpers.AppHelper import app
class Activity:
TAB_CONTAINER = SimpleNamespace(by=None, selector=None)
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")
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.XPATH, selector="//*[contains(@name, '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)
NOT_SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR = SimpleNamespace(by=None, selector=None)
SYNCED_ACTIVITY_STATUS = SimpleNamespace(by=By.NAME, selector=None)
@staticmethod
def get_not_synced_file_selector(resource):
return {
"column": 1,
"container": Activity.NOT_SYNCED_TABLE,
"text": resource,
"type": "QModelIndex",
}
@staticmethod
def get_not_synced_status(row):
return squish.waitForObjectExists(
{
"column": 6,
"row": row,
"container": Activity.NOT_SYNCED_TABLE,
"type": "QModelIndex",
}
).text
@staticmethod
def click_tab(tab_name):
selector = Activity.SUBTAB_CONTAINER.selector.format(tab_name=tab_name)
app().find_element(Activity.SUBTAB_CONTAINER.by, selector).click()
@staticmethod
def check_file_exist(filename):
squish.waitForObjectExists(
Activity.get_not_synced_file_selector(
RegularExpression(build_conflicted_regex(filename))
)
)
@staticmethod
def is_resource_blacklisted(filename):
result = squish.waitFor(
lambda: Activity.has_sync_status(filename, "Blacklisted"),
get_config("maxSyncTimeout") * 1000,
)
return result
@staticmethod
def is_resource_ignored(filename):
result = squish.waitFor(
lambda: Activity.has_sync_status(filename, "File Ignored"),
get_config("maxSyncTimeout") * 1000,
)
return result
@staticmethod
def is_resource_excluded(filename):
result = squish.waitFor(
lambda: Activity.has_sync_status(filename, "Excluded"),
get_config("maxSyncTimeout") * 1000,
)
return result
@staticmethod
def has_sync_status(filename, status):
try:
app().find_element(Activity.SYNCED_ACTIVITY_STATUS.by, status)
return True
except NoSuchElementException:
return False
@staticmethod
def select_synced_filter(sync_filter):
menu = app().find_element(
Activity.LOCAL_ACTIVITY_FILTER_BUTTON.by,
Activity.LOCAL_ACTIVITY_FILTER_BUTTON.selector,
)
menu.click()
# NOTE: Filter options are not visible in the accessibility tree.
# As a workaround, select the second filter option (which is an account filter).
# This means we cannot select a specific account filter for now.
menu.send_keys(Keys.ARROW_DOWN)
menu.send_keys(Keys.ARROW_DOWN)
menu.send_keys(Keys.ENTER)
# 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.LOCAL_ACTIVITY_TABLE,
"text": resource,
"type": "QModelIndex",
}
@staticmethod
def get_synced_table_column_number_by_name(column_name):
return squish.waitForObject(
{
"container": Activity.SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR,
"text": column_name,
"type": "HeaderViewItem",
"visible": True,
}
)["section"]
@staticmethod
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):
squish.clickButton(squish.waitForObject(Activity.NOT_SYNCED_FILTER_BUTTON))
squish.activateItem(
squish.waitForObjectItem(
Activity.NOT_SYNCED_FILTER_OPTION_SELECTOR, filter_option
)
)
@staticmethod
def get_not_synced_table_column_number_by_name(column_name):
return squish.waitForObject(
{
"container": Activity.NOT_SYNCED_ACTIVITY_TABLE_HEADER_SELECTOR,
"text": column_name,
"type": "HeaderViewItem",
"visible": True,
}
)["section"]
@staticmethod
def check_not_synced_table(resource, status, account):
try:
file_row = squish.waitForObject(
Activity.get_not_synced_file_selector(resource),
get_config("lowestSyncTimeout") * 1000,
)["row"]
squish.waitForObjectExists(
{
"column": Activity.get_not_synced_table_column_number_by_name(
"Status"
),
"row": file_row,
"container": Activity.NOT_SYNCED_TABLE,
"text": status,
"type": "QModelIndex",
}
)
squish.waitForObjectExists(
{
"column": Activity.get_not_synced_table_column_number_by_name(
"Account"
),
"row": file_row,
"container": Activity.NOT_SYNCED_TABLE,
"text": account,
"type": "QModelIndex",
}
)
return True
except:
return False
@@ -0,0 +1,42 @@
from types import SimpleNamespace
from appium.webdriver.common.appiumby import AppiumBy as By
from pageObjects.AccountConnectionWizard import AccountConnectionWizard
from helpers.WebUIHelper import authorize_via_webui
from helpers.AppHelper import app
class EnterPassword:
LOGIN_CONTAINER = SimpleNamespace(by=None, selector=None)
LOGIN_USER_LABEL = SimpleNamespace(
by=By.XPATH,
selector="//filler[@name='Login required']//label[contains(@name, 'Connecting')]",
)
USERNAME_BOX = SimpleNamespace(by=None, selector=None)
LOGOUT_BUTTON = SimpleNamespace(by=None, selector=None)
def get_username(self):
# Parse username from the login label:
label = (
app()
.find_element(
EnterPassword.LOGIN_USER_LABEL.by,
EnterPassword.LOGIN_USER_LABEL.selector,
)
.text
)
username = label.split(" ", maxsplit=2)[1]
return username.capitalize()
def oidc_relogin(self, username, password):
AccountConnectionWizard.copy_login_url()
authorize_via_webui(username, password)
def relogin(self, username, password, oauth=False):
self.oidc_relogin(username, password)
def login_after_setup(self, username, password):
self.oidc_relogin(username, password)
def accept_certificate(self):
AccountConnectionWizard.accept_certificate()
@@ -0,0 +1,297 @@
from datetime import datetime
import test
import names
import squish
import object # pylint: disable=redefined-builtin
class PublicLinkDialog:
PUBLIC_LINKS_TAB = {
"container": names.sharingDialog_qt_tabwidget_tabbar_QTabBar,
"text": "Public Links",
"type": "TabItem",
}
PASSWORD_CHECKBOX = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "checkBox_password",
"type": "QCheckBox",
"visible": 1,
}
PASSWORD_INPUT_FIELD = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "lineEdit_password",
"type": "QLineEdit",
"visible": 1,
}
EXPIRYDATE_CHECKBOX = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "checkBox_expire",
"type": "QCheckBox",
"visible": 1,
}
CREATE_SHARE_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "createShareButton",
"type": "QPushButton",
"visible": 1,
}
PUBLIC_LINK_NAME = {
"column": 0,
"container": names.oCC_ShareLinkWidget_linkShares_QTableWidget,
"row": 0,
"type": "QModelIndex",
}
EXPIRATION_DATE_FIELD = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "qt_spinbox_lineedit",
"type": "QLineEdit",
"visible": 1,
}
READ_ONLY_RADIO_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "radio_readOnly",
"type": "QRadioButton",
"visible": 1,
}
READ_WRITE_RADIO_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "radio_readWrite",
"type": "QRadioButton",
"visible": 1,
}
UPLOAD_ONLY_RADIO_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "radio_uploadOnly",
"type": "QRadioButton",
"visible": 1,
}
DELETE_LINK_BUTTON = {
"container": names.oCC_ShareLinkWidget_linkShares_QTableWidget,
"type": "QToolButton",
"occurrence": 2,
}
CONFIRM_LINK_DELETE_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"text": "Delete",
"type": "QPushButton",
}
UPDATE_PASSWORD_BUTTON = {
"container": names.qt_tabwidget_stackedwidget_OCC_ShareLinkWidget_OCC_ShareLinkWidget,
"name": "pushButton_setPassword",
"type": "QPushButton",
"visible": 1,
}
DATE_FORMATS = [
"%m/%d/%Y",
"%d/%m/%Y",
"%Y-%m-%d",
"%d-%m-%Y",
"%Y/%m/%d",
"%m/%d/%y",
"%d/%m/%y",
]
# to store current default public link expiry date
default_expiry_date = ""
@staticmethod
def parse_date(date):
for date_format in PublicLinkDialog.DATE_FORMATS:
try:
date = str(datetime.strptime(date, date_format))
date = date.split(" ", maxsplit=1)[0]
break
except:
pass
return date
@staticmethod
def set_default_expiry_date(default_date):
PublicLinkDialog.default_expiry_date = PublicLinkDialog.parse_date(default_date)
@staticmethod
def get_default_expiry_date():
return PublicLinkDialog.default_expiry_date
@staticmethod
def open_public_link_tab():
squish.mouseClick(squish.waitForObject(PublicLinkDialog.PUBLIC_LINKS_TAB))
@staticmethod
def create_public_link(password="", permissions="", expire_date=""):
permission_locator = ""
if permissions:
permission_locator = PublicLinkDialog.get_radio_object_for_permssion(
permissions
)
if permission_locator:
squish.clickButton(squish.waitForObject(permission_locator))
if password:
PublicLinkDialog.set_password(password)
if expire_date:
PublicLinkDialog.set_expiration_date(expire_date)
squish.clickButton(squish.waitForObject(PublicLinkDialog.CREATE_SHARE_BUTTON))
squish.waitFor(
lambda: (
squish.waitForObject(PublicLinkDialog.PUBLIC_LINK_NAME).displayText
== "Public link"
)
)
@staticmethod
def toggle_password():
squish.clickButton(squish.waitForObject(PublicLinkDialog.PASSWORD_CHECKBOX))
@staticmethod
def set_password(password):
if not squish.waitForObjectExists(PublicLinkDialog.PASSWORD_CHECKBOX).checked:
PublicLinkDialog.toggle_password()
squish.mouseClick(squish.waitForObject(PublicLinkDialog.PASSWORD_INPUT_FIELD))
squish.type(
squish.waitForObject(PublicLinkDialog.PASSWORD_INPUT_FIELD),
password,
)
@staticmethod
def toggle_expiration_date():
squish.clickButton(squish.waitForObject(PublicLinkDialog.EXPIRYDATE_CHECKBOX))
@staticmethod
def set_expiration_date(expire_date):
enabled = squish.waitForObjectExists(
PublicLinkDialog.EXPIRYDATE_CHECKBOX
).checked
if not enabled:
PublicLinkDialog.toggle_expiration_date()
if not expire_date == "default":
exp_date = datetime.strptime(expire_date, "%Y-%m-%d")
exp_year = exp_date.year - 2000
squish.mouseClick(
squish.waitForObject(PublicLinkDialog.EXPIRATION_DATE_FIELD)
)
# Move the cursor to year (last) field and enter the year
squish.nativeType("<Ctrl+Right>")
squish.nativeType("<Ctrl+Right>")
squish.nativeType(exp_year)
# Move the cursor to day (middle) field and enter the day
squish.nativeType("<Ctrl+Left>")
squish.nativeType(exp_date.day)
# Move the cursor to month (first) field and enter the month
# Backspace works most of the time, so we clear the data using backspace
squish.nativeType("<Ctrl+Left>")
squish.nativeType("<Ctrl+Left>")
squish.nativeType("<Right>")
squish.nativeType("<Backspace>")
squish.nativeType("<Backspace>")
squish.nativeType(exp_date.month)
squish.nativeType("<Return>")
actual_date = str(
squish.waitForObjectExists(
PublicLinkDialog.EXPIRATION_DATE_FIELD
).displayText
)
expected_date = f"{exp_date.month}/{exp_date.day}/{exp_year}"
if not actual_date == expected_date:
test.log(
f"Expected date: {expected_date} is not same as that of actual date: {actual_date}"
+ ", trying workaround"
)
# retry with workaround
PublicLinkDialog.set_expiration_date_with_workaround(
exp_year, exp_date.month, exp_date.day
)
actual_date = str(
squish.waitForObjectExists(
PublicLinkDialog.EXPIRATION_DATE_FIELD
).displayText
)
if not actual_date == expected_date:
test.fail(
f"workaround failed:\nactual date: {actual_date}\nexpected date: {expected_date}"
)
else:
default_date = str(
squish.waitForObjectExists(
PublicLinkDialog.EXPIRATION_DATE_FIELD
).displayText
)
PublicLinkDialog.set_default_expiry_date(default_date)
# This workaround is needed because the above function 'set_expiration_date' can not set the month field sometime.
# See for more details: https://github.com/owncloud/client/issues/9218
@staticmethod
def set_expiration_date_with_workaround(year, month, day):
squish.mouseClick(squish.waitForObject(PublicLinkDialog.EXPIRATION_DATE_FIELD))
# date can only be set to future date. But sometimes it can not modify the month field in first attempt.
# date format is 'm/d/yy', so we have to edit 'month' first
# then 'day' and then 'year'.
# Delete data from month field
squish.nativeType("<Delete>")
squish.nativeType("<Delete>")
squish.nativeType(month)
squish.nativeType(day)
squish.nativeType(year)
squish.nativeType("<Return>")
@staticmethod
def get_radio_object_for_permssion(permissions):
permission_locator = None
if permissions in ("Download / View", "Viewer"):
permission_locator = PublicLinkDialog.READ_ONLY_RADIO_BUTTON
elif permissions in ("Download / View / Edit", "Editor"):
permission_locator = PublicLinkDialog.READ_WRITE_RADIO_BUTTON
elif permissions in ("Upload only (File Drop)", "Contributor"):
permission_locator = PublicLinkDialog.UPLOAD_ONLY_RADIO_BUTTON
else:
raise LookupError("No such radio object found for given permission")
return permission_locator
@staticmethod
def create_public_link_with_role(role):
permission_locator = PublicLinkDialog.get_radio_object_for_permssion(role)
squish.clickButton(squish.waitForObject(permission_locator))
squish.clickButton(squish.waitForObject(PublicLinkDialog.CREATE_SHARE_BUTTON))
squish.waitFor(
lambda: (
squish.findObject(PublicLinkDialog.PUBLIC_LINK_NAME).displayText
== "Public link"
)
)
@staticmethod
def change_password(password):
PublicLinkDialog.set_password(password)
squish.clickButton(
squish.waitForObject(PublicLinkDialog.UPDATE_PASSWORD_BUTTON)
)
@staticmethod
def delete_public_link():
squish.clickButton(squish.waitForObject(PublicLinkDialog.DELETE_LINK_BUTTON))
squish.clickButton(
squish.waitForObject(PublicLinkDialog.CONFIRM_LINK_DELETE_BUTTON)
)
squish.waitFor(
lambda: (not object.exists(PublicLinkDialog.DELETE_LINK_BUTTON)),
)
@staticmethod
def get_expiration_date():
default_date = str(
squish.waitForObjectExists(
PublicLinkDialog.EXPIRATION_DATE_FIELD
).displayText
)
return PublicLinkDialog.parse_date(default_date)
+101
View File
@@ -0,0 +1,101 @@
from types import SimpleNamespace
from appium.webdriver.common.appiumby import AppiumBy as By
from helpers.AppHelper import app
class Settings:
CHECKBOX_OPTION_ITEM = SimpleNamespace(by=None, selector=None)
NETWORK_OPTION_ITEM = SimpleNamespace(by=None, selector=None)
ABOUT_BUTTON = SimpleNamespace(by=By.NAME, selector="About")
ABOUT_DIALOG = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | About]")
ABOUT_DIALOG_OK_BUTTON = SimpleNamespace(by=By.NAME, selector="OK")
GENERAL_SETTING_START_ON_LOGIN = SimpleNamespace(
by=By.XPATH, selector="//panel/*[@name='Start on Login']"
)
GENERAL_SETTING_LANGUAGE = SimpleNamespace(
by=By.XPATH, selector="//panel/label[@name='Language']"
)
ADVANCED_SETTING_SYNC_HIDDEN_FILES = SimpleNamespace(
by=By.XPATH, selector="//panel/*[@name='Sync hidden files']"
)
ADVANCED_SETTING_EDIT_IGNORED_FILES = SimpleNamespace(
by=By.XPATH, selector="//panel/*[@name='Edit Ignored Files']"
)
ADVANCED_SETTING_LOG_SETTINGS = SimpleNamespace(
by=By.XPATH, selector="//panel/*[@name='Log Settings']"
)
NETWORK_SETTING_DOWNLOAD_BANDWIDTH = SimpleNamespace(
by=By.XPATH, selector="//panel[@name='Download Bandwidth']"
)
NETWORK_SETTING_UPLOAD_BANDWIDTH = SimpleNamespace(
by=By.XPATH, selector="//panel[@name='Upload Bandwidth']"
)
@staticmethod
def get_checkbox_option_selector(name):
selector = Settings.CHECKBOX_OPTION_ITEM.copy()
selector.update({"name": name})
if name == "languageDropdown":
selector.update({"type": "QComboBox"})
elif name in ("ignoredFilesButton", "logSettingsButton"):
selector.update({"type": "QPushButton"})
return selector
@staticmethod
def get_network_option_selector(name):
selector = Settings.NETWORK_OPTION_ITEM.copy()
selector.update({"name": name})
return selector
@staticmethod
def has_general_setting(setting):
if setting.lower() == "start on login":
locator = Settings.GENERAL_SETTING_START_ON_LOGIN
elif setting.lower() == "language":
locator = Settings.GENERAL_SETTING_LANGUAGE
else:
raise ValueError(f"Unknown general setting: {setting}")
return app().find_element(locator.by, locator.selector).is_displayed()
@staticmethod
def has_advanced_setting(setting):
if setting.lower() == "sync hidden files":
locator = Settings.ADVANCED_SETTING_SYNC_HIDDEN_FILES
elif setting.lower() == "edit ignored files":
locator = Settings.ADVANCED_SETTING_EDIT_IGNORED_FILES
elif setting.lower() == "log settings":
locator = Settings.ADVANCED_SETTING_LOG_SETTINGS
else:
raise ValueError(f"Unknown advanced setting: {setting}")
return app().find_element(locator.by, locator.selector).is_displayed()
@staticmethod
def has_network_setting(setting):
if setting.lower() == "download bandwidth":
locator = Settings.NETWORK_SETTING_DOWNLOAD_BANDWIDTH
elif setting.lower() == "upload bandwidth":
locator = Settings.NETWORK_SETTING_UPLOAD_BANDWIDTH
else:
raise ValueError(f"Unknown network setting: {setting}")
return app().find_element(locator.by, locator.selector).is_displayed()
@staticmethod
def open_about_dialog():
app().find_element(
Settings.ABOUT_BUTTON.by, Settings.ABOUT_BUTTON.selector
).click()
@staticmethod
def has_about_dialog():
return (
app()
.find_element(Settings.ABOUT_DIALOG.by, Settings.ABOUT_DIALOG.selector)
.is_displayed()
)
@staticmethod
def close_about_dialog():
app().find_element(
Settings.ABOUT_DIALOG_OK_BUTTON.by, Settings.ABOUT_DIALOG_OK_BUTTON.selector
).click()
@@ -0,0 +1,131 @@
from types import SimpleNamespace
from appium.webdriver.common.appiumby import AppiumBy as By
from selenium.common.exceptions import NoSuchElementException
from helpers.ConfigHelper import get_config
from helpers.AppHelper import app
class SyncConnection:
ACCOUNT_CONNECTION_CONTAINER = SimpleNamespace(
by=By.NAME, selector="Sync connections"
)
FOLDER_SYNC_CONNECTION_MENU_BUTTON = SimpleNamespace(
by=By.NAME,
selector="{sync_folder},Success,Local folder: {sync_path}{sync_folder}",
)
MENU_ITEM = SimpleNamespace(by=By.NAME, selector=None)
SELECTIVE_SYNC_APPLY_BUTTON = SimpleNamespace(by=None, selector=None)
CANCEL_FOLDER_SYNC_CONNECTION_DIALOG = SimpleNamespace(by=None, selector=None)
CONFIRM_FOLDER_SYNC_CONNECTION_REMOVE = SimpleNamespace(
by=By.NAME, selector="Remove Space"
)
PERMISSION_ERROR_LABEL = SimpleNamespace(by=None, selector=None)
@staticmethod
def get_current_account_connection():
connections = app().find_elements(
SyncConnection.ACCOUNT_CONNECTION_CONTAINER.by,
SyncConnection.ACCOUNT_CONNECTION_CONTAINER.selector,
)
for connection in connections:
# use the active connection
if connection.get_attribute("showing") == "true":
return connection
return None
@staticmethod
def open_menu(sync_folder=None):
if sync_folder is None:
sync_folder = get_config('syncConnectionName')
connection = SyncConnection.get_current_account_connection()
menu_button = connection.find_element(
SyncConnection.FOLDER_SYNC_CONNECTION_MENU_BUTTON.by,
SyncConnection.FOLDER_SYNC_CONNECTION_MENU_BUTTON.selector.format(
sync_folder=sync_folder,
sync_path=get_config('currentUserSyncPath'),
),
)
menu_button.native_click(button='right')
@staticmethod
def perform_action(action):
SyncConnection.open_menu()
app().find_element(SyncConnection.MENU_ITEM.by, action).click()
@staticmethod
def force_sync():
SyncConnection.perform_action("Force sync now")
@staticmethod
def pause_sync():
SyncConnection.perform_action("Pause sync")
@staticmethod
def resume_sync():
SyncConnection.perform_action("Resume sync")
@staticmethod
def has_menu_item(item):
return squish.waitForObjectItem(SyncConnection.MENU_ITEM, item)
@staticmethod
def menu_item_exists(menu_item):
obj = SyncConnection.MENU_ITEM.copy()
obj.update({"type": "QAction", "text": menu_item})
return object.exists(obj)
@staticmethod
def choose_what_to_sync():
SyncConnection.open_menu()
SyncConnection.perform_action("Choose what to sync")
@staticmethod
def has_sync_connection(sync_folder):
connection = SyncConnection.get_current_account_connection()
try:
connection.find_element(
SyncConnection.FOLDER_SYNC_CONNECTION_MENU_BUTTON.by,
SyncConnection.FOLDER_SYNC_CONNECTION_MENU_BUTTON.selector.format(
sync_folder=sync_folder,
sync_path=get_config('currentUserSyncPath'),
),
)
return True
except NoSuchElementException:
return False
@staticmethod
def remove_folder_sync_connection():
SyncConnection.perform_action("Remove Space")
@staticmethod
def cancel_folder_sync_connection_removal():
squish.clickButton(
squish.waitForObject(SyncConnection.CANCEL_FOLDER_SYNC_CONNECTION_DIALOG)
)
@staticmethod
def confirm_folder_sync_connection_removal():
app().find_element(
SyncConnection.CONFIRM_FOLDER_SYNC_CONNECTION_REMOVE.by,
SyncConnection.CONFIRM_FOLDER_SYNC_CONNECTION_REMOVE.selector,
).click()
@staticmethod
def wait_for_error_label(to_exist=True):
"""Wait for permission error label to appear or disappear"""
status = squish.waitFor(
lambda: object.exists(SyncConnection.PERMISSION_ERROR_LABEL) == to_exist,
get_config("maxSyncTimeout") * 1000,
)
if not status:
action = "appear" if to_exist else "disappear"
raise AssertionError(f"Permission error label did not {action}")
@staticmethod
def get_permission_error_message():
"""Get the permission error message text"""
SyncConnection.wait_for_error_label(True) # Wait for label to appear
return str(squish.waitForObject(SyncConnection.PERMISSION_ERROR_LABEL).text)
@@ -0,0 +1,283 @@
from types import SimpleNamespace
from appium.webdriver.common.appiumby import AppiumBy as By
from selenium.webdriver.common.keys import Keys
from helpers.SetupClientHelper import get_current_user_sync_path
from helpers.AppHelper import app
class SyncConnectionWizard:
CHOOSE_LOCAL_SYNC_FOLDER = SimpleNamespace(
by=By.ACCESSIBILITY_ID, selector="localFolderLineEdit"
)
BACK_BUTTON = SimpleNamespace(by=By.NAME, selector="< Back")
NEXT_BUTTON = SimpleNamespace(by=By.NAME, selector="Next >")
SELECTIVE_SYNC_ROOT_FOLDER = SimpleNamespace(by=None, selector=None)
ADD_SYNC_CONNECTION_BUTTON = SimpleNamespace(
by=By.XPATH, selector="//dialog[@name='Add Space']//*[@name='Add Space']"
)
REMOTE_FOLDER_TREE = SimpleNamespace(by=None, selector=None)
SELECTIVE_SYNC_TREE_HEADER = SimpleNamespace(by=None, selector=None)
CANCEL_FOLDER_SYNC_CONNECTION_WIZARD = SimpleNamespace(
by=By.NAME, selector="Cancel"
)
SPACES_LIST = SimpleNamespace(by=By.NAME, selector="Spaces list")
SPACE_NAME_SELECTOR = SimpleNamespace(by=By.NAME, selector="{space_name},")
CREATE_REMOTE_FOLDER_BUTTON = SimpleNamespace(by=None, selector=None)
CREATE_REMOTE_FOLDER_INPUT = SimpleNamespace(by=None, selector=None)
CREATE_REMOTE_FOLDER_CONFIRM_BUTTON = SimpleNamespace(by=None, selector=None)
REFRESH_BUTTON = SimpleNamespace(by=None, selector=None)
REMOTE_FOLDER_SELECTION_INPUT = SimpleNamespace(by=None, selector=None)
ADD_FOLDER_SYNC_BUTTON = SimpleNamespace(by=None, selector=None)
WARN_LABEL = SimpleNamespace(by=None, selector=None)
CHOOSE_WHAT_TO_SYNC_FOLDER_TREE = SimpleNamespace(by=None, selector=None)
@staticmethod
def set_sync_path_oc(sync_path):
if not sync_path:
sync_path = get_current_user_sync_path()
sync_path_input = app().find_element(
SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER.by,
SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER.selector,
)
sync_path_input.clear()
sync_path_input.send_keys(sync_path)
SyncConnectionWizard.next_step()
@staticmethod
def set_sync_path(sync_path=""):
SyncConnectionWizard.set_sync_path_oc(sync_path)
@staticmethod
def next_step():
next_button = app().find_element(
SyncConnectionWizard.NEXT_BUTTON.by,
SyncConnectionWizard.NEXT_BUTTON.selector,
)
if not next_button.is_enabled():
raise AssertionError("Next button is not enabled")
next_button.click()
@staticmethod
def back():
squish.clickButton(squish.waitForObject(SyncConnectionWizard.BACK_BUTTON))
@staticmethod
def select_remote_destination_folder(folder):
squish.mouseClick(
squish.waitForObjectItem(SyncConnectionWizard.REMOTE_FOLDER_TREE, folder)
)
SyncConnectionWizard.next_step()
@staticmethod
def deselect_all_remote_folders():
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
@staticmethod
def sort_by(header_text):
squish.mouseClick(
squish.waitForObject(
{
"container": SyncConnectionWizard.SELECTIVE_SYNC_TREE_HEADER,
"text": header_text,
"type": "HeaderViewItem",
"visible": True,
}
)
)
@staticmethod
def add_sync_connection():
app().find_element(
SyncConnectionWizard.ADD_SYNC_CONNECTION_BUTTON.by,
SyncConnectionWizard.ADD_SYNC_CONNECTION_BUTTON.selector,
).click()
@staticmethod
def get_item_name_from_row(row_index):
folder_row = {
"row": row_index,
"container": SyncConnectionWizard.SELECTIVE_SYNC_ROOT_FOLDER,
"type": "QModelIndex",
}
return str(squish.waitForObjectExists(folder_row).displayText)
@staticmethod
def is_root_folder_checked():
state = squish.waitForObject(SyncConnectionWizard.SELECTIVE_SYNC_ROOT_FOLDER)[
"checkState"
]
return state == "checked"
@staticmethod
def cancel_folder_sync_connection_wizard():
app().find_element(
SyncConnectionWizard.CANCEL_FOLDER_SYNC_CONNECTION_WIZARD.by,
SyncConnectionWizard.CANCEL_FOLDER_SYNC_CONNECTION_WIZARD.selector,
).click()
@staticmethod
def select_space(space_name):
spaces_list = app().find_element(
SyncConnectionWizard.SPACES_LIST.by,
SyncConnectionWizard.SPACES_LIST.selector,
)
space_item = spaces_list.find_element(
SyncConnectionWizard.SPACE_NAME_SELECTOR.by,
SyncConnectionWizard.SPACE_NAME_SELECTOR.selector.format(
space_name=space_name
),
)
# ISSUE: https://github.com/opencloud-eu/desktop/pull/879
# Cannot select space by click event
# Select space using keyboard events as a workaround
# TODO: Remove 'send_keys' and uncomment 'click' action
space_item.send_keys(Keys.ARROW_DOWN)
# space_item.click()
if space_item.get_attribute("selected") != "true":
raise AssertionError("Failed to select the space: " + space_name)
@staticmethod
def sync_space(space_name):
SyncConnectionWizard.set_sync_path(get_current_user_sync_path())
SyncConnectionWizard.select_space(space_name)
SyncConnectionWizard.next_step()
SyncConnectionWizard.add_sync_connection()
@staticmethod
def create_folder_in_remote_destination(folder_name):
squish.clickButton(
squish.waitForObject(SyncConnectionWizard.CREATE_REMOTE_FOLDER_BUTTON)
)
squish.type(
squish.waitForObject(SyncConnectionWizard.CREATE_REMOTE_FOLDER_INPUT),
folder_name,
)
squish.clickButton(
squish.waitForObject(
SyncConnectionWizard.CREATE_REMOTE_FOLDER_CONFIRM_BUTTON
)
)
@staticmethod
def refresh_remote():
squish.clickButton(squish.waitForObject(SyncConnectionWizard.REFRESH_BUTTON))
@staticmethod
def is_remote_folder_selected(folder_selector):
return squish.waitForObjectExists(folder_selector).selected
@staticmethod
def open_sync_connection_wizard():
squish.mouseClick(
squish.waitForObject(SyncConnectionWizard.ADD_FOLDER_SYNC_BUTTON)
)
@staticmethod
def get_local_sync_path():
return str(
squish.waitForObjectExists(
SyncConnectionWizard.CHOOSE_LOCAL_SYNC_FOLDER
).displayText
)
@staticmethod
def get_warn_label():
return str(squish.waitForObjectExists(SyncConnectionWizard.WARN_LABEL).text)
@staticmethod
def is_add_sync_folder_button_enabled():
return squish.waitForObjectExists(
SyncConnectionWizard.ADD_FOLDER_SYNC_BUTTON
).enabled
@staticmethod
def select_or_unselect_folders_to_sync(folders, select=True):
expected_state = "true" if select else "false"
if select:
SyncConnectionWizard.deselect_all_remote_folders()
for folder_path in folders:
parents = folder_path.strip("/").split("/")
target_folder = parents.pop()
parent_element = None
parent_position = 0
for parent in parents:
p_elements = app().find_elements(By.NAME, parent)
# select nested folders based on the position of the parent folder
for p_element in p_elements:
if (
p_element.get_attribute("checked") == 'true'
and p_element.rect["x"] > parent_position
):
parent_element = p_element
parent_position = p_element.rect["x"]
break
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 = None
target_folders = app().find_elements(By.NAME, target_folder)
# select the folder that is inside the current parent position
for folder in target_folders:
if folder.rect["x"] > parent_position:
folder_element = folder
break
is_checked = folder_element.get_attribute("checked")
# return early if the folder is already in the expected state.
if is_checked == expected_state:
return
folder_element.native_click()
if not folder_element.is_selected():
raise AssertionError(f"Failed to focus folder: {target_folder}")
folder_element.native_send_keys(Keys.SPACE) # toggle the folder selection
is_checked = folder_element.get_attribute("checked")
if is_checked != expected_state:
raise AssertionError(
f"Failed to {'select' if select else 'unselect'} folder: {folder_path}"
)
@staticmethod
def confirm_choose_what_to_sync_selection():
app().find_element(By.NAME, "OK").click()
@staticmethod
def __handle_folder_selection(folders, should_select, new_sync_connection_wizard):
SyncConnectionWizard.select_or_unselect_folders_to_sync(folders, should_select)
if new_sync_connection_wizard:
SyncConnectionWizard.add_sync_connection()
else:
SyncConnectionWizard.confirm_choose_what_to_sync_selection()
@staticmethod
def unselect_folders_to_sync(folders, new_sync_connection_wizard=False):
SyncConnectionWizard.__handle_folder_selection(
folders,
should_select=False,
new_sync_connection_wizard=new_sync_connection_wizard,
)
@staticmethod
def select_folders_to_sync(folders, new_sync_connection_wizard=False):
SyncConnectionWizard.__handle_folder_selection(
folders,
should_select=True,
new_sync_connection_wizard=new_sync_connection_wizard,
)
+181
View File
@@ -0,0 +1,181 @@
from types import SimpleNamespace
from urllib.parse import urlparse
from appium.webdriver.common.appiumby import AppiumBy as By
from selenium.common.exceptions import NoSuchElementException
from helpers.AppHelper import app
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)
NAVIGATION_BAR = SimpleNamespace(
by=By.XPATH, selector="//*[@name='Navigation bar']/.."
)
ACCOUNT_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | {text}]")
ADD_ACCOUNT_BUTTON = SimpleNamespace(
by=By.CLASS_NAME, selector="[push button | Add Account]"
)
ACTIVITY_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | Activity]")
SETTINGS_TAB = SimpleNamespace(by=By.CLASS_NAME, selector="[page tab | Settings]")
QUIT_BUTTON = SimpleNamespace(by=By.CLASS_NAME, selector="[push button | Quit]")
CONFIRM_QUIT_BUTTON = SimpleNamespace(
by=By.NAME,
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 {
"container": names.dialogStack_quickWidget_QQuickWidget,
"text": item_name,
"type": "Label",
"visible": True,
}
@staticmethod
def has_tab(tab_name):
if tab_name.lower() == "add account":
tab = Toolbar.ADD_ACCOUNT_BUTTON
elif tab_name.lower() == "activity":
tab = Toolbar.ACTIVITY_TAB
elif tab_name.lower() == "settings":
tab = Toolbar.SETTINGS_TAB
elif tab_name.lower() == "quit":
tab = Toolbar.QUIT_BUTTON
else:
raise ValueError(f"Unknown tab: {tab_name}")
return app().find_element(tab.by, tab.selector).is_displayed()
@staticmethod
def open_activity():
tab = app().find_element(Toolbar.ACTIVITY_TAB.by, Toolbar.ACTIVITY_TAB.selector)
# ISSUE: https://github.com/opencloud-eu/desktop/pull/879
# Cannot select navigation tab by click event
# Select the navigation tab using keyboard events as a workaround
# 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():
app().find_element(
Toolbar.ADD_ACCOUNT_BUTTON.by,
Toolbar.ADD_ACCOUNT_BUTTON.selector,
).click()
@staticmethod
def open_account(username):
account_tab = Toolbar.get_account(username)
# ISSUE: https://github.com/opencloud-eu/desktop/pull/879
# Cannot activate account tab by click event
# Select the account tab using keyboard events as a workaround
# TODO: Remove the workaround and uncomment 'click' action
# account_tab.click()
account_tab.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):
return str(
squish.waitForObjectExists(
Toolbar.get_item_selector(displayname + "\n" + host)
).text
)
@staticmethod
def open_settings_tab():
tab = app().find_element(Toolbar.SETTINGS_TAB.by, Toolbar.SETTINGS_TAB.selector)
# ISSUE: https://github.com/opencloud-eu/desktop/pull/879
# Cannot select navigation tab by click event
# Select the navigation tab using keyboard events as a workaround
# TODO: Remove the workaround and uncomment 'click' action
# tab.click()
tab.native_click()
if tab.get_attribute("checked") != "true":
raise AssertionError("Settings tab is not active")
@staticmethod
def quit_qsfera():
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()
@staticmethod
def get_accounts():
accounts = {}
selectors = {}
children_obj = object.children(squish.waitForObjectExists(Toolbar.TOOLBAR_ROW))
account_idx = 1
for obj in children_obj:
if hasattr(obj, "accountState"):
account_info = {
"displayname": str(obj.accountState.account.davDisplayName),
"hostname": str(obj.accountState.account.hostName),
"initials": str(obj.accountState.account.initials),
"current": obj.checked,
}
account_locator = Toolbar.ACCOUNT_TAB.copy()
if account_idx > 1:
account_locator.update({"occurrence": account_idx})
account_locator.update({"text": account_info["hostname"]})
accounts[account_info["displayname"]] = account_info
selectors[account_info["displayname"]] = obj
account_idx += 1
return accounts, selectors
@staticmethod
def get_account(username):
display_name = get_displayname_for_user(username)
server_host = urlparse(get_config('localBackendUrl')).netloc
account_label = f"{display_name}@{server_host}"
account = None
try:
account = app().find_element(
Toolbar.ACCOUNT_TAB.by,
Toolbar.ACCOUNT_TAB.selector.format(text=account_label),
)
except NoSuchElementException:
pass
return account
@staticmethod
def get_active_account():
accounts, selectors = Toolbar.get_accounts()
for account, info in accounts.items():
if info["current"]:
return info, selectors[account]
return None, None
@staticmethod
def account_has_focus(username):
account = Toolbar.get_account(username)
return account.get_attribute("checked") == "true"
@staticmethod
def account_exists(username):
account = Toolbar.get_account(username)
return account is not None