Run the sample native app visual test

Step 1: Setup environment

Requirements

  • Python 3
  • Pytest framework
  • Appium client
  • Access key
    • You can generate an Access key using the following instruction: Access key
  • Billing unit
    • The billing unit is your team’s unique identifier, or just the word “personal” in the case of an individual account.
      More information can be found here.

Step 2: Upload and install an App

Before running tests, several preparations are required:

  • Upload the app (only once).
  • Find and take a Device
  • Install the App

All of these actions can be performed automatically via API.
For more details, please refer to the Upload and Install document.

Step 3: Add the interaction with visual testing API

After setup up the environment, you need to add the interaction with the visual testing API:

from typing import Optional

import pytest
import requests
from pydantic import BaseModel, Field

class VisualBuildInputData(BaseModel):
    name: str
    project: str
    branch: str


class VisualSnapshotInputData(BaseModel):
    name: str
    build_id: str
    image: Path
    test_name: Optional[str]
    suite_name: Optional[str]
    device: Optional[str]


class VisualBuildData(BaseModel):
    id: str
    name: str
    project: str
    branch: str
    status: str


class VisualSnapshotData(BaseModel):
    id: str
    name: str
    build_id: str = Field(alias="buildId")
    test_name: str = Field(alias="testName")
    suite_name: str = Field(alias="suiteName")
    device: Optional[str]


class VisualTestingAPI:
    def __init__(self, base_url: str, api_key: str):
        self.base_url = base_url
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        self.api_key = api_key



    def create_build(self, input_data: VisualBuildInputData) -> VisualBuildData:
        url = f"{self.base_url}/builds"
        response = requests.post(url, headers=self.headers, json=input_data.dict())
        response.raise_for_status()  # Raise an error if the request failed
        return VisualBuildData(**response.json())

    def finish_build(self, build_id: str) -> None:
        url = f"{self.base_url}/builds/{build_id}/finish"
        response = requests.post(url, headers=self.headers)
        response.raise_for_status()

    def submit_snapshot(self, data: VisualSnapshotInputData) -> VisualSnapshotData:
        url = f"{self.base_url}/builds/{data.build_id}/snapshots"
        files = {
            "image": (data.image.name, open(data.image, "rb"), "image/png")
        }
        payload = {
            "name": data.name,
            "testName": data.test_name,
            "suiteName": data.suite_name,
            "device": data.device,
        }
        response = requests.post(url, headers={"Authorization": f"Bearer {self.api_key}"}, files=files, data=payload)
        response.raise_for_status()  # Raise an error if the request failed
        return VisualSnapshotData(**response.json())

Step 4: Run sample test

After setting up the environment, installing the native App, and integrating the visual testing API, you can run your first native visual test

import tempfile
from pathlib import Path
from typing import Optional

import pytest
import requests
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from appium.webdriver.common.mobileby import MobileBy
from pydantic import BaseModel, Field
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait

PROJECT_NAME = "--------- BILLING UNIT -----------"
ACCESS_KEY = "--------- YOUR ACCESS KEY -----------"

APPIUM_HUB = "app.mobitru.com"
DEVICE_UDID = "00008101-001608482602001E"
BUNDLE_ID = "com.epam.mobitru.demoapp"
USER_NAME = "testuser@mobitru.com"
PASSWORD = "password1"
DEFAULT_IMPL_TIMEOUT_SEC = 3
DEFAULT_WAIT_TIMEOUT_SEC = 10
VISUAL_TESTING_API_HOST = "visual.mobitru.com"
VISUAL_TESTING_API_BASE_URL = f"https://{VISUAL_TESTING_API_HOST}/billing/unit/{PROJECT_NAME}/api/v1"


@pytest.fixture()
def visual_testing_api(request):
    return VisualTestingAPI(base_url=VISUAL_TESTING_API_BASE_URL, api_key=ACCESS_KEY)


@pytest.fixture()
def visual_testing_build(visual_testing_api):
    """create a new build """
    build_input_data = VisualBuildInputData(name='Build 1', project='Python iOS Native Automation Demo', branch='master')
    build_data = visual_testing_api.create_build(build_input_data)
    yield build_data
    visual_testing_api.finish_build(build_data.id)


@pytest.fixture(scope="function")
def driver():
    """Fixture to set up and tear down the Appium driver."""
    options = {
        "platformName": "iOS",
        "automationName": "XCUITest",
        "keepDevice": True,
        "udid": DEVICE_UDID,
        "bundleId": BUNDLE_ID
    }

    driver = webdriver.Remote(
        command_executor=f"https://{PROJECT_NAME}:{ACCESS_KEY}@{APPIUM_HUB}/wd/hub",
        desired_capabilities=options
    )

    # Implicit wait for devices with low performance
    driver.implicitly_wait(DEFAULT_IMPL_TIMEOUT_SEC)

    yield driver

    # Teardown
    driver.quit()


def test_demo(driver, visual_testing_api, visual_testing_build):
    """Test case for visual testing demo demo."""
    test_name = 'demo test ios'
    driver.find_element(AppiumBy.XPATH, "//XCUIElementTypeTextField[starts-with(@name,'Login')]").send_keys(
        USER_NAME)
    submit_snapshot(driver, result_name='after_enter_login', test_name=test_name,
                    visual_build_data=visual_testing_build,
                    visual_testing_api=visual_testing_api)
    driver.find_element(AppiumBy.XPATH,
                        "//XCUIElementTypeSecureTextField[starts-with(@name,'Password')]").send_keys(
        PASSWORD)
    submit_snapshot(driver, result_name='after_enter_password', test_name=test_name,
                    visual_build_data=visual_testing_build,
                    visual_testing_api=visual_testing_api)
    driver.find_element(AppiumBy.XPATH, "//XCUIElementTypeButton[starts-with(@name,'Sign in')]").click()
    WebDriverWait(driver, DEFAULT_WAIT_TIMEOUT_SEC).until(
        EC.presence_of_element_located((MobileBy.XPATH, "//*[starts-with(@name,'productHeaderViewLabel')]"))
    )
    submit_snapshot(driver, result_name='after_login', test_name=test_name,
                    visual_build_data=visual_testing_build,
                    visual_testing_api=visual_testing_api)


def submit_snapshot(
        driver,
        result_name,
        test_name,
        visual_build_data: VisualBuildData,
        visual_testing_api: VisualTestingAPI
) -> VisualSnapshotData:
    # Capture a screenshot and return the temporary file path
    screenshot_path = capture_screenshot(driver)

    # Build VisualSnapshotInputData
    snapshot_input_data = VisualSnapshotInputData(
        name=result_name,
        build_id=visual_build_data.id,
        test_name=test_name,
        device=driver.capabilities.get('udid'),
        suite_name=visual_build_data.project,  # Assuming suite name is the project name
        image=screenshot_path  # Screenshot file
    )
    return visual_testing_api.submit_snapshot(snapshot_input_data)


def capture_screenshot(driver) -> Path:
    with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix=".png") as tmp_file:
        # Get the path of the temporary file
        screenshot_path = tmp_file.name
        driver.save_screenshot(screenshot_path)  # Save the screenshot
        return Path(screenshot_path)
Scroll to Top