<file_sep>Appium-Python-Client==0.49
selenium==4.0.0a3<file_sep>from appium.webdriver import Remote, WebElement
from appium.webdriver.common.mobileby import MobileBy
from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException, TimeoutException
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait


class BaseElement:

    def __init__(self, driver: Remote, locator_value: str, locator_by: str = MobileBy.ID):
        self.driver = driver
        self.locator_value = locator_value
        self.locator_by = locator_by

    def wait_for_element(self, timeout: int = 3) -> WebElement:
        return WebDriverWait(driver=self.driver, timeout=timeout) \
            .until(expected_conditions.presence_of_element_located((self.locator_by, self.locator_value)))

    def is_visible(self) -> bool:
        try:
            return self.wait_for_element().is_displayed()
        except (NoSuchElementException, StaleElementReferenceException, TimeoutException):
            return False


class BaseButton(BaseElement):
    def __init__(self, driver: Remote, locator_value: str, locator_by: str = MobileBy.ID, return_view=None):
        super(BaseButton, self).__init__(driver=driver, locator_value=locator_value, locator_by=locator_by)
        self.return_view = return_view

    def click(self):
        self.wait_for_element().click()
        return self.return_view


class BaseInput(BaseElement):

    def __init__(self, driver: Remote, locator_value: str, locator_by: str = MobileBy.ID):
        super(BaseInput, self).__init__(driver=driver, locator_value=locator_value, locator_by=locator_by)

    def set_value(self, value) -> None:
        self.wait_for_element().set_value(value)


class BaseText(BaseElement):
    def __init__(self, driver: Remote, locator_value: str, locator_by: str = MobileBy.ID):
        super(BaseText, self).__init__(driver=driver, locator_value=locator_value, locator_by=locator_by)

    @property
    def text(self) -> str:
        return self.wait_for_element().text
<file_sep>from appium.webdriver import Remote


def setup_driver():
    capabilities = dict()
    capabilities['deviceName'] = 'nexus_6'
    capabilities['platformName'] = 'Android'
    capabilities['appiumVersion'] = '1.15.1'
    capabilities['platformVersion'] = '9.0'
    capabilities['newCommandTimeout'] = 600
    capabilities['automationName'] = 'UiAutomator2'
    capabilities['noReset'] = False
    capabilities['appPackage'] = 'com.truecaller'
    capabilities['appActivity'] = 'com.truecaller.ui.TruecallerInit'
    capabilities['appWaitActivity'] = 'com.truecaller.ui.*'
    driver = Remote('http://localhost:4723/wd/hub', capabilities)
    driver.reset()
    driver.start_activity(capabilities['appPackage'], capabilities['appActivity'],
                          app_wait_activity=capabilities['appWaitActivity'])
    return driver
<file_sep>import sys

import json

from driver_setup import setup_driver
from views import StartView, CreateProfileView, CallsView, SearchView


def get_truecaller_user_data(phone_number: str):
    driver = setup_driver()
    start_view = StartView(driver)
    start_view.get_started_button.click()
    start_view.allow_permissions()
    if start_view.number_button.is_visible():
        start_view.number_button.click()
    start_view.agree_button.click()
    create_profile_view = CreateProfileView(driver)
    create_profile_view.fill_profile()
    calls_view = CallsView(driver)
    calls_view.truecaller_logo.click()
    search_view = SearchView(driver)
    search_view.search_input.set_value(phone_number)
    info_view = search_view.click_first_result()
    user_data = info_view.get_user_data()
    with open('output/user_data.json', 'w', encoding='utf-8') as f:
        json.dump(user_data, f, indent=4)
    driver.quit()


if __name__ == "__main__":
    get_truecaller_user_data(sys.argv[1])
<file_sep>## Test Task
This repository contains my solution to the Truecaller test task

### Required installations
* Download and install Android SDK: https://developer.android.com/studio/
* Download, install and run Appium: http://appium.io/
* Having Python3 installed with pip clone the repo and navigate to the root directory.
Install requirements: pip3 install requirements.txt
* Install Truecaller app on a real Android device: https://play.google.com/store/apps/details?id=com.truecaller&hl=en

### How to run it
* Connect your real Android device with PC via cable and enable USB debugging. Make sure WiFi or mobile data is turned on.
Do NOT run the script on your real device if you are already using Truecaller app! All application data will be deleted before the run
* Open command line and run truecaller.py file with a phone number as an argument
```python3 <path/to/the/file/truecaller.py> +1234567890```

Result file example:

```json
{
    "first_name": "Test",
    "last_name": "Name",
    "email": "<EMAIL>",
    "location": "Ukraine"
}
```<file_sep>import time

from appium.webdriver import Remote
from appium.webdriver.common.mobileby import MobileBy
from appium.webdriver.common.touch_action import TouchAction
from selenium.common.exceptions import NoSuchElementException, InvalidElementStateException
from typing import Dict

from elements import BaseButton, BaseInput, BaseText


class BaseView:
    def __init__(self, driver: Remote):
        self.driver = driver
        self.next_button = BaseButton(driver=self.driver, locator_value='com.truecaller:id/nextButton')
        self.continue_button = BaseButton(driver=self.driver, locator_value='android:id/button1')
        self.cancel_button = BaseButton(driver=self.driver, locator_value='android:id/button2')
        self.next = BaseButton(driver=self.driver, locator_value='com.truecaller:id/next')


class StartView(BaseView):
    def __init__(self, driver: Remote):
        super(StartView, self).__init__(driver)
        self.get_started_button = self.next_button
        self.allow_button = BaseButton(driver=self.driver,
                                       locator_value='com.android.packageinstaller:id/permission_allow_button')
        self.number_button = BaseButton(driver=self.driver, locator_value='com.truecaller:id/wizard_subscription_name',
                                        return_view=CreateProfileView)
        self.agree_button = BaseButton(driver=self.driver, locator_value='com.truecaller:id/agreeButton')

    def allow_permissions(self) -> None:
        if self.cancel_button.is_visible():
            self.cancel_button.click()
        if self.continue_button.is_visible():
            self.continue_button.click()
        if self.allow_button.is_visible():
            for _ in range(4):
                self.allow_button.click()


class CreateProfileView(BaseView):
    def __init__(self, driver: Remote):
        super(CreateProfileView, self).__init__(driver)
        self.type_name_button = BaseButton(driver=self.driver, locator_value='com.truecaller:id/manualInputButton')
        self.first_name_input = BaseInput(driver=self.driver, locator_value='com.truecaller:id/firstName')
        self.last_name_input = BaseInput(driver=self.driver, locator_value='com.truecaller:id/lastName')
        self.later_button = BaseButton(driver=self.driver, locator_value="//*[@text='LATER']",
                                       locator_by=MobileBy.XPATH, return_view=CallsView)

    def fill_profile(self) -> None:
        if not self.next.is_visible():
            self.type_name_button.wait_for_element(timeout=30)
            self.type_name_button.click()
            self.first_name_input.set_value('Test')
            self.last_name_input.set_value('Name')
            self.next_button.click()
        for _ in range(3):
            self.next.click()
        self.cancel_button.click()
        time.sleep(1)
        self.cancel_button.click()
        self.next.click()
        self.later_button.click()


class CallsView(BaseView):
    def __init__(self, driver: Remote):
        super(CallsView, self).__init__(driver)
        self.truecaller_logo = BaseButton(driver=self.driver, locator_value='com.truecaller:id/truecaller_logo',
                                          return_view=SearchView)


class UserInfoView(BaseView):
    def __init__(self, driver: Remote):
        super(UserInfoView, self).__init__(driver)
        self.user_name_text = BaseText(driver=self.driver, locator_value='com.truecaller:id/name_or_number')
        self.email_text = BaseText(
            driver=self.driver,
            locator_value="(//*[contains(@resource-id,'callerDetailedUserInfoContainer')]//android.widget.TextView)[1]",
            locator_by=MobileBy.XPATH)
        self.location_text = BaseText(
            driver=self.driver,
            locator_value="(//*[contains(@resource-id,'callerDetailedUserInfoContainer')]//android.widget.TextView)[2]",
            locator_by=MobileBy.XPATH)

    def get_user_data(self) -> Dict[str, str]:
        user_data = dict()
        user_name = self.user_name_text.text.split()
        user_data['first_name'] = user_name[0]
        try:
            user_data['last_name'] = user_name[1]
        except KeyError:
            user_data['last_name'] = str()
        try:
            user_data['email'] = self.email_text.text
        except NoSuchElementException:
            user_data['email'] = str()
        try:
            user_data['location'] = self.location_text.text
        except NoSuchElementException:
            user_data['location'] = str()
        return user_data


class SearchInput(BaseInput):
    def __init__(self, driver: Remote):
        super(SearchInput, self).__init__(driver=driver, locator_value='com.truecaller:id/search_field')
        self.camera_pop_up = BaseButton(driver=self.driver,
                                        locator_value="//*[contains(@text,'Point your camera at any phone')]",
                                        locator_by=MobileBy.XPATH)

    def set_value(self, value: str) -> None:
        if self.camera_pop_up.is_visible():
            try:
                action = TouchAction(self.driver)
                action.tap(None, 241, 720).perform()
            except InvalidElementStateException:
                pass
        super(SearchInput, self).set_value(value=value)


class SearchView(BaseView):
    def __init__(self, driver: Remote):
        super(SearchView, self).__init__(driver)
        self.search_input = SearchInput(driver=self.driver)

    def click_first_result(self) -> UserInfoView:
        time.sleep(3)
        element = BaseButton(driver=self.driver,
                             locator_value='//androidx.recyclerview.widget.RecyclerView/android.view.ViewGroup[1]',
                             locator_by=MobileBy.XPATH)
        element.click()
        return UserInfoView(self.driver)
