huskypo


Namehuskypo JSON
Version 1.4.0 PyPI version JSON
download
home_pagehttps://github.com/uujohnnyuu/huskyPO
SummaryUI Automation Page Objects design pattern.
upload_time2025-01-07 02:56:47
maintainerNone
docs_urlNone
authorJohnny
requires_pythonNone
licenseApache 2.0
keywords huskypo page object selenium appium automation
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # huskyPO
---

## Copyright
- Developer: Johnny Chou
---

## Overview
- **huskyPO** stands for "husky Page Object", named "husky" to reflect 
the package’s elegance and agility, as well as the author’s fondness.
- **huskyPO** is a UI Page Object package built with Python’s data descriptors.
- **huskyPO** can simplify and enhance the creation of 
Python Selenium and Appium UI automation scripts.
---

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

## Page Object Example Code
1. Construct a Page object in any Python file, each page recommended to be a separate Page class.
```python
# my_page.py


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


class MyPage(Page):
    
    # Static element: The most common way to set up Page objects.
    # Element is a data descriptor of Page, allowing simple setup as shown below:
    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: Rarely used, typically determined during test case execution.
    # Must use @dynamic to enable the descriptor's functionality.
    @dynamic
    def search_result(self, order: int = 1):
        return Element(By.XPATH, f'//h3[{order}]', remark=f'Search result no.{order}')
    
    # For dynamic elements as properties, use the following format:
    @property
    @dynamic
    def keyword_results(self):
        return Elements(By.XPATH, f'//*[contains(text(), "{Keyword.text1}")]')
    
    # To record dynamic elements statically, use standard data descriptor dynamic assignment:
    # 1. Create a data descriptor object (e.g., static_element1).
    static_element1 = Element()
    
    # 2. Define a function to assign a tuple to the descriptor, 
    # the parameter logic is the same as Element(s).
    def dynamic_element(self, par) -> Element:
        self.static_element1 = (By.XPATH, f'//*[contains(text(), "{par}")]', f'This is {par}')
        return self.static_element1
    
    # 3. Alternatively, use a dict for clearer parameter names.
    def dynamic_element(self, par) -> Element:
        self.static_element1 = {
            'by': By.XPATH, 
            'value': f'//*[contains(text(), "{par}")]', 
            'remark': f'This is {par}'}
        return self.static_element1
```

2. After constructing the Page object, you can begin writing test cases.
```python
# test_my_page.py


from selenium import webdriver
from my_page import MyPage


class TestMyPage:
    
    driver = webdriver.Chrome()

    # Set up a page object. All actions will be triggered from this object.
    my_page = MyPage(driver)

    # The Page object can also call driver-related methods.
    my_page.get("https://google.com")

    # Example of a wait usage:
    # Wait until an element is visible, then take a screenshot.
    my_page.search_field.wait_visible()
    my_page.save_screenshot("my/file/image1.png")

    # All actions automatically handle explicit waits.
    # No need to manually call wait methods unless required, 
    # e.g. Equivalent to: 
    # my_page.search_field.wait_clickable().send_keys(keyword).wait_clickable().submit()
    my_page.search_field.send_keys(keyword).submit()

    # Various wait states are available.
    my_page.loading_image.wait_absent()
    my_page.search_results.wait_all_visible()
    my_page.save_screenshot("my/file/image2.png")

    # Assertions can be made directly:
    search_keyword = 'dinner'
    assert my_page.keyword_results(search_keyword).quantity > 1
    assert search_keyword in my_page.search_result1.text

    # Reuse found elements through existing sessions:
    # Once an element (e.g., `my_page.search_result1`) is located, 
    # it will use the same session unless the element becomes stale.
    # No need to store the found element in a separate variable.
    # Just perform actions directly:
    my_page.search_result1.click()
    ...

    driver.close()
```
---

## Timeout Global Settings
1.	In addition to setting timeouts for individual elements and methods, 
a global timeout setting is also available. See the example below:
```python
from huskyPO import Timeout


# Set the default timeout for all Elements.
# The huskyPO default is 30 seconds. You can change it as needed:
Timeout.DEFAULT = 60

# If you don’t want any waiting, you can also set it to 0:
Timeout.DEFAULT = 0

# Set the reraise behavior for timeouts on all Elements.
# The huskyPO default is True, with the following logic:
# True: Raise a TimeoutException if the element times out.
# False: Return False if the element times out, without raising a TimeoutException.
Timeout.RERAISE = False
```

2.	The priority order for timeout values is as follows:
- P1: Method-Level:
    - `page.element.wait_method(timeout=20)`
- P2: Element-Level:
    - `element = Element(..., timeout=10, ...)`
- P3: Global-Level:
    - `Timeout.DEFAULT = 60`

3.	The priority order for timeout reraise behavior is as follows:
- P1: Method-Level:
    - `page.element.wait_method(reraise=True)`
- P2: Global-Level:
    - `Timeout.RERAISE = False`
---

## Wait Actions
We offer a comprehensive set of wait methods, 
extending the official expected_conditions in `ec_extension.py` 
and encapsulating them into corresponding methods. 
Below are the extended methods for Element(s):
```python
# 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()

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

## Appium Extended Actions
We have extended Appium with highly convenient action features, including:
```python
# Offset allows you to define swipe directions. 
# It supports eight standard directions: 
# UP, DOWN, LEFT, RIGHT, UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT.
# Area lets you define the swipeable range, 
# defaulting to the full device screen (FULL), or you can customize it.

from huskypo import Offset, Area

# Page swiping. Refer to function docstrings for details.
page.swipe_by(Offset.UP, Area.FULL)
page.swipe_by(Offset.DOWN)
page.swipe_by(Offset.LEFT)
page.swipe_by(Offset.RIGHT)
page.swipe_by(Offset.UPPER_LEFT)
page.swipe_by(Offset.UPPER_RIGHT)
page.swipe_by(Offset.LOWER_LEFT)
page.swipe_by(Offset.LOWER_RIGHT)

# Page flicking. Refer to function docstrings for details.
# All Offset directions are supported.
page.flick_by(Offset.UP, Area.FULL)

# Element swiping until an element is visible.
# All Offset directions are supported.
page.element.swipe_by(Offset.UP, Area.FULL)

# Element flicking until an element is visible.
# All Offset directions are supported.
page.element.flick_by(Offset.UP, Area.FULL)

# Page draw lines.
# Define dots coordinates
dots = [{"x": x1, "y": y1}, {"x": x2, "y": y2}, {"x": x3, "y": y3}, ...]
# Alternatively, use element locations if available
dots = page.elements.locations
page.draw_lines(dots)

# Page draw gesture.
# 9-grid coordinates, or define your own
dots = page.elements.locations  
# 9-grid gesture string (1–9 represent grid positions). This example draws a reverse Z.
gesture = "9875321"  
page.draw_gesture(dots, gesture)
```

## Other Actions
All Selenium and Appium features are included. Here are some examples:
```python
# ActionChains
page.element.scroll_to_element().perform()
page.element.move_to_element().drag_and_drop().perform()

# Temporarily store ActionChains and execute later
page.element.move_to_element().drag_and_drop()
...  # Process other logic before executing perform()
page.element.perform()

# Select options
page.element.options
page.element.select_by_value()
```

## Logstack
Using logstack to log specific frame information.
The logstack module extends logging functionality, 
allowing you to capture information for specific frames, 
such as those starting with a designated prefix (e.g., test), 
without tracing all frames manually.
```python
from huskypo import logstack, logconfig
import logging  # Optional: You can also use logging for basic configuration.

# Configure logging using either logging.basicConfig() or logconfig.basic()
logconfig.basic(file="./log.log")  # Simplified configuration with common settings

# Use logstack in your code to log specific frames
def some_func():
    ...
    # Logs information from the first frame with the prefix (default: test)
    logstack.info("Log from some function.", prefix="test")

def test_func():
    ...
    # Logs frame info for test_func, not some_func
    some_func()

# Example log output:
# 2025-01-04 18:20:48 | INFO | testing.py:32 | test_func | Log from some function.
```

## TODO
- Continuously monitor new features in Selenium 4.0 and Appium 2.0.
- Other optimizations.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/uujohnnyuu/huskyPO",
    "name": "huskypo",
    "maintainer": null,
    "docs_url": null,
    "requires_python": null,
    "maintainer_email": null,
    "keywords": "huskypo, page object, selenium, appium, automation",
    "author": "Johnny",
    "author_email": "johnny071531@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/95/68/9c0b0d87dc6197fa87bb074770b42618c64a5df477afb24e0b33b9d7ce47/huskypo-1.4.0.tar.gz",
    "platform": null,
    "description": "# huskyPO\n---\n\n## Copyright\n- Developer: Johnny Chou\n---\n\n## Overview\n- **huskyPO** stands for \"husky Page Object\", named \"husky\" to reflect \nthe package\u2019s elegance and agility, as well as the author\u2019s fondness.\n- **huskyPO** is a UI Page Object package built with Python\u2019s data descriptors.\n- **huskyPO** can simplify and enhance the creation of \nPython Selenium and Appium UI automation scripts.\n---\n\n## Usage\n- **Build page objects** simply and elegantly using the `Page` and `Element(s)` classes.\n- **Write test scripts** simply and elegantly using the constructed Page objects.\n---\n\n## Page Object Example Code\n1. Construct a Page object in any Python file, each page recommended to be a separate Page class.\n```python\n# my_page.py\n\n\nfrom huskypo import Page, Element, Elements\nfrom huskypo import By\nfrom huskypo import dynamic\n\n\nclass MyPage(Page):\n    \n    # Static element: The most common way to set up Page objects.\n    # Element is a data descriptor of Page, allowing simple setup as shown below:\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: Rarely used, typically determined during test case execution.\n    # Must use @dynamic to enable the descriptor's functionality.\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    # For dynamic elements as properties, use the following format:\n    @property\n    @dynamic\n    def keyword_results(self):\n        return Elements(By.XPATH, f'//*[contains(text(), \"{Keyword.text1}\")]')\n    \n    # To record dynamic elements statically, use standard data descriptor dynamic assignment:\n    # 1. Create a data descriptor object (e.g., static_element1).\n    static_element1 = Element()\n    \n    # 2. Define a function to assign a tuple to the descriptor, \n    # the parameter logic is the same as Element(s).\n    def dynamic_element(self, par) -> Element:\n        self.static_element1 = (By.XPATH, f'//*[contains(text(), \"{par}\")]', f'This is {par}')\n        return self.static_element1\n    \n    # 3. Alternatively, use a dict for clearer parameter names.\n    def dynamic_element(self, par) -> Element:\n        self.static_element1 = {\n            'by': By.XPATH, \n            'value': f'//*[contains(text(), \"{par}\")]', \n            'remark': f'This is {par}'}\n        return self.static_element1\n```\n\n2. After constructing the Page object, you can begin writing test cases.\n```python\n# test_my_page.py\n\n\nfrom selenium import webdriver\nfrom my_page import MyPage\n\n\nclass TestMyPage:\n    \n    driver = webdriver.Chrome()\n\n    # Set up a page object. All actions will be triggered from this object.\n    my_page = MyPage(driver)\n\n    # The Page object can also call driver-related methods.\n    my_page.get(\"https://google.com\")\n\n    # Example of a wait usage:\n    # Wait until an element is visible, then take a screenshot.\n    my_page.search_field.wait_visible()\n    my_page.save_screenshot(\"my/file/image1.png\")\n\n    # All actions automatically handle explicit waits.\n    # No need to manually call wait methods unless required, \n    # e.g. Equivalent to: \n    # my_page.search_field.wait_clickable().send_keys(keyword).wait_clickable().submit()\n    my_page.search_field.send_keys(keyword).submit()\n\n    # Various wait states are available.\n    my_page.loading_image.wait_absent()\n    my_page.search_results.wait_all_visible()\n    my_page.save_screenshot(\"my/file/image2.png\")\n\n    # Assertions can be made directly:\n    search_keyword = 'dinner'\n    assert my_page.keyword_results(search_keyword).quantity > 1\n    assert search_keyword in my_page.search_result1.text\n\n    # Reuse found elements through existing sessions:\n    # Once an element (e.g., `my_page.search_result1`) is located, \n    # it will use the same session unless the element becomes stale.\n    # No need to store the found element in a separate variable.\n    # Just perform actions directly:\n    my_page.search_result1.click()\n    ...\n\n    driver.close()\n```\n---\n\n## Timeout Global Settings\n1.\tIn addition to setting timeouts for individual elements and methods, \na global timeout setting is also available. See the example below:\n```python\nfrom huskyPO import Timeout\n\n\n# Set the default timeout for all Elements.\n# The huskyPO default is 30 seconds. You can change it as needed:\nTimeout.DEFAULT = 60\n\n# If you don\u2019t want any waiting, you can also set it to 0:\nTimeout.DEFAULT = 0\n\n# Set the reraise behavior for timeouts on all Elements.\n# The huskyPO default is True, with the following logic:\n# True: Raise a TimeoutException if the element times out.\n# False: Return False if the element times out, without raising a TimeoutException.\nTimeout.RERAISE = False\n```\n\n2.\tThe priority order for timeout values is as follows:\n- P1: Method-Level:\n    - `page.element.wait_method(timeout=20)`\n- P2: Element-Level:\n    - `element = Element(..., timeout=10, ...)`\n- P3: Global-Level:\n    - `Timeout.DEFAULT = 60`\n\n3.\tThe priority order for timeout reraise behavior is as follows:\n- P1: Method-Level:\n    - `page.element.wait_method(reraise=True)`\n- P2: Global-Level:\n    - `Timeout.RERAISE = False`\n---\n\n## Wait Actions\nWe offer a comprehensive set of wait methods, \nextending the official expected_conditions in `ec_extension.py` \nand encapsulating them into corresponding methods. \nBelow are the extended methods for Element(s):\n```python\n# 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# Elements\npage.elements.wait_all_present()\npage.elements.wait_all_absent()\npage.elements.wait_all_visible()\npage.elements.wait_any_visible()\n```\n---\n\n## Appium Extended Actions\nWe have extended Appium with highly convenient action features, including:\n```python\n# Offset allows you to define swipe directions. \n# It supports eight standard directions: \n# UP, DOWN, LEFT, RIGHT, UPPER_LEFT, UPPER_RIGHT, LOWER_LEFT, LOWER_RIGHT.\n# Area lets you define the swipeable range, \n# defaulting to the full device screen (FULL), or you can customize it.\n\nfrom huskypo import Offset, Area\n\n# Page swiping. Refer to function docstrings for details.\npage.swipe_by(Offset.UP, Area.FULL)\npage.swipe_by(Offset.DOWN)\npage.swipe_by(Offset.LEFT)\npage.swipe_by(Offset.RIGHT)\npage.swipe_by(Offset.UPPER_LEFT)\npage.swipe_by(Offset.UPPER_RIGHT)\npage.swipe_by(Offset.LOWER_LEFT)\npage.swipe_by(Offset.LOWER_RIGHT)\n\n# Page flicking. Refer to function docstrings for details.\n# All Offset directions are supported.\npage.flick_by(Offset.UP, Area.FULL)\n\n# Element swiping until an element is visible.\n# All Offset directions are supported.\npage.element.swipe_by(Offset.UP, Area.FULL)\n\n# Element flicking until an element is visible.\n# All Offset directions are supported.\npage.element.flick_by(Offset.UP, Area.FULL)\n\n# Page draw lines.\n# Define dots coordinates\ndots = [{\"x\": x1, \"y\": y1}, {\"x\": x2, \"y\": y2}, {\"x\": x3, \"y\": y3}, ...]\n# Alternatively, use element locations if available\ndots = page.elements.locations\npage.draw_lines(dots)\n\n# Page draw gesture.\n# 9-grid coordinates, or define your own\ndots = page.elements.locations  \n# 9-grid gesture string (1\u20139 represent grid positions). This example draws a reverse Z.\ngesture = \"9875321\"  \npage.draw_gesture(dots, gesture)\n```\n\n## Other Actions\nAll Selenium and Appium features are included. Here are some examples:\n```python\n# ActionChains\npage.element.scroll_to_element().perform()\npage.element.move_to_element().drag_and_drop().perform()\n\n# Temporarily store ActionChains and execute later\npage.element.move_to_element().drag_and_drop()\n...  # Process other logic before executing perform()\npage.element.perform()\n\n# Select options\npage.element.options\npage.element.select_by_value()\n```\n\n## Logstack\nUsing logstack to log specific frame information.\nThe logstack module extends logging functionality, \nallowing you to capture information for specific frames, \nsuch as those starting with a designated prefix (e.g., test), \nwithout tracing all frames manually.\n```python\nfrom huskypo import logstack, logconfig\nimport logging  # Optional: You can also use logging for basic configuration.\n\n# Configure logging using either logging.basicConfig() or logconfig.basic()\nlogconfig.basic(file=\"./log.log\")  # Simplified configuration with common settings\n\n# Use logstack in your code to log specific frames\ndef some_func():\n    ...\n    # Logs information from the first frame with the prefix (default: test)\n    logstack.info(\"Log from some function.\", prefix=\"test\")\n\ndef test_func():\n    ...\n    # Logs frame info for test_func, not some_func\n    some_func()\n\n# Example log output:\n# 2025-01-04 18:20:48 | INFO | testing.py:32 | test_func | Log from some function.\n```\n\n## TODO\n- Continuously monitor new features in Selenium 4.0 and Appium 2.0.\n- Other optimizations.\n",
    "bugtrack_url": null,
    "license": "Apache 2.0",
    "summary": "UI Automation Page Objects design pattern.",
    "version": "1.4.0",
    "project_urls": {
        "Homepage": "https://github.com/uujohnnyuu/huskyPO"
    },
    "split_keywords": [
        "huskypo",
        " page object",
        " selenium",
        " appium",
        " automation"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "712ee42d9e78e9515a044040a20dda89981ff0c3ef3d155d026ad532ea0c856c",
                "md5": "fb0d3a5121b262f8aebe410864373361",
                "sha256": "5117d83a8c531f49f6638e1522518109186b2c898be3d8f211702ffca5e7d836"
            },
            "downloads": -1,
            "filename": "huskypo-1.4.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fb0d3a5121b262f8aebe410864373361",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 55918,
            "upload_time": "2025-01-07T02:56:44",
            "upload_time_iso_8601": "2025-01-07T02:56:44.950990Z",
            "url": "https://files.pythonhosted.org/packages/71/2e/e42d9e78e9515a044040a20dda89981ff0c3ef3d155d026ad532ea0c856c/huskypo-1.4.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "95689c0b0d87dc6197fa87bb074770b42618c64a5df477afb24e0b33b9d7ce47",
                "md5": "8594ede7c089462284701d22168c421d",
                "sha256": "c2fb272fcfd082c6c8f34841045167dae7a24c363087fa83f15b6535d919e7c6"
            },
            "downloads": -1,
            "filename": "huskypo-1.4.0.tar.gz",
            "has_sig": false,
            "md5_digest": "8594ede7c089462284701d22168c421d",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 54895,
            "upload_time": "2025-01-07T02:56:47",
            "upload_time_iso_8601": "2025-01-07T02:56:47.593427Z",
            "url": "https://files.pythonhosted.org/packages/95/68/9c0b0d87dc6197fa87bb074770b42618c64a5df477afb24e0b33b9d7ce47/huskypo-1.4.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-01-07 02:56:47",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "uujohnnyuu",
    "github_project": "huskyPO",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "huskypo"
}
        
Elapsed time: 0.56410s