diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst
index 2a3e56e..036f29f 100644
--- a/docs/CHANGELOG.rst
+++ b/docs/CHANGELOG.rst
@@ -3,6 +3,11 @@ Version history
We follow `Semantic Versions `_.
+0.8.2 (29.11.24)
+*******************************************************************************
+- Add ability to specify ``TypeAlias`` as ``_item class`` and use
+ ``ListComponent`` as a parameterized type
+
0.8.1 (25.11.24)
*******************************************************************************
- Improve getting ``item class`` from first ``ListComponent`` generic variable.
diff --git a/pomcorn/component.py b/pomcorn/component.py
index b1376ad..834dcda 100644
--- a/pomcorn/component.py
+++ b/pomcorn/component.py
@@ -1,3 +1,4 @@
+import typing
from inspect import isclass
from typing import (
Any,
@@ -207,6 +208,43 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
item_locator: locators.XPathLocator | None = None
relative_item_locator: locators.XPathLocator | None = None
+ def __class_getitem__(cls, item: tuple[type, ...]) -> Any:
+ """Create parameterized versions of generic classes.
+
+ This method is called when the class is used as a parameterized type,
+ such as MyGeneric[int] or MyGeneric[List[str]].
+
+ We override this method to store values passed in generic parameters.
+
+ Args:
+ cls - The generic class itself.
+ item - The type used for parameterization.
+
+ Returns:
+ type: A parameterized version of the class with the specified type.
+
+ """
+ list_cls = super().__class_getitem__(item) # type: ignore
+ cls.__parameters__ = item # type: ignore
+ return list_cls
+
+ def __init__(
+ self,
+ page: TPage,
+ base_locator: locators.XPathLocator | None = None,
+ wait_until_visible: bool = True,
+ ) -> None:
+ # If `_item_class` was not specified in `__init_subclass__`, this means
+ # that `ListComponent` is used as a parameterized type
+ # (e.g., `List[ItemClass, Page]`).
+ if isinstance(self._item_class, _EmptyValue):
+ # In this way we check the stored generic parameters and, if first
+ # from them is valid, set it as `_item_class`
+ first_generic_param = self.__parameters__[0]
+ if self.is_valid_item_class(first_generic_param):
+ self._item_class = first_generic_param
+ super().__init__(page, base_locator, wait_until_visible)
+
def __init_subclass__(cls) -> None:
"""Run logic for getting/overriding item_class attr for subclasses."""
super().__init_subclass__()
@@ -297,10 +335,19 @@ def get_list_item_class(cls) -> type[ListItemType] | None:
def is_valid_item_class(cls, item_class: Any) -> bool:
"""Check that specified ``item_class`` is valid.
- Valid `item_class` should be a class and subclass of ``Component``.
+ Valid ``item_class`` should be
+ * a class and subclass of ``Component``
+ * or TypeAlias based on ``Component``
"""
- return isclass(item_class) and issubclass(item_class, Component)
+ if isclass(item_class) and issubclass(item_class, Component):
+ return True
+
+ if isinstance(item_class, typing._GenericAlias): # type: ignore
+ type_alias = item_class.__origin__ # type: ignore
+ return isclass(type_alias) and issubclass(type_alias, Component)
+
+ return False
def get_item_by_text(self, text: str) -> ListItemType:
"""Get list item by text."""
diff --git a/pyproject.toml b/pyproject.toml
index e9936d6..ba70148 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pomcorn"
-version = "0.8.1"
+version = "0.8.2"
description = "Base implementation of Page Object Model"
authors = [
"Saritasa ",
diff --git a/tests/list_component/test_item_class.py b/tests/list_component/test_item_class.py
index d7bde11..b98380e 100644
--- a/tests/list_component/test_item_class.py
+++ b/tests/list_component/test_item_class.py
@@ -1,4 +1,4 @@
-from typing import Generic, TypeVar
+from typing import Generic, TypeAlias, TypeVar
import pytest
@@ -105,3 +105,34 @@ class List(BaseList[Param]):
# Ensure that `List.item_class` has correct type
list_cls = List(fake_page)
assert list_cls._item_class is ItemClass
+
+
+def test_set_item_class_without_inheritance(fake_page: Page) -> None:
+ """Check that item_class will be correct in not inherited class."""
+
+ class BaseList(Generic[TItem, TPage], ListComponent[TItem, TPage]):
+ """Base list component without specified Generic variables."""
+
+ base_locator = locators.XPathLocator("html") # required
+ relative_item_locator = locators.XPathLocator("body") # required
+ wait_until_visible = lambda _: True # to not wait anything
+
+ # Prepare base list component without specified Generic variables
+ list_cls = BaseList[ItemClass, Page](fake_page)
+ # Ensure that `InheritedList.item_class` has correct type
+ assert list_cls._item_class is ItemClass
+
+
+# Type alias for check that ItemClass can be also a TypeAlias
+TypeAliasItemClass: TypeAlias = Component[Page]
+
+
+def test_item_class_can_be_type_alias(fake_page: Page) -> None:
+ """Check that item_class can be a type alias based on ``Component``."""
+
+ class List(ListComponent[TypeAliasItemClass, Page]):
+ base_locator = locators.XPathLocator("html") # required
+ wait_until_visible = lambda _: True # to not wait anything
+
+ list_cls = List(fake_page)
+ assert list_cls._item_class is TypeAliasItemClass