UI Automation Page Objects design pattern.
Reason this release was yanked:
v1.0.7
Project description
huskium
Table of Contents
- Overview
- Usage
- Page Object Example Code
- Timeout Settings
- Cache Settings
- Log Settings
- Wait Actions
- Appium Extended Actions
- Action Chains
- Select Actions
- Inheritance
- 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).
Usage
- Build page objects simply and elegantly using the
PageandElement(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
Release history Release notifications | RSS feed
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)
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
huskium-1.0.6-py3-none-any.whl
(48.5 kB
view details)
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e363b998ba622356d714c93ce5f1e233b100e25b20e3138bed4e04c3e3a993d
|
|
| MD5 |
9b31b6120b066607566dfa495e4eb59f
|
|
| BLAKE2b-256 |
13fcca4a8b016d59312fe7ecabec1ccf646a05e59270d2c21e7b3d95d4e381ad
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1563af421adf3895f303ea9f5c4d02dc5e14cd76a30b29b940720b20cd90b1b7
|
|
| MD5 |
b528d505c7fd2bf3a4bc0e7d23f8be31
|
|
| BLAKE2b-256 |
0466efcd6c6ff847d4aa035d12e2c12dfa969de40e68ba9194ef849b067e21a1
|