Skip to content

Commit

Permalink
[#562] NEW: core to web + mobile split
Browse files Browse the repository at this point in the history
- copy&paste Browser, Element, Collection into selene.web.*
- `core._browser.Browser` (that will be now a "core general context" and should work for all platforms, that's why we don't need the following...):
  - remove:
    - `execute_script`
    - `save_screenshot`
    - `last_screenshot`
    - `save_page_source`
    - `last_page_source`
    - `close_current_tab`
    - `clear_local_storage`
    - `clear_session_storage`
  - deprecate:
    - `switch_to_next_tab`
    - `switch_to_previous_tab`
    - `switch_to_tab`
    - `switch_to`
  • Loading branch information
yashaka committed Jan 29, 2025
1 parent d8e23c6 commit c6eae7f
Show file tree
Hide file tree
Showing 26 changed files with 2,607 additions and 799 deletions.
2 changes: 2 additions & 0 deletions .pylint-disabled-rules
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ typevar-name-incorrect-variance,
anomalous-backslash-in-string,
use-dict-literal,
too-many-statements,
attribute-defined-outside-init,
too-many-return-statements,
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,30 @@ check vscode pylance, mypy, jetbrains qodana...

... maybe even from properties? (but should work out of the box if @property is applied as last)

### DOING: split into core, web, mobile

Done:
- copy&paste Browser, Element, Collection into selene.web.*
- `core._browser.Browser` (that will be now a "core general context" and should work for all platforms, that's why we don't need the following...):
- removed:
- `execute_script`
- `save_screenshot`
- `last_screenshot`
- `save_page_source`
- `last_page_source`
- `close_current_tab`
- `clear_local_storage`
- `clear_session_storage`
- deprecated:
- `switch_to_next_tab`
- `switch_to_previous_tab`
- `switch_to_tab`
- `switch_to`

Next:
- make core.Element a base class for web.Element
- extend web.Element with more web-specific commands (shadow-root, frames, etc.)

### Deprecated conditions

- `be.present` in favor of `be.present_in_dom`
Expand Down Expand Up @@ -531,7 +555,7 @@ Yet, marked as experimental... Because of some questions like:
- should there be one option to rule them all? even not just in conditions of queries?
- etc.

### Document command.py and query.py on module level
### Documented command.py and query.py on module level

Providing a brief overview of the modules and how to define your own custom commands and queries. See official docs to check new articles in Reference.

Expand Down
6 changes: 3 additions & 3 deletions examples/custom_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def fn(entity: Browser) -> None:
entity.element('#new-todo').type('one more').press_enter()
raise AssertionError(f'actual produced todos were: {size}')

return BrowserCondition(f'have produced {number} todos', fn)
return Condition(f'have produced {number} todos', fn)


def test_wait_for_produced_todos_v2():
Expand Down Expand Up @@ -118,7 +118,7 @@ def test_wait_for_notification_after_reload_v3():
"""
browser.open('https://the-internet.herokuapp.com/notification_message_rendered')

def notification_on_reload(message: str) -> BrowserCondition:
def notification_on_reload(message: str) -> Condition[Browser]:
def fn(entity: Browser):
entity.element('[href*=notification_message]').click()
notification = entity.element('#flash')
Expand All @@ -133,7 +133,7 @@ def fn(entity: Browser):
f'\t actual: {actual}'
)

return BrowserCondition(
return Condition(
f'received message {message} on reload',
fn,
)
Expand Down
1,366 changes: 766 additions & 600 deletions poetry.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,9 @@ skip-string-normalization = 1
typevar-name-incorrect-variance,
anomalous-backslash-in-string,
too-many-statements,
use-dict-literal''']
use-dict-literal,
attribute-defined-outside-init,
too-many-return-statements''']
[tool.pylint.'REPORTS']
evaluation=['10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)']
output-format=['colorized']
Expand Down
3 changes: 2 additions & 1 deletion selene/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

Config = _CustomConfigForCustomBrowser

from .core._browser import Browser as _CustomBrowser
# from .core._browser import Browser as _CustomBrowser
from .web import Browser as _CustomBrowser

Browser = _CustomBrowser

Expand Down
4 changes: 3 additions & 1 deletion selene/_managed.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from selene.core.configuration import Config
from selene.core._browser import Browser

# from selene.core._browser import Browser
from selene.web import Browser

config = Config()
browser = Browser(config)
2 changes: 2 additions & 0 deletions selene/common/_typing_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,5 +210,7 @@ def not_predicate(entity: E) -> bool:
# TODO: should we define __name__ and __qualname__ on it?


# TODO: should we change it to Query[E, None | Any]?
# so it will be easier to define inline conditions where lambda returns not None
class Command(Query[E, None]):
pass
7 changes: 3 additions & 4 deletions selene/core/_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@

from selene.core.entity import Element
from selene.core.configuration import Config
from selene.core.exceptions import _SeleneError
from selene.common._typing_functions import Query, Command


Expand All @@ -45,9 +44,9 @@ def _ensure_located(element: Element | WebElement | None) -> WebElement | None:

def _ensure_located(element):
return (
element.get(Query('locate webelement', lambda it: it.locate()))
if isinstance(element, Element)
else element
element
if isinstance(element, WebElement)
else element.get(Query('locate webelement', lambda it: it.locate()))
)


Expand Down
130 changes: 27 additions & 103 deletions selene/core/_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,41 +116,6 @@ def open(self, relative_or_absolute_url: Optional[str] = None) -> Browser:

return self

# TODO: should we deprecate all switch_to_* methods?
def switch_to_next_tab(self) -> Browser:
from selene.core import query

self.driver.switch_to.window(query.next_tab(self))

# TODO: should we use waiting version here (and in other similar cases)?
# self.perform(Command(
# 'open next tab',
# lambda browser: browser.driver.switch_to.window(query.next_tab(self))))

return self

def switch_to_previous_tab(self) -> Browser:
from selene.core import query

self.driver.switch_to.window(query.previous_tab(self))
return self

def switch_to_tab(self, index_or_name: Union[int, str]) -> Browser:
if isinstance(index_or_name, int):
index = index_or_name
from selene.core import query

self.driver.switch_to.window(query.tab(index)(self))
else:
self.driver.switch_to.window(index_or_name)

return self

# TODO: consider deprecating
@property
def switch_to(self) -> SwitchTo:
return self.driver.switch_to

# TODO: should we add also a shortcut for self.driver.switch_to.alert ?
# if we don't need to switch_to.'back' after switch to alert - then for sure we should...
# question is - should we implement our own alert as waiting entity?
Expand All @@ -172,92 +137,51 @@ def close(self) -> Browser:

# --- Deprecated --- #

# TODO: should we keep it?
def execute_script(self, script, *args):
warnings.warn(
'consider using browser.driver.execute_script '
'instead of browser.execute_script',
PendingDeprecationWarning,
)
return self.driver.execute_script(script, *args)

# TODO: should we move it to query.* and/or command.*?
# like `browser.get(query.screenshot)` ?
# like `browser.perform(command.save_screenshot)` ?
# TODO: deprecate file name, use path
# because we can path folder path not file path and it will work
def save_screenshot(self, file: Optional[str] = None):
warnings.warn(
'browser.save_screenshot is deprecated, '
'use browser.get(query.screenshot_saved())',
DeprecationWarning,
)

from selene.core import query # type: ignore

return self.get(query.screenshot_saved()) # type: ignore

@property
def last_screenshot(self) -> str:
warnings.warn(
'browser.last_screenshot is deprecated, '
'use browser.config.last_screenshot',
DeprecationWarning,
)
return self.config.last_screenshot # type: ignore

def save_page_source(self, file: Optional[str] = None) -> Optional[str]:
def switch_to_next_tab(self) -> Browser:
warnings.warn(
'browser.save_page_source is deprecated, '
'use browser.get(query.page_source_saved())',
'browser.switch_to_next_tab is deprecated',
DeprecationWarning,
)
from selene.core import query

if file is None:
file = self.config._generate_filename(suffix='.html') # type: ignore

saved_file = WebHelper(self.driver).save_page_source(file)
self.driver.switch_to.window(query.next_tab(self))

self.config.last_page_source = saved_file # type: ignore
# TODO: should we use waiting version here (and in other similar cases)?
# self.perform(Command(
# 'open next tab',
# lambda browser: browser.driver.switch_to.window(query.next_tab(self))))

return saved_file
return self

@property
def last_page_source(self) -> str:
def switch_to_previous_tab(self) -> Browser:
warnings.warn(
'browser.last_page_source is deprecated, '
'use browser.config.last_page_source',
'browser.switch_to_previous_tab is deprecated',
DeprecationWarning,
)
return self.config.last_page_source # type: ignore
from selene.core import query

def close_current_tab(self) -> Browser:
warnings.warn(
'deprecated because the «tab» term is not relevant for mobile; '
'use a `browser.close()` or `browser.driver.close()` instead',
DeprecationWarning,
)
self.driver.close()
self.driver.switch_to.window(query.previous_tab(self))
return self

def clear_local_storage(self) -> Browser:
def switch_to_tab(self, index_or_name: Union[int, str]) -> Browser:
warnings.warn(
'deprecated because of js nature and not-relevance for mobile; '
'use `browser.perform(command.js.clear_local_storage)` instead',
'browser.switch_to_tab is deprecated',
DeprecationWarning,
)
from selene.core import command
if isinstance(index_or_name, int):
index = index_or_name
from selene.core import query

self.driver.switch_to.window(query.tab(index)(self))
else:
self.driver.switch_to.window(index_or_name)

self.perform(command.js.clear_local_storage)
return self

def clear_session_storage(self) -> Browser:
@property
def switch_to(self) -> SwitchTo:
warnings.warn(
'deprecated because of js nature and not-relevance for mobile; '
'use `browser.perform(command.js.clear_session_storage)` instead',
DeprecationWarning,
'browser.switch_to is considered to be deprecated',
PendingDeprecationWarning,
)
from selene.core import command

self.perform(command.js.clear_session_storage)
return self
return self.driver.switch_to
10 changes: 0 additions & 10 deletions selene/core/_browser.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,3 @@ class Browser(WaitingEntity['Browser']):
def switch_to(self) -> SwitchTo: ...
def quit(self) -> None: ...
def close(self) -> Browser: ...
def execute_script(self, script, *args): ...
# def save_screenshot(self, file: Optional[str] = ...): ...
# @property
# def last_screenshot(self) -> str: ...
# def save_page_source(self, file: Optional[str] = ...) -> Optional[str]: ...
# @property
# def last_page_source(self) -> str: ...
# def close_current_tab(self) -> Browser: ...
# def clear_local_storage(self) -> Browser: ...
# def clear_session_storage(self) -> Browser: ...
Loading

0 comments on commit c6eae7f

Please sign in to comment.