A production-style Python + pytest automation framework covering three platforms:
| Platform | App Under Test | Technology |
|---|---|---|
| Desktop | Windows Calculator | pywinauto (UIA backend) |
| Mobile | Android Calculator | Appium + UiAutomator2 |
| Web | Google Search | Selenium 4 + webdriver-manager |
Automation_Desktop_Mobile_Web/
│
├── config/ ← All configuration (no magic strings in tests)
│ ├── settings.py ← Global constants & timeouts
│ ├── desktop_config.py ← Windows app settings
│ ├── mobile_config.py ← Appium / Android capabilities
│ └── web_config.py ← Browser / Selenium settings
│
├── core/ ← Framework foundation (abstract base classes)
│ ├── base_driver.py ← ABC enforcing start() / quit() contract
│ ├── base_page.py ← ABC that every Page Object inherits from
│ └── logger.py ← Centralised logging (never use print())
│
├── drivers/ ← Platform-specific driver setup
│ ├── desktop_driver.py ← Launches calc.exe via pywinauto
│ ├── mobile_driver.py ← Opens Appium session for Android
│ └── web_driver.py ← Chrome / Firefox driver factory
│
├── locators/ ← All element locators — ONE place to update
│ ├── desktop/calculator_locators.py
│ ├── mobile/calculator_locators.py
│ └── web/google_locators.py
│
├── pages/ ← Page Object Model (POM) layer
│ ├── desktop/calculator_page.py
│ ├── mobile/calculator_page.py
│ ├── web/google_home_page.py
│ └── web/google_results_page.py
│
├── tests/ ← Test suites (one folder per platform)
│ ├── conftest.py ← Shared fixtures (desktop_window, mobile_driver, web_driver)
│ ├── desktop/test_desktop_calculator.py
│ ├── mobile/test_mobile_calculator.py
│ └── web/test_google_search.py
│
├── utils/
│ └── helpers.py ← Pure utility functions (retry, normalize_number)
│
├── pytest.ini ← Test runner configuration & marker registration
└── requirements.txt
# 1. Create and activate a virtual environment
python -m venv .venv
.venv\Scripts\activate # Windows
# 2. Install all dependencies
pip install -r requirements.txt# All desktop tests
pytest tests/desktop/ -m desktop
# Single test
pytest tests/desktop/test_desktop_calculator.py::test_addition# Prerequisites:
# 1. appium (start Appium server)
# 2. emulator -avd <AVD_NAME> (boot emulator)
# 3. adb devices (verify device visible)
pytest tests/mobile/ -m mobile
# Custom Appium URL:
pytest tests/mobile/ --appium-url=http://127.0.0.1:4723# Chrome (default)
pytest tests/web/ -m web
# Firefox
pytest tests/web/ -m web --browser=firefox
# Headless (CI mode — no browser window)
pytest tests/web/ -m web --headlesspytest -vpytest -m "not mobile" # skip mobile
pytest -m "desktop or web" # run only desktop + webpytest --html=reports/report.html --self-contained-htmlEvery screen has ONE class. Tests call methods, never use locators directly.
# Without POM (bad — locators duplicated in every test):
driver.find_element(By.NAME, "q").send_keys("Python")
# With POM (good — locator lives in GoogleHomePage):
google.enter_search_query("Python")BaseDriver and BasePage enforce that every subclass implements required methods.
class BaseDriver(ABC):
@abstractmethod
def start(self): ...
@abstractmethod
def quit(self): ...@pytest.fixture(scope="function") # new driver per test (safest)
@pytest.fixture(scope="module") # shared within one file (faster)
@pytest.fixture(scope="session") # shared for entire run (use with care)@pytest.mark.parametrize("expression, expected", [
("1+1=", "2"),
("12+7=", "19"),
])
def test_addition(desktop_window, expression, expected):
...# NEVER: time.sleep(3)
# ALWAYS: WebDriverWait
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.NAME, "q"))
)@pytest.fixture
def web_driver():
drv = WebDriver()
driver = drv.start() # SETUP
yield driver # test runs here
drv.quit() # TEARDOWN — always runs| Question | Answer |
|---|---|
| What is POM? | Design pattern where each screen = one class owning its locators and actions |
| implicit vs explicit wait? | Implicit: global timeout on every find; Explicit: per-element wait with conditions |
| Why yield in fixtures? | Guarantees teardown runs even if the test raises an exception |
| What is conftest.py? | Auto-loaded pytest plugin file; fixtures defined here are available without importing |
| How do you run only smoke tests? | pytest -m smoke |
| Why separate locators from pages? | Single place to update when UI changes; pages stay clean |
| ABC vs Interface? | Python has no interface keyword; ABCs (abstract methods) serve the same purpose |