huskium


Namehuskium JSON
Version 1.0.7.post1 PyPI version JSON
download
home_pagehttps://github.com/uujohnnyuu/huskium
SummaryUI Automation Page Objects design pattern.
upload_time2025-02-25 03:40:49
maintainerNone
docs_urlNone
authorJohnny
requires_pythonNone
licenseApache 2.0
keywords huskium huskypo selenium appium page object automation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # huskium

## Table of Contents
- [Overview](#overview)
- [Usage](#usage)
- [Page Object Example Code](#page-object-example-code)
- [Timeout Settings](#timeout-settings)
- [Cache Settings](#cache-settings)
- [Log Settings](#log-settings)
- [Wait Actions](#wait-actions)
- [Appium Extended Actions](#appium-extended-actions)
- [Action Chains](#action-chains)
- [Select Actions](#select-actions)
- [Inheritance](#inheritance)
- [TODO](#todo)

---

## Copyright
### Developer: Johnny Chou

---

## Overview
- **huskium** is a Page Object framework built on Selenium and Appium.
- It leverages Python’s data descriptors to simplify and enhance UI automation.
- Currently tracking Appium v4.5.0 (released on 2025/01/22).
- Sphinx: https://uujohnnyuu.github.io/huskium/

---

## Usage
- **Build page objects** simply and elegantly using the `Page` and `Element(s)` classes.
- **Write test scripts** easily with the constructed Page objects.

---

## Page Object Example Code

### 1. Page Object
```python
# my_page.py

from huskium import Page, Element, Elements, By, dynamic

class MyPage(Page):

    # Static element: the standard way to define element.
    search_field = Element(By.NAME, 'q', remark='Search input box')
    search_results = Elements(By.TAG_NAME, 'h3', remark='All search results')
    search_result1 = Element(By.XPATH, '(//h3)[1]', remark='First search result')

    # Dynamic element: mainly used when element attributes are determined at runtime.
    @dynamic
    def search_result(self, order: int = 1):
        return Element(By.XPATH, f'(//h3)[{order}]', remark=f'Search result no.{order}')
```

### 2. Test Case
```python
# test_my_page.py

from selenium import webdriver
from my_page import MyPage

driver = webdriver.Chrome()
my_page = MyPage(driver)

my_page.get("https://google.com")

# Perform actions with automatic explicit waits.
my_page.search_field.send_keys("Selenium").submit()
my_page.search_results.wait_all_visible()
my_page.save_screenshot("screenshot.png")

assert "Selenium" in my_page.search_result1.text
my_page.search_result1.click()

driver.close()
```

### 3. Advanced Dynamic Element
```python
from huskium import Page, Element, By

class MyPage(Page):
    
    # Set a static element first. 
    static_search_result = Element()  

    # Method 1: Call `dynamic` to set `static_search_result`.
    def dynamic_search_result(self, order: int = 1):
        return self.static_search_result.dynamic(By.XPATH, f'(//h3)[{order}]', remark=f'NO.{order}')

    # Method 2: Use the data descriptor mechanism.
    def dynamic_search_result(self, order: int = 1):
        self.static_search_result = Element(By.XPATH, f'(//h3)[{order}]', remark=f'NO.{order}')
        return self.static_search_result
```

---

## Timeout Settings

### 1. Global Configuration
```python
from huskium import Timeout

# Default timeout for all Elements.
Timeout.DEFAULT = 30

# True: Raising TiemoutException if the process timed out.
# False: Return False if the process timed out.
Timeout.RERAISE = True
```

### 2. Priority
- **P1**: Method-Level (`page.element.wait_method(timeout=20)`)
- **P2**: Element-Level (`Element(..., timeout=10, ...)`)
- **P3**: Global-Level (`Timeout.DEFAULT = 60`)

---

## Cache Settings

### 1. Global Configuration
Caches the WebElement for each `Element` to improve performance.
Note that `Elements` does not support caching, as multiple elements are highly unstable.
```python
from huskium import Cache

# True: Caches the WebElement for each Element to improve performance.
# False: Does not cache the WebElement, re-locating the element for each operation.
Cache.ELEMENT = True
```

### 2. Priority
- **P1**: Element-Level (`Element(..., cache=False)`)
- **P2**: Global-Level (`Cache.ELEMENT = False`)

---

## Log Settings

### 1. Dubug Log Configuration
```python
from huskium import Log

# Capture log messages from frames where the name starts with 'test'.
# Set to None to disable filtering.
Log.PREFIX_FILTER.prefix = 'test'  

# Specify whether to filter logs by function name.
# If False, filtering is based on file (module) name instead.
Log.PREFIX_FILTER.funcframe = True

# Set to True for case-insensitive filtering.
Log.PREFIX_FILTER.lower = True
```

### 2. Debug Log Display Example
```log
# When Log.PREFIX_FILTER.prefix = None, logging behaves normally, 
# showing the first frame (stacklevel = 1).
2025-02-11 11:13:08 | DEBUG | element.py:574 | wait_clickable | Element(logout_button): Some message.

# When Log.PREFIX_FILTER.prefix = 'test', 
# logs display the first frame with a name starting with 'test' (stacklevel >= 1).
# This helps quickly trace the module and line where the issue occurs.
2025-02-11 11:13:22 | DEBUG | test_game.py:64 | test_game_flow | Element(logout_button): Some message.
```

### 3. Customize Log Filter
You can also apply the provided filters to your own logging as follows:
```python
from huskium import PrefixFilter, FuncPrefixFilter, FilePrefixFilter

# PrefixFilter includes both FuncPrefixFilter and FilePrefixFilter.
prefix_filter = PrefixFilter('test')
logging.getLogger().addFilter(prefix_filter)

# You can update the filter dynamically by accessing the attribute.
prefix_filter.prefix = 'others'
prefix_filter.funcframe = False
prefix_filter.lower = False

# If you only want to display module frames, directly use `FilePrefixFilter`.
run_module_filter = FilePrefixFilter('run')
logging.getLogger().addFilter(run_module_filter)

# If you only want to display func frames, directly use `FuncPrefixFilter`.
test_func_filter = FuncPrefixFilter('test')
logging.getLogger().addFilter(test_func_filter)
```
---

## Wait Actions

### 1. Basic Element Status
```python
# Single Element
page.element.wait_present()
page.element.wait_absent()
page.element.wait_visible()
page.element.wait_invisible()
page.element.wait_clickable()
page.element.wait_unclickable()
page.element.wait_selected()
page.element.wait_unselected()

# Multiple Elements
page.elements.wait_all_present()
page.elements.wait_all_absent()
page.elements.wait_all_visible()
page.elements.wait_any_visible()
```

### 2. Reverse Element States with Presence Check
```python
# For invisible and unclickable elements, absence is allowed by setting present=False:
page.element.wait_invisible(present=False)  # Can be either absent or invisible
page.element.wait_unclickable(present=False)  # Can be either absent or unclickable
```

---

## Appium Extended Actions

### Appium 2.0+ Usage
```python
from huskium import Offset, Area

# Page swipe or flick.
page.swipe_by()  # Default Offset.UP, Area.FULL
page.flick_by()  # Default Offset.UP, Area.FULL
page.swipe_by(Offset.UPPER_RUGHT, Area.FULL)
page.flick_by(Offset.LOWER_LEFT)

# Element swipe or flick until visible.
page.element.swipe_by()  # Default Offset.UP, Area.FULL
page.element.flick_by()  # Default Offset.UP, Area.FULL
page.element.swipe_by(Offset.UPPER_RUGHT)
page.element.flick_by(Offset.LOWER_LEFT, Area.FULL)

# Drawing gestures (e.g., "9875321" for reverse Z)
dots = page.elements.locations
page.draw_gesture(dots, "9875321")

# Drawing any lines between dots
dots = page.elements.locations
page.draw_lines(dots)
```

---

## Action Chains
```python
page.element.move_to_element().drag_and_drop().perform()
page.scroll_from_origin().double_click().perform()

# or
page.element.move_to_element().drag_and_drop()
page.scroll_from_origin().double_click()
...  # do something
page.perform()  # perform all actions
```

---

## Select Actions
```python
page.element.options
page.element.select_by_value("option_value")
```

---

## Inheritance

```python
from huskium import Page as HuskyPage, Element as HuskyElement

class Page(HuskyPage):
    def extended_func(self, par):
        ...

class Element(HuskyElement):
    def extended_func(self, par):
        ...
```

---

## TODO
- Keep tracking Appium version updates.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/uujohnnyuu/huskium",
    "name": "huskium",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "huskium, huskypo, selenium, appium, page object, automation",
    "author": "Johnny",
    "author_email": "johnny071531@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/ba/3c/072021c435060da2bf05a990952c60aacffef93f27c1105ddc03cb8dbc16/huskium-1.0.7.post1.tar.gz",
    "platform": null,
    "description": "# huskium\n\n## Table of Contents\n- [Overview](#overview)\n- [Usage](#usage)\n- [Page Object Example Code](#page-object-example-code)\n- [Timeout Settings](#timeout-settings)\n- [Cache Settings](#cache-settings)\n- [Log Settings](#log-settings)\n- [Wait Actions](#wait-actions)\n- [Appium Extended Actions](#appium-extended-actions)\n- [Action Chains](#action-chains)\n- [Select Actions](#select-actions)\n- [Inheritance](#inheritance)\n- [TODO](#todo)\n\n---\n\n## Copyright\n### Developer: Johnny Chou\n\n---\n\n## Overview\n- **huskium** is a Page Object framework built on Selenium and Appium.\n- It leverages Python\u2019s data descriptors to simplify and enhance UI automation.\n- Currently tracking Appium v4.5.0 (released on 2025/01/22).\n- Sphinx: https://uujohnnyuu.github.io/huskium/\n\n---\n\n## Usage\n- **Build page objects** simply and elegantly using the `Page` and `Element(s)` classes.\n- **Write test scripts** easily with the constructed Page objects.\n\n---\n\n## Page Object Example Code\n\n### 1. Page Object\n```python\n# my_page.py\n\nfrom huskium import Page, Element, Elements, By, dynamic\n\nclass MyPage(Page):\n\n    # Static element: the standard way to define element.\n    search_field = Element(By.NAME, 'q', remark='Search input box')\n    search_results = Elements(By.TAG_NAME, 'h3', remark='All search results')\n    search_result1 = Element(By.XPATH, '(//h3)[1]', remark='First search result')\n\n    # Dynamic element: mainly used when element attributes are determined at runtime.\n    @dynamic\n    def search_result(self, order: int = 1):\n        return Element(By.XPATH, f'(//h3)[{order}]', remark=f'Search result no.{order}')\n```\n\n### 2. Test Case\n```python\n# test_my_page.py\n\nfrom selenium import webdriver\nfrom my_page import MyPage\n\ndriver = webdriver.Chrome()\nmy_page = MyPage(driver)\n\nmy_page.get(\"https://google.com\")\n\n# Perform actions with automatic explicit waits.\nmy_page.search_field.send_keys(\"Selenium\").submit()\nmy_page.search_results.wait_all_visible()\nmy_page.save_screenshot(\"screenshot.png\")\n\nassert \"Selenium\" in my_page.search_result1.text\nmy_page.search_result1.click()\n\ndriver.close()\n```\n\n### 3. Advanced Dynamic Element\n```python\nfrom huskium import Page, Element, By\n\nclass MyPage(Page):\n    \n    # Set a static element first. \n    static_search_result = Element()  \n\n    # Method 1: Call `dynamic` to set `static_search_result`.\n    def dynamic_search_result(self, order: int = 1):\n        return self.static_search_result.dynamic(By.XPATH, f'(//h3)[{order}]', remark=f'NO.{order}')\n\n    # Method 2: Use the data descriptor mechanism.\n    def dynamic_search_result(self, order: int = 1):\n        self.static_search_result = Element(By.XPATH, f'(//h3)[{order}]', remark=f'NO.{order}')\n        return self.static_search_result\n```\n\n---\n\n## Timeout Settings\n\n### 1. Global Configuration\n```python\nfrom huskium import Timeout\n\n# Default timeout for all Elements.\nTimeout.DEFAULT = 30\n\n# True: Raising TiemoutException if the process timed out.\n# False: Return False if the process timed out.\nTimeout.RERAISE = True\n```\n\n### 2. Priority\n- **P1**: Method-Level (`page.element.wait_method(timeout=20)`)\n- **P2**: Element-Level (`Element(..., timeout=10, ...)`)\n- **P3**: Global-Level (`Timeout.DEFAULT = 60`)\n\n---\n\n## Cache Settings\n\n### 1. Global Configuration\nCaches the WebElement for each `Element` to improve performance.\nNote that `Elements` does not support caching, as multiple elements are highly unstable.\n```python\nfrom huskium import Cache\n\n# True: Caches the WebElement for each Element to improve performance.\n# False: Does not cache the WebElement, re-locating the element for each operation.\nCache.ELEMENT = True\n```\n\n### 2. Priority\n- **P1**: Element-Level (`Element(..., cache=False)`)\n- **P2**: Global-Level (`Cache.ELEMENT = False`)\n\n---\n\n## Log Settings\n\n### 1. Dubug Log Configuration\n```python\nfrom huskium import Log\n\n# Capture log messages from frames where the name starts with 'test'.\n# Set to None to disable filtering.\nLog.PREFIX_FILTER.prefix = 'test'  \n\n# Specify whether to filter logs by function name.\n# If False, filtering is based on file (module) name instead.\nLog.PREFIX_FILTER.funcframe = True\n\n# Set to True for case-insensitive filtering.\nLog.PREFIX_FILTER.lower = True\n```\n\n### 2. Debug Log Display Example\n```log\n# When Log.PREFIX_FILTER.prefix = None, logging behaves normally, \n# showing the first frame (stacklevel = 1).\n2025-02-11 11:13:08 | DEBUG | element.py:574 | wait_clickable | Element(logout_button): Some message.\n\n# When Log.PREFIX_FILTER.prefix = 'test', \n# logs display the first frame with a name starting with 'test' (stacklevel >= 1).\n# This helps quickly trace the module and line where the issue occurs.\n2025-02-11 11:13:22 | DEBUG | test_game.py:64 | test_game_flow | Element(logout_button): Some message.\n```\n\n### 3. Customize Log Filter\nYou can also apply the provided filters to your own logging as follows:\n```python\nfrom huskium import PrefixFilter, FuncPrefixFilter, FilePrefixFilter\n\n# PrefixFilter includes both FuncPrefixFilter and FilePrefixFilter.\nprefix_filter = PrefixFilter('test')\nlogging.getLogger().addFilter(prefix_filter)\n\n# You can update the filter dynamically by accessing the attribute.\nprefix_filter.prefix = 'others'\nprefix_filter.funcframe = False\nprefix_filter.lower = False\n\n# If you only want to display module frames, directly use `FilePrefixFilter`.\nrun_module_filter = FilePrefixFilter('run')\nlogging.getLogger().addFilter(run_module_filter)\n\n# If you only want to display func frames, directly use `FuncPrefixFilter`.\ntest_func_filter = FuncPrefixFilter('test')\nlogging.getLogger().addFilter(test_func_filter)\n```\n---\n\n## Wait Actions\n\n### 1. Basic Element Status\n```python\n# Single Element\npage.element.wait_present()\npage.element.wait_absent()\npage.element.wait_visible()\npage.element.wait_invisible()\npage.element.wait_clickable()\npage.element.wait_unclickable()\npage.element.wait_selected()\npage.element.wait_unselected()\n\n# Multiple Elements\npage.elements.wait_all_present()\npage.elements.wait_all_absent()\npage.elements.wait_all_visible()\npage.elements.wait_any_visible()\n```\n\n### 2. Reverse Element States with Presence Check\n```python\n# For invisible and unclickable elements, absence is allowed by setting present=False:\npage.element.wait_invisible(present=False)  # Can be either absent or invisible\npage.element.wait_unclickable(present=False)  # Can be either absent or unclickable\n```\n\n---\n\n## Appium Extended Actions\n\n### Appium 2.0+ Usage\n```python\nfrom huskium import Offset, Area\n\n# Page swipe or flick.\npage.swipe_by()  # Default Offset.UP, Area.FULL\npage.flick_by()  # Default Offset.UP, Area.FULL\npage.swipe_by(Offset.UPPER_RUGHT, Area.FULL)\npage.flick_by(Offset.LOWER_LEFT)\n\n# Element swipe or flick until visible.\npage.element.swipe_by()  # Default Offset.UP, Area.FULL\npage.element.flick_by()  # Default Offset.UP, Area.FULL\npage.element.swipe_by(Offset.UPPER_RUGHT)\npage.element.flick_by(Offset.LOWER_LEFT, Area.FULL)\n\n# Drawing gestures (e.g., \"9875321\" for reverse Z)\ndots = page.elements.locations\npage.draw_gesture(dots, \"9875321\")\n\n# Drawing any lines between dots\ndots = page.elements.locations\npage.draw_lines(dots)\n```\n\n---\n\n## Action Chains\n```python\npage.element.move_to_element().drag_and_drop().perform()\npage.scroll_from_origin().double_click().perform()\n\n# or\npage.element.move_to_element().drag_and_drop()\npage.scroll_from_origin().double_click()\n...  # do something\npage.perform()  # perform all actions\n```\n\n---\n\n## Select Actions\n```python\npage.element.options\npage.element.select_by_value(\"option_value\")\n```\n\n---\n\n## Inheritance\n\n```python\nfrom huskium import Page as HuskyPage, Element as HuskyElement\n\nclass Page(HuskyPage):\n    def extended_func(self, par):\n        ...\n\nclass Element(HuskyElement):\n    def extended_func(self, par):\n        ...\n```\n\n---\n\n## TODO\n- Keep tracking Appium version updates.\n",
    "bugtrack_url": null,
    "license": "Apache 2.0",
    "summary": "UI Automation Page Objects design pattern.",
    "version": "1.0.7.post1",
    "project_urls": {
        "Homepage": "https://github.com/uujohnnyuu/huskium"
    },
    "split_keywords": [
        "huskium",
        " huskypo",
        " selenium",
        " appium",
        " page object",
        " automation"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "300a80e0d98354ff137fdf687694c92f5a55febe245207d155331f857fa2b973",
                "md5": "86253f2b5bbeb8408577b2aaf2f4e107",
                "sha256": "22234b165e208b9d1e2934e4e69e9ce1994c23008c59df2937a946776f146e50"
            },
            "downloads": -1,
            "filename": "huskium-1.0.7.post1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "86253f2b5bbeb8408577b2aaf2f4e107",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 46391,
            "upload_time": "2025-02-25T03:40:47",
            "upload_time_iso_8601": "2025-02-25T03:40:47.747084Z",
            "url": "https://files.pythonhosted.org/packages/30/0a/80e0d98354ff137fdf687694c92f5a55febe245207d155331f857fa2b973/huskium-1.0.7.post1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ba3c072021c435060da2bf05a990952c60aacffef93f27c1105ddc03cb8dbc16",
                "md5": "542cca8803a51c9d9464ae1593c91bdc",
                "sha256": "fdbcf9a8f6b8d350c7497c77dcb9ee8890b7bf54138e27ffb8a5b9669a0261a8"
            },
            "downloads": -1,
            "filename": "huskium-1.0.7.post1.tar.gz",
            "has_sig": false,
            "md5_digest": "542cca8803a51c9d9464ae1593c91bdc",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 44434,
            "upload_time": "2025-02-25T03:40:49",
            "upload_time_iso_8601": "2025-02-25T03:40:49.936257Z",
            "url": "https://files.pythonhosted.org/packages/ba/3c/072021c435060da2bf05a990952c60aacffef93f27c1105ddc03cb8dbc16/huskium-1.0.7.post1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-02-25 03:40:49",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "uujohnnyuu",
    "github_project": "huskium",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "huskium"
}
        
Elapsed time: 8.09869s