Skip to main content

UI Automation Page Objects design pattern.

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'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

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. Dubug Log Configuration

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: bool = True

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

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

# 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

# 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.7.tar.gz (44.3 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.7-py3-none-any.whl (46.3 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: huskium-1.0.7.tar.gz
  • Upload date:
  • Size: 44.3 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.7.tar.gz
Algorithm Hash digest
SHA256 86814fa4337c54acd510edb9fcb441346c51dc13380c840578087fcd47a0afad
MD5 0e955f84609cfad9561914c5ae00b633
BLAKE2b-256 a2a6929b2153c652b3479a67b95d1dc1db60b7294924bbda2dbd44fbf758d388

See more details on using hashes here.

File details

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

File metadata

  • Download URL: huskium-1.0.7-py3-none-any.whl
  • Upload date:
  • Size: 46.3 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.7-py3-none-any.whl
Algorithm Hash digest
SHA256 c0acd67240457396ae3a4d69bbc29a0b418a4bc1b4ffea86676ee65f42d3097a
MD5 44645cd2c4fd1a5bf2d5339538cdd03c
BLAKE2b-256 b158bb1525cf249315fb5a47b23063e62794d6a383c6de6ebd1fe75061c0331e

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