Skip to content

Commit

Permalink
Remove simple Component
Browse files Browse the repository at this point in the history
We decided to keep only one view of the component
(with the base locator), because practice has shown
that component always has a locator that can be
called base locator, and waiting for default display
makes tests more stable.

We rename `ComponentWithBaseLocator` to `Component`
and remove simple `Component` class.
  • Loading branch information
M1troll committed May 16, 2024
1 parent 9f17710 commit 3e6fc36
Show file tree
Hide file tree
Showing 26 changed files with 411 additions and 386 deletions.
142 changes: 142 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Pomcorn

![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml) ![PyPI](https://img.shields.io/pypi/v/pomcorn) ![PyPI - Status](https://img.shields.io/pypi/status/pomcorn) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pomcorn) ![PyPI - License](https://img.shields.io/pypi/l/pomcorn) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pomcorn) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)

**Pomcorn**, or **Page Object Model corn**, is a Python package that contains base classes to create systems based on [Selenium](https://github.com/SeleniumHQ/selenium#selenium) framework and **Page Object Model** pattern. You can read more about this pattern [here](https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/). The package can be used to create autotesting systems, parsing scripts and anything that requires
interaction with the browser.

The package includes next base classes to create Page Object Model (``POM``) pages:

```mermaid
classDiagram
WebView <|-- Component
WebView <|-- Page
Component <|-- ListComponent
Component .. Locator
Page .. Component
class WebView{
-webdriver: Webdriver
}
class Page{
+wait_until_loaded()
+open()
}
class Component{
-page: Page
-base_locator: Locator
+ wait_until_visible()
}
class ListComponent{
-item_locator: Locator
+count()
+all()
+get_item_by_text()
}
class Locator{
-query: String
}
```

It also includes [classes to locate elements](https://pomcorn.readthedocs.io/en/latest/locators.html) on the web page and a number of additional [waiting conditions](https://pomcorn.readthedocs.io/en/latest/waits_conditions.html>).

## Installation

You can install it by **pip**:

```bash
pip install pomcorn
```

Or **poetry**:

```bash
poetry add pomcorn
```

## Documentation

Link to the documentation: [http://pomcorn.rtfd.io/](http://pomcorn.rtfd.io/).

## Usage

You need to [install pomcorn](https://pomcorn.readthedocs.io/en/latest/installation.html) and [Chrome webdriver](https://pomcorn.readthedocs.io/en/latest/installation.html#chrome-driver).

Below is the code that opens ``PyPI.org``, searches for packages by name and prints names of found packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**, **Component**, **ListComponent** and **Element**.

```python

from typing import Self

from selenium.webdriver import Chrome
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webdriver import WebDriver

from pomcorn import Component, Element, ListComponent, Page, locators


# Prepare base page
class PyPIPage(Page):

APP_ROOT = "https://pypi.org"

def check_page_is_loaded(self) -> bool:
return self.init_element(locators.TagNameLocator("main")).is_displayed

@property
def search(self) -> Element[locators.XPathLocator]:
return self.init_element(locators.IdLocator("search"))


# Prepare components
Package = Component[PyPIPage]


class PackageList(ListComponent[Package, PyPIPage]):

item_class = Package

@property
def base_item_locator(self) -> locators.XPathLocator:
return self.base_locator // locators.ClassLocator("snippet__name")

@property
def names(self) -> list[str]:
return [package.body.get_text() for package in self.all]


# Prepare search page
class SearchPage(PyPIPage):

@classmethod
def open(cls, webdriver: WebDriver, **kwargs) -> Self:
pypi_page = super().open(webdriver, **kwargs)
# Specific logic for PyPI for an open search page
pypi_page.search.fill("")
pypi_page.search.send_keys(Keys.ENTER)
return cls(webdriver, **kwargs)

@property
def results(self) -> PackageList:
return PackageList(
page=self,
base_locator=locators.PropertyLocator(
prop="aria-label",
value="Search results",
),
)

def find(self, query: str) -> PackageList:
self.search.fill(query)
self.search.send_keys(Keys.ENTER)
return self.results


search_page = SearchPage.open(webdriver=Chrome())
print(search_page.find("saritasa").names)
```

For more information about package classes, you can read in [Object Hierarchy](https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html) and [Developer Interface](https://pomcorn.readthedocs.io/en/latest/developer_interface.html).

Also you can try our [demo autotests project](https://pomcorn.readthedocs.io/en/latest/demo.html).
163 changes: 0 additions & 163 deletions README.rst

This file was deleted.

1 change: 1 addition & 0 deletions cspell.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,4 @@ words:
- autosectionlabel
- bugname
- borderless
- sphinxcontrib
2 changes: 1 addition & 1 deletion demo/pages/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .base import PyPIComponent, PyPIComponentWithBaseLocator, PyPIPage
from .base import PyPIComponent, PyPIPage
from .help_page import HelpPage
from .index_page import IndexPage
from .package_details_page import PackageDetailsPage
Expand Down
2 changes: 1 addition & 1 deletion demo/pages/base/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from .base_components import PyPIComponent, PyPIComponentWithBaseLocator
from .base_components import PyPIComponent
from .base_page import PyPIPage
5 changes: 2 additions & 3 deletions demo/pages/base/base_components.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from typing import TypeAlias

from pomcorn import Component, ComponentWithBaseLocator
from pomcorn import Component

from .base_page import PyPIPage

# In order not to specify generics every time, it's comfy to create your own
# type alias for `Component` and `ComponentWithBaseLocator`.
# type alias for `Component`.
PyPIComponent: TypeAlias = Component[PyPIPage]
PyPIComponentWithBaseLocator: TypeAlias = ComponentWithBaseLocator[PyPIPage]
2 changes: 1 addition & 1 deletion demo/pages/base/base_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def __init__(
self.logo = self.init_element(
# The ``locator=`` keyword is optional here, but we recommend using
# it to be consistent with the method of the same name in
# ``ComponentWithBaseLocator``. Same with ``init_elements``.
# ``Component``. Same with ``init_elements``.
locator=locators.ClassLocator("site-header__logo"),
)

Expand Down
7 changes: 3 additions & 4 deletions demo/pages/common/navigation_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

from typing import TYPE_CHECKING

from pages import PyPIComponentWithBaseLocator, PyPIPage
from pages import PyPIComponent, PyPIPage

from pomcorn import locators

if TYPE_CHECKING:
from pages.help_page import HelpPage


# Here `ComponentWithBaseLocator` is used because unlike `Component`,
# this component implements methods of waiting until the component becomes
# `Component` implements methods of waiting until the component becomes
# visible / invisible, including in `__init__` method. This will allow to make
# tests more stable because the component will wait until it becomes visible
# before returning its instance.
class Navbar(PyPIComponentWithBaseLocator):
class Navbar(PyPIComponent):
"""Component representing navigation bar in the top of web application."""

def __init__(
Expand Down
4 changes: 2 additions & 2 deletions demo/pages/common/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from typing import TYPE_CHECKING

from pages import PyPIComponentWithBaseLocator
from pages import PyPIComponent
from selenium.webdriver.common.keys import Keys

from pomcorn import locators
Expand All @@ -11,7 +11,7 @@
from pages.search_page import SearchPage


class Search(PyPIComponentWithBaseLocator):
class Search(PyPIComponent):
"""Component representing the search input field."""

# If you are not going to write anything in ``__init__`` and only want
Expand Down
Loading

0 comments on commit 3e6fc36

Please sign in to comment.