예제 #1
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.categories_general = CategoriesGeneral(self.driver)
     self.category_detail = CategoryDetail(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.rs = Resolutions(self.driver)
예제 #2
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.period_filter = PeriodFilter(self.driver)
     self.rs = Resolutions(self.driver)
     self.timeline_general = TimelineGeneral(self.driver)
     self.transaction_detail = TransactionDetail(self.driver)
예제 #3
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.rs = Resolutions(self.driver)
     self.wallets_general = WalletsGeneral(self.driver)
     self.wallet_detail = WalletDetail(self.driver)
     self.wallets_overview = WalletOverview(self.driver)
예제 #4
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.budget_detail = BudgetDetail(self.driver)
     self.budgets_general = BudgetsGeneral(self.driver)
     self.budget_overview = BudgetOverview(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.rs = Resolutions(self.driver)
     self.transaction_detail = TransactionDetail(self.driver)
예제 #5
0
class ElementWrapper:

    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.log = logging.getLogger('log')
        self.rs = Resolutions(self.driver)

    def get_by_type(self, locator):
        """
        :param locator: str
        :return: selecting method
        """
        if str(locator).startswith("//") or str(locator).startswith("(//"):
            return MobileBy.XPATH
        elif str(locator).startswith("com.android") or str(locator).startswith("android:id"):
            return MobileBy.ID
        elif str(locator).startswith('label'):
            return MobileBy.IOS_PREDICATE
        elif str(locator).startswith('**'):
            return MobileBy.IOS_CLASS_CHAIN
        else:
            return MobileBy.ACCESSIBILITY_ID

    def get_element(self, locator):
        """
        :param locator: str
        :return: element
        """
        by_type = self.get_by_type(locator)
        element = self.driver.find_element(by_type, locator)
        return element

    def get_elements(self, locator):
        """
        :param locator: str
        :return: list of elements
        """
        by_type = self.get_by_type(locator)
        element_list = self.driver.find_elements(by_type, locator)
        if len(element_list) < 1 or element_list is None:
            self.log.error(f"element_list {locator} NOT found")
        return element_list

    def is_element_present(self, locator):
        """ Checking if element is present in DOM
        :param locator: str
        :return: bool
        """
        try:
            element = self.get_element(locator)
            if element is not None:
                self.log.info(f"element {locator} found")
                return True
            else:
                return False
        except NoSuchElementException:
            self.log.info(f"element {locator} NOT found")
            return False

    def are_elements_present(self, locator):
        """Checking if elements are present in DOM
        :param locator: str
        :return: bool
        """
        element_list = self.get_elements(locator)
        if len(element_list) > 0:
            self.log.info(f"element_list {locator} found")
            return True
        else:
            self.log.info(f"element list {locator} NOT found")
            return False

    def get_text_of_element(self, locator):
        """ Getting text of selected element
        :param locator: str
        :return: str
        """
        try:
            element = self.get_element(locator)
            return element.text
        except NoSuchElementException:
            self.log.info(f"element {locator} NOT found, can't get the text of element")

    def get_text_of_elements(self, locator):
        """ Getting texts of selected elements
        :param locator: str
        :return: list of str
        """
        try:
            element_list = self.get_elements(locator)
            texts = []
            for i in element_list:
                texts.append(i.text)
            return texts
        except NoSuchElementException:
            self.log.info(f"elements {locator} NOT found, can't get the text of elements")

    def wait_till_element_is_visible(self, locator, timeout_seconds):
        """ Methods waits till element is visible in DOM
        :param locator: str
        :param timeout_seconds: int
        """
        timeout = time.time() + timeout_seconds
        visibility_of_element = self.is_element_present(locator)
        while visibility_of_element is False:
            time.sleep(0.1)
            visibility_of_element = self.is_element_present(locator)
            if time.time() > timeout:
                raise NoSuchElementException(f"element {locator} NOT found")

    def wait_till_element_is_not_visible(self, locator, timeout_seconds):
        """ Method waits till element is no more visible in DOM
        :param locator: str
        :param timeout_seconds: int
        """
        timeout = time.time() + timeout_seconds
        visibility_of_element = self.is_element_present(locator)
        while visibility_of_element is True:
            time.sleep(0.1)
            visibility_of_element = self.is_element_present(locator)
            if time.time() > timeout:
                raise NoSuchElementException(f"element {locator} dont't disappeared")

    def tap_element(self, locator):
        """ Method taps on element
        :param locator: str
        """
        self.action.tap(self.get_element(locator)).perform()

    def wait_and_tap_element(self, locator, timeout_seconds):
        """ Method waits till element is visible in DOM and taps on it
        :param locator: str
        :param timeout_seconds: int
        """
        self.wait_till_element_is_visible(locator, timeout_seconds)
        self.action.tap(self.get_element(locator)).perform()

    def get_attribute(self, locator, attribute):
        """ Getting attribute of selected element
        :param locator: str
        :param attribute:  str
        :return: str
        """
        byType = self.get_by_type(locator)
        return self.driver.find_element(byType, locator).get_attribute(attribute)

    def get_attributes(self, locator, attribute):
        """ Getting attribute of selected elements
        :param locator: str
        :param attribute:  str
        :return: list of str
        """
        by_type = self.get_by_type(locator)
        element_list = self.driver.find_elements(by_type, locator)
        attributes = []
        for i in element_list:
            attributes.append(i.get_attribute(attribute))
        return attributes

    def swipe_if_element_not_present(self, locator):
        """ If the element is not present in DOM, it will swipe to it
        :param locator: str
        """
        element_present = self.is_element_present(locator)
        timeout = time.time() + 30
        while element_present is False:
            res = self.rs.get_resolution()
            if PLATFORM == "Android":
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["element_not_present_swipe_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["element_not_present_swipe_y_end"]) \
                    .release().perform()
            else:
                self.driver.execute_script("mobile: dragFromToForDuration",
                                           {"duration": "0.1",
                                            "fromX": self.rs.all_resolutions[f"{res}"]["x"],
                                            "fromY": self.rs.all_resolutions[f"{res}"][
                                                "element_not_present_swipe_y_start"],
                                            "toX": self.rs.all_resolutions[f"{res}"]["x"],
                                            "toY": self.rs.all_resolutions[f"{res}"][
                                                "element_not_present_swipe_y_end"]})
            element_present = self.is_element_present(locator)
            if time.time() > timeout:
                break
예제 #6
0
class WalletsActions:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)
        self.wallets_general = WalletsGeneral(self.driver)
        self.wallet_detail = WalletDetail(self.driver)
        self.wallets_overview = WalletOverview(self.driver)

    def create_wallet(self, name, amount, currency, categories):
        """ Opens wallet create screen and sets requested attributes
        :param name: str or None
        :param amount: str or None
        :param currency: str or None
        :param categories: str or int or None
        """
        self.wallets_general.go_to_wallets()
        self.open_wallet_create_screen()
        self.wallet_detail.set_name(name)
        if amount is not None:
            self.wallet_detail.set_amount(amount)
        if currency is not None:
            self.wallet_detail.set_currency(currency)
        if categories is not None:
            self.wallet_detail.set_categories(categories)

    def open_wallet_create_screen(self):
        """Opens wallet create screen"""
        self.ew.wait_till_element_is_visible(
            self.wallets_general.WALLETS_ANIMATED_HEADER, 10)

        if PLATFORM == "Android":
            add_button_visible = self.ew.is_element_present(
                self.wallets_general.ADD_WALLET_BUTTON)
            while add_button_visible is False:
                res = self.rs.get_resolution()
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["wallets_overview_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["wallets_overview_y_end"]) \
                    .release().perform()
                add_button_visible = self.ew.is_element_present(
                    self.wallets_general.ADD_WALLET_BUTTON)
        else:
            add_button_visible = self.ew.get_attribute(
                self.wallets_general.ADD_WALLET_BUTTON, "visible")
            while add_button_visible == "false":
                res = self.rs.get_resolution()
                self.driver.execute_script(
                    "mobile: dragFromToForDuration", {
                        "duration":
                        "0.1",
                        "fromX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "fromY":
                        self.rs.all_resolutions[f"{res}"]
                        ["wallets_overview_y_start"],
                        "toX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "toY":
                        self.rs.all_resolutions[f"{res}"]
                        ["wallets_overview_y_end"]
                    })
                add_button_visible = self.ew.get_attribute(
                    self.wallets_general.ADD_WALLET_BUTTON, "visible")

        self.ew.wait_and_tap_element(self.wallets_general.ADD_WALLET_BUTTON, 5)
        self.ew.wait_till_element_is_visible(self.wallet_detail.WALLET_HEADER,
                                             10)

    def save_wallet(self):
        """Clicks on save wallet button"""
        if self.driver.is_keyboard_shown():
            self.driver.hide_keyboard()
        self.ew.wait_and_tap_element(self.wallet_detail.SAVE_WALLET_BUTTON, 10)
        self.ew.wait_till_element_is_not_visible(
            self.wallet_detail.SAVE_WALLET_BUTTON, 10)

    def open_wallet(self):
        """Opens existing wallet"""
        self.wallets_general.go_to_wallets()
        self.ew.wait_till_element_is_visible(
            self.wallets_general.WALLETS_ANIMATED_HEADER, 10)
        self.ew.wait_and_tap_element(self.wallets_general.WALLET_ITEM, 5)
        self.ew.wait_and_tap_element(self.wallets_overview.EDIT_BUTTON, 5)
        self.ew.wait_till_element_is_visible(self.wallet_detail.WALLET_HEADER,
                                             5)

    def edit_wallet(self, name, amount, currency, categories):
        """Changes requested attributes of wallet
        :param name: str or None
        :param amount: str or None
        :param currency: str or None
        :param categories: str or int or None
        """
        self.open_wallet()
        if name is not None:
            if PLATFORM == "Android":
                self.ew.get_element(self.wallet_detail.NAME_INPUT).clear()
            else:
                self.ew.get_element(
                    self.wallet_detail.SELECTED_NAME_IOS).clear()
            self.wallet_detail.set_name(name)
        if amount is not None:
            self.ew.wait_and_tap_element(self.wallet_detail.AMOUNT_INPUT, 5)
            self.ew.wait_till_element_is_visible(
                self.wallet_detail.NUMPAD_CLEAR, 10)
            for i in range(6):
                self.ew.tap_element(self.wallet_detail.NUMPAD_CLEAR)
            self.ew.tap_element(self.wallet_detail.NUMPAD_BACKDROP)
            self.wallet_detail.set_amount(amount)
        if currency is not None:
            self.wallet_detail.set_currency(currency)
        if categories is not None:
            self.wallet_detail.set_categories(categories)

    def delete_wallet(self):
        """Deletes wallet from wallet detail"""
        self.ew.wait_and_tap_element(self.wallet_detail.TRASH_ICON, 10)
        self.ew.wait_and_tap_element(self.wallet_detail.DELETE_BUTTON, 10)
        self.ew.wait_till_element_is_visible(
            self.wallets_general.WALLETS_ANIMATED_HEADER, 10)

    def invite_user_to_wallet(self):
        """Opens wallet and invitation screen"""
        self.open_wallet()
        self.wallet_detail.invite_user()
예제 #7
0
class BudgetDetail():
    # OTHER
    BUDGET_HEADER = "Budget Header"
    SAVE_BUDGET_BUTTON = "Save Budget Button"
    TRASH_ICON = "Trash Icon"
    DELETE_BUTTON = "Delete"

    if PLATFORM == "Android":
        DISCARD_CHANGES = "android:id/button1"
    else:
        DISCARD_CHANGES = "Discard changes"

    # NAME
    NAME_INPUT = "Name Input"
    SELECTED_NAME_IOS = '**/XCUIElementTypeTextField[`label == "Name Input"`]'

    # AMOUNT
    if PLATFORM == "Android":
        AMOUNT_INPUT = "Amount Input"
    else:
        AMOUNT_INPUT = '**/XCUIElementTypeOther[`label == "Amount Input"`][2]'
    SELECTED_AMOUNT = "Currency Input"

    # KEYBOARD
    KEYBOARD = {"0": "Numpad 0", "1": "Numpad 1", "2": "Numpad 2", "3": "Numpad 3", "4": "Numpad 4", "5": "Numpad 5",
                "6": "Numpad 6", "7": "Numpad 7", "8": "Numpad 8", "9": "Numpad 9",
                ".": "Numpad Decimal Point", ",": "Numpad Decimal Point"}
    NUMPAD_BACKDROP = "Numpad Backdrop"
    NUMPAD_CLEAR = "Numpad Clear"

    # CURRENCY
    CURRENCY = "Currency"
    if PLATFORM == "Android":
        SELECTED_CURRENCY = '//android.view.ViewGroup[@content-desc="Currency"]/android.view.ViewGroup/android.view.ViewGroup/android.widget.EditText'
        CURRENCY_PICKER = "Select currency Picker"
    else:
        SELECTED_CURRENCY = '**/XCUIElementTypeOther[`label == "Currency"`][1]'
        CURRENCY_PICKER = 'label == "Select currency"'

    # WALLETS
    if PLATFORM == "Android":
        WALLET_ITEM = '//android.view.ViewGroup[@content-desc="Select Wallets Picker"]/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup/android.view.ViewGroup'
        WALLETS = "Wallets"
    else:
        WALLET_ITEM = "Wallet Item"
        WALLETS = 'label == "Wallets"'
    WALLET_PICKER = "Select Wallets Picker"
    SELECTED_WALLETS_ANDROID = '//android.view.ViewGroup[@content-desc="Wallets"]/android.view.ViewGroup/android.widget.TextView[2]'
    SELECTED_WALLETS_ANDROID_2 = '//android.view.ViewGroup[@content-desc="Wallets"]/android.widget.TextView[2]'

    # CATEGORIES
    CATEGORIES = "Categories"
    HEADER_BUDGET_FOR = "Header Budget For"
    SELECT_ALL_CHECKED = "Select All-checked"
    SELECT_ALL_UNCHECKED = "Select All-unchecked"
    SELECT_ALL_PART = "Select All-part"
    if PLATFORM == "Android":
        CATEGORIES = "Categories"
        CATEGORY_ITEM = '//android.view.ViewGroup[@content-desc="Category Item"]/android.view.ViewGroup'
        SELECTED_CATEGORIES_ANDROID = '//android.view.ViewGroup[@content-desc="Categories"]/android.widget.TextView[2]'
        SELECTED_CATEGORIES_ANDROID_2 = '//android.view.ViewGroup[@content-desc="Categories"]/android.view.ViewGroup/android.widget.TextView[2]'
    else:
        CATEGORY_ITEM = 'label == "Category Item"'
        CATEGORIES = 'label == "Categories"'
    BACK_BUTTON = "Back Button"

    # RECURRENCE
    if PLATFORM == "Android":
        RECURRENCE = "Recurrence"
        SELECTED_RECURRENCE_ANDROID = '//android.view.ViewGroup[@content-desc="Recurrence"]/android.widget.TextView[2]'
        SELECTED_RECURRENCE_ANDROID_EDIT = '//android.view.ViewGroup[@content-desc="Recurrence"]/android.view.ViewGroup/android.widget.TextView[2]'
    else:
        RECURRENCE = 'label == "Recurrence"'
    RECURRENCE_PICKER = "Recurrence Picker"

    # START DATE
    if PLATFORM == "Android":
        START_DATE = "Start Date"
    else:
        START_DATE = 'label == "Start Date"'
    CALENDAR_PICKER = "Select date Picker"

    # END DATE
    if PLATFORM == "Android":
        END_DATE = "End Date"
    else:
        END_DATE = 'label == "End Date"'

    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)
        self.transaction_detail = TransactionDetail(self.driver)

    def set_name(self, name):
        """ Insert name into name input
        :param name: str
        """
        if name == "random":
            name = ''.join([random.choice(string.ascii_lowercase + string.digits) for n in range(0, 8)])

        self.ew.wait_till_element_is_visible(self.NAME_INPUT, 5)
        self.ew.get_element(self.NAME_INPUT).send_keys(name)

        vr.validate_input_against_output(name, self.get_name())

    def get_name(self):
        """ Gets name of budget from name input
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.NAME_INPUT, 5)

        if PLATFORM == "Android":
            return self.ew.get_text_of_element(self.NAME_INPUT)
        else:
            return self.ew.get_text_of_element(self.SELECTED_NAME_IOS)

    def set_amount(self, amount):
        """ Insert amount into amount input
        :param amount: str
        """
        if amount == "random":
            amount = str(random.randint(1, 99))

        self.ew.wait_and_tap_element(self.AMOUNT_INPUT, 5)
        self.ew.wait_till_element_is_visible(self.KEYBOARD["1"], 10)
        amount_list = list(amount)
        for i in amount_list:
            self.ew.wait_and_tap_element(self.KEYBOARD[i], 5)
        self.ew.wait_and_tap_element(self.NUMPAD_BACKDROP, 5)

        vr.validate_input_against_output(amount, self.get_amount())

    def get_amount(self):
        """ Gets amount of budget from amount input
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.AMOUNT_INPUT, 5)
        if PLATFORM == "Android":
            return self.ew.get_text_of_element(self.SELECTED_AMOUNT)
        else:
            return self.ew.get_attribute(self.AMOUNT_INPUT, "name")

    def set_currency(self, currency):
        """ Selects currency of budget
        :param currency: str
        """
        if currency == "random":
            currency = random.choice(vs.accessible_currencies)

        self.ew.wait_and_tap_element(self.CURRENCY, 5)
        self.ew.wait_till_element_is_visible(self.CURRENCY_PICKER, 10)
        self.ew.wait_and_tap_element(f"Currency {currency}", 10)
        self.ew.wait_till_element_is_not_visible(self.CURRENCY_PICKER, 10)
        vr.validate_input_against_output(currency, self.get_currency())

    def get_currency(self):
        """ Gets selected currency of budget
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.CURRENCY, 5)
        if PLATFORM == "Android":
            return self.ew.get_text_of_element(self.SELECTED_CURRENCY)
        else:
            return self.ew.get_attribute(self.SELECTED_CURRENCY, "name")

    def set_wallets(self, wallets):
        """ Selects wallets from wallets picker
        :param wallets: str or int
        """

        self.ew.wait_and_tap_element(self.WALLETS, 5)
        self.ew.wait_till_element_is_visible(self.WALLET_PICKER, 5)

        all_visible_wallets = self.count_wallets()[0]
        selected_wallets = self.count_wallets()[1]
        non_selected_wallets = self.count_wallets()[2]
        total_wallets = len(all_visible_wallets)
        total_selected_wallets = len(selected_wallets)
        total_non_selected_wallets = len(non_selected_wallets)

        if wallets == "random":
            wallets_to_select = random.sample(all_visible_wallets, random.randrange(0, len(all_visible_wallets)))
            for i in wallets_to_select:
                if PLATFORM == "Android":
                    self.ew.tap_element(i)
                else:
                    self.ew.tap_element(f'label == "{i}"')
        elif wallets == "all_selected":
            if total_wallets != total_selected_wallets:
                for i in non_selected_wallets:
                    if PLATFORM == "Android":
                        self.ew.tap_element(i)
                    else:
                        self.ew.tap_element(f'label == "{i}"')
        elif wallets == "all_unselected":
            if total_wallets != total_non_selected_wallets:
                for i in selected_wallets:
                    if PLATFORM == "Android":
                        self.ew.tap_element(i)
                    else:
                        self.ew.tap_element(f'label == "{i}"')
        elif isinstance(wallets, int):
            x = 0
            actual_selected_wallets = self.count_wallets()[1]
            for i in all_visible_wallets:
                x = x + 1
                if x <= wallets and len(actual_selected_wallets) > 1:
                    if PLATFORM == "Android":
                        self.ew.tap_element(i)
                    else:
                        self.ew.tap_element(f'label == "{i}"')
                    actual_selected_wallets = self.count_wallets()[1]

        selected_wallets = self.count_wallets()[1]
        total_wallets = len(self.count_wallets()[0])
        total_selected_wallets = len(selected_wallets)

        if total_wallets == total_selected_wallets:
            v_input = "All Wallets"
        elif total_selected_wallets == 1:
            v_input = selected_wallets[0].split('-')[0]
        else:
            v_input = str(total_selected_wallets)

        self.ew.tap_element('Backdrop')
        vr.validate_input_against_output(v_input, self.get_wallets())

    def count_wallets(self):
        """ Gets all visible wallets, currently selected wallets and not_selected wallets from wallets picker
        :return: tuple of lists
        """
        if PLATFORM == "Android":
            all_visible_wallets = self.ew.get_attributes(self.WALLET_ITEM, "content-desc")
        else:
            all_visible_wallets = self.ew.get_attributes(self.WALLET_ITEM, "label")

        selected_wallets = []
        non_selected_wallets = []
        for i in all_visible_wallets:
            if i.endswith('true'):
                selected_wallets.append(i)
            else:
                non_selected_wallets.append(i)
        return (all_visible_wallets, selected_wallets, non_selected_wallets)

    def get_wallets(self):
        """ Gets selected wallets (number, name or 'All Wallets')
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.WALLETS, 5)

        if PLATFORM == "Android":
            result = self.ew.get_text_of_element(self.SELECTED_WALLETS_ANDROID)
            if result is None:
                result = self.ew.get_text_of_element(self.SELECTED_WALLETS_ANDROID_2)
        else:
            result = self.ew.get_attribute(self.WALLETS, "name")
        return result

    def set_categories(self, categories):
        """ Selects requested number of categories
        :param categories: str
        """

        self.ew.wait_and_tap_element(self.CATEGORIES, 5)
        self.ew.wait_till_element_is_visible(self.HEADER_BUDGET_FOR, 5)

        all_visible_categories = self.count_categories()[0]

        if categories == "random":
            categories = random.randrange(0, len(all_visible_categories))

        if categories == "all_selected":
            if self.ew.is_element_present(self.SELECT_ALL_UNCHECKED):
                self.ew.tap_element(self.SELECT_ALL_UNCHECKED)
            elif self.ew.is_element_present(self.SELECT_ALL_PART):
                self.ew.tap_element(self.SELECT_ALL_PART)
                self.ew.tap_element(self.SELECT_ALL_UNCHECKED)
        elif categories == "all_unselected":
            if self.ew.is_element_present(self.SELECT_ALL_CHECKED):
                self.ew.tap_element(self.SELECT_ALL_CHECKED)
            elif self.ew.is_element_present(self.SELECT_ALL_PART):
                self.ew.tap_element(self.SELECT_ALL_PART)
        elif isinstance(categories, int):
            if self.ew.is_element_present(self.SELECT_ALL_CHECKED):
                self.ew.tap_element(self.SELECT_ALL_CHECKED)
            elif self.ew.is_element_present(self.SELECT_ALL_PART):
                self.ew.tap_element(self.SELECT_ALL_PART)
            x = 0
            all_visible_categories = self.count_categories()[0]
            for i in all_visible_categories:
                x = x + 1
                if x <= categories:
                    self.ew.tap_element(i)

        if self.ew.is_element_present(self.SELECT_ALL_CHECKED):
            v_input = "All Expenses"
        else:
            v_input = str(len(self.count_categories()[1]))

        self.ew.tap_element(self.BACK_BUTTON)
        vr.validate_input_against_output(v_input, self.get_categories())

    def count_categories(self):
        """ Gets all visible, currently selected and non selected categories inside category picker
        :return: tuple of lists
        """
        if PLATFORM == "Android":
            all_visible_categories = self.ew.get_attributes(self.CATEGORY_ITEM, "content-desc")
        else:
            all_items = self.ew.get_attributes(self.CATEGORY_ITEM, "name")
            all_visible_categories = []
            for i in all_items:
                if i != "Category Item":
                    all_visible_categories.append(i)

        selected_categories = []
        non_selected_categories = []
        for i in all_visible_categories:
            if i.endswith('true'):
                selected_categories.append(i)
            else:
                non_selected_categories.append(i)
        return (all_visible_categories, selected_categories, non_selected_categories)

    def get_categories(self):
        """ Gets number of selected categories
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.CATEGORIES, 5)

        if PLATFORM == "Android":

            result = self.ew.get_text_of_element(self.SELECTED_CATEGORIES_ANDROID)
            if result is None:
                result = self.ew.get_text_of_element(self.SELECTED_CATEGORIES_ANDROID_2)
            return result
        else:
            return self.ew.get_attribute(self.CATEGORIES, "name")

    def set_recurrence(self, recurrence):
        """ Selects requested recurrence of budget
        :param recurrence: str
        """
        self.ew.wait_and_tap_element(self.RECURRENCE, 5)
        self.ew.wait_till_element_is_visible(self.RECURRENCE_PICKER, 5)

        if recurrence == "random":
            recurrence = random.choice(vs.budget_recurrences)

        res = self.rs.get_resolution()
        if PLATFORM == "Android":
            item_visible = self.ew.is_element_present(recurrence)
            while item_visible is False:
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["default_picker_up_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["default_picker_up_y_end"]) \
                    .release().perform()
                item_visible = self.ew.is_element_present(recurrence)
            self.ew.wait_and_tap_element(recurrence, 5)
        else:
            item_visible = self.ew.get_attribute(recurrence, "visible")
            while item_visible == "false":
                self.driver.execute_script("mobile: dragFromToForDuration",
                                           {"duration": "0.1",
                                            "fromX": self.rs.all_resolutions[f"{res}"]["x"],
                                            "fromY": self.rs.all_resolutions[f"{res}"]["default_picker_up_y_start"],
                                            "toX": self.rs.all_resolutions[f"{res}"]["x"],
                                            "toY": self.rs.all_resolutions[f"{res}"]["default_picker_up_y_end"]})
                item_visible = self.ew.get_attribute(recurrence, "visible")
            self.driver.execute_script("mobile: tap", {"x": 100, "y": 50, "element": self.ew.get_element(recurrence)})

        vr.validate_input_against_output(recurrence, self.get_recurrence())

    def get_recurrence(self):
        """ Gets selected recurrence
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.RECURRENCE, 5)
        if PLATFORM == "Android":
            recurrence = self.ew.get_text_of_element(self.SELECTED_RECURRENCE_ANDROID)
            if recurrence is None:
                recurrence = self.ew.get_text_of_element(self.SELECTED_RECURRENCE_ANDROID_EDIT)
        else:
            recurrence = self.ew.get_attribute(self.RECURRENCE, "name")
        return recurrence

    def set_start_date(self, start_date):
        """ Selects start date of budget
        :param start_date: str
        """
        if start_date == "random":
            start_date = str(
                datetime.date(int(datetime.date.today().year), random.randint(1, 12), random.randint(1, 28)))
        elif start_date == "future":
            start_date = str(datetime.date.today() + datetime.timedelta(days=random.randint(1, 5)))
        elif start_date == "today":
            start_date = str(datetime.date.today())
        elif start_date == "yesterday":
            start_date = str(datetime.date.today() - datetime.timedelta(days=1))
        elif start_date == "tomorrow":
            start_date = str(datetime.date.today() + datetime.timedelta(days=1))

        self.ew.wait_and_tap_element(self.START_DATE, 5)
        self.ew.wait_till_element_is_visible(self.CALENDAR_PICKER, 5)
        self.transaction_detail.set_calendar_month_year(start_date)
        self.transaction_detail.set_calendar_day(start_date)
        vr.validate_input_against_output(start_date, self.transaction_detail.get_date("start"))

    def set_end_date(self, end_date):
        """ Selects end date of budget
        :param end_date: str
        """
        start_date = self.transaction_detail.get_date("start")
        year_start, month_start, day_start = (int(x) for x in start_date.split('-'))
        start_date = datetime.date(year_start, month_start, day_start)

        if end_date == "random":
            end_date = str(start_date + datetime.timedelta(days=random.randint(1, 30)))
        elif end_date == "day_after_start_date":
            end_date = str(start_date + datetime.timedelta(days=1))
        else:
            year_end, month_end, day_end = (int(x) for x in end_date.split('-'))
            end_date = datetime.date(year_end, month_end, day_end)
            if start_date < end_date:
                end_date = str(end_date)
            else:
                raise ValueError(f"endDate {end_date} is not older than start_date {str(start_date)}")

        self.ew.wait_and_tap_element(self.END_DATE, 5)
        self.ew.wait_till_element_is_visible(self.CALENDAR_PICKER, 5)
        self.transaction_detail.set_calendar_month_year(end_date)
        self.transaction_detail.set_calendar_day(end_date)

        vr.validate_input_against_output(end_date, self.transaction_detail.get_date("end"))
예제 #8
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.rs = Resolutions(self.driver)
     self.transaction_detail = TransactionDetail(self.driver)
예제 #9
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.ew = ElementWrapper(self.driver)
     self.rs = Resolutions(self.driver)
예제 #10
0
class ExportActions:
    BACKDROP = "Backdrop"

    # WALLETS
    WALLETS = "Wallets"

    # PERIOD
    PERIOD = "Period"
    PERIOD_SIZE_PICKER = "Period size Picker"
    SELECT_DATE_RANGE_PICKER = "Select date range Picker"

    # FORMAT
    FORMAT = "Format"
    FORMAT_PICKER = "Format Picker"
    if PLATFORM == "Android":
        XLSX_TRUE = "Excel (.xlsx)-true"
    else:
        XLSX_TRUE = 'label == "Excel (.xlsx)-true"'

    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)

    def set_period(self, period):
        """ Selects period from period picker
        :param period: str
        """

        self.ew.wait_and_tap_element(self.PERIOD, 10)
        self.ew.wait_till_element_is_visible(self.PERIOD_SIZE_PICKER, 10)

        if period == "random":
            period = random.choice(vs.export_periods)

        res = self.rs.get_resolution()
        if PLATFORM == "Android":
            item_visible = self.ew.is_element_present(period)
            while item_visible is False:
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["default_picker_up_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["default_picker_up_y_end"]) \
                    .release().perform()
                item_visible = self.ew.is_element_present(period)
            self.ew.wait_and_tap_element(period, 5)
        else:
            item_visible = self.ew.get_attribute(period, "visible")
            while item_visible == "false":
                self.driver.execute_script(
                    "mobile: dragFromToForDuration", {
                        "duration":
                        "0.1",
                        "fromX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "fromY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_start"],
                        "toX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "toY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_end"]
                    })
                item_visible = self.ew.get_attribute(period, "visible")
            self.driver.execute_script("mobile: tap", {
                "x": 100,
                "y": 50,
                "element": self.ew.get_element(period)
            })

    def set_format(self, format):
        """ Selects format of file from format picker
        :param format: str
        """

        self.ew.wait_and_tap_element(self.FORMAT, 10)
        self.ew.wait_till_element_is_visible(self.FORMAT_PICKER, 10)

        if format == "random":
            format = random.choice(vs.export_formats)

        self.ew.wait_and_tap_element(format, 5)
예제 #11
0
class BudgetActions():
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.budgets_general = BudgetsGeneral(self.driver)
        self.budget_detail = BudgetDetail(self.driver)
        self.budget_overview = BudgetOverview(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)

    def create_budget(self, name, amount, currency, wallets, categories,
                      recurrence, start_date, end_date):
        """ Opens budget create screen and sets requested attributes of budget
        :param name: str
        :param amount: str
        :param currency: str or None
        :param wallets: str or int or None
        :param categories: str or int or None
        :param recurrence: str or None
        :param start_date: str or None
        :param end_date: str or None
        """
        self.budgets_general.go_to_budgets()
        self.open_budget_create_screen()
        self.budget_detail.set_name(name)
        self.budget_detail.set_amount(amount)
        if currency is not None:
            self.budget_detail.set_currency(currency)
        if wallets is not None:
            self.budget_detail.set_wallets(wallets)
        if categories is not None:
            self.budget_detail.set_categories(categories)
        if recurrence is not None:
            self.budget_detail.set_recurrence(recurrence)
        if start_date is not None:
            self.budget_detail.set_start_date(start_date)
        if end_date is not None:
            self.budget_detail.set_end_date(end_date)

    def save_budget(self):
        """Clicks on save budget button"""
        if self.driver.is_keyboard_shown():
            self.driver.hide_keyboard()
        self.ew.wait_and_tap_element(self.budget_detail.SAVE_BUDGET_BUTTON, 10)
        self.ew.wait_till_element_is_not_visible(
            self.budget_detail.SAVE_BUDGET_BUTTON, 10)

    def open_budget_create_screen(self):
        """Opens budget create screen, if button is not visible it swipes to it"""
        self.ew.wait_till_element_is_visible(
            self.budgets_general.BUDGETS_HEADER, 10)
        if PLATFORM == "Android":
            add_button_visible = self.ew.is_element_present(
                self.budgets_general.ADD_BUDGET_BUTTON)
            while add_button_visible is False:
                res = self.rs.get_resolution()
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["budget_overview_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["budget_overview_y_end"]) \
                    .release().perform()
                add_button_visible = self.ew.is_element_present(
                    self.budgets_general.ADD_BUDGET_BUTTON)
        else:
            add_button_visible = self.ew.get_attribute(
                self.budgets_general.ADD_BUDGET_BUTTON, "visible")
            while add_button_visible == "false":
                res = self.rs.get_resolution()
                self.driver.execute_script(
                    "mobile: dragFromToForDuration", {
                        "duration":
                        "0.1",
                        "fromX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "fromY":
                        self.rs.all_resolutions[f"{res}"]
                        ["budget_overview_y_start"],
                        "toX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "toY":
                        self.rs.all_resolutions[f"{res}"]
                        ["budget_overview_y_end"]
                    })
                add_button_visible = self.ew.get_attribute(
                    self.budgets_general.ADD_BUDGET_BUTTON, "visible")

        self.ew.wait_and_tap_element(self.budgets_general.ADD_BUDGET_BUTTON, 5)
        self.ew.wait_till_element_is_visible(self.budget_detail.BUDGET_HEADER,
                                             10)

    def open_budget(self):
        """Opens existing budget detail screen, if there is no budget created yet, it creates one"""
        self.budgets_general.go_to_budgets()
        self.ew.wait_till_element_is_visible(
            self.budgets_general.BUDGETS_HEADER, 5)
        if self.ew.is_element_present(
                self.budgets_general.BUDGET_ITEM) is False:
            self.create_budget(name="random",
                               amount="random",
                               currency=None,
                               wallets=None,
                               categories=None,
                               recurrence=None,
                               start_date=None,
                               end_date=None)
            self.save_budget()
            self.budgets_general.go_to_budgets()
        self.ew.wait_and_tap_element(self.budgets_general.BUDGET_ITEM, 5)
        self.ew.wait_and_tap_element(
            self.budget_overview.BUDGET_SETTINGS_BUTTON, 5)
        self.ew.wait_till_element_is_visible(self.budget_detail.BUDGET_HEADER,
                                             5)

    def edit_budget(self, name, amount, currency, wallets, categories,
                    recurrence, start_date, end_date):
        """ Opens budget edit screen and sets requested attributes of budget
        :param name: str or None
        :param amount: str or None
        :param currency: str or None
        :param wallets: str or int or None
        :param categories:  str or int or None
        :param recurrence: str or None
        :param start_date: str or None
        :param end_date: str or None
        """
        self.open_budget()
        if name is not None:
            if PLATFORM == "Android":
                self.ew.get_element(self.budget_detail.NAME_INPUT).clear()
            else:
                self.ew.get_element(
                    self.budget_detail.SELECTED_NAME_IOS).clear()
            self.budget_detail.set_name(name)
        if amount is not None:
            self.ew.wait_and_tap_element(self.budget_detail.AMOUNT_INPUT, 5)
            self.ew.wait_till_element_is_visible(
                self.budget_detail.NUMPAD_CLEAR, 10)
            for i in range(6):
                self.ew.tap_element(self.budget_detail.NUMPAD_CLEAR)
            self.ew.tap_element(self.budget_detail.NUMPAD_BACKDROP)
            self.budget_detail.set_amount(amount)
        if currency is not None:
            self.budget_detail.set_currency(currency)
        if wallets is not None:
            self.budget_detail.set_wallets(wallets)
        if categories is not None:
            self.budget_detail.set_categories(categories)
        if recurrence is not None:
            self.budget_detail.set_recurrence(recurrence)
        if start_date is not None:
            self.budget_detail.set_start_date(start_date)
        if end_date is not None:
            self.budget_detail.set_end_date(end_date)

    def delete_budget(self):
        """Deletes existing budget from budget detail screen"""
        self.ew.wait_and_tap_element(self.budget_detail.TRASH_ICON, 10)
        self.ew.wait_and_tap_element(self.budget_detail.DELETE_BUTTON, 10)
        self.ew.wait_till_element_is_visible(
            self.budgets_general.BUDGETS_HEADER, 10)
예제 #12
0
class TransactionTemplateValidator:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.period_filter = PeriodFilter(self.driver)
        self.rs = Resolutions(self.driver)
        self.timeline_general = TimelineGeneral(self.driver)
        self.transaction_detail = TransactionDetail(self.driver)
        self.transaction_validator = TransactionValidator(self.driver)

    def get_all_attributes(self):
        """ Getting all attributes of transaction template
        :return: dict
        """
        all_attributes = {
            "category": self.transaction_detail.get_category(),
            "amount": self.transaction_detail.get_amount(),
            "wallet_amount": self.transaction_detail.get_wallet_amount(),
            "currency": self.transaction_detail.get_currency(),
            "wallet": self.transaction_detail.get_wallet("transaction"),
            "start_date": self.transaction_detail.get_date("start"),
            "note": self.transaction_detail.get_note(),
            "labels": self.transaction_detail.get_labels(True),
            "photo": self.transaction_detail.get_photo(),
            "recurrence": self.transaction_detail.get_recurrence(),
            "end_date": self.transaction_detail.get_date("end"),
            "reminder": self.transaction_detail.get_reminder(),
        }

        return all_attributes

    def is_transaction_template_on_timeline(self, attributes):
        """ Checking if template is visible inside Scheduled section
        :param attributes: dict
        :return: bool
        """
        transaction_locator = f"regular/" \
                              f"{attributes['category']}/" \
                              f"{self.transaction_validator.adjust_amounts(attributes['amount'], attributes['wallet_amount'])[0]}/" \
                              f"{self.transaction_validator.adjust_amounts(attributes['amount'], attributes['wallet_amount'])[1]}/" \
                              f"{attributes['wallet']}/" \
                              f"undefined/" \
                              f"{self.transaction_validator.adjust_note(attributes['note'])}/" \
                              f"{self.transaction_validator.adjust_labels(attributes['labels'])}/" \
                              f"{str(attributes['photo']).lower()}/" \
                              f"{self.adjust_recurrence(attributes['recurrence'])}/" \
                              f"{self.adjust_end_date(attributes['end_date'])}/" \
                              f"{self.transaction_validator.adjust_reminder(attributes['reminder'])}"

        print(f'LOCATOR: {transaction_locator}')

        self.transaction_validator.prepare_timeline(
            attributes['start_date'],
            self.adjust_recurrence(attributes['recurrence']))

        android_timeout = time.time() + 60
        ios_timeout = time.time() + 5
        res = self.rs.get_resolution()
        is_transaction_present = self.ew.is_element_present(
            transaction_locator)

        while is_transaction_present is False:
            if PLATFORM == "Android":
                self.transaction_validator.swipe_android(res)
                is_transaction_present = self.ew.is_element_present(
                    transaction_locator)
                if time.time() > android_timeout:
                    return False
            else:
                is_transaction_present = self.ew.is_element_present(
                    transaction_locator)
                if time.time() > ios_timeout:
                    return False
        return True

    def adjust_recurrence(self, recurrence):
        """ Adjusting recurrence for template locator
        :param recurrence: str
        :return: str
        """
        if recurrence is None or recurrence == "never":
            return "undefined"
        else:
            return recurrence

    def adjust_end_date(self, end_date):
        """ Adjusting end date for template locator
        :param end_date: str
        :return: str
        """
        if end_date is None:
            return "undefined"
        else:
            return end_date
예제 #13
0
 def __init__(self, driver):
     self.driver = driver
     self.action = TouchAction(self.driver)
     self.log = logging.getLogger('log')
     self.rs = Resolutions(self.driver)
예제 #14
0
class TransactionValidator:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.period_filter = PeriodFilter(self.driver)
        self.rs = Resolutions(self.driver)
        self.timeline_general = TimelineGeneral(self.driver)
        self.transaction_detail = TransactionDetail(self.driver)

    def get_all_attributes(self):
        """ Getting all attributes of transaction
        :return: dict
        """
        all_attributes = {
            "category": self.transaction_detail.get_category(),
            "amount": self.transaction_detail.get_amount(),
            "wallet_amount": self.transaction_detail.get_wallet_amount(),
            "currency": self.transaction_detail.get_currency(),
            "wallet": self.transaction_detail.get_wallet("transaction"),
            "start_date": self.transaction_detail.get_date("start"),
            "note": self.transaction_detail.get_note(),
            "labels": self.transaction_detail.get_labels(True),
            "photo": self.transaction_detail.get_photo(),
            "reminder": self.transaction_detail.get_reminder(),
        }

        return all_attributes

    def is_transaction_on_timeline(self, attributes):
        """ Checking if transaction is visible inside Timeline or Scheduled section
        :param attributes: dict
        :return: bool
        """
        transaction_locator = f"regular/" \
                              f"{attributes['category']}/" \
                              f"{self.adjust_amounts(attributes['amount'], attributes['wallet_amount'])[0]}/" \
                              f"{self.adjust_amounts(attributes['amount'], attributes['wallet_amount'])[1]}/" \
                              f"{attributes['wallet']}/" \
                              f"undefined/" \
                              f"{self.adjust_note(attributes['note'])}/" \
                              f"{self.adjust_labels(attributes['labels'])}/" \
                              f"{str(attributes['photo']).lower()}/" \
                              f"undefined/" \
                              f"undefined/" \
                              f"{self.adjust_reminder(attributes['reminder'])}"

        print(f'LOCATOR: {transaction_locator}')

        self.prepare_timeline(attributes['start_date'], "undefined")

        android_timeout = time.time() + 60
        ios_timeout = time.time() + 5
        res = self.rs.get_resolution()
        is_transaction_present = self.ew.is_element_present(
            transaction_locator)

        while is_transaction_present is False:
            if PLATFORM == "Android":
                self.swipe_android(res)
                is_transaction_present = self.ew.is_element_present(
                    transaction_locator)
                if time.time() > android_timeout:
                    return False
            else:
                is_transaction_present = self.ew.is_element_present(
                    transaction_locator)
                if time.time() > ios_timeout:
                    return False
        return True

    def adjust_amounts(self, amount, wallet_amount):
        """ Adjusting amount for transaction locator
        :param amount: str
        :param wallet_amount: str
        :return: list of str
        """
        if wallet_amount is None:
            amount_final = amount
            wallet_amount_final = "undefined"
        else:
            amount_final = ""
            for i in wallet_amount:
                if i in [
                        "-", ".", "0", "1", "2", "3", "4", "5", "6", "7", "8",
                        "9"
                ]:
                    amount_final = amount_final + i

            wallet_amount_final = "{:.2f}".format(float(amount))
        return ["{:.2f}".format(float(amount_final)), wallet_amount_final]

    def adjust_note(self, note):
        """ Adjusting note for transaction locator
        :param note: str
        :return: str
        """
        if note is None:
            note = ""
        return note

    def adjust_labels(self, labels):
        """ Adjusting labels for transaction locator
        :param labels: list of str
        :return: list of str
        """
        if len(labels) > 0:
            labels_final = ""
            for i in labels:
                labels_final = labels_final + f",{i}"
            labels_final = labels_final[1:]
        else:
            labels_final = "undefined"

        return labels_final

    def adjust_reminder(self, reminder):
        """ Adjusting reminder for transaction locator
        :param reminder: str
        :return: str
        """
        if reminder is None or reminder == "Never":
            return "undefined"
        else:
            return reminder

    def prepare_timeline(self, start_date, recurrence):
        """ Prepares timeline for transaction search. Opening scheduled screen if transaction has future date.
        :param start_date: str
        :param recurrence: str
        """
        self.ew.wait_till_element_is_visible(
            self.timeline_general.NAVIGATION_TIMELINE, 30)
        year, month, day = (int(x) for x in start_date.split('-'))
        date = datetime.date(year, month, day)
        today = datetime.date.today()

        if date > today or recurrence != "undefined":

            if self.ew.is_element_present(
                    self.timeline_general.SCHEDULED_SCREEN) is False:
                self.ew.wait_till_element_is_visible(
                    self.timeline_general.TRANSACTION_SECTION, 20)
                self.timeline_general.open_scheduled_section()
            else:
                if PLATFORM == "Android":
                    time.sleep(5)
                else:
                    time.sleep(2)

        elif date < today:
            self.period_filter.set_filter_period(
                self.period_filter.ALL_TIME_PERIOD)

    def swipe_android(self, resolution):
        """ Looks into past by swiping on android phones
        :param resolution: str
        :return:
        """
        self.action.long_press(None, self.rs.all_resolutions[f"{resolution}"]["x"],
                               self.rs.all_resolutions[f"{resolution}"]["transaction_timeline_up_y_start"]) \
            .move_to(None, self.rs.all_resolutions[f"{resolution}"]["x"],
                     self.rs.all_resolutions[f"{resolution}"]["transaction_timeline_up_y_end"]) \
            .release().perform()
예제 #15
0
class BudgetValidator:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.budget_detail = BudgetDetail(self.driver)
        self.budgets_general = BudgetsGeneral(self.driver)
        self.budget_overview = BudgetOverview(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)
        self.transaction_detail = TransactionDetail(self.driver)

    def get_all_attributes(self):
        """ Getting all attributes of budget
        :return: dict
        """
        all_attributes = {
            "name": self.budget_detail.get_name(),
            "amount": self.budget_detail.get_amount(),
            "currency": self.budget_detail.get_currency(),
            "wallets": self.budget_detail.get_wallets(),
            "categories": self.budget_detail.get_categories(),
            "recurrence": self.budget_detail.get_recurrence(),
            "start_date": self.transaction_detail.get_date("start"),
            "end_date": self.transaction_detail.get_date("end")
        }

        return all_attributes

    def is_budget_existing(self, attributes):
        """ Checking if budget is visible inside Budgets section
        :param attributes: dict
        :return: bool
        """
        budget_locator = f"{attributes['name']}/" \
                         f"{attributes['amount']}/" \
                         f"{attributes['currency']}/" \
                         f"{self.adjust_wallets(attributes['wallets'])}/" \
                         f"{self.adjust_categories(attributes['categories'])}/" \
                         f"{self.adjust_recurrence(attributes['recurrence'])}/" \
                         f"{attributes['start_date']}/" \
                         f"{self.adjust_end_date(attributes['end_date'], attributes['start_date'], attributes['recurrence'])}"

        print(f'ATTRIBUTES: {attributes}')
        print(f'LOCATOR: {budget_locator}')

        if self.ew.is_element_present(self.budget_overview.OVERVIEW_BUTTON):
            self.ew.tap_element(self.budget_overview.BACK_BUTTON)
            self.ew.wait_till_element_is_visible(
                self.budgets_general.BUDGETS_HEADER, 10)

        android_timeout = time.time() + 30
        ios_timeout = time.time() + 5
        res = self.rs.get_resolution()
        is_budget_present = self.ew.is_element_present(budget_locator)
        while is_budget_present is False:
            if PLATFORM == "Android":
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["budget_overview_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["budget_overview_y_end"]) \
                    .release().perform()
                is_budget_present = self.ew.is_element_present(budget_locator)
                if time.time() > android_timeout:
                    return False
            else:
                is_budget_present = self.ew.is_element_present(budget_locator)
                if time.time() > ios_timeout:
                    return False
        return True

    def adjust_wallets(self, wallets):
        """ Adjusting wallets for budget locator
        :param wallets: str
        :return: str
        """
        if wallets == "All Wallets":
            return "undefined"
        elif wallets in ["0", "2", "3", "4", "5", "6", "7", "8", "9", "10"]:
            return wallets
        else:
            return "1"

    def adjust_categories(self, categories):
        """ Adjusting categories for budget locator
        :param categories: str
        :return: str
        """
        if categories == "All Expenses":
            return "undefined"
        else:
            return categories

    def adjust_recurrence(self, recurrence):
        """ Adjusting recurrence for recurrence locator
        :param recurrence: str
        :return: str
        """
        recurrences_in_app = [
            "once", "day", "week", "every two weeks", "month", "year"
        ]
        return recurrences_in_app[vs.budget_recurrences.index(recurrence)]

    def adjust_end_date(self, end_date, start_date, recurrence):
        """ Adjusting end date for budget locator
        :param end_date: str
        :param start_date: str
        :param recurrence: str
        :return: str
        """
        if end_date is None:
            year_start, month_start, day_start = (
                int(x) for x in start_date.split('-'))
            start_date = datetime.date(year_start, month_start, day_start)

            if (year_start % 4) == 0:
                if (year_start % 100) == 0:
                    if (year_start % 400) == 0:
                        is_year_leap = True
                    else:
                        is_year_leap = False
                else:
                    is_year_leap = True
            else:
                is_year_leap = False

            if recurrence == "Daily":
                end_date = start_date
            elif recurrence == "Weekly":
                end_date = str(start_date + datetime.timedelta(days=6))
            elif recurrence == "Biweekly":
                end_date = str(start_date + datetime.timedelta(days=13))
            elif recurrence == "Monthly":
                if month_start in ["01", "03", "05", "07", "08", "10", "12"]:
                    end_date = str(start_date + datetime.timedelta(days=30))
                elif month_start in ["04", "06", "09", "11"]:
                    end_date = str(start_date + datetime.timedelta(days=29))
                else:
                    if is_year_leap:
                        end_date = str(start_date +
                                       datetime.timedelta(days=28))
                    else:
                        end_date = str(start_date +
                                       datetime.timedelta(days=27))
            elif recurrence == "Yearly":
                if is_year_leap:
                    end_date = str(start_date + datetime.timedelta(days=365))
                else:
                    end_date = str(start_date + datetime.timedelta(days=364))

        return end_date
class TransferTemplateValidator:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.period_filter = PeriodFilter(self.driver)
        self.rs = Resolutions(self.driver)
        self.timeline_general = TimelineGeneral(self.driver)
        self.transaction_detail = TransactionDetail(self.driver)
        self.transaction_template_validator = TransactionTemplateValidator(
            self.driver)
        self.transaction_validator = TransactionValidator(self.driver)
        self.transfer_validator = TransferValidator(self.driver)

    def get_all_attributes(self):
        """ Getting all attributes of transfer template
        :return: dict
        """
        all_attributes = {
            "amount":
            self.transaction_detail.get_amount(),
            "currency":
            self.transaction_detail.get_currency(),
            "wallet_amount":
            self.transaction_detail.get_wallet_amount(),
            "outgoing_wallet":
            self.transaction_detail.get_wallet("transfer_outgoing"),
            "incoming_wallet":
            self.transaction_detail.get_wallet("transfer_incoming"),
            "start_date":
            self.transaction_detail.get_date("start"),
            "note":
            self.transaction_detail.get_note(),
            "recurrence":
            self.transaction_detail.get_recurrence(),
            "end_date":
            self.transaction_detail.get_date("end"),
            "reminder":
            self.transaction_detail.get_reminder(),
        }

        return all_attributes

    def is_transfer_template_on_timeline(self, attributes):
        """ Checking if transfer template is visible inside Scheduled section
        :param attributes: dict
        :return: bool
        """
        if "Out of Spendee" not in [
                attributes['outgoing_wallet'], attributes['incoming_wallet']
        ]:
            is_two_way_transfer = True
        else:
            is_two_way_transfer = False

        transfer_locator = f"transfer/" \
                           f"undefined/" \
                           f"{self.transfer_validator.adjust_amounts(attributes['amount'], attributes['wallet_amount'], attributes['currency'], attributes['outgoing_wallet'], attributes['incoming_wallet'])[0]}/" \
                           f"{self.transfer_validator.adjust_amounts(attributes['amount'], attributes['wallet_amount'], attributes['currency'], attributes['outgoing_wallet'], attributes['incoming_wallet'])[1]}/" \
                           f"{self.transfer_validator.adjust_wallets(attributes['outgoing_wallet'], attributes['incoming_wallet'])[0]}/" \
                           f"{self.transfer_validator.adjust_wallets(attributes['outgoing_wallet'], attributes['incoming_wallet'])[1]}/" \
                           f"{self.transaction_validator.adjust_note(attributes['note'])}/" \
                           f"undefined/" \
                           f"false/" \
                           f"{self.transaction_template_validator.adjust_recurrence(attributes['recurrence'])}/" \
                           f"{self.transaction_template_validator.adjust_end_date(attributes['end_date'])}/" \
                           f"{self.transaction_validator.adjust_reminder(attributes['reminder'])}"

        print(f'LOCATOR: {transfer_locator}')

        if is_two_way_transfer:
            _, _, amount, wallet_amount, outgoing_wallet, incoming_wallet, _, _, _, recurrence, _, _ = (
                str(x) for x in transfer_locator.split('/'))
            s_out = transfer_locator.split("/")
            s_in = transfer_locator.split("/")

            s_out[2] = f"-{amount}"
            if wallet_amount != "undefined":
                s_out[3] = f"-{wallet_amount}"

            if recurrence == "undefined":
                s_in[4] = incoming_wallet
                s_in[5] = outgoing_wallet

            transfer_outgoing_locator = '/'.join(s_out)
            transfer_incoming_locator = '/'.join(s_in)

            print(f'OUTGOING LOCATOR: {transfer_outgoing_locator}')
            print(f'INCOMING LOCATOR: {transfer_incoming_locator}')

        self.transaction_validator.prepare_timeline(
            attributes['start_date'],
            self.transaction_template_validator.adjust_recurrence(
                attributes['recurrence']))

        android_timeout = time.time() + 60
        ios_timeout = time.time() + 5
        res = self.rs.get_resolution()

        if is_two_way_transfer:
            locator = transfer_outgoing_locator
        else:
            locator = transfer_locator
        is_transfer_present = self.ew.is_element_present(locator)
        while is_transfer_present is False:
            if PLATFORM == "Android":
                self.transaction_validator.swipe_android(res)
                is_transfer_present = self.ew.is_element_present(locator)
                if time.time() > android_timeout:
                    return False
            else:
                is_transfer_present = self.ew.is_element_present(locator)
                if time.time() > ios_timeout:
                    return False
        if is_two_way_transfer and self.ew.is_element_present(
                transfer_incoming_locator) is False:
            return False
        return True
예제 #17
0
class TransactionDetail:
    # OTHER
    BACK_BUTTON = "Back Button"
    if PLATFORM == "Android":
        TRANSACTION_HEADER_TITLE = "Transaction Header Title"
    else:
        TRANSACTION_HEADER_TITLE = '**/XCUIElementTypeStaticText[`label == "Transaction Header Title"`]'
    SAVE_TRANSACTION_BUTTON = "Save Transaction Button"
    TRASH_ICON = "Trash Icon"
    DELETE_BUTTON = "Delete"

    # KEYBOARD
    KEYBOARD = {
        "0": "Numpad 0",
        "1": "Numpad 1",
        "2": "Numpad 2",
        "3": "Numpad 3",
        "4": "Numpad 4",
        "5": "Numpad 5",
        "6": "Numpad 6",
        "7": "Numpad 7",
        "8": "Numpad 8",
        "9": "Numpad 9",
        ".": "Numpad Decimal Point",
        ",": "Numpad Decimal Point"
    }
    NUMPAD_BACKDROP = "Numpad Backdrop"
    NUMPAD_CLEAR = "Numpad Clear"

    # TYPE AND CATEGORY
    CATEGORY_ICON = "Category Icon"
    EXPENSES_PICKER = "Expenses Picker"
    INCOME_PICKER = "Income Picker"
    TRANSACTION_PICKER = "Transaction Picker"
    TRANSFER_PICKER = "Transfer Picker"
    if PLATFORM == "Android":
        SELECTED_TYPE = "Selected Type"
    else:
        SELECTED_TYPE = '**/XCUIElementTypeStaticText[`label == "Selected Type"`]'
    GEAR_ICON = "Gear Icon"
    CONFIRM_CATEGORY_ICON = "Confirm Category Icon"

    # AMOUNT
    if PLATFORM == "Android":
        AMOUNT_INPUT = "//android.view.ViewGroup[@content-desc='Amount Input']/android.view.ViewGroup/android.widget.TextView"
        WALLET_AMOUNT = "//android.view.ViewGroup[@content-desc='Wallet Price']/android.widget.TextView"
    else:
        AMOUNT_INPUT = 'label == "Amount Input"'
        WALLET_AMOUNT = "//XCUIElementTypeOther[@name='Wallet Price']/XCUIElementTypeStaticText"

    # CURRENCY
    CONFIRM_BUTTON = "Confirm Button"
    if PLATFORM == "Android":
        CURRENCY = "Currency"
    else:
        CURRENCY = "(//XCUIElementTypeOther[@name='Currency'])[1]/XCUIElementTypeOther"
    SELECTED_CURRENCY_ANDROID = "//android.view.ViewGroup[@content-desc='Currency']/android.widget.TextView"

    # WALLET
    if PLATFORM == "Android":
        WALLET = "Wallet"
        OUTGOING_WALLET = "Outgoing Wallet"
        INCOMING_WALLET = "Incoming Wallet"
        WALLET_ITEM = '//android.view.ViewGroup[@content-desc="Select Wallet Picker"]/android.widget.ScrollView/' \
                      'android.view.ViewGroup/android.widget.ScrollView/android.view.ViewGroup/android.view.ViewGroup'
    else:
        WALLET = 'label == "Wallet"'
        OUTGOING_WALLET = 'label == "Outgoing Wallet"'
        INCOMING_WALLET = 'label == "Incoming Wallet"'
        WALLET_ITEM = "Wallet Item"
    WALLET_PICKER = "Select Wallet Picker"

    SELECTED_WALLET_ANDROID = "//android.view.ViewGroup[@content-desc='Wallet']//android.widget.TextView[2]"
    SELECTED_OUTGOING_WALLET_ANDROID = "//android.view.ViewGroup[@content-desc='Outgoing Wallet']//android.widget.TextView[2]"
    SELECTED_INCOMING_WALLET_ANDROID = "//android.view.ViewGroup[@content-desc='Incoming Wallet']//android.widget.TextView[2]"

    # START DATE
    CALENDAR_PICKER = "Select date Picker"
    SELECTED_START_DATE_ANDROID = "//android.view.ViewGroup[@content-desc='Start Date']/android.view.ViewGroup/android.widget.TextView"
    SELECTED_START_DATE_ANDROID_2 = "//android.view.ViewGroup[@content-desc='Start Date']/android.widget.TextView"
    SELECTED_END_DATE_ANDROID = "//android.view.ViewGroup[@content-desc='End Date']/android.view.ViewGroup/android.widget.TextView[2]"
    SELECTED_END_DATE_ANDROID_2 = "//android.view.ViewGroup[@content-desc='End Date']/android.widget.TextView[2]"
    SELECTED_START_DATE_ANDROID_BUDGET = '//android.view.ViewGroup[@content-desc="Start Date"]/android.view.ViewGroup/android.widget.TextView[2]'
    SELECTED_START_DATE_ANDROID_BUDGET_2 = '//android.view.ViewGroup[@content-desc="Start Date"]/android.widget.TextView[2]'
    SELECTED_END_DATE_ANDROID_BUDGET = '//android.view.ViewGroup[@content-desc="End Date"]/android.view.ViewGroup/android.widget.TextView[2]'
    SELECTED_END_DATE_ANDROID_BUDGET_2 = '//android.view.ViewGroup[@content-desc="End Date"]/android.widget.TextView[2]'
    if PLATFORM == "Android":
        START_DATE = "Start Date"
        ACTUAL_MONTH_YEAR = "//android.widget.SeekBar/android.widget.TextView"
    else:
        START_DATE = 'label == "Start Date"'
        ACTUAL_MONTH_YEAR = "(//XCUIElementTypeOther[@name='Select date Picker']//XCUIElementTypeOther[contains(@name,'undefined')])[2]"

    # NOTE
    if PLATFORM == "Android":
        NOTE = "Note"
        EXISTING_NOTE = "Note"
    else:
        NOTE = "//XCUIElementTypeTextView[@name='Note Write a note']"
        EXISTING_NOTE = '**/XCUIElementTypeTextView[`label == "Note"`]'
    SELECTED_NOTE_IOS = "Note"
    NOTE_ELEMENT = "Note Element"

    # LABELS
    LABELS = "Labels"
    LABEL_ITEM = "Label Item"
    if PLATFORM == "Android":
        LABEL_INPUT = "//android.widget.EditText"
    else:
        LABEL_INPUT = "//XCUIElementTypeTextField"
    NON_EXISTING_LABEL = "Non Existing Label"
    VISIBLE_LABELS_ANDROID = "//android.view.ViewGroup[@content-desc='Label Item']/android.widget.TextView"
    VISIBLE_LABELS_IOS = "//XCUIElementTypeOther[@name='Label Item']/XCUIElementTypeOther"
    SELECTED_LABELS_ANDROID = "//android.view.ViewGroup[@content-desc='Check Mark']/android.view.ViewGroup/ancestor::*[1]/following-sibling::*[1]"
    PREMIUM_LABEL_ALERT = "Premium Label Alert"
    NOT_NOW_BUTTON = "Now now"

    # PHOTO
    PHOTO = "Photo"
    SELECTED_PHOTO = "Selected Photo"
    if PLATFORM == "Android":
        CHOOSE_PHOTO = "//android.widget.TextView[2]"
        PHOTO_FOLDER = "//android.widget.RelativeLayout"
        PHOTO_ITEM = "(//android.support.v7.widget.RecyclerView/android.view.ViewGroup)[1]"
    else:
        CHOOSE_PHOTO = "Choose from Library…"
        PHOTO_FOLDER = "All Photos"
        PHOTO_ITEM = "(//XCUIElementTypeImage)[1]"
    ALLOW_PHOTO_ACCESS_ANDROID = "com.android.packageinstaller:id/permission_allow_button"

    # RECURRENCE
    if PLATFORM == "Android":
        RECURRENCE = "Recurrence"
    else:
        RECURRENCE = 'label == "Recurrence"'
    RECURRENCE_PICKER = "Recurrence Picker"
    SELECTED_RECURRENCE_ANDROID = "//android.view.ViewGroup[@content-desc='Recurrence']/android.view.ViewGroup/android.widget.TextView[2]"
    SELECTED_RECURRENCE_ANDROID_EDIT = '//android.view.ViewGroup[@content-desc="Recurrence"]/android.widget.TextView[2]'

    # END DATE
    if PLATFORM == "Android":
        END_DATE = "End Date"
    else:
        END_DATE = 'label == "End Date"'

    # REMINDER
    if PLATFORM == "Android":
        REMINDER = "Reminder"
    else:
        REMINDER = 'label == "Reminder"'
    REMINDER_PICKER = "Reminder Picker"
    SELECTED_REMINDER_ANDROID = "//android.view.ViewGroup[@content-desc='Reminder']/android.view.ViewGroup/android.widget.TextView[2]"

    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)

    def set_type_of_transaction(self, transaction_type):
        """ Selects type of transaction
        :param transaction_type: str
        """
        if transaction_type == "random":
            transaction_type = random.choice(
                [self.EXPENSES_PICKER, self.INCOME_PICKER])
        elif transaction_type == "opposite":
            actual_type = self.get_type_of_transaction()
            if actual_type == "Expenses":
                transaction_type = self.INCOME_PICKER
            else:
                transaction_type = self.EXPENSES_PICKER
        elif transaction_type == "expenses":
            transaction_type = self.EXPENSES_PICKER
        elif transaction_type == "income":
            transaction_type = self.INCOME_PICKER

        if transaction_type == self.EXPENSES_PICKER:
            v_input = "Expenses"
        else:
            v_input = "Income"

        self.ew.wait_and_tap_element(transaction_type, 5)
        if PLATFORM == "Android":
            time.sleep(0.5)

        vr.validate_input_against_output(v_input,
                                         self.get_type_of_transaction())

    def get_type_of_transaction(self):
        """ Gets type of transaction
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.SELECTED_TYPE, 5)
        if PLATFORM == "Android":
            return self.ew.get_text_of_element(self.SELECTED_TYPE)
        else:
            return self.ew.get_attribute(self.SELECTED_TYPE, "name")

    def set_type_to_transfer(self):
        """Will select type as transfer"""
        self.ew.wait_and_tap_element(self.TRANSFER_PICKER, 5)

        self.ew.wait_till_element_is_not_visible(self.TRANSFER_PICKER, 5)
        if self.ew.is_element_present(self.NUMPAD_BACKDROP):
            pass
        else:
            vr.validate_input_against_output("Transfer", self.get_category())

    def open_type_picker(self):
        """Opens type picker"""
        self.ew.wait_and_tap_element(self.CATEGORY_ICON, 10)

    def set_category(self, category):
        """ Selects category from category picker
        :param category: str
        """
        self.ew.wait_till_element_is_visible(self.TRANSACTION_PICKER, 5)
        if category == "random":
            category_visible = False
            timeout = time.time() + 15
            while category_visible is False:
                category = random.choice(vs.default_set_of_categories)
                category_visible = self.ew.is_element_present(
                    f"Category {category}")
                if time.time() > timeout:
                    break

        self.ew.tap_element(f"Category {category}")

        self.ew.wait_till_element_is_not_visible(self.TRANSACTION_PICKER, 5)
        if self.ew.is_element_present(self.NUMPAD_BACKDROP):
            pass
        else:
            vr.validate_input_against_output(category, self.get_category())

    def get_category(self):
        """ Gets selected category
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.TRANSACTION_HEADER_TITLE, 5)
        if PLATFORM == "Android":
            category = self.ew.get_text_of_element(
                self.TRANSACTION_HEADER_TITLE).split(" ")[1:]
        else:
            category = self.ew.get_attribute(self.TRANSACTION_HEADER_TITLE,
                                             "name").split(" ")[1:]
        return ' '.join(category)

    def set_amount(self, amount):
        """ Insert amount into amount input
        :param amount: str
        """
        if amount == "random":
            amount = str(random.randint(1, 99))

        self.ew.wait_till_element_is_visible(self.KEYBOARD["1"], 10)
        amount_list = list(amount)
        for i in amount_list:
            self.ew.wait_and_tap_element(self.KEYBOARD[i], 5)
        self.ew.wait_and_tap_element(self.NUMPAD_BACKDROP, 5)

        v_output = self.get_amount()
        if v_output.startswith("-"):
            v_output = v_output[1:]
        vr.validate_input_against_output(''.join(str(i) for i in amount_list),
                                         v_output)

    def get_amount(self):
        """ Gets amount from amount picker
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.AMOUNT_INPUT, 5)
        if PLATFORM == "Android":
            return self.ew.get_text_of_element(self.AMOUNT_INPUT)
        else:
            amount = self.ew.get_attribute(self.AMOUNT_INPUT, "name")
            if amount.startswith("+"):
                amount = amount[1:]
            return amount

    def get_wallet_amount(self):
        """ Gets wallet's amount when category has different currency
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.AMOUNT_INPUT, 5)
        try:
            if PLATFORM == "Android":
                return self.ew.get_text_of_element(self.WALLET_AMOUNT)
            else:
                return self.ew.get_attribute(self.WALLET_AMOUNT, "name")
        except NoSuchElementException:
            return None

    def set_currency(self, currency):
        """ Selects currency
        :param currency: str
        """
        if currency == "random":
            currency = random.choice(vs.accessible_currencies)
        self.ew.wait_and_tap_element(self.CURRENCY, 5)
        self.ew.wait_and_tap_element(f"Currency {currency}", 10)
        self.set_exchange_rate()

        vr.validate_input_against_output(currency, self.get_currency())

    def get_currency(self):
        """ Gets selected currency
        :return: str
        """
        self.ew.wait_till_element_is_visible(self.CURRENCY, 5)
        if PLATFORM == "Android":
            return self.ew.get_attribute(self.SELECTED_CURRENCY_ANDROID,
                                         "content-desc")
        else:
            return self.ew.get_attribute(self.CURRENCY, "name")

    def set_exchange_rate(self):
        self.ew.wait_and_tap_element(self.CONFIRM_BUTTON, 10)

    def set_wallet(self, wallet, type_of_wallet):
        """ Selects requested wallet from requested wallet picker
        :param wallet: str
        :param type_of_wallet: str
        """
        selected_wallet = self.get_wallet(type_of_wallet)
        if type_of_wallet == "transaction":
            self.ew.wait_and_tap_element(self.WALLET, 5)
        elif type_of_wallet == "transfer_outgoing":
            self.ew.wait_and_tap_element(self.OUTGOING_WALLET, 5)
        elif type_of_wallet == "transfer_incoming":
            self.ew.wait_and_tap_element(self.INCOMING_WALLET, 5)

        self.ew.wait_till_element_is_visible(self.WALLET_PICKER, 5)
        wallets_in_picker = self.get_wallets_in_picker()

        if wallet == "random":
            wallet = random.choice(wallets_in_picker)
            if PLATFORM == "Android":
                self.ew.tap_element(wallet)
            else:
                self.ew.tap_element(f'label == "{wallet}"')
        elif wallet == "different":
            wallets_in_picker.remove(selected_wallet)
            wallet = random.choice(wallets_in_picker)
            if PLATFORM == "Android":
                self.ew.tap_element(wallet)
            else:
                self.ew.tap_element(f'label == "{wallet}"')
        elif wallet == "oos":

            for i in wallets_in_picker:
                if i.startswith('Out of Spendee'):
                    postfix_oos = i.split('-')[1]

            wallet = f"Out of Spendee-{postfix_oos}"
            if PLATFORM == "Android":
                self.ew.tap_element(wallet)
            else:
                self.ew.tap_element(f'label == "{wallet}"')
        elif wallet == "not_oos":
            if self.ew.is_element_present("Out of Spendee-false"):
                wallets_in_picker.remove("Out of Spendee-false")
            elif self.ew.is_element_present("Out of Spendee-true"):
                wallets_in_picker.remove("Out of Spendee-true")
            wallet = random.choice(wallets_in_picker)
            if PLATFORM == "Android":
                self.ew.tap_element(wallet)
            else:
                self.ew.tap_element(f'label == "{wallet}"')
        else:
            if PLATFORM == "Android":
                self.ew.tap_element(wallet)
            else:
                self.ew.tap_element(f'label == "{wallet}"')

        self.ew.wait_till_element_is_not_visible(self.WALLET_PICKER, 5)
        if self.ew.is_element_present(self.CONFIRM_BUTTON):
            self.set_exchange_rate()

        v_input = wallet.split('-')[0]
        vr.validate_input_against_output(v_input,
                                         self.get_wallet(type_of_wallet))

    def get_wallet(self, type_of_wallet):
        """ Gets wallet name from requested wallet picker
        :param type_of_wallet: str
        :return: str
        """

        if PLATFORM == "Android":
            if type_of_wallet == "transaction":
                self.ew.wait_till_element_is_visible(
                    self.SELECTED_WALLET_ANDROID, 5)
                return self.ew.get_text_of_element(
                    self.SELECTED_WALLET_ANDROID)
            elif type_of_wallet == "transfer_outgoing":
                self.ew.wait_till_element_is_visible(
                    self.SELECTED_OUTGOING_WALLET_ANDROID, 5)
                return self.ew.get_text_of_element(
                    self.SELECTED_OUTGOING_WALLET_ANDROID)
            elif type_of_wallet == "transfer_incoming":
                self.ew.wait_till_element_is_visible(
                    self.SELECTED_INCOMING_WALLET_ANDROID, 5)
                return self.ew.get_text_of_element(
                    self.SELECTED_INCOMING_WALLET_ANDROID)
        else:
            if type_of_wallet == "transaction":
                self.ew.wait_till_element_is_visible(self.WALLET, 5)
                return self.ew.get_attribute(self.WALLET, "name")
            elif type_of_wallet == "transfer_outgoing":
                self.ew.wait_till_element_is_visible(self.OUTGOING_WALLET, 5)
                return self.ew.get_attribute(self.OUTGOING_WALLET, "name")
            elif type_of_wallet == "transfer_incoming":
                self.ew.wait_till_element_is_visible(self.INCOMING_WALLET, 5)
                return self.ew.get_attribute(self.INCOMING_WALLET, "name")

    def get_wallets_in_picker(self):
        """ Gets wallets names visible inside picker
        :return: list of str
        """
        if PLATFORM == "Android":
            return self.ew.get_attributes(self.WALLET_ITEM, "content-desc")
        else:
            return self.ew.get_attributes(self.WALLET_ITEM, "label")

    def set_start_date(self, start_date):
        """ Sets start date
        :param start_date: str
        """
        if start_date == "random":
            start_date = str(
                datetime.date(int(datetime.date.today().year),
                              random.randint(1, 12), random.randint(1, 28)))
        elif start_date == "past":
            start_date = str(datetime.date.today() -
                             datetime.timedelta(days=random.randint(1, 30)))
        elif start_date == "future":
            start_date = str(datetime.date.today() +
                             datetime.timedelta(days=random.randint(1, 30)))
        elif start_date == "today":
            start_date = str(datetime.date.today())
        elif start_date == "yesterday":
            start_date = str(datetime.date.today() -
                             datetime.timedelta(days=1))
        elif start_date == "tomorrow":
            start_date = str(datetime.date.today() +
                             datetime.timedelta(days=1))

        self.ew.wait_and_tap_element(self.START_DATE, 5)
        self.ew.wait_till_element_is_visible(self.CALENDAR_PICKER, 5)
        self.set_calendar_month_year(start_date)
        self.set_calendar_day(start_date)

        vr.validate_input_against_output(start_date, self.get_date("start"))

    def set_calendar_month_year(self, date):
        """ Swipes to requested month and year inside date picker
        :param date: str
        """
        year, month, day = (int(x) for x in date.split('-'))
        if PLATFORM == "Android":
            month_in_app = vs.calendar_months[self.ew.get_text_of_element(
                self.ACTUAL_MONTH_YEAR).split(" ")[0]]
            year_in_app = int(
                self.ew.get_text_of_element(
                    self.ACTUAL_MONTH_YEAR).split(" ")[1])
        else:
            month_in_app = vs.calendar_months[self.ew.get_attribute(
                self.ACTUAL_MONTH_YEAR, "label").split(" ")[0]]
            year_in_app = int(
                self.ew.get_attribute(self.ACTUAL_MONTH_YEAR,
                                      "label").split(" ")[1])

        direction = None
        if (year > year_in_app) or (year == year_in_app
                                    and month > month_in_app):
            direction = "up"
        elif (year < year_in_app) or (year == year_in_app
                                      and month < month_in_app):
            direction = "down"

        iterations = abs((year - year_in_app) * 12 + (month - month_in_app))
        res = self.rs.get_resolution()

        if PLATFORM == "Android":
            if direction == "up":
                for i in range(iterations):
                    self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                           self.rs.all_resolutions[f"{res}"]["calendar_picker_up_y_start"]) \
                        .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                                 self.rs.all_resolutions[f"{res}"]["calendar_picker_up_y_end"]) \
                        .release().perform()
            elif direction == "down":
                for i in range(iterations):
                    for d in range(2):
                        self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                               self.rs.all_resolutions[f"{res}"]["calendar_picker_down_y_start"]) \
                            .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                                     self.rs.all_resolutions[f"{res}"]["calendar_picker_down_y_end"]) \
                            .release().perform()
        elif PLATFORM == "iOS":
            if direction == "up":
                for i in range(iterations):
                    self.driver.execute_script(
                        "mobile: dragFromToForDuration", {
                            "duration":
                            "0.1",
                            "fromX":
                            self.rs.all_resolutions[f"{res}"]["x"],
                            "fromY":
                            self.rs.all_resolutions[f"{res}"]
                            ["calendar_picker_up_y_start"],
                            "toX":
                            self.rs.all_resolutions[f"{res}"]["x"],
                            "toY":
                            self.rs.all_resolutions[f"{res}"]
                            ["calendar_picker_up_y_end"]
                        })
            elif direction == "down":
                for i in range(iterations):
                    self.driver.execute_script(
                        "mobile: dragFromToForDuration", {
                            "duration":
                            "0.1",
                            "fromX":
                            self.rs.all_resolutions[f"{res}"]["x"],
                            "fromY":
                            self.rs.all_resolutions[f"{res}"]
                            ["calendar_picker_down_y_start"],
                            "toX":
                            self.rs.all_resolutions[f"{res}"]["x"],
                            "toY":
                            self.rs.all_resolutions[f"{res}"]
                            ["calendar_picker_down_y_end"]
                        })

    def set_calendar_day(self, date):
        """ Selects requested day inside date picker
        :param date: str
        """
        if PLATFORM == "Android":
            year, month, day = (int(x) for x in date.split('-'))
            if date == str(datetime.date.today()):
                self.ew.tap_element(
                    f"today {datetime.date(year, month, day).strftime('%A')} {datetime.date(year, month, day).strftime('%B')} {day} selected You have no entries for this day "
                )
            else:
                self.ew.tap_element(
                    f" {datetime.date(year, month, day).strftime('%A')} {datetime.date(year, month, day).strftime('%B')} {day} "
                )

        elif PLATFORM == "iOS":
            self.ew.wait_and_tap_element(
                f"native.calendar.SELECT_DATE_SLOT-{date}", 10)

    def get_date(self, type_of_date):
        """ Gets selected date
        :param type_of_date: str
        """
        is_transaction = self.ew.is_element_present(
            self.TRANSACTION_HEADER_TITLE)
        if type_of_date == "start":
            date_ios = self.START_DATE
            if is_transaction:
                date_android = self.SELECTED_START_DATE_ANDROID
            else:
                date_android = self.SELECTED_START_DATE_ANDROID_BUDGET
        else:
            date_ios = self.END_DATE
            if is_transaction:
                date_android = self.SELECTED_END_DATE_ANDROID
            else:
                date_android = self.SELECTED_END_DATE_ANDROID_BUDGET
        try:
            if PLATFORM == "Android":
                try:
                    self.ew.wait_till_element_is_visible(date_android, 5)
                except NoSuchElementException:
                    if type_of_date == "start":
                        if is_transaction:
                            date_android = self.SELECTED_START_DATE_ANDROID_2
                        else:
                            date_android = self.SELECTED_START_DATE_ANDROID_BUDGET_2
                    else:
                        if is_transaction:
                            date_android = self.SELECTED_END_DATE_ANDROID_2
                        else:
                            date_android = self.SELECTED_END_DATE_ANDROID_BUDGET_2
                date_in_app = self.ew.get_text_of_element(date_android)
            else:
                self.ew.wait_till_element_is_visible(date_ios, 5)
                date_in_app = self.ew.get_attribute(date_ios, "name")
        except (NoSuchElementException, AttributeError):
            return None

        if date_in_app == "Today" or date_in_app == "Yesterday?":
            date = str(datetime.date.today())
        elif date_in_app == "Yesterday":
            date = str(datetime.date.today() - datetime.timedelta(days=1))
        elif date_in_app == "Tomorrow":
            date = str(datetime.date.today() + datetime.timedelta(days=1))
        elif date_in_app is None or date_in_app == "Never":
            return None
        else:
            try:
                month, day, year = (str(x) for x in date_in_app.split(' '))
                day = day.replace(",", "")
            except ValueError:
                month, day = (str(x) for x in date_in_app.split(' '))
                year = str(datetime.date.today().year)
            month = str(datetime.datetime.strptime(month, "%B").month).zfill(2)
            date = f"{year}-{month}-{day.zfill(2)}"

        return date

    def set_note(self, note):
        """ Insert note into note input
        :param note: str
        """
        if note == "random":
            note = ''.join([
                random.choice(string.ascii_lowercase + string.digits)
                for n in range(0, 8)
            ])
        self.ew.wait_till_element_is_visible(self.NOTE_ELEMENT, 5)
        self.ew.get_element(self.NOTE).send_keys(note)
        if self.driver.is_keyboard_shown():
            self.ew.tap_element(self.NOTE_ELEMENT)

        vr.validate_input_against_output(note, self.get_note())

    def get_note(self):
        """ Gets note from note picker
        :return: str
        """
        try:
            if PLATFORM == "Android":
                note = self.ew.get_text_of_element(self.NOTE)
                if note == "Write a note":
                    note = ""
                return note
            else:
                return self.ew.get_text_of_elements(self.SELECTED_NOTE_IOS)[2]
        except IndexError:
            return None

    def set_label(self, label):
        """ Selects label from visible labels, if there is no requested label, it creates one
        :param label: str
        """
        self.ew.wait_till_element_is_visible(self.LABELS, 5)
        if label == "random":
            if self.ew.is_element_present(self.LABEL_ITEM):
                labels = self.get_labels(False)
                label = random.choice(labels)
                if PLATFORM == "Android":
                    self.ew.tap_element(label)
                    time.sleep(0.5)
                else:
                    i = labels.index(label)
                    self.action.tap(self.ew.get_elements(
                        self.LABEL_ITEM)[i]).perform()
                vr.validate_input_against_more_outputs(label,
                                                       self.get_labels(True))
            else:
                self.create_label(label)
        else:
            if PLATFORM == "Android":
                if self.ew.is_element_present(label):
                    self.ew.tap_element(label)
                    time.sleep(0.5)
                    vr.validate_input_against_more_outputs(
                        label, self.get_labels(True))
                else:
                    self.create_label(label)
            else:
                labels = self.get_labels(False)
                if label in labels:
                    i = labels.index(label)
                    self.action.tap(self.ew.get_elements(
                        self.LABEL_ITEM)[i]).perform()
                    vr.validate_input_against_more_outputs(
                        label, self.get_labels(True))
                else:
                    self.create_label(label)

    def fast_select_labels(self, number):
        """ Selects more labels on transaction detail screen
        :param number: int
        """
        self.ew.wait_till_element_is_visible(self.LABELS, 5)
        if self.ew.is_element_present(self.LABEL_ITEM):
            labels = self.get_labels(False)
            x = 0
            for i in labels:
                x = x + 1
                if x <= number:
                    if PLATFORM == "Android":
                        self.ew.tap_element(i)
                        time.sleep(0.5)
                    else:
                        label = labels.index(i)
                        self.action.tap(
                            self.ew.get_elements(
                                self.LABEL_ITEM)[label]).perform()

    def create_label(self, name):
        """ Creates labels on label picker
        :param name: str
        :return:
        """
        if name == "random":
            name = ''.join([
                random.choice(string.ascii_lowercase + string.digits)
                for n in range(0, 8)
            ])

        self.ew.tap_element(self.LABELS)
        self.ew.wait_and_tap_element(self.LABEL_INPUT, 5)
        self.ew.get_element(self.LABEL_INPUT).send_keys(name)
        self.ew.wait_and_tap_element(self.NON_EXISTING_LABEL, 5)
        self.ew.tap_element(self.BACK_BUTTON)

        vr.validate_input_against_more_outputs(name, self.get_labels(True))

    def get_labels(self, only_selected):
        """ Returns all visible or only selected labels
        :param only_selected: bool
        :return: list of str
        """
        self.ew.wait_till_element_is_visible(self.LABELS, 5)
        if PLATFORM == "Android":
            if only_selected:
                labels = self.ew.get_text_of_elements(
                    self.SELECTED_LABELS_ANDROID)
            else:
                labels = self.ew.get_text_of_elements(
                    self.VISIBLE_LABELS_ANDROID)
        else:
            attributes = self.ew.get_attributes(self.VISIBLE_LABELS_IOS,
                                                "name")
            labels = []
            for i in attributes:
                if only_selected:
                    if i.endswith("false") is False:
                        labels.append(i.split("-")[0])
                else:
                    labels.append(i.split("-")[0])

        return labels

    def set_photo(self):
        """Selects photo"""
        self.ew.wait_and_tap_element(self.PHOTO, 5)
        self.ew.wait_and_tap_element(self.CHOOSE_PHOTO, 5)
        if self.ew.is_element_present(self.ALLOW_PHOTO_ACCESS_ANDROID):
            self.ew.tap_element(self.ALLOW_PHOTO_ACCESS_ANDROID)
        if PLATFORM == "Android":
            self.ew.wait_and_tap_element(self.PHOTO_FOLDER, 5)
        self.ew.wait_and_tap_element(self.PHOTO_ITEM, 5)

        vr.validate_input_against_output(True, self.get_photo())

    def get_photo(self):
        """ Returns true if photo is selected
        :return: bool
        """
        try:
            self.ew.wait_till_element_is_visible(self.PHOTO, 5)
            self.ew.wait_till_element_is_visible(self.SELECTED_PHOTO, 3)
        except:
            NoSuchElementException()
        return self.ew.is_element_present(self.SELECTED_PHOTO)

    def set_recurrence(self, recurrence):
        """ Swipes to and selects requested recurrence
        :param recurrence: string
        """
        if recurrence == "random":
            recurrence = random.choice(vs.recurrences)

        self.ew.wait_and_tap_element(self.RECURRENCE, 5)
        self.ew.wait_till_element_is_visible(self.RECURRENCE_PICKER, 5)

        res = self.rs.get_resolution()
        if PLATFORM == "Android":
            item_visible = self.ew.is_element_present(recurrence)
            while item_visible is False:
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["default_picker_up_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["default_picker_up_y_end"]) \
                    .release().perform()
                item_visible = self.ew.is_element_present(recurrence)
            self.ew.wait_and_tap_element(recurrence, 5)
        else:
            item_visible = self.ew.get_attribute(recurrence, "visible")
            while item_visible == "false":
                self.driver.execute_script(
                    "mobile: dragFromToForDuration", {
                        "duration":
                        "0.1",
                        "fromX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "fromY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_start"],
                        "toX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "toY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_end"]
                    })
                item_visible = self.ew.get_attribute(recurrence, "visible")
            self.driver.execute_script(
                "mobile: tap", {
                    "x": 100,
                    "y": 50,
                    "element": self.ew.get_element(recurrence)
                })

        vr.validate_input_against_output(recurrence, self.get_recurrence())

    def get_recurrence(self):
        """ Gets selected recurrence
        :return: str
        """
        try:
            self.ew.wait_till_element_is_visible(self.RECURRENCE, 5)
            if PLATFORM == "Android":
                try:
                    recurrence = self.ew.get_text_of_element(
                        self.SELECTED_RECURRENCE_ANDROID).lower()
                except AttributeError:
                    recurrence = self.ew.get_text_of_element(
                        self.SELECTED_RECURRENCE_ANDROID_EDIT).lower()
            else:
                recurrence = self.ew.get_attribute(self.RECURRENCE,
                                                   "name").lower()

            if recurrence != "never" and PLATFORM == "Android":
                recurrences_in_app = [
                    "every day", "every 2 days", "every work day",
                    "every week", "every 2 weeks", "every 4 weeks",
                    "every month", "every 2 months", "every 3 months",
                    "every 6 months", "every year"
                ]
                recurrence = vs.recurrences[recurrences_in_app.index(
                    recurrence)]
            return recurrence
        except (AttributeError, NoSuchElementException):
            return None

    def set_end_date(self, end_date):
        """ Selects end date from date picker
        :param end_date: str
        """
        start_date = self.get_date("start")
        year_start, month_start, day_start = (int(x)
                                              for x in start_date.split('-'))
        start_date = datetime.date(year_start, month_start, day_start)

        if end_date == "random":
            end_date = str(start_date +
                           datetime.timedelta(days=random.randint(1, 30)))
        elif end_date == "day_after_start_date":
            end_date = str(start_date + datetime.timedelta(days=1))
        else:
            year_end, month_end, day_end = (int(x)
                                            for x in end_date.split('-'))
            end_date = datetime.date(year_end, month_end, day_end)
            if start_date < end_date:
                end_date = str(end_date)
            else:
                raise ValueError(
                    f"endDate {end_date} is not older than start_date {str(start_date)}"
                )

        self.ew.wait_and_tap_element(self.END_DATE, 5)
        self.ew.wait_till_element_is_visible(self.CALENDAR_PICKER, 5)
        self.set_calendar_month_year(end_date)
        self.set_calendar_day(end_date)

        vr.validate_input_against_output(end_date, self.get_date("end"))

    def set_reminder(self, reminder):
        """ Swipes to and selects requested reminder
        :param reminder: str
        """
        if reminder == "random":
            reminder = random.choice(vs.reminders)

        self.ew.wait_and_tap_element(self.REMINDER, 5)
        self.ew.wait_till_element_is_visible(self.REMINDER_PICKER, 5)

        res = self.rs.get_resolution()
        if PLATFORM == "Android":
            item_visible = self.ew.is_element_present(reminder)
            while item_visible is False:
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["default_picker_up_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["default_picker_up_y_end"]) \
                    .release().perform()
                item_visible = self.ew.is_element_present(reminder)
            self.ew.wait_and_tap_element(reminder, 5)
        else:
            item_visible = self.ew.get_attribute(reminder, "visible")
            while item_visible == "false":
                self.driver.execute_script(
                    "mobile: dragFromToForDuration", {
                        "duration":
                        "0.1",
                        "fromX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "fromY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_start"],
                        "toX":
                        self.rs.all_resolutions[f"{res}"]["x"],
                        "toY":
                        self.rs.all_resolutions[f"{res}"]
                        ["default_picker_up_y_end"]
                    })
                item_visible = self.ew.get_attribute(reminder, "visible")
            self.driver.execute_script("mobile: tap", {
                "x": 100,
                "y": 50,
                "element": self.ew.get_element(reminder)
            })

        vr.validate_input_against_output(reminder, self.get_reminder())

    def get_reminder(self):
        """ Gets selected reminder
        :return: str
        """
        try:
            self.ew.wait_till_element_is_visible(self.REMINDER, 5)
            if PLATFORM == "Android":
                reminder = self.ew.get_text_of_element(
                    self.SELECTED_REMINDER_ANDROID)
            else:
                reminder = self.ew.get_attribute(self.REMINDER, "name")

            if reminder != "Never":
                reminders_in_app = [
                    "On a transaction date", "1 day before", "2 days before",
                    "3 days before", "4 days before", "5 days before",
                    "6 days before", "7 days before"
                ]
                reminder = vs.reminders[reminders_in_app.index(reminder)]
            return reminder
        except (ValueError, NoSuchElementException):
            return None
예제 #18
0
class CategoryValidator:
    def __init__(self, driver):
        self.driver = driver
        self.action = TouchAction(self.driver)
        self.categories_general = CategoriesGeneral(self.driver)
        self.category_detail = CategoryDetail(self.driver)
        self.ew = ElementWrapper(self.driver)
        self.rs = Resolutions(self.driver)

    def get_all_attributes(self):
        """ Getting all attributes of category
        :return: dict
        """
        all_attributes = {
            "name": self.category_detail.get_name(),
            "color": self.category_detail.get_color(),
            "image": self.category_detail.get_image()
        }

        return all_attributes

    def is_category_existing(self, attributes):
        """ Checking if category is visible in category list
        :param attributes: dict
        :return: bool
        """
        category_locator = f"{attributes['name']}/" \
                           f"{attributes['color']}/" \
                           f"{attributes['image']}"

        print(f'ATTRIBUTES: {attributes}')
        print(f'LOCATOR: {category_locator}')

        android_timeout = time.time() + 30
        ios_timeout = time.time() + 5
        res = self.rs.get_resolution()
        is_category_present = self.ew.is_element_present(category_locator)
        while is_category_present is False:
            if PLATFORM == "Android":
                self.action.long_press(None, self.rs.all_resolutions[f"{res}"]["x"],
                                       self.rs.all_resolutions[f"{res}"]["categories_y_start"]) \
                    .move_to(None, self.rs.all_resolutions[f"{res}"]["x"],
                             self.rs.all_resolutions[f"{res}"]["categories_y_end"]) \
                    .release().perform()
                is_category_present = self.ew.is_element_present(
                    category_locator)
                if time.time() > android_timeout:
                    return False
            else:
                is_category_present = self.ew.is_element_present(
                    category_locator)
                if time.time() > ios_timeout:
                    return False
        return True

    def get_selected_categories(self):
        """ Gets all values of selected categories in merge process
        :return: tuple
        """
        categories = self.ew.get_attributes(
            self.categories_general.CATEGORY_INFO, "content-desc")
        name1, color1, image1 = categories[0].split('/')
        attributes1 = {"name": name1, "color": color1, "image": image1}
        name2, color2, image2 = categories[1].split('/')
        attributes2 = {"name": name2, "color": color2, "image": image2}
        return (attributes1, attributes2)