UI Automation Page Objects design pattern.
Project description
huskium
Table of Contents
- Overview
- Usage
- Page Object Example Code
- Timeout Value Settings
- Timeout Reraise 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 utilizes Python’s data descriptors to enhance UI automation.
- Currently tracking Appium v4.5.0 (released on 2025/01/22).
- Sphinx documentation: https://uujohnnyuu.github.io/huskium/
Usage
- Build page objects easily with the
PageandElement(s)classes. - Write test scripts quickly using 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: standard way to define an 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: used when 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'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 Value Settings
1. Page Timeout Value Configuration (P3)
Sets the default timeout for the Page and all Element objects within it.
This value applies if neither the Element nor the wait method specifies a timeout.
my_page = MyPage(driver, timeout=10)
2. Element Timeout Value Configuration (P2)
Sets a specific timeout for an Element.
This value applies if the wait method does not specify a timeout.
my_element = Element(..., timeout=20, ...)
Or reset element timeout during test execution.
my_page.my_element.reset_timeout(7)
3. Wait Timeout Value Configuration (P1)
Defines the timeout for a specific wait method call.
This overrides both the Page and Element timeout settings but does not persist for future operations.
my_page = MyPage(driver, timeout=10)
# Temporarily set my_element's timeout to 3 seconds for this wait operation.
my_page.my_element.wait_visible(timeout=3)
my_page.save_screenshot()
4. Timeout Value Priority
- P1: Method-Level (
page.element.wait_method(timeout=20)) - P2: Element-Level (
Element(..., timeout=10, ...)) - P3: Page-Level (
Page(..., timeout=30, ...))
Timeout Reraise Settings
The reraise parameter controls whether a TimeoutException is raised when a timeout occurs.
- If reraise=True, a TimeoutException is raised.
- If reraise=False, the method returns False.
1. Methods with the Reraise Parameter
Defines the default reraise behavior for Page and Element methods that accept the reraise parameter. If not set, the Page’s reraise state is used.
my_page = MyPage(driver, timeout=30, reraise=True)
# Page methods.
my_page.url_is() # Raises TimeoutException on timeout.
my_page.url_is(reraise=False) # Returns False on timeout.
# Element methods.
my_page.my_element.wait_present() # Raises TimeoutException on timeout.
my_page.my_element.wait_present(reraise=False) # Returns False on timeout.
2. ### Methods without the Reraise Parameter
Certain methods do not accept the reraise parameter and will always raise a TimeoutException. Returning None in these cases would make WebDriver/WebElement calls ineffective.
Note: Currently, WebDriver methods that do not support the reraise parameter do not require explicit waits. Therefore, the following examples only apply to WebElement methods.
my_page = MyPage(driver, timeout=30, reraise=False)
# The page's reraise setting does not affect WebElement methods.
my_page.my_element.text # Raises TimeoutException on timeout.
my_page.my_element.click() # Raises TimeoutException on timeout.
Cache Settings
1. Element Global Cache Configuration (P2)
Sets whether Element globally caches by default. Note that Elements does not have this setting (multiple elements should be re-searched).
Element.enable_default_cache() # Enable global default cache
Element.disable_default_cache() # Disable global default cache
Element.default_cache() # Current global default cache setting
2. Element Object Cache Configuration (P1)
Sets whether the Element object caches, overriding the global setting.
my_element = Element(..., cache=None, ...) # Use global setting
my_element = Element(..., cache=True, ...) # my_element caches
my_element = Element(..., cache=False, ...) # my_element does not cache
Or reset during test execution.
my_page.my_element.unset_cache() # Use global setting
my_page.my_element.enable_cache() # my_element caches
my_page.my_element.disable_cache() # my_element does not cache
3. Cache Priority
- P1: Element-Object-Level (
Element(..., cache=False, ...)) - P2: Element-Global-Level (
Element.enable_cache())
Log Settings
1. Debug Log Configuration
from huskium import LogConfig
# Capture log messages from frames where the name starts with 'test'.
# Set to None to disable filtering.
LogConfig.PREFIX_FILTER.reset_prefix('test')
# Set to True for case-insensitive filtering.
LogConfig.PREFIX_FILTER.reset_islower(True)
# Specify whether to filter logs by function name.
# If False, filtering is based on file (module) name instead.
LogConfig.PREFIX_FILTER.reset_isfunc(True)
# Whether to record current frame info in the record object.
# This is useful for assert exception messages for quicker debugging.
LogConfig.PREFIX_FILTER.reset_torecord(True)
assert condition, LogConfig.PREFIX_FILTER.record
2. Debug Log Display Example
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 apply the provided filters to your own logging as follows.
from huskium import PrefixFilter, FuncPrefixFilter, FilePrefixFilter
# PrefixFilter includes both FuncPrefixFilter and FilePrefixFilter.
prefix_filter = PrefixFilter('test')
logging.getLogger().addFilter(prefix_filter)
If you want to display only module frames, use FilePrefixFilter.
run_module_filter = FilePrefixFilter('run')
logging.getLogger().addFilter(run_module_filter)
If you want to display only function frames, use FuncPrefixFilter.
test_func_filter = FuncPrefixFilter('test')
logging.getLogger().addFilter(test_func_filter)
You can reset the filter attributes during testing.
xxx_filter.reset_prefix('run')
xxx_filter.reset_islower(False)
xxx_filter.reset_torecord(True)
# This is only for PrefixFilter.
# True for FuncPrefixFilter; False for FilePrefixFilter.
prefix_filter.reset_isfunc(False)
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_RIGHT, 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_RIGHT)
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 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
Continue 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
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
File details
Details for the file huskium-1.1.0.tar.gz.
File metadata
- Download URL: huskium-1.1.0.tar.gz
- Upload date:
- Size: 44.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.11.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3712e51d1a5a5a89667b0529124007732d4ed01fa9b7a9f803a3f635d5eace0b
|
|
| MD5 |
eafdaad730825e3588f0a9f6b91f3ec8
|
|
| BLAKE2b-256 |
92b2c96a188c987cf956f40d0b1ffe88653af6c61a433becb0a4a62c5e1c02ca
|
File details
Details for the file huskium-1.1.0-py3-none-any.whl.
File metadata
- Download URL: huskium-1.1.0-py3-none-any.whl
- Upload date:
- Size: 45.6 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 |
b5245d9fc4628e9011752e36e7db55c74a5e56359c297bce8fb5959283b3ec08
|
|
| MD5 |
a570ea3e41f087fc796d95fe6b39d198
|
|
| BLAKE2b-256 |
6c9b5103f2ce5411b995b3ac05be374116a42ac11cca75ba17a7e4e749c4d96b
|