# 📱 Shadowstep (in development)
> Powerful and resilient Appium-based framework for Android UI automation.
[](https://pypi.org/project/appium-python-client-shadowstep/)
[](https://github.com/molokov-klim/Appium-Python-Client-Shadowstep/blob/main/LICENSE)

> [**Shadowstep**](https://www.twitch.tv/packetoff), step into the shadows and build your way
---
## 🔍 Overview
**Shadowstep** is an open-source framework, battle-tested and evolving.
It introduces powerful abstractions for Android testing: dynamic element wrappers, retry logic, visual change detection, and custom ADB terminal integration.
---
## ✨ Features
- 📲 **Robust UI Automation** – with custom `Element` class and retryable tap/click logic
- 🔁 **Automatic Session Recovery** – handles `NoSuchDriver`, `InvalidSessionId`, and reconnects
- 🎯 **Dict-to-XPath Locator DSL** – write intuitive locators like `{"class": "TextView", "text": "OK"}`
- 🎥 **Video + Screenshot Reporting** – Allure integration with visual context for failed steps
- 📷 **Visual DOM/Window Waits** – wait for or detect screen changes by screenshot diffs
- 👤 **Direct ADB Access** – push/pull/install/uninstall/interact with device via custom ADB wrapper
- 🧱 **Testable Components** – override every interaction and build new ones with ease
---
## 🚀 Quickstart
### 1. 📦 Installation
```bash
pip install appium-python-client-shadowstep
```
---
### 2. ⚙️ Integration via Composition
> ⚠️ Do **not** inherit from `Shadowstep` directly. Use composition to preserve singleton behavior.
```python
from shadowstep.shadowstep import Shadowstep
class ExamplePlatform:
def __init__(self):
self.app = Shadowstep.get_instance()
def __getattr__(self, item):
return getattr(self.app, item)
```
---
## 📚 PageObject Navigator
### ✅ Requirements for Shadowstep Pages (Auto-discovery)
### 📦 1. File Location
- Must reside in a directory named `pages`
- Filename must start with `page` and end with `.py`
> Example: `applications/android_settings/android_settings_7/pages/page_main/page_main.py`
### 🧩 2. Class Name
- Must start with `Page`, e.g. `PageMain7`
### 🧬 3. Inheritance
- Must inherit from `PageBase`:
```python
from shadowstep.page_base import PageBaseShadowstep
class PageMain7(PageBaseShadowstep): ...
```
### 🧠 4. Required: `edges` Property
Each page must define:
```python
@property
def edges(self) -> Dict[str, Callable[[], PageBase]]: # bullshit, typing here no needed
return {
"PageWifi7": self.to_wifi
}
```
Used by the navigation system to build the screen transition graph.
### 🔄 5. Navigation Methods
- Methods listed in `edges` must:
- trigger interaction (e.g. `tap()`)
- return the corresponding Page instance via `self.app.get_page(...)`
```python
def to_wifi(self) -> PageBase:
self.wifi.tap()
return self.app.get_page("PageWifi7")
```
### 🌐 6. Auto-discovery Mechanism
The `Shadowstep._auto_discover_pages()` method:
- Iterates over all paths in `sys.path`
- Looks for directories named `pages`
- Skips ignored folders (e.g. `__pycache__`, `venv`, etc.)
- Imports every module with a filename starting with `page`
- Registers each class that:
- starts with `Page`
- is a subclass of `PageBase`
- is **not** the base class itself
- Stores them in `self.pages`
- Adds them to the `PageNavigator`
---
## 📄 Example Page Class
```python
from shadowstep.page_base import PageBaseShadowstep
from shadowstep.element.element import Element
from typing import Dict, Callable
class PageExample(PageBaseShadowstep):
@property
def edges(self) -> Dict[str, Callable[[], PageBaseShadowstep]]:
return {"PageNext": self.to_next}
def to_next(self) -> PageBaseShadowstep:
self.next_button.tap()
return self.app.get_page("PageNext")
@property
def next_button(self) -> Element:
return self.app.get_element(locator={"text": "Next"})
```
---
## 🔮 Example Test
```python
def test_wifi_navigation(example_platform: ExamplePlatform):
page = example_platform.get_page("PageMain7")
assert page.is_current_page()
wifi_page = page.to_wifi()
assert wifi_page.is_current_page()
```
---
## 🔧 Under the Hood
- Supports retry logic with session recovery
- Lazy element evaluation until interaction
- ADB integration via custom wrapper
- Navigator auto-registers page transitions as a graph
---
## 🚫 Limitations
- Currently Android-only
- Web support not implemented
- Visual detection (image matching) WIP
---
## ✍️ Contributing
We welcome pull requests! Please open an issue before submitting large changes.
---
## ⚖️ License
[MIT License](LICENSE)
Raw data
{
"_id": null,
"home_page": null,
"name": "appium-python-client-shadowstep",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "appium, testing, uiautomator2, android, automation, framework",
"author": null,
"author_email": "Klim Molokov <klim.molokov@yandex.ru>",
"download_url": "https://files.pythonhosted.org/packages/fa/3e/6aa8d160e72c6367f307c9ac5e42df10150133b74a3210271d3502430926/appium_python_client_shadowstep-0.16.43.tar.gz",
"platform": null,
"description": "# \ud83d\udcf1 Shadowstep (in development)\n\n> Powerful and resilient Appium-based framework for Android UI automation.\n\n[](https://pypi.org/project/appium-python-client-shadowstep/)\n[](https://github.com/molokov-klim/Appium-Python-Client-Shadowstep/blob/main/LICENSE)\n\n\n\n> [**Shadowstep**](https://www.twitch.tv/packetoff), step into the shadows and build your way\n---\n\n## \ud83d\udd0d Overview\n**Shadowstep** is an open-source framework, battle-tested and evolving.\nIt introduces powerful abstractions for Android testing: dynamic element wrappers, retry logic, visual change detection, and custom ADB terminal integration.\n\n---\n\n## \u2728 Features\n\n- \ud83d\udcf2 **Robust UI Automation** \u2013 with custom `Element` class and retryable tap/click logic\n- \ud83d\udd01 **Automatic Session Recovery** \u2013 handles `NoSuchDriver`, `InvalidSessionId`, and reconnects\n- \ud83c\udfaf **Dict-to-XPath Locator DSL** \u2013 write intuitive locators like `{\"class\": \"TextView\", \"text\": \"OK\"}`\n- \ud83c\udfa5 **Video + Screenshot Reporting** \u2013 Allure integration with visual context for failed steps\n- \ud83d\udcf7 **Visual DOM/Window Waits** \u2013 wait for or detect screen changes by screenshot diffs\n- \ud83d\udc64 **Direct ADB Access** \u2013 push/pull/install/uninstall/interact with device via custom ADB wrapper\n- \ud83e\uddf1 **Testable Components** \u2013 override every interaction and build new ones with ease\n\n---\n\n## \ud83d\ude80 Quickstart\n\n### 1. \ud83d\udce6 Installation\n\n```bash\npip install appium-python-client-shadowstep\n```\n\n---\n\n### 2. \u2699\ufe0f Integration via Composition\n\n> \u26a0\ufe0f Do **not** inherit from `Shadowstep` directly. Use composition to preserve singleton behavior.\n\n```python\nfrom shadowstep.shadowstep import Shadowstep\n\nclass ExamplePlatform:\n def __init__(self):\n self.app = Shadowstep.get_instance()\n\n def __getattr__(self, item):\n return getattr(self.app, item)\n```\n\n---\n\n## \ud83d\udcda PageObject Navigator\n\n### \u2705 Requirements for Shadowstep Pages (Auto-discovery)\n\n### \ud83d\udce6 1. File Location\n- Must reside in a directory named `pages`\n- Filename must start with `page` and end with `.py`\n\n> Example: `applications/android_settings/android_settings_7/pages/page_main/page_main.py`\n\n### \ud83e\udde9 2. Class Name\n- Must start with `Page`, e.g. `PageMain7`\n\n### \ud83e\uddec 3. Inheritance\n- Must inherit from `PageBase`:\n\n```python\nfrom shadowstep.page_base import PageBaseShadowstep\n\n\nclass PageMain7(PageBaseShadowstep): ...\n```\n\n### \ud83e\udde0 4. Required: `edges` Property\nEach page must define:\n\n```python\n@property\ndef edges(self) -> Dict[str, Callable[[], PageBase]]: # bullshit, typing here no needed\n return {\n \"PageWifi7\": self.to_wifi\n }\n```\n\nUsed by the navigation system to build the screen transition graph.\n\n### \ud83d\udd04 5. Navigation Methods\n- Methods listed in `edges` must:\n - trigger interaction (e.g. `tap()`)\n - return the corresponding Page instance via `self.app.get_page(...)`\n\n```python\ndef to_wifi(self) -> PageBase:\n self.wifi.tap()\n return self.app.get_page(\"PageWifi7\")\n```\n\n### \ud83c\udf10 6. Auto-discovery Mechanism\n\nThe `Shadowstep._auto_discover_pages()` method:\n\n- Iterates over all paths in `sys.path`\n- Looks for directories named `pages`\n- Skips ignored folders (e.g. `__pycache__`, `venv`, etc.)\n- Imports every module with a filename starting with `page`\n- Registers each class that:\n - starts with `Page`\n - is a subclass of `PageBase`\n - is **not** the base class itself\n- Stores them in `self.pages`\n- Adds them to the `PageNavigator`\n\n---\n\n## \ud83d\udcc4 Example Page Class\n\n```python\nfrom shadowstep.page_base import PageBaseShadowstep\nfrom shadowstep.element.element import Element\nfrom typing import Dict, Callable\n\n\nclass PageExample(PageBaseShadowstep):\n @property\n def edges(self) -> Dict[str, Callable[[], PageBaseShadowstep]]:\n return {\"PageNext\": self.to_next}\n\n def to_next(self) -> PageBaseShadowstep:\n self.next_button.tap()\n return self.app.get_page(\"PageNext\")\n\n @property\n def next_button(self) -> Element:\n return self.app.get_element(locator={\"text\": \"Next\"})\n```\n\n---\n\n## \ud83d\udd2e Example Test\n\n```python\ndef test_wifi_navigation(example_platform: ExamplePlatform):\n page = example_platform.get_page(\"PageMain7\")\n assert page.is_current_page()\n\n wifi_page = page.to_wifi()\n assert wifi_page.is_current_page()\n```\n\n---\n\n## \ud83d\udd27 Under the Hood\n- Supports retry logic with session recovery\n- Lazy element evaluation until interaction\n- ADB integration via custom wrapper\n- Navigator auto-registers page transitions as a graph\n\n---\n\n## \ud83d\udeab Limitations\n- Currently Android-only\n- Web support not implemented\n- Visual detection (image matching) WIP\n\n---\n\n## \u270d\ufe0f Contributing\nWe welcome pull requests! Please open an issue before submitting large changes.\n\n---\n\n## \u2696\ufe0f License\n[MIT License](LICENSE)\n\n",
"bugtrack_url": null,
"license": null,
"summary": "UI Testing Framework powered by Appium Python Client",
"version": "0.16.43",
"project_urls": {
"Repository": "https://github.com/molokov-klim/Appium-Python-Client-Shadowstep"
},
"split_keywords": [
"appium",
" testing",
" uiautomator2",
" android",
" automation",
" framework"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "aa0c0fc434b4b241d6539a2aa6d7a8071adfaca9bfb369b27d5c791b18a3c99e",
"md5": "538f16f915d396302ddf67da14277507",
"sha256": "d888ef139793de462c44a9a17e058065bbb051366d0b3f864e6412f7bc1dc3f4"
},
"downloads": -1,
"filename": "appium_python_client_shadowstep-0.16.43-py3-none-any.whl",
"has_sig": false,
"md5_digest": "538f16f915d396302ddf67da14277507",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 96164,
"upload_time": "2025-04-29T14:41:36",
"upload_time_iso_8601": "2025-04-29T14:41:36.162014Z",
"url": "https://files.pythonhosted.org/packages/aa/0c/0fc434b4b241d6539a2aa6d7a8071adfaca9bfb369b27d5c791b18a3c99e/appium_python_client_shadowstep-0.16.43-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "fa3e6aa8d160e72c6367f307c9ac5e42df10150133b74a3210271d3502430926",
"md5": "0b6b52040985910c53c3b6f70c29b29b",
"sha256": "a8159e1e45e5692e09a1e33c930d452a86540261ed44fefbe5a3438a3ce7ec36"
},
"downloads": -1,
"filename": "appium_python_client_shadowstep-0.16.43.tar.gz",
"has_sig": false,
"md5_digest": "0b6b52040985910c53c3b6f70c29b29b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 90372,
"upload_time": "2025-04-29T14:41:37",
"upload_time_iso_8601": "2025-04-29T14:41:37.451102Z",
"url": "https://files.pythonhosted.org/packages/fa/3e/6aa8d160e72c6367f307c9ac5e42df10150133b74a3210271d3502430926/appium_python_client_shadowstep-0.16.43.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-04-29 14:41:37",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "molokov-klim",
"github_project": "Appium-Python-Client-Shadowstep",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"lcname": "appium-python-client-shadowstep"
}