Skip to content

Commit

Permalink
[#505] NEW: draft POM implementation via descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
yashaka committed Jul 20, 2024
1 parent abab8a4 commit 1365a7a
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 7 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ check vscode pylance, mypy, jetbrains qodana...

### TODO: Location strategy?

### TODO: basic Element descriptors?
### DOING: basic Element descriptors?

### Deprecated conditions

Expand Down
105 changes: 105 additions & 0 deletions selene/support/_pom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# MIT License
#
# Copyright (c) 2024 Iakiv Kramarenko
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
from typing_extensions import Tuple, cast

import selene
from selene.core.entity import Element, Collection


# TODO: should we built these descriptors into Element and Collection classes?
# or should we ship them in separate module? (kind of from selene.pom import By, AllBy)
# todo: choose best naming...
# > ContextElement + ContextAll
# > By + AllBy
# > OneBy + AllBy
# > By(...).one + By(...)
# > Element + AllElements
# > Element + AllElements
# > Element + All
# > The + All
# > One + All
# > S + SS
# > Element.inside + AllElements.inside
# > Element.inside + Collection.inside
# > Element.inside + AllElements.inside
# > Inner + InnerAll
# > Inside + InsideAll
# > InnerElement
# > The
class _Element: # todo: consider implementing LocationContext interface
def __init__(self, selector: str | Tuple[str, str], _context=None):
self.__selector = selector
self.__context = _context

def Element(self, selector: str | Tuple[str, str]) -> _Element:
return _Element(selector, _context=self)

def All(self, selector: str | Tuple[str, str]) -> _All:
return _All(selector, _context=self)

# --- Descriptor --- #

def __set_name__(self, owner, name):
self.__name = name # TODO: use it

# TODO: consider caching
def __get__(self, instance, owner):
self.__context = self.__context or getattr(instance, 'context', selene.browser)
self.__as_context = cast(Element, self.__context.element(self.__selector))

return self.__as_context

# --- LocationContext --- #

def element(self, selector: str | Tuple[str, str]):
return self.__as_context.element(selector)

def all(self, selector: str | Tuple[str, str]) -> Collection:
return self.__as_context.all(selector)


class _All:

def __init__(self, selector: str | Tuple[str, str], _context=None):
self.__selector = selector
self.__context = _context

# --- Descriptor --- #

def __set_name__(self, owner, name):
self.__name = name # TODO: use it

# TODO: consider caching
def __get__(self, instance, owner) -> Element:
self.__context = self.__context or getattr(instance, 'context', selene.browser)
self.__as_context = self.__context.all(self.__selector)

return self.__as_context

# --- FilteringContext --- #

# TODO: implement...


S = _Element
SS = _All
32 changes: 26 additions & 6 deletions tests/integration/element__perform__drag_and_drop_to_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import selene
from selene import command, be, have, query
from selene.support._pom import _Element
from tests.integration.helpers.givenpage import GivenPage


Expand Down Expand Up @@ -151,15 +152,34 @@ def test_drags_source_and_drops_it_to_target_with_forced_retry(session_browser):
browser.element('#target2').element('#draggable').should(be.present_in_dom)


class ReactContinuousSlider:
# Example of the classic OOP-like PageObject pattern
class __X_ReactContinuousSlider:
def __init__(self, browser: Optional[selene.Browser]):
self.browser = browser if browser else selene.browser
self.container = self.browser.element('#ContinuousSlider+*')
self.thumb = self.container.element('.MuiSlider-thumb')
self.context = self.browser.element('#ContinuousSlider+*')

self.thumb = self.context.element('.MuiSlider-thumb')
self.thumb_input = self.thumb.element('input')
self.volume_up = self.container.element('[data-testid=VolumeUpIcon]')
self.volume_down = self.container.element('[data-testid=VolumeDownIcon]')
self.rail = self.container.element('.MuiSlider-rail')
self.volume_up = self.context.element('[data-testid=VolumeUpIcon]')
self.volume_down = self.context.element('[data-testid=VolumeDownIcon]')
self.rail = self.context.element('.MuiSlider-rail')

def open(self):
self.browser.open('https://mui.com/material-ui/react-slider/#ContinuousSlider')
return self


# Example of the POM-like PageObject pattern
class ReactContinuousSlider:
thumb = _Element('.MuiSlider-thumb')
thumb_input = thumb.Element('input')
volume_up = _Element('[data-testid=VolumeUpIcon]')
volume_down = _Element('[data-testid=VolumeDownIcon]')
rail = _Element('.MuiSlider-rail')

def __init__(self, browser: Optional[selene.Browser]):
self.browser = browser or selene.browser
self.context = self.browser.element('#ContinuousSlider+*')

def open(self):
self.browser.open('https://mui.com/material-ui/react-slider/#ContinuousSlider')
Expand Down

0 comments on commit 1365a7a

Please sign in to comment.