Skip to main content

UI Automation Page Objects design pattern.

Reason this release was yanked:

v1.0.7

Project description

huskium

Table of Contents


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).

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

# 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

# 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

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'Search 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'Search NO.{order}')
        return self.static_search_result

Timeout Settings

1. Global Configuration

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.

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. Global Configuration

from huskium import Log

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

# Specify whether to filter logs by function name.
# If False, filtering is based on filename (module) instead.
Log.FUNCFRAME: bool = True

# Set to True for case-insensitive filtering.
Log.LOWER: bool = True

2. Log Filter

from huskium import PrefixFilter, FuncnamePrefixFilter, FilenamePrefixFilter

# Apply a filter to customize logging.
# PrefixFilter includes both FuncnamePrefixFilter and FilenamePrefixFilter.
# If Log.FUNCNAME = True, FuncnamePrefixFilter is used; otherwise, FilenamePrefixFilter is applied.
logging.getLogger().addFilter(PrefixFilter())

# Use specific filters independently if needed, regardless of Log.FUNCNAME.
logging.getLogger().addFilter(FilenamePrefixFilter())

# It is recommended to configure logging per module.
# Huskium’s core modules already define LOGGER, so Log settings control behavior externally.
LOGGER = logging.getLogger(__name__)
FILTER = FuncnamePrefixFilter()
LOGGER.addFilter(FILTER)

3. Log Display Example

# When Log.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 = '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.

Wait Actions

1. Basic Element Status

# 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

# 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

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

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

page.element.options
page.element.select_by_value("option_value")

Inheritance

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

huskium-1.0.6.tar.gz (47.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

huskium-1.0.6-py3-none-any.whl (48.5 kB view details)

Uploaded Python 3

File details

Details for the file huskium-1.0.6.tar.gz.

File metadata

  • Download URL: huskium-1.0.6.tar.gz
  • Upload date:
  • Size: 47.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.3

File hashes

Hashes for huskium-1.0.6.tar.gz
Algorithm Hash digest
SHA256 5e363b998ba622356d714c93ce5f1e233b100e25b20e3138bed4e04c3e3a993d
MD5 9b31b6120b066607566dfa495e4eb59f
BLAKE2b-256 13fcca4a8b016d59312fe7ecabec1ccf646a05e59270d2c21e7b3d95d4e381ad

See more details on using hashes here.

File details

Details for the file huskium-1.0.6-py3-none-any.whl.

File metadata

  • Download URL: huskium-1.0.6-py3-none-any.whl
  • Upload date:
  • Size: 48.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.3

File hashes

Hashes for huskium-1.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 1563af421adf3895f303ea9f5c4d02dc5e14cd76a30b29b940720b20cd90b1b7
MD5 b528d505c7fd2bf3a4bc0e7d23f8be31
BLAKE2b-256 0466efcd6c6ff847d4aa035d12e2c12dfa969de40e68ba9194ef849b067e21a1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page