# Appium-based Test Automation Framework for the Apollo Go application

Overview
=============
* Framework is based on the page object model.
* Allure is used as a reporting tool to present the results of the tests.
* Locators are stored in JSON files.
* Reading test data from JSON files.
* Reading credentials from environment variables.
* Integrated API client.
* Integrated DB client.
* Supports Requests interception/mocking data with configured proxy.
* Supports parallel execution using up to 4 iOS simulators
* Supports verification and asserts from assertpy library.
* Supports taking screenshots during failure and verification step.

SETUP:
===
1. Install Xcode. Go to App Store. Search for Xcode

2. Install node and npm: https://nodejs.org/en/download

3. Install the latest Appium and required dependencies globally using NPM.
Appium is an open-source test automation framework for use with native, hybrid, and mobile web apps. 
It drives iOS, Android, and Windows apps using the WebDriver protocol.
https://appium.io
Run the following command in the terminal to install Appium:
```
npm install -g appium@next
```

4. Appium's primary support for automating iOS apps is via the XCUITest driver. This driver leverages Apple's XCUITest libraries under the hood in order to facilitate automation of your app.
Run the following command in the terminal to install XCUITest driver:
```
appium driver install xcuitest
```

5. Create a new virtual environment:
```
python -m venv /path/to/new/virtual/environment
```

6. Move the pip.conf file located in the configs directory to the root of your virtual environment.

7. Install all requirements and dependencies:
```sh
pip install -r requirements/base-internal.txt
```

8. Download actual build in .app format and put it into root folder of the framework

9. Launch and start appium CLI

10. Make sure you have all required .env variables set:
    export BROWSER_NAME=ios
    export SELENIUM_TIMEOUT=20
    export SELENIUM_ENV=mobile



RUN:
====

Run all tests:
```sh
make test
```

Run specific test suite:
```sh
pytest --html=report.html --self-contained-html  --alluredir=allure_report tests/test_smoke.py -n 4 --reruns 1
```

To run tests in parallel add option:
```
-n {streams_count}
```

To add reruns for failed test:
```
--reruns {reruns_count}
```

REPORTING:
===
Both Allure and pytest's reports are available

Generating allure report:
```sh
make report
```
PyTest report is available after run as report.html file

CONFIGURATION OF THE FRAMEWORK
===
All test-data related configuration is stored under app/config/config.json file.

    [{
    "browser": "ios",
    "base_url": "https://qa.apollo.stream",
    "spotify_trackId": "1rgnBhdG2JDFTbYkYRZAku",
    "spotify_track_isrc": "QZES71982312"
    }]
    
And read the required field with AppConfig reader:

    self.api_client = ApiClient(AppConfig.get("api_auth_url"), AppConfig.get("qa_api_url"), 60)
    
From data safety perspective all credentials should be stored in env variables and can be readed via CoreConfig:

    self.token_portal = self.api_client.authorize() 


CREATION OF YOUR OWN UI TEST:
===
All application-related code is stored in app/ directory.
Pages, actions and business logic are stored in app/pages/ directory and named accordingly to represented page.

    @allure.step("verifying successful login with valid credentials")
    def login(self, email, password):
        self.wait_for_element_visible(*self.locator(self.login_locators, "sign_in"))
        self.click_element(*self.locator(self.login_locators, "sign_in"))
        self.wait_for_element_visible(*self.locator(self.login_locators, "email_field"))
        self.click_element(*self.locator(self.login_locators, "email_field"))
        self.send_text(email, *self.locator(self.login_locators, "email_field"))
        self.click_element(*self.locator(self.login_locators, "password_field"))
        self.send_text(password, *self.locator(self.login_locators, "password_field"))
        self.click_element(*self.locator(self.login_locators, "login_btn"))

UI elements described in app/element/ directory  in JSON files named accordingly to represented page. Locator description includes related page, location strategy (xpath, css..) and locator itself.

         [{
            "pageName": "LoginPage",
            "name": "login_button",
            "locateUsing": "xpath",
            "locator": "//a[contains(text(),'Log In')]"
         }]

Tests are stored in app/tests/ dir and describe test flow, test file should contain 'test' in file name in order to pytest can collect it (test_login.py)':

    @allure.story("POC scope")
    @allure.severity(allure.severity_level.CRITICAL)
    @allure.title("Verifying all users notifications")
    def test_login(self):
        login = CoreConfig.NOTIFICATIONS_EMAIL
        password = CoreConfig.NOTIFICATIONS_PASSWORD

        self.loginPage.login(login, password)
        self.ts.markFinal(self.loginPage.is_logged_in(), "user is logged in")

CREATION OF YOUR OWN API TEST:
===
API helper available under framework/api/ directory. Just add your endpoint description in appropriate client and don't forget to import it in your test.

    @allure.step("Getting portal playlists data from /spotify/filtr-track-playlists/?")
    def get_spotify_playlists_data_portal(self, *parameters, token):
        headers = {'authorization': 'Bearer {}'.format(token)}
        api_string = '/apollo-api/spotify/filtr-track-playlists/?'

        params_list = "&".join(parameters)
        resp = self._s.get(self.host_endpoint + api_string + params_list,
                           headers=headers, timeout=self.timeout)
        self.log.info(self.host_endpoint + api_string + params_list)
        response = {'status_code': resp.status_code, 'body': resp.json()}
        self.log.info(response)
        return response


    portal_start = self.api_client.get_spotify_playlists_data_portal("trackID=2ksOAxtIxY8yElEWw8RhgK",
                                                                     "limit=100",
                                                                     "offset=100",
                                                                     "market=us",
                                                                     token=self.token_mobile)
    total_potal = portal_start['body']['pagination']['total']
    
    
ESTABLISHING DATABASE CONNECTION:
===
In order to connect to the data base make sure your has tunnel into Sony area up and running.
Set the following creds in env variables:

MYSQL_DB_NAME
MYSQL_DB_HOST
MYSQL_DB_PORT
MYSQL_DB_USER
MYSQL_DB_PASS

And pass it to the DB helper:

    self.sql_client = MySqlClient(
            host=CoreConfig.MYSQL_DB_HOST,
            database=CoreConfig.MYSQL_DB_NAME,
            user=CoreConfig.MYSQL_DB_USER,
            password=CoreConfig.MYSQL_DB_PASS,
        )
        
Add your own queries to framework/db/mysql_db_client.py

TAKING SCREENSHOTS
===
Screenshots will be automatically taken during failure and attached to the allure report.
All screenshots will be saved under app/screenshots/ dir. 
    
    
CODE RECOMMENDATIONS AND STYLE GUIDE: 
====================================
1. Overall code style is following PEP8 (https://www.python.org/dev/peps/pep-0008/)
2. Tests are groped in sub-folder: tests_api for API and data validation checks, and tests_ui for UI E2E scenarios
3. UI test scripts built with usage of PageObject pattern
4. API-tests are written in procedure-like style to support parametrization
5. Code separated into 3 the layers: JSON describing app’s elements with class-chain(iOS) or xpath locators, page layer with business logic describing interactions with the app, test layer manipulating with the business logic to perform actual verifications.
6. Tests are grouped into separate files/suites accordingly to existing features
7. To save time for execution you can logically group several test cases on the same feature into E2E scenario
8. General test structure is AAA (Arrange, Act, Assert) test data on top, then business actions, then verifications
9. For the test data use the API, put preparation methods into helpers/api_data_provider.py
10. Move common data preparation tests out of the test on top of the test class
11. Avoid usage of hardcoded values and “magic numbers”, create variables with meaningful names to represent them or use config.json as data storage 
12. Use fixtures for tests setup/teardown
13. Each UI test should use @allure decorators to include actual name, description and links to test cases in Jira
14. For iOS tests usage of “class-chain” is a MUST as time saving strategy, xPath can be used only for exceptions like locating elements with axises or usage of ‘NOT’ boolean expression (consider creation of a ticket for adding test-id by developers team)
15. Naming: use meaningful nouns for objects, elements and variables, verbs with prefixes for methods and add "is"/"are" for verification methods
16. Use domain language from the manual test-cases for naming
17. Code structure on the pages layer is: dynamic elements description as constants, actions steps, getters/setters, verifications, common steps 
18. Make sure verifications steps return booleans, getters return values and actions return class object to support chain of invocations on a same page(if action leads to another logical page - return object of this page)
19. Don't hesitate to comment workarounds and complex business logic
20. Use chain of invocations in act section of a test case to reduce code lines
21. Remove unused code
22. Naming for API calls in /api folder is a full name of endpoint path including version (e.g GET /apollo-api/v1/apple-music/track/previous-playlists/ > get_v1_apple_music_track_previous_playlists(self, *parameters, token))
23. USE LINTERS BEFORE CREATION OF A PR "make pre-commit"
24. Always create appropriate branch once you start work on a new task
25. Create and push appropriate tag after release (ex.: git tag v13.0.35)
26. Use docstrings with detailed descriptions for helpers and utils methods
27. Don't create silent verifications, make sure each verification step contains clear error message in case of failure